oecs-sharp/OECS/SystemGroup.cs

115 lines
3.4 KiB
C#
Raw Permalink Normal View History

namespace OECS;
/// <summary>
/// Manages a group of systems, running them in registration order
/// against a specific <see cref="World"/>.
///
/// Owns the interrupt store — when a pending interrupt exists at the start
/// of a tick, system execution is skipped until the interrupt is resolved.
/// </summary>
public class SystemGroup
{
private readonly World _world;
private readonly List<ISystem> _systems = new();
private readonly InterruptStore _interrupts = new();
/// <summary>
/// Creates a system group bound to the given world.
/// Injects the interrupt store into the world so <see cref="World.Interrupt{T}"/>
/// and <see cref="IInterruptHandlerCommand{TInterrupt}"/> can route through it.
/// </summary>
public SystemGroup(World world)
{
_world = world ?? throw new ArgumentNullException(nameof(world));
_world._interruptStore = _interrupts;
}
/// <summary>
/// The number of systems in this group.
/// </summary>
public int Count => _systems.Count;
/// <summary>
/// Adds a system to the group. Systems execute in the order they are added.
/// </summary>
public void Add(ISystem system)
{
_systems.Add(system);
}
/// <summary>
/// Removes a system from the group.
/// </summary>
public void Remove(ISystem system)
{
_systems.Remove(system);
}
/// <summary>
/// Runs all systems with a timed tick.
/// Systems that implement <see cref="ITickedSystem"/> receive the tick data;
/// plain <see cref="ISystem"/> implementations receive only the world.
/// </summary>
public void RunTimed(float deltaTime)
{
RunAll(Tick.Timed(deltaTime));
}
/// <summary>
/// Runs all systems with a logical tick.
/// </summary>
public void RunLogical()
{
RunAll(Tick.Logical());
}
private void RunAll(Tick tick)
{
// Drain commands enqueued before the tick so the first system
// sees their effects. This also processes interrupt handler
// commands that may resolve pending interrupts.
_world.ExecuteCommands();
// If an interrupt is still pending after draining commands,
// skip all systems for this tick. The world is fully consistent
// (the previous tick completed) and is safe to serialize.
if (_interrupts.HasAny)
{
_world.PostChanges();
return;
}
_world.BeginBatching();
foreach (var system in _systems)
{
if (system is ITickedSystem ticked)
{
ticked.Run(_world, tick);
}
else
{
system.Run(_world);
}
// Drain commands after each system so subsequent systems
// see the effects of commands enqueued by prior systems.
_world.ExecuteCommands();
// Flush pending mutations so subsequent systems see
// entity/component changes from commands and previous systems.
_world.FlushPendingMutations();
// Post changes after each system so subscribers see
// incremental updates.
_world.PostChanges();
}
_world.EndBatching();
// Drain any remaining commands (e.g., those enqueued outside systems).
_world.ExecuteCommands();
// Post any remaining changes (e.g., from command execution).
_world.PostChanges();
}
}