mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-04-21 06:48:11 -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
121 lines
3.2 KiB
C#
121 lines
3.2 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Updates NavMeshAgent ground positions in parallel during PrePhysicsStep.
|
|
/// </summary>
|
|
internal sealed class NavMeshGameSystem : GameObjectSystem
|
|
{
|
|
private readonly List<NavMeshAgent> _agents = new();
|
|
|
|
public NavMeshGameSystem( Scene scene ) : base( scene )
|
|
{
|
|
// Listen to StartFixedUpdate to run before physics
|
|
Listen( Stage.StartFixedUpdate, -100, UpdateAgentGoundZ, "UpdateAgentGroundZ" );
|
|
}
|
|
|
|
void UpdateAgentGoundZ()
|
|
{
|
|
using var _ = PerformanceStats.Timings.NavMesh.Scope();
|
|
|
|
_agents.Clear();
|
|
Scene.GetAll<NavMeshAgent>( _agents );
|
|
if ( _agents.Count == 0 ) return;
|
|
|
|
System.Threading.Tasks.Parallel.ForEach( Partitioner.Create( _agents, loadBalance: true ), FindPhysicsGroundZ );
|
|
}
|
|
|
|
/// <summary>
|
|
/// We are tracing in the following interval (scale not accurate)
|
|
/// x
|
|
/// |
|
|
/// | We start a certain distance above the agents capsules center
|
|
/// |
|
|
/// |
|
|
/// |
|
|
/// -------
|
|
/// | | |
|
|
/// | x | -- Trace Start (in lower third)
|
|
/// | | |
|
|
/// -------
|
|
/// |
|
|
/// |
|
|
/// ~~~~~~~ -- Potential ground
|
|
/// |
|
|
/// |
|
|
/// |
|
|
/// x We trace down the same distance
|
|
///
|
|
/// In case of multiple hits we prefer the once closest to the agent's capsule center
|
|
/// </summary>
|
|
private void FindPhysicsGroundZ( NavMeshAgent agent )
|
|
{
|
|
if ( agent.agentInternal == null ) return;
|
|
|
|
if ( agent.timeUntilNextGroundTrace > 0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Introduce some random jitter so not all agents trace on the same frame
|
|
agent.timeUntilNextGroundTrace = Random.Shared.Int( 2, 4 ) * Time.Delta;
|
|
|
|
var footRadius = agent.agentInternal.option.radius * 0.1f;
|
|
var traceStartOffset = MathF.Max( 64f, Scene.NavMesh.AgentHeight ) * 8f;
|
|
|
|
var navMeshPos = agent.AgentPosition;
|
|
|
|
var traceStart = navMeshPos + Vector3.Up * Scene.NavMesh.AgentHeight * 0.3f;
|
|
|
|
var baseTrace =
|
|
Scene.Trace
|
|
.IgnoreDynamic()
|
|
.IgnoreGameObjectHierarchy( agent.GameObject )
|
|
.WithAnyTags( Scene.NavMesh.IncludedBodies )
|
|
.WithCollisionRules( agent.Tags )
|
|
.WithoutTags( Scene.NavMesh.ExcludedBodies );
|
|
|
|
if ( !Scene.NavMesh.IncludeStaticBodies )
|
|
{
|
|
baseTrace = baseTrace.IgnoreStatic();
|
|
}
|
|
if ( !Scene.NavMesh.IncludeKeyframedBodies )
|
|
{
|
|
baseTrace = baseTrace.IgnoreKeyframed();
|
|
}
|
|
|
|
var downTrace = baseTrace.Sphere( footRadius, traceStart, traceStart + Vector3.Down * traceStartOffset );
|
|
var upTrace = baseTrace.Sphere( footRadius, traceStart, traceStart + Vector3.Up * traceStartOffset );
|
|
|
|
var downResult = downTrace.Run();
|
|
var upResult = upTrace.Run();
|
|
|
|
var bestZ = 0f;
|
|
var closestDistanceToTraceStart = float.MaxValue;
|
|
|
|
// Process downTrace result
|
|
if ( downResult.Hit )
|
|
{
|
|
var distance = MathF.Abs( downResult.HitPosition.z - traceStart.z );
|
|
if ( distance < closestDistanceToTraceStart )
|
|
{
|
|
bestZ = downResult.HitPosition.z;
|
|
closestDistanceToTraceStart = distance;
|
|
}
|
|
}
|
|
|
|
// Process upTrace result
|
|
if ( upResult.Hit )
|
|
{
|
|
var distance = MathF.Abs( upResult.HitPosition.z - traceStart.z );
|
|
if ( distance < closestDistanceToTraceStart )
|
|
{
|
|
bestZ = upResult.HitPosition.z;
|
|
}
|
|
}
|
|
|
|
agent.groundTraceZ = bestZ;
|
|
}
|
|
}
|