mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-02 17:00:23 -04:00
Fixes timing scopes to more accurately represent a per-frame main thread breakdown, and prevents spikes when GC is executed. - **GcPause** - New separate timing scope showing time spent in GC per frame - GC pause time is subtracted from all other scopes, so each scope now only tracks its own code execution and no longer includes GC overhead - e.g. when GC occurs during the audio scope, the audio scope no longer spikes to 20ms - **AudioMixingThread** removed from the main scopes - Runs on a separate thread, so its timings are effectively meaningless in the main thread view - All other scopes are main thread only - No longer relevant given the audio optimisation work done over the past months - **Scene** scope removed - Didn't make much sense as it was an aggregate wrapping many other timing scopes - Replaced with a finer `Update` scope that tracks `Component.FixedUpdate`/`Update` - **Editor** scope no longer shows in-game - Scopes reschuffled - e.g. verlet rope physics traces are now tracked under the physics scope - Audio occlusion queries are now tracked under the audio scope https://files.facepunch.com/lolleko/2026/March/02_12-59-QuixoticMarten.png
96 lines
2.1 KiB
C#
96 lines
2.1 KiB
C#
using Sandbox.Audio;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Apply DSP to mixer when listener is inside a DspVolume
|
|
/// </summary>
|
|
[Expose]
|
|
sealed class DspVolumeGameSystem : GameObjectSystem<DspVolumeGameSystem>
|
|
{
|
|
public DspVolumeGameSystem( Scene scene ) : base( scene )
|
|
{
|
|
Listen( Stage.FinishUpdate, 0, Update, "Dsp Update" );
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
base.Dispose();
|
|
|
|
var gameMixer = Mixer.FindMixerByName( "Game" );
|
|
if ( gameMixer is null ) return;
|
|
|
|
foreach ( var processor in _entries.Values )
|
|
{
|
|
gameMixer.RemoveProcessor( processor.processor );
|
|
}
|
|
}
|
|
|
|
HashSet<string> _active { get; set; }
|
|
|
|
record class Entry( DspProcessor processor, bool active );
|
|
Dictionary<string, Entry> _entries = new();
|
|
|
|
void Update()
|
|
{
|
|
using var _ = PerformanceStats.Timings.Audio.Scope();
|
|
|
|
if ( Scene.IsEditor )
|
|
return;
|
|
|
|
var gameMixer = Mixer.FindMixerByName( "Game" );
|
|
if ( gameMixer is null ) return;
|
|
|
|
int lastPriority = int.MinValue;
|
|
string found = default;
|
|
|
|
foreach ( var volume in Scene.Volumes.FindAll<DspVolume>( Sound.Listener.Position ) )
|
|
{
|
|
int priority = volume.Priority;
|
|
|
|
if ( priority < lastPriority )
|
|
continue;
|
|
|
|
lastPriority = priority;
|
|
found = volume.Dsp.Name;
|
|
}
|
|
|
|
if ( !string.IsNullOrWhiteSpace( found ) && !_entries.ContainsKey( found ) )
|
|
{
|
|
var processor = new DspProcessor();
|
|
|
|
processor.Effect = found;
|
|
processor.Mix = 0;
|
|
|
|
gameMixer.AddProcessor( processor );
|
|
_entries[found] = new Entry( processor, true );
|
|
}
|
|
|
|
foreach ( var entry in _entries )
|
|
{
|
|
var mixTarget = found == entry.Key ? 1 : 0;
|
|
|
|
entry.Value.processor.Mix = entry.Value.processor.Mix.Approach( mixTarget, Time.Delta );
|
|
}
|
|
|
|
foreach ( var entry in _entries.Where( x => x.Value.processor.Mix <= 0 ).ToArray() )
|
|
{
|
|
gameMixer.RemoveProcessor( entry.Value.processor );
|
|
_entries.Remove( entry.Key );
|
|
}
|
|
|
|
}
|
|
|
|
private void TryAdd( string name )
|
|
{
|
|
if ( _active.Contains( name ) ) return;
|
|
_active.Add( name );
|
|
}
|
|
|
|
private void TryRemove( string name )
|
|
{
|
|
if ( !_active.Contains( name ) ) return;
|
|
_active.Remove( name );
|
|
}
|
|
}
|