diff --git a/OECS/ISystem.cs b/OECS/ISystem.cs
index 4ed3d14..9582c4f 100644
--- a/OECS/ISystem.cs
+++ b/OECS/ISystem.cs
@@ -2,12 +2,44 @@ namespace OECS;
///
/// A system that runs on every tick.
-/// Systems receive the world in their method.
+/// Implement with the system logic.
+/// Call to execute with batching.
///
public interface ISystem
{
///
- /// Executes the system logic for this tick.
+ /// Implement this with the system's logic for this tick.
+ /// The caller (e.g., ) wraps this
+ /// in a batching scope so mutations and component accesses
+ /// are automatically tracked.
///
- void Run(World world);
+ void RunImpl(World world);
+}
+
+///
+/// Extension methods for running systems with proper batching.
+///
+public static class SystemExtensions
+{
+ ///
+ /// Runs a system with a batching scope. Structural mutations are
+ /// buffered, and calls are
+ /// auto-tracked for modification until the system returns.
+ ///
+ public static void Run(this ISystem system, World world)
+ {
+ world.BeginBatching();
+ system.RunImpl(world);
+ world.EndBatching();
+ }
+
+ ///
+ /// Runs a ticked system with a batching scope.
+ ///
+ public static void Run(this ITickedSystem system, World world, Tick tick)
+ {
+ world.BeginBatching();
+ system.RunImpl(world, tick);
+ world.EndBatching();
+ }
}
\ No newline at end of file
diff --git a/OECS/ITickedSystem.cs b/OECS/ITickedSystem.cs
index 7d1addc..757be8f 100644
--- a/OECS/ITickedSystem.cs
+++ b/OECS/ITickedSystem.cs
@@ -7,7 +7,7 @@ namespace OECS;
public interface ITickedSystem : ISystem
{
///
- /// Executes the system logic with tick information.
+ /// Implement this with the system's ticked logic.
///
- void Run(World world, Tick tick);
+ void RunImpl(World world, Tick tick);
}
\ No newline at end of file
diff --git a/OECS/SystemGroup.cs b/OECS/SystemGroup.cs
index bc4dbc1..d110d67 100644
--- a/OECS/SystemGroup.cs
+++ b/OECS/SystemGroup.cs
@@ -62,6 +62,7 @@ public class SystemGroup
// sees their effects.
_world.ExecuteCommands();
+ _world.BeginBatching();
foreach (var system in _systems)
{
if (system is ITickedSystem ticked)
@@ -77,10 +78,15 @@ public class SystemGroup
// 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();
diff --git a/OECS/World.cs b/OECS/World.cs
index 19bdcc2..44e0e3c 100644
--- a/OECS/World.cs
+++ b/OECS/World.cs
@@ -19,10 +19,11 @@ public class World : IDisposable
private readonly Dictionary _singletonEntities = new();
private bool _disposed;
- // Deferred structural mutation support: when iterating entities,
- // AddComponent, RemoveComponent, and DestroyEntity are buffered and
- // applied after the iteration completes.
- private int _iterationDepth;
+ // Deferred structural mutation support: when inside a batching scope
+ // (e.g., a foreach iteration or a system run), AddComponent,
+ // RemoveComponent, and DestroyEntity are buffered and applied when
+ // the outermost scope ends.
+ private int _batchingDepth;
private readonly List _pendingMutations = new();
private enum PendingMutationKind { AddComponent, RemoveComponent, DestroyEntity }
@@ -35,10 +36,10 @@ public class World : IDisposable
public Type ComponentType;
}
- // Auto-dirty-marking: during iteration, component accesses via
- // GetComponent are tracked. When the iteration scope ends,
- // all accessed components are automatically marked as modified.
- // Outside of iteration, MarkModified must be called explicitly.
+ // Auto-dirty-marking: during a batching scope, component accesses via
+ // GetComponent are tracked. When the scope ends, all accessed
+ // components are automatically marked as modified.
+ // Outside of a batching scope, MarkModified must be called explicitly.
private readonly HashSet<(Entity Entity, Type ComponentType)> _accessedComponents = new();
private readonly HashSet<(Entity Entity, Type ComponentType)> _markedModified = new();
@@ -86,7 +87,7 @@ public class World : IDisposable
///
public void DestroyEntity(Entity entity)
{
- if (_iterationDepth > 0)
+ if (_batchingDepth > 0)
{
_pendingMutations.Add(new PendingMutation
{
@@ -157,7 +158,7 @@ public class World : IDisposable
///
public void AddComponent(Entity entity, T component) where T : struct
{
- if (_iterationDepth > 0)
+ if (_batchingDepth > 0)
{
_pendingMutations.Add(new PendingMutation
{
@@ -226,7 +227,7 @@ public class World : IDisposable
///
public void RemoveComponent(Entity entity) where T : struct
{
- if (_iterationDepth > 0)
+ if (_batchingDepth > 0)
{
_pendingMutations.Add(new PendingMutation
{
@@ -269,7 +270,7 @@ public class World : IDisposable
///
public ref T GetComponent(Entity entity) where T : struct
{
- if (_iterationDepth > 0)
+ if (_batchingDepth > 0)
_accessedComponents.Add((entity, typeof(T)));
return ref _components.Get(entity);
}
@@ -465,23 +466,25 @@ public class World : IDisposable
// ── Iteration Lifecycle ──────────────────────────────────────────
///
- /// Begins an iteration scope. Structural mutations are buffered
- /// until is called.
+ /// Begins a batching scope. Structural mutations (AddComponent,
+ /// RemoveComponent, DestroyEntity) are buffered until the outermost
+ /// call. Component accesses via
+ /// are auto-tracked for modification.
///
- internal void BeginIteration()
+ internal void BeginBatching()
{
- _iterationDepth++;
+ _batchingDepth++;
}
///
- /// 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
/// component modifications are posted.
///
- internal void EndIteration()
+ internal void EndBatching()
{
- _iterationDepth--;
- if (_iterationDepth == 0)
+ _batchingDepth--;
+ if (_batchingDepth == 0)
{
// Auto-mark all components accessed via GetComponent
// during the iteration as modified.
@@ -499,7 +502,7 @@ public class World : IDisposable
}
}
- private void FlushPendingMutations()
+ internal void FlushPendingMutations()
{
if (_pendingMutations.Count == 0)
return;
diff --git a/OECS/WorldQueryExtensions.cs b/OECS/WorldQueryExtensions.cs
index 811cc20..bec8a8f 100644
--- a/OECS/WorldQueryExtensions.cs
+++ b/OECS/WorldQueryExtensions.cs
@@ -117,7 +117,7 @@ public ref struct Select1 where T1 : struct
_set = _store.GetSet(typeof(T1)) as SparseSet;
_count = _set?.Count ?? 0;
_index = -1;
- _world.BeginIteration();
+ _world.BeginBatching();
}
/// The current entity handle.
@@ -147,7 +147,7 @@ public ref struct Select1 where T1 : struct
public void Dispose()
{
- _world.EndIteration();
+ _world.EndBatching();
}
private bool PassesWithout()
@@ -188,7 +188,7 @@ public ref struct Select2 where T1 : struct where T2 : struct
_swapped = c2 < c1;
_count = _swapped ? c2 : c1;
_index = -1;
- _world.BeginIteration();
+ _world.BeginBatching();
}
public Entity Entity { get; private set; }
@@ -221,7 +221,7 @@ public ref struct Select2 where T1 : struct where T2 : struct
public Select2 GetEnumerator() => this;
- public void Dispose() => _world.EndIteration();
+ public void Dispose() => _world.EndBatching();
private bool PassesWithout()
{
@@ -268,7 +268,7 @@ public ref struct Select3
else { _driver = 0; _count = c1; }
_index = -1;
- _world.BeginIteration();
+ _world.BeginBatching();
}
public Entity Entity { get; private set; }
@@ -310,7 +310,7 @@ public ref struct Select3
public Select3 GetEnumerator() => this;
- public void Dispose() => _world.EndIteration();
+ public void Dispose() => _world.EndBatching();
private bool PassesWithout()
{
@@ -360,7 +360,7 @@ public ref struct Select4
_count = min;
_index = -1;
- _world.BeginIteration();
+ _world.BeginBatching();
}
public Entity Entity { get; private set; }
@@ -412,7 +412,7 @@ public ref struct Select4
public Select4 GetEnumerator() => this;
- public void Dispose() => _world.EndIteration();
+ public void Dispose() => _world.EndBatching();
private bool PassesWithout()
{
@@ -466,7 +466,7 @@ public ref struct Select5
_count = min;
_index = -1;
- _world.BeginIteration();
+ _world.BeginBatching();
}
public Entity Entity { get; private set; }
@@ -530,7 +530,7 @@ public ref struct Select5
public Select5 GetEnumerator() => this;
- public void Dispose() => _world.EndIteration();
+ public void Dispose() => _world.EndBatching();
private bool PassesWithout()
{
@@ -588,7 +588,7 @@ public ref struct Select6
_count = min;
_index = -1;
- _world.BeginIteration();
+ _world.BeginBatching();
}
public Entity Entity { get; private set; }
@@ -666,7 +666,7 @@ public ref struct Select6
public Select6 GetEnumerator() => this;
- public void Dispose() => _world.EndIteration();
+ public void Dispose() => _world.EndBatching();
private bool PassesWithout()
{