feat: implement auto-dirty-marking for query iterators
Introduce `TrackAccess` in `World` to record component access during batching scopes. Update `SelectN` iterators to provide `RefN` for mutable, tracked access and `ValN` for read-only, untracked access.
This commit is contained in:
parent
5d7eb14911
commit
1e8e4e1b38
|
|
@ -117,7 +117,7 @@ public class CommandTests
|
|||
var positions = new List<(float X, float Y, int Health)>();
|
||||
foreach (var it in world.Select<TestPosition, TestHealth>())
|
||||
{
|
||||
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<TestPosition>())
|
||||
{
|
||||
positions.Add((it.Item1.X, it.Item1.Y));
|
||||
positions.Add((it.Val1.X, it.Val1.Y));
|
||||
}
|
||||
|
||||
positions.Should().BeEquivalentTo([(10, 20)]);
|
||||
|
|
|
|||
|
|
@ -169,9 +169,9 @@ public class EdgeCaseTests
|
|||
foreach (var it in world.Select<Position, Velocity, Health, Frozen>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public class QueryTests
|
|||
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
||||
|
||||
foreach (var it in world.Select<Position>())
|
||||
it.Item1.X = 42;
|
||||
it.Ref1.X = 42;
|
||||
|
||||
ref var pos = ref world.GetComponent<Position>(entity);
|
||||
pos.X.Should().Be(42);
|
||||
|
|
@ -129,9 +129,9 @@ public class QueryTests
|
|||
foreach (var it in world.Select<Position, Velocity, Health>())
|
||||
{
|
||||
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<Position, Velocity>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ public class RelationshipTests
|
|||
foreach (var it in world.Select<Relationship<ChildOf, ParentOf>>())
|
||||
{
|
||||
results.Add(it.Entity);
|
||||
it.Item1.Target.Should().Be(parent);
|
||||
it.Val1.Target.Should().Be(parent);
|
||||
}
|
||||
|
||||
results.Should().BeEquivalentTo([child]);
|
||||
|
|
|
|||
|
|
@ -275,6 +275,17 @@ public class World : IDisposable
|
|||
return ref _components.Get<T>(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a component access for auto-dirty-marking without returning
|
||||
/// a ref. Used by <see cref="SelectN"/> iterators when they expose
|
||||
/// component refs directly from the sparse set.
|
||||
/// </summary>
|
||||
internal void TrackAccess(Entity entity, Type componentType)
|
||||
{
|
||||
if (_batchingDepth > 0)
|
||||
_accessedComponents.Add((entity, componentType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the component of type <typeparamref name="T"/>
|
||||
/// for the given entity. Never auto-marks as modified — use this when
|
||||
|
|
|
|||
|
|
@ -126,8 +126,25 @@ public ref struct Select1<T1> where T1 : struct
|
|||
/// <summary>The current enumerator (required for foreach duck-typing).</summary>
|
||||
public Select1<T1> Current => this;
|
||||
|
||||
/// <summary>Read-write reference to the current entity's component of type <typeparamref name="T1"/>.</summary>
|
||||
public ref T1 Item1 => ref _set!.Get(Entity);
|
||||
/// <summary>
|
||||
/// Mutable reference to the current entity's component of type <typeparamref name="T1"/>.
|
||||
/// Access is tracked for auto-dirty-marking during a batching scope.
|
||||
/// </summary>
|
||||
public ref T1 Ref1
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
_world.TrackAccess(Entity, typeof(T1));
|
||||
return ref _set!.Get(Entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read-only reference to the current entity's component of type <typeparamref name="T1"/>.
|
||||
/// Access is NOT tracked for auto-dirty-marking.
|
||||
/// </summary>
|
||||
public ref readonly T1 Val1 => ref _set!.Get(Entity);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
|
|
@ -193,8 +210,29 @@ public ref struct Select2<T1, T2> where T1 : struct where T2 : struct
|
|||
|
||||
public Entity Entity { get; private set; }
|
||||
public Select2<T1, T2> 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<T1, T2, T3>
|
|||
|
||||
public Entity Entity { get; private set; }
|
||||
public Select3<T1, T2, T3> 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<T1, T2, T3, T4>
|
|||
|
||||
public Entity Entity { get; private set; }
|
||||
public Select4<T1, T2, T3, T4> 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<T1, T2, T3, T4, T5>
|
|||
|
||||
public Entity Entity { get; private set; }
|
||||
public Select5<T1, T2, T3, T4, T5> 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<T1, T2, T3, T4, T5, T6>
|
|||
|
||||
public Entity Entity { get; private set; }
|
||||
public Select6<T1, T2, T3, T4, T5, T6> 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<T1, T2, T3, T4, T5, T6>
|
|||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue