refactor: rename iteration to batching and expand scope
Rename `BeginIteration`/`EndIteration` to `BeginBatching`/`EndBatching` to reflect that the mechanism now covers both entity iteration and system execution. - Update `ISystem` and `ITickedSystem` to use `RunImpl` for logic. - Add `SystemExtensions.Run` to wrap system execution in a batching scope. - Update `SystemGroup` to wrap system execution in a batching scope. - Update `WorldQueryExtensions` to use the new batching terminology.
This commit is contained in:
parent
96d732d6ab
commit
e460c0e70f
|
|
@ -2,12 +2,44 @@ namespace OECS;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A system that runs on every tick.
|
/// A system that runs on every tick.
|
||||||
/// Systems receive the world in their <see cref="Run"/> method.
|
/// Implement <see cref="RunImpl"/> with the system logic.
|
||||||
|
/// Call <see cref="ISystem.Run"/> to execute with batching.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISystem
|
public interface ISystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the system logic for this tick.
|
/// Implement this with the system's logic for this tick.
|
||||||
|
/// The caller (e.g., <see cref="ISystem.Run"/>) wraps this
|
||||||
|
/// in a batching scope so mutations and component accesses
|
||||||
|
/// are automatically tracked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Run(World world);
|
void RunImpl(World world);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for running systems with proper batching.
|
||||||
|
/// </summary>
|
||||||
|
public static class SystemExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a system with a batching scope. Structural mutations are
|
||||||
|
/// buffered, and <see cref="World.GetComponent{T}"/> calls are
|
||||||
|
/// auto-tracked for modification until the system returns.
|
||||||
|
/// </summary>
|
||||||
|
public static void Run(this ISystem system, World world)
|
||||||
|
{
|
||||||
|
world.BeginBatching();
|
||||||
|
system.RunImpl(world);
|
||||||
|
world.EndBatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a ticked system with a batching scope.
|
||||||
|
/// </summary>
|
||||||
|
public static void Run(this ITickedSystem system, World world, Tick tick)
|
||||||
|
{
|
||||||
|
world.BeginBatching();
|
||||||
|
system.RunImpl(world, tick);
|
||||||
|
world.EndBatching();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ namespace OECS;
|
||||||
public interface ITickedSystem : ISystem
|
public interface ITickedSystem : ISystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the system logic with tick information.
|
/// Implement this with the system's ticked logic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Run(World world, Tick tick);
|
void RunImpl(World world, Tick tick);
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +62,7 @@ public class SystemGroup
|
||||||
// sees their effects.
|
// sees their effects.
|
||||||
_world.ExecuteCommands();
|
_world.ExecuteCommands();
|
||||||
|
|
||||||
|
_world.BeginBatching();
|
||||||
foreach (var system in _systems)
|
foreach (var system in _systems)
|
||||||
{
|
{
|
||||||
if (system is ITickedSystem ticked)
|
if (system is ITickedSystem ticked)
|
||||||
|
|
@ -77,10 +78,15 @@ public class SystemGroup
|
||||||
// see the effects of commands enqueued by prior systems.
|
// see the effects of commands enqueued by prior systems.
|
||||||
_world.ExecuteCommands();
|
_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
|
// Post changes after each system so subscribers see
|
||||||
// incremental updates.
|
// incremental updates.
|
||||||
_world.PostChanges();
|
_world.PostChanges();
|
||||||
}
|
}
|
||||||
|
_world.EndBatching();
|
||||||
|
|
||||||
// Drain any remaining commands (e.g., those enqueued outside systems).
|
// Drain any remaining commands (e.g., those enqueued outside systems).
|
||||||
_world.ExecuteCommands();
|
_world.ExecuteCommands();
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ public class World : IDisposable
|
||||||
private readonly Dictionary<Type, Entity> _singletonEntities = new();
|
private readonly Dictionary<Type, Entity> _singletonEntities = new();
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
// Deferred structural mutation support: when iterating entities,
|
// Deferred structural mutation support: when inside a batching scope
|
||||||
// AddComponent, RemoveComponent, and DestroyEntity are buffered and
|
// (e.g., a foreach iteration or a system run), AddComponent,
|
||||||
// applied after the iteration completes.
|
// RemoveComponent, and DestroyEntity are buffered and applied when
|
||||||
private int _iterationDepth;
|
// the outermost scope ends.
|
||||||
|
private int _batchingDepth;
|
||||||
private readonly List<PendingMutation> _pendingMutations = new();
|
private readonly List<PendingMutation> _pendingMutations = new();
|
||||||
|
|
||||||
private enum PendingMutationKind { AddComponent, RemoveComponent, DestroyEntity }
|
private enum PendingMutationKind { AddComponent, RemoveComponent, DestroyEntity }
|
||||||
|
|
@ -35,10 +36,10 @@ public class World : IDisposable
|
||||||
public Type ComponentType;
|
public Type ComponentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-dirty-marking: during iteration, component accesses via
|
// Auto-dirty-marking: during a batching scope, component accesses via
|
||||||
// GetComponent<T> are tracked. When the iteration scope ends,
|
// GetComponent<T> are tracked. When the scope ends, all accessed
|
||||||
// all accessed components are automatically marked as modified.
|
// components are automatically marked as modified.
|
||||||
// Outside of iteration, MarkModified<T> must be called explicitly.
|
// Outside of a batching scope, MarkModified<T> must be called explicitly.
|
||||||
private readonly HashSet<(Entity Entity, Type ComponentType)> _accessedComponents = new();
|
private readonly HashSet<(Entity Entity, Type ComponentType)> _accessedComponents = new();
|
||||||
private readonly HashSet<(Entity Entity, Type ComponentType)> _markedModified = new();
|
private readonly HashSet<(Entity Entity, Type ComponentType)> _markedModified = new();
|
||||||
|
|
||||||
|
|
@ -86,7 +87,7 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DestroyEntity(Entity entity)
|
public void DestroyEntity(Entity entity)
|
||||||
{
|
{
|
||||||
if (_iterationDepth > 0)
|
if (_batchingDepth > 0)
|
||||||
{
|
{
|
||||||
_pendingMutations.Add(new PendingMutation
|
_pendingMutations.Add(new PendingMutation
|
||||||
{
|
{
|
||||||
|
|
@ -157,7 +158,7 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddComponent<T>(Entity entity, T component) where T : struct
|
public void AddComponent<T>(Entity entity, T component) where T : struct
|
||||||
{
|
{
|
||||||
if (_iterationDepth > 0)
|
if (_batchingDepth > 0)
|
||||||
{
|
{
|
||||||
_pendingMutations.Add(new PendingMutation
|
_pendingMutations.Add(new PendingMutation
|
||||||
{
|
{
|
||||||
|
|
@ -226,7 +227,7 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RemoveComponent<T>(Entity entity) where T : struct
|
public void RemoveComponent<T>(Entity entity) where T : struct
|
||||||
{
|
{
|
||||||
if (_iterationDepth > 0)
|
if (_batchingDepth > 0)
|
||||||
{
|
{
|
||||||
_pendingMutations.Add(new PendingMutation
|
_pendingMutations.Add(new PendingMutation
|
||||||
{
|
{
|
||||||
|
|
@ -269,7 +270,7 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ref T GetComponent<T>(Entity entity) where T : struct
|
public ref T GetComponent<T>(Entity entity) where T : struct
|
||||||
{
|
{
|
||||||
if (_iterationDepth > 0)
|
if (_batchingDepth > 0)
|
||||||
_accessedComponents.Add((entity, typeof(T)));
|
_accessedComponents.Add((entity, typeof(T)));
|
||||||
return ref _components.Get<T>(entity);
|
return ref _components.Get<T>(entity);
|
||||||
}
|
}
|
||||||
|
|
@ -465,23 +466,25 @@ public class World : IDisposable
|
||||||
// ── Iteration Lifecycle ──────────────────────────────────────────
|
// ── Iteration Lifecycle ──────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begins an iteration scope. Structural mutations are buffered
|
/// Begins a batching scope. Structural mutations (AddComponent,
|
||||||
/// until <see cref="EndIteration"/> is called.
|
/// RemoveComponent, DestroyEntity) are buffered until the outermost
|
||||||
|
/// <see cref="EndBatching"/> call. Component accesses via
|
||||||
|
/// <see cref="GetComponent{T}"/> are auto-tracked for modification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void BeginIteration()
|
internal void BeginBatching()
|
||||||
{
|
{
|
||||||
_iterationDepth++;
|
_batchingDepth++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ends an iteration scope. When the outermost scope ends, all
|
/// Ends a batching scope. When the outermost scope ends, all
|
||||||
/// buffered structural mutations are applied and auto-tracked
|
/// buffered structural mutations are applied and auto-tracked
|
||||||
/// component modifications are posted.
|
/// component modifications are posted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void EndIteration()
|
internal void EndBatching()
|
||||||
{
|
{
|
||||||
_iterationDepth--;
|
_batchingDepth--;
|
||||||
if (_iterationDepth == 0)
|
if (_batchingDepth == 0)
|
||||||
{
|
{
|
||||||
// Auto-mark all components accessed via GetComponent<T>
|
// Auto-mark all components accessed via GetComponent<T>
|
||||||
// during the iteration as modified.
|
// during the iteration as modified.
|
||||||
|
|
@ -499,7 +502,7 @@ public class World : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FlushPendingMutations()
|
internal void FlushPendingMutations()
|
||||||
{
|
{
|
||||||
if (_pendingMutations.Count == 0)
|
if (_pendingMutations.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ public ref struct Select1<T1> where T1 : struct
|
||||||
_set = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
_set = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
_count = _set?.Count ?? 0;
|
_count = _set?.Count ?? 0;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_world.BeginIteration();
|
_world.BeginBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The current entity handle.</summary>
|
/// <summary>The current entity handle.</summary>
|
||||||
|
|
@ -147,7 +147,7 @@ public ref struct Select1<T1> where T1 : struct
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_world.EndIteration();
|
_world.EndBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool PassesWithout()
|
private bool PassesWithout()
|
||||||
|
|
@ -188,7 +188,7 @@ public ref struct Select2<T1, T2> where T1 : struct where T2 : struct
|
||||||
_swapped = c2 < c1;
|
_swapped = c2 < c1;
|
||||||
_count = _swapped ? c2 : c1;
|
_count = _swapped ? c2 : c1;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_world.BeginIteration();
|
_world.BeginBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity Entity { get; private set; }
|
public Entity Entity { get; private set; }
|
||||||
|
|
@ -221,7 +221,7 @@ public ref struct Select2<T1, T2> where T1 : struct where T2 : struct
|
||||||
|
|
||||||
public Select2<T1, T2> GetEnumerator() => this;
|
public Select2<T1, T2> GetEnumerator() => this;
|
||||||
|
|
||||||
public void Dispose() => _world.EndIteration();
|
public void Dispose() => _world.EndBatching();
|
||||||
|
|
||||||
private bool PassesWithout()
|
private bool PassesWithout()
|
||||||
{
|
{
|
||||||
|
|
@ -268,7 +268,7 @@ public ref struct Select3<T1, T2, T3>
|
||||||
else { _driver = 0; _count = c1; }
|
else { _driver = 0; _count = c1; }
|
||||||
|
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_world.BeginIteration();
|
_world.BeginBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity Entity { get; private set; }
|
public Entity Entity { get; private set; }
|
||||||
|
|
@ -310,7 +310,7 @@ public ref struct Select3<T1, T2, T3>
|
||||||
|
|
||||||
public Select3<T1, T2, T3> GetEnumerator() => this;
|
public Select3<T1, T2, T3> GetEnumerator() => this;
|
||||||
|
|
||||||
public void Dispose() => _world.EndIteration();
|
public void Dispose() => _world.EndBatching();
|
||||||
|
|
||||||
private bool PassesWithout()
|
private bool PassesWithout()
|
||||||
{
|
{
|
||||||
|
|
@ -360,7 +360,7 @@ public ref struct Select4<T1, T2, T3, T4>
|
||||||
_count = min;
|
_count = min;
|
||||||
|
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_world.BeginIteration();
|
_world.BeginBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity Entity { get; private set; }
|
public Entity Entity { get; private set; }
|
||||||
|
|
@ -412,7 +412,7 @@ public ref struct Select4<T1, T2, T3, T4>
|
||||||
|
|
||||||
public Select4<T1, T2, T3, T4> GetEnumerator() => this;
|
public Select4<T1, T2, T3, T4> GetEnumerator() => this;
|
||||||
|
|
||||||
public void Dispose() => _world.EndIteration();
|
public void Dispose() => _world.EndBatching();
|
||||||
|
|
||||||
private bool PassesWithout()
|
private bool PassesWithout()
|
||||||
{
|
{
|
||||||
|
|
@ -466,7 +466,7 @@ public ref struct Select5<T1, T2, T3, T4, T5>
|
||||||
_count = min;
|
_count = min;
|
||||||
|
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_world.BeginIteration();
|
_world.BeginBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity Entity { get; private set; }
|
public Entity Entity { get; private set; }
|
||||||
|
|
@ -530,7 +530,7 @@ public ref struct Select5<T1, T2, T3, T4, T5>
|
||||||
|
|
||||||
public Select5<T1, T2, T3, T4, T5> GetEnumerator() => this;
|
public Select5<T1, T2, T3, T4, T5> GetEnumerator() => this;
|
||||||
|
|
||||||
public void Dispose() => _world.EndIteration();
|
public void Dispose() => _world.EndBatching();
|
||||||
|
|
||||||
private bool PassesWithout()
|
private bool PassesWithout()
|
||||||
{
|
{
|
||||||
|
|
@ -588,7 +588,7 @@ public ref struct Select6<T1, T2, T3, T4, T5, T6>
|
||||||
_count = min;
|
_count = min;
|
||||||
|
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_world.BeginIteration();
|
_world.BeginBatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity Entity { get; private set; }
|
public Entity Entity { get; private set; }
|
||||||
|
|
@ -666,7 +666,7 @@ public ref struct Select6<T1, T2, T3, T4, T5, T6>
|
||||||
|
|
||||||
public Select6<T1, T2, T3, T4, T5, T6> GetEnumerator() => this;
|
public Select6<T1, T2, T3, T4, T5, T6> GetEnumerator() => this;
|
||||||
|
|
||||||
public void Dispose() => _world.EndIteration();
|
public void Dispose() => _world.EndBatching();
|
||||||
|
|
||||||
private bool PassesWithout()
|
private bool PassesWithout()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue