diff --git a/OECS.Tests/CommandTests.cs b/OECS.Tests/CommandTests.cs index f49d58c..0050478 100644 --- a/OECS.Tests/CommandTests.cs +++ b/OECS.Tests/CommandTests.cs @@ -117,7 +117,7 @@ public class CommandTests var positions = new List<(float X, float Y, int Health)>(); foreach (var it in world.Select()) { - positions.Add((it.Item1.X, it.Item1.Y, it.Item2.Value)); + positions.Add((it.Val1.X, it.Val1.Y, it.Val2.Value)); } positions.Should().BeEquivalentTo([ @@ -152,7 +152,7 @@ public class CommandTests var positions = new List<(float X, float Y)>(); foreach (var it in world.Select()) { - positions.Add((it.Item1.X, it.Item1.Y)); + positions.Add((it.Val1.X, it.Val1.Y)); } positions.Should().BeEquivalentTo([(10, 20)]); diff --git a/OECS.Tests/EdgeCaseTests.cs b/OECS.Tests/EdgeCaseTests.cs index dc2a440..4b486a6 100644 --- a/OECS.Tests/EdgeCaseTests.cs +++ b/OECS.Tests/EdgeCaseTests.cs @@ -169,9 +169,9 @@ public class EdgeCaseTests foreach (var it in world.Select()) { count++; - it.Item1.X.Should().Be(1); - it.Item2.Y.Should().Be(4); - it.Item3.Value.Should().Be(100); + it.Val1.X.Should().Be(1); + it.Val2.Y.Should().Be(4); + it.Val3.Value.Should().Be(100); } count.Should().Be(1); } diff --git a/OECS.Tests/QueryAndSystemTests.cs b/OECS.Tests/QueryAndSystemTests.cs index bac8dff..203489a 100644 --- a/OECS.Tests/QueryAndSystemTests.cs +++ b/OECS.Tests/QueryAndSystemTests.cs @@ -90,7 +90,7 @@ public class QueryTests world.AddComponent(entity, new Position { X = 0, Y = 0 }); foreach (var it in world.Select()) - it.Item1.X = 42; + it.Ref1.X = 42; ref var pos = ref world.GetComponent(entity); pos.X.Should().Be(42); @@ -129,9 +129,9 @@ public class QueryTests foreach (var it in world.Select()) { count++; - it.Item1.X.Should().Be(1); - it.Item2.Y.Should().Be(4); - it.Item3.Value.Should().Be(100); + it.Val1.X.Should().Be(1); + it.Val2.Y.Should().Be(4); + it.Val3.Value.Should().Be(100); } count.Should().Be(1); @@ -166,8 +166,8 @@ public class SystemTests float dt = tick.DeltaTime; foreach (var it in world.Select()) { - it.Item1.X += it.Item2.X * dt; - it.Item1.Y += it.Item2.Y * dt; + it.Ref1.X += it.Val2.X * dt; + it.Ref1.Y += it.Val2.Y * dt; } } } diff --git a/OECS.Tests/RelationshipTests.cs b/OECS.Tests/RelationshipTests.cs index 2b20ea2..70f1669 100644 --- a/OECS.Tests/RelationshipTests.cs +++ b/OECS.Tests/RelationshipTests.cs @@ -191,7 +191,7 @@ public class RelationshipTests foreach (var it in world.Select>()) { results.Add(it.Entity); - it.Item1.Target.Should().Be(parent); + it.Val1.Target.Should().Be(parent); } results.Should().BeEquivalentTo([child]); diff --git a/OECS/World.cs b/OECS/World.cs index 44e0e3c..efa17ef 100644 --- a/OECS/World.cs +++ b/OECS/World.cs @@ -275,6 +275,17 @@ public class World : IDisposable return ref _components.Get(entity); } + /// + /// Tracks a component access for auto-dirty-marking without returning + /// a ref. Used by iterators when they expose + /// component refs directly from the sparse set. + /// + internal void TrackAccess(Entity entity, Type componentType) + { + if (_batchingDepth > 0) + _accessedComponents.Add((entity, componentType)); + } + /// /// Returns a copy of the component of type /// for the given entity. Never auto-marks as modified — use this when diff --git a/OECS/WorldQueryExtensions.cs b/OECS/WorldQueryExtensions.cs index bec8a8f..5d03770 100644 --- a/OECS/WorldQueryExtensions.cs +++ b/OECS/WorldQueryExtensions.cs @@ -126,8 +126,25 @@ public ref struct Select1 where T1 : struct /// The current enumerator (required for foreach duck-typing). public Select1 Current => this; - /// Read-write reference to the current entity's component of type . - public ref T1 Item1 => ref _set!.Get(Entity); + /// + /// Mutable reference to the current entity's component of type . + /// Access is tracked for auto-dirty-marking during a batching scope. + /// + public ref T1 Ref1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + _world.TrackAccess(Entity, typeof(T1)); + return ref _set!.Get(Entity); + } + } + + /// + /// Read-only reference to the current entity's component of type . + /// Access is NOT tracked for auto-dirty-marking. + /// + public ref readonly T1 Val1 => ref _set!.Get(Entity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() @@ -193,8 +210,29 @@ public ref struct Select2 where T1 : struct where T2 : struct public Entity Entity { get; private set; } public Select2 Current => this; - public ref T1 Item1 => ref _set1!.Get(Entity); - public ref T2 Item2 => ref _set2!.Get(Entity); + + public ref T1 Ref1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + _world.TrackAccess(Entity, typeof(T1)); + return ref _set1!.Get(Entity); + } + } + + public ref T2 Ref2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + _world.TrackAccess(Entity, typeof(T2)); + return ref _set2!.Get(Entity); + } + } + + public ref readonly T1 Val1 => ref _set1!.Get(Entity); + public ref readonly T2 Val2 => ref _set2!.Get(Entity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() @@ -273,9 +311,14 @@ public ref struct Select3 public Entity Entity { get; private set; } public Select3 Current => this; - public ref T1 Item1 => ref _set1!.Get(Entity); - public ref T2 Item2 => ref _set2!.Get(Entity); - public ref T3 Item3 => ref _set3!.Get(Entity); + + public ref T1 Ref1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T1)); return ref _set1!.Get(Entity); } } + public ref T2 Ref2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T2)); return ref _set2!.Get(Entity); } } + public ref T3 Ref3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T3)); return ref _set3!.Get(Entity); } } + + public ref readonly T1 Val1 => ref _set1!.Get(Entity); + public ref readonly T2 Val2 => ref _set2!.Get(Entity); + public ref readonly T3 Val3 => ref _set3!.Get(Entity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() @@ -365,10 +408,16 @@ public ref struct Select4 public Entity Entity { get; private set; } public Select4 Current => this; - public ref T1 Item1 => ref _set1!.Get(Entity); - public ref T2 Item2 => ref _set2!.Get(Entity); - public ref T3 Item3 => ref _set3!.Get(Entity); - public ref T4 Item4 => ref _set4!.Get(Entity); + + public ref T1 Ref1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T1)); return ref _set1!.Get(Entity); } } + public ref T2 Ref2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T2)); return ref _set2!.Get(Entity); } } + public ref T3 Ref3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T3)); return ref _set3!.Get(Entity); } } + public ref T4 Ref4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T4)); return ref _set4!.Get(Entity); } } + + public ref readonly T1 Val1 => ref _set1!.Get(Entity); + public ref readonly T2 Val2 => ref _set2!.Get(Entity); + public ref readonly T3 Val3 => ref _set3!.Get(Entity); + public ref readonly T4 Val4 => ref _set4!.Get(Entity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() @@ -471,11 +520,18 @@ public ref struct Select5 public Entity Entity { get; private set; } public Select5 Current => this; - public ref T1 Item1 => ref _set1!.Get(Entity); - public ref T2 Item2 => ref _set2!.Get(Entity); - public ref T3 Item3 => ref _set3!.Get(Entity); - public ref T4 Item4 => ref _set4!.Get(Entity); - public ref T5 Item5 => ref _set5!.Get(Entity); + + public ref T1 Ref1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T1)); return ref _set1!.Get(Entity); } } + public ref T2 Ref2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T2)); return ref _set2!.Get(Entity); } } + public ref T3 Ref3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T3)); return ref _set3!.Get(Entity); } } + public ref T4 Ref4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T4)); return ref _set4!.Get(Entity); } } + public ref T5 Ref5 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T5)); return ref _set5!.Get(Entity); } } + + public ref readonly T1 Val1 => ref _set1!.Get(Entity); + public ref readonly T2 Val2 => ref _set2!.Get(Entity); + public ref readonly T3 Val3 => ref _set3!.Get(Entity); + public ref readonly T4 Val4 => ref _set4!.Get(Entity); + public ref readonly T5 Val5 => ref _set5!.Get(Entity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() @@ -593,12 +649,20 @@ public ref struct Select6 public Entity Entity { get; private set; } public Select6 Current => this; - public ref T1 Item1 => ref _set1!.Get(Entity); - public ref T2 Item2 => ref _set2!.Get(Entity); - public ref T3 Item3 => ref _set3!.Get(Entity); - public ref T4 Item4 => ref _set4!.Get(Entity); - public ref T5 Item5 => ref _set5!.Get(Entity); - public ref T6 Item6 => ref _set6!.Get(Entity); + + public ref T1 Ref1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T1)); return ref _set1!.Get(Entity); } } + public ref T2 Ref2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T2)); return ref _set2!.Get(Entity); } } + public ref T3 Ref3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T3)); return ref _set3!.Get(Entity); } } + public ref T4 Ref4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T4)); return ref _set4!.Get(Entity); } } + public ref T5 Ref5 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T5)); return ref _set5!.Get(Entity); } } + public ref T6 Ref6 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { _world.TrackAccess(Entity, typeof(T6)); return ref _set6!.Get(Entity); } } + + public ref readonly T1 Val1 => ref _set1!.Get(Entity); + public ref readonly T2 Val2 => ref _set2!.Get(Entity); + public ref readonly T3 Val3 => ref _set3!.Get(Entity); + public ref readonly T4 Val4 => ref _set4!.Get(Entity); + public ref readonly T5 Val5 => ref _set5!.Get(Entity); + public ref readonly T6 Val6 => ref _set6!.Get(Entity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() @@ -679,4 +743,4 @@ public ref struct Select6 } return true; } -} \ No newline at end of file +}