refactor: simplify component iteration and singleton handling

Update the ECS engine to support automatic mutation tracking for
singletons during iteration, removing the need for manual
`MarkModified` calls.

- Update `ComponentDiscoveryGenerator` to detect singleton components.
- Refactor `PlaceMarkCommand` and `WinCheckSystem` to leverage
  automatic mutation marking.
- Replace `ForEach` usage with `Select` and `ItemN` access in tests
  and systems to align with the updated iterator API.
This commit is contained in:
2026-07-21 09:15:27 +08:00
parent b98e8d66af
commit 96d732d6ab
6 changed files with 76 additions and 56 deletions
+11 -12
View File
@@ -15,22 +15,23 @@ public struct PlaceMarkCommand : ICommand
public void Execute(World world)
{
ref var state = ref world.GetSingleton<GameState>();
if (state.Status != GameStatus.Playing)
// Read-only check before iteration — never auto-marks.
if (world.ReadSingleton<GameState>().Status != GameStatus.Playing)
return;
// Find the cell entity at (Row, Col) that has no Mark.
// Use the iterator API with early-exit via break.
var query = world.Query().With<Cell>().Without<Mark>().Build();
var query = new Query<Cell>().Without<Mark>();
Entity? target = null;
using var iter = world.Select<Cell>(query);
// Fetch singleton ref inside iteration so mutations are auto-marked.
using var iter = world.Select(query);
ref var state = ref world.GetSingleton<GameState>();
while (iter.MoveNext())
{
if (iter.Current1.Row == Row && iter.Current1.Col == Col)
if (iter.Item1.Row == Row && iter.Item1.Col == Col)
{
target = iter.CurrentEntity;
target = iter.Entity;
break;
}
}
@@ -38,13 +39,11 @@ public struct PlaceMarkCommand : ICommand
if (target == null)
return; // Cell already occupied or invalid position.
// Place the mark. ComponentAdded is sufficient — MarkModified
// is only needed when mutating an existing component via GetComponent<T>.
// Place the mark.
world.AddComponent(target.Value, new Mark { Player = state.CurrentPlayer });
// Advance turn.
// Advance turn. Mutations auto-marked via EndIteration — no MarkModified needed.
state.MoveCount++;
state.CurrentPlayer = state.CurrentPlayer == Player.X ? Player.O : Player.X;
world.MarkModified<GameState>(World.SingletonEntity);
}
}