AI/AgentTickSystem.cs
using System.Diagnostics;
namespace HC3;
/// <summary>
/// Batch-processes all agent updates
/// </summary>
public sealed class AgentTickSystem : GameObjectSystem<AgentTickSystem>
{
[ConVar( "hc3.debug.agenttick" )]
public static bool DebugTiming { get; set; }
/// <summary>
/// Last measured update time in milliseconds (movement, animation, wearables).
/// </summary>
public static float LastUpdateMs { get; private set; }
/// <summary>
/// Last measured fixed update time in milliseconds (AI scoring, needs).
/// </summary>
public static float LastFixedUpdateMs { get; private set; }
private readonly Stopwatch _sw = new();
private RealTimeSince _lastLog;
public AgentTickSystem( Scene scene ) : base( scene )
{
Listen( Stage.StartUpdate, 0, OnUpdate, "AgentTickSystem.Update" );
Listen( Stage.StartFixedUpdate, 0, OnFixedUpdate, "AgentTickSystem.FixedUpdate" );
}
private void OnUpdate()
{
if ( DebugTiming ) _sw.Restart();
// Single pass: movement + animation + agent-specific updates
foreach ( var agent in Scene.GetAll<Agent>() )
{
if ( !agent.Active ) continue;
if ( agent is IPlacementObject { IsPlaced: false } ) continue;
agent.Controller?.Tick();
agent.Tick();
}
if ( DebugTiming )
{
_sw.Stop();
LastUpdateMs = (float)_sw.Elapsed.TotalMilliseconds;
if ( _lastLog > 1f )
{
_lastLog = 0;
}
}
}
private void OnFixedUpdate()
{
if ( DebugTiming ) _sw.Restart();
// Single pass: AI scoring + needs decay
foreach ( var agent in Scene.GetAll<Agent>() )
{
if ( !agent.Active ) continue;
if ( agent is IPlacementObject { IsPlaced: false } ) continue;
agent.ActionController?.Tick();
agent.FixedTick();
}
if ( DebugTiming )
{
_sw.Stop();
LastFixedUpdateMs = (float)_sw.Elapsed.TotalMilliseconds;
}
}
}