Ambient sound performance improvements (#12756)

This commit is contained in:
Leon Friedrich
2022-11-28 14:10:40 +13:00
committed by GitHub
parent cd6d4cb83c
commit 8ec2a9ec74
2 changed files with 84 additions and 102 deletions

View File

@@ -39,14 +39,22 @@ namespace Content.Client.Audio
private TimeSpan _targetTime = TimeSpan.Zero; private TimeSpan _targetTime = TimeSpan.Zero;
private float _ambienceVolume = 0.0f; private float _ambienceVolume = 0.0f;
// Note that except for some rare exceptions, every ambient sound source appears to be static. So:
// TODO AMBIENT SOUND Use only static queries
// This would make all the lookups significantly faster. There are some rare exceptions, like flies, vehicles,
// and the singularity. But those can just play sounds via some other system. Alternatively: give ambient sound
// its own client-side tree to avoid this issue altogether.
private static LookupFlags _flags = LookupFlags.Static | LookupFlags.Dynamic | LookupFlags.Sundries;
private static AudioParams _params = AudioParams.Default.WithVariation(0.01f).WithLoop(true).WithAttenuation(Attenuation.LinearDistance);
/// <summary> /// <summary>
/// How many times we can be playing 1 particular sound at once. /// How many times we can be playing 1 particular sound at once.
/// </summary> /// </summary>
private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f)); private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f));
private readonly Dictionary<AmbientSoundComponent, (IPlayingAudioStream? Stream, string Sound)> _playingSounds = new(); private readonly Dictionary<AmbientSoundComponent, (IPlayingAudioStream? Stream, string Sound)> _playingSounds = new();
private readonly Dictionary<string, int> _playingCount = new();
private const float RangeBuffer = 3f;
public bool OverlayEnabled public bool OverlayEnabled
{ {
@@ -94,8 +102,13 @@ namespace Content.Client.Audio
private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args) private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args)
{ {
if (_playingSounds.Remove(component, out var sound)) if (!_playingSounds.Remove(component, out var sound))
sound.Stream?.Stop(); return;
sound.Stream?.Stop();
_playingCount[sound.Sound] -= 1;
if (_playingCount[sound.Sound] == 0)
_playingCount.Remove(sound.Sound);
} }
private void SetAmbienceVolume(float value) => _ambienceVolume = value; private void SetAmbienceVolume(float value) => _ambienceVolume = value;
@@ -143,15 +156,13 @@ namespace Content.Client.Audio
_targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown); _targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown);
var player = _playerManager.LocalPlayer?.ControlledEntity; var player = _playerManager.LocalPlayer?.ControlledEntity;
if (!EntityManager.TryGetComponent(player, out TransformComponent? playerManager)) if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
{ {
ClearSounds(); ClearSounds();
return; return;
} }
var coordinates = playerManager.Coordinates; ProcessNearbyAmbience(xform);
ProcessNearbyAmbience(coordinates);
} }
private void ClearSounds() private void ClearSounds()
@@ -164,36 +175,37 @@ namespace Content.Client.Audio
_playingSounds.Clear(); _playingSounds.Clear();
} }
private Dictionary<string, List<AmbientSoundComponent>> GetNearbySources(EntityCoordinates coordinates) private Dictionary<string, SortedList<float, AmbientSoundComponent>> GetNearbySources(TransformComponent playerXform, MapCoordinates coords, EntityQuery<TransformComponent> xformQuery)
{ {
//TODO: Make this produce a hashset of nearby entities again. var sourceDict = new Dictionary<string, SortedList<float, AmbientSoundComponent>>(16);
var sourceDict = new Dictionary<string, List<AmbientSoundComponent>>(16);
var ambientQuery = GetEntityQuery<AmbientSoundComponent>(); var ambientQuery = GetEntityQuery<AmbientSoundComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var entity in _lookup.GetEntitiesInRange(coordinates, _maxAmbientRange + RangeBuffer)) // TODO add variant of GetComponentsInRange that also returns the transform component.
foreach (var entity in _lookup.GetEntitiesInRange(coords, _maxAmbientRange, flags: _flags))
{ {
if (!ambientQuery.TryGetComponent(entity, out var ambientComp) || if (!ambientQuery.TryGetComponent(entity, out var ambientComp) || !ambientComp.Enabled)
!ambientComp.Enabled ||
!xformQuery.GetComponent(entity).Coordinates.TryDistance(EntityManager, coordinates, out var range) ||
range > ambientComp.Range)
{
continue; continue;
}
var key = _audio.GetSound(ambientComp.Sound); var xform = xformQuery.GetComponent(entity);
var delta = xform.ParentUid == playerXform.ParentUid
? xform.LocalPosition - playerXform.LocalPosition
: xform.WorldPosition - coords.Position;
if (!sourceDict.ContainsKey(key)) var range = delta.Length;
sourceDict[key] = new List<AmbientSoundComponent>(MaxSingleSound); if (range >= ambientComp.Range)
continue;
sourceDict[key].Add(ambientComp); string key;
}
// TODO: Just store the distance from above... if (ambientComp.Sound is SoundPathSpecifier path)
foreach (var (key, val) in sourceDict) key = path.Path?.ToString() ?? string.Empty;
{ else
sourceDict[key] = val.OrderByDescending(x => key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
Transform(x.Owner).Coordinates.TryDistance(EntityManager, coordinates, out var dist) ? dist * (x.Volume + 32) : float.MaxValue).ToList();
var list = sourceDict.GetOrNew(key);
// Prioritize far away & loud sounds.
list.Add(-range * (ambientComp.Volume + 32), ambientComp);
} }
return sourceDict; return sourceDict;
@@ -202,98 +214,68 @@ namespace Content.Client.Audio
/// <summary> /// <summary>
/// Get a list of ambient components in range and determine which ones to start playing. /// Get a list of ambient components in range and determine which ones to start playing.
/// </summary> /// </summary>
private void ProcessNearbyAmbience(EntityCoordinates coordinates) private void ProcessNearbyAmbience(TransformComponent playerXform)
{ {
var compsInRange= GetNearbySources(coordinates); var query = GetEntityQuery<TransformComponent>();
var mapPos = playerXform.MapPosition;
var keys = compsInRange.Keys.ToHashSet(); // Remove out-of-range ambiences
foreach (var (comp, sound) in _playingSounds)
while (keys.Count != 0)
{ {
if (_playingSounds.Count >= _maxAmbientCount) var entity = comp.Owner;
if (comp.Enabled && query.TryGetComponent(entity, out var xform) && xform.MapID == playerXform.MapID)
{ {
/* var distance = (xform.ParentUid == playerXform.ParentUid)
// Go through and remove everything from compSet ? xform.LocalPosition - playerXform.LocalPosition
foreach (var toRemove in keys.SelectMany(key => compsInRange[key])) : xform.WorldPosition - mapPos.Position;
{
compSet.Remove(toRemove.Owner);
}
*/
break; if (distance.LengthSquared < comp.Range * comp.Range)
continue;
} }
foreach (var key in keys) sound.Stream?.Stop();
_playingSounds.Remove(comp);
_playingCount[sound.Sound] -= 1;
if (_playingCount[sound.Sound] == 0)
_playingCount.Remove(sound.Sound);
}
if (_playingSounds.Count >= _maxAmbientCount)
return;
// Add in range ambiences
foreach (var (key, sources) in GetNearbySources(playerXform, mapPos, query))
{
if (_playingSounds.Count >= _maxAmbientCount)
break;
if (_playingCount.TryGetValue(key, out var playingCount) && playingCount >= MaxSingleSound)
continue;
foreach (var comp in sources.Values)
{ {
if (_playingSounds.Count >= _maxAmbientCount)
break;
if (compsInRange[key].Count == 0)
{
keys.Remove(key);
continue;
}
var comp = compsInRange[key].Pop();
if (_playingSounds.ContainsKey(comp)) if (_playingSounds.ContainsKey(comp))
continue; continue;
var sound = _audio.GetSound(comp.Sound); var audioParams = _params
.AddVolume(comp.Volume + _ambienceVolume)
if (PlayingCount(sound) >= MaxSingleSound)
{
keys.Remove(key);
/*foreach (var toRemove in compsInRange[key])
{
Logger.Debug($"removing {toRemove.Owner} from set.");
compSet.Remove(toRemove.Owner);
}*/
compsInRange[key].Clear(); // reduce work later should we overrun the max sounds.
continue;
}
var audioParams = AudioParams.Default
.WithVariation(0.01f)
.WithVolume(comp.Volume + _ambienceVolume)
.WithLoop(true)
.WithAttenuation(Attenuation.LinearDistance)
// Randomise start so 2 sources don't increase their volume. // Randomise start so 2 sources don't increase their volume.
.WithPlayOffset(_random.NextFloat(0.0f, 100.0f)) .WithPlayOffset(_random.NextFloat(0.0f, 100.0f))
.WithMaxDistance(comp.Range); .WithMaxDistance(comp.Range);
var stream = _audio.PlayPvs(comp.Sound, comp.Owner, audioParams); var stream = _audio.PlayPvs(comp.Sound, comp.Owner, audioParams);
if (stream != null)
_playingSounds[comp] = (stream, key);
if (stream == null) continue; playingCount++;
if (_playingSounds.Count >= _maxAmbientCount)
_playingSounds[comp] = (stream, sound); break;
} }
_playingCount[key] = playingCount;
} }
foreach (var (comp, sound) in _playingSounds) DebugTools.Assert(_playingCount.All(x => x.Value == PlayingCount(x.Key)));
{
var entity = comp.Owner;
if (comp.Deleted || // includes entity deletion
!comp.Enabled ||
!EntityManager.GetComponent<TransformComponent>(entity).Coordinates
.TryDistance(EntityManager, coordinates, out var range) ||
range > comp.Range)
{
sound.Stream?.Stop();
_playingSounds.Remove(comp);
}
}
//TODO: Put this code back in place! Currently not done this way because of GetEntitiesInRange being funny.
/*
foreach (var (comp, sound) in _playingSounds)
{
if (compSet.Contains(comp.Owner)) continue;
Logger.Debug($"Cancelled {comp.Owner}");
_playingSounds[comp].Stream?.Stop();
_playingSounds.Remove(comp);
}
*/
} }
} }
} }

View File

@@ -48,7 +48,7 @@ namespace Content.Shared.CCVar
/// How large of a range to sample for ambience. /// How large of a range to sample for ambience.
/// </summary> /// </summary>
public static readonly CVarDef<float> AmbientRange = public static readonly CVarDef<float> AmbientRange =
CVarDef.Create("ambience.range", 5f, CVar.REPLICATED | CVar.SERVER); CVarDef.Create("ambience.range", 8f, CVar.REPLICATED | CVar.SERVER);
/// <summary> /// <summary>
/// Maximum simultaneous ambient sounds. /// Maximum simultaneous ambient sounds.