refactor: simplify iterator usage and improve command execution

- Replace manual `while (iter.MoveNext())` loops with `foreach` loops in
  tests
- Implement batching and mutation flushing in `CommandQueue.ExecuteAll`
  to
  ensure chained commands see each other's changes
This commit is contained in:
hypercross 2026-07-21 10:09:40 +08:00
parent d9cd943c52
commit 5d7eb14911
4 changed files with 31 additions and 39 deletions

View File

@ -12,10 +12,9 @@ public class GameFlowTests
{ {
var (world, _) = SetupGame(); var (world, _) = SetupGame();
var query = new Query<Cell>().Without<Mark>();
var emptyCount = 0; var emptyCount = 0;
using var iter = world.Select(query); foreach (var _ in world.Select(new Query<Cell>().Without<Mark>()))
while (iter.MoveNext()) emptyCount++; emptyCount++;
emptyCount.Should().Be(9); emptyCount.Should().Be(9);
} }
@ -29,13 +28,12 @@ public class GameFlowTests
group.RunLogical(); group.RunLogical();
var markedCount = 0; var markedCount = 0;
using var iter = world.Select<Cell, Mark>(); foreach (var it in world.Select<Cell, Mark>())
while (iter.MoveNext())
{ {
markedCount++; markedCount++;
iter.Item1.Row.Should().Be(0); it.Item1.Row.Should().Be(0);
iter.Item1.Col.Should().Be(0); it.Item1.Col.Should().Be(0);
iter.Item2.Player.Should().Be(Player.X); it.Item2.Player.Should().Be(Player.X);
} }
markedCount.Should().Be(1); markedCount.Should().Be(1);
@ -67,11 +65,8 @@ public class GameFlowTests
// Only one mark should exist at (0,0), and it should still be X. // Only one mark should exist at (0,0), and it should still be X.
var marks = new List<(int Row, int Col, Player Player)>(); var marks = new List<(int Row, int Col, Player Player)>();
using var iter3 = world.Select<Cell, Mark>(); foreach (var it in world.Select<Cell, Mark>())
while (iter3.MoveNext()) marks.Add((it.Item1.Row, it.Item1.Col, it.Item2.Player));
{
marks.Add((iter3.Item1.Row, iter3.Item1.Col, iter3.Item2.Player));
}
marks.Should().ContainSingle() marks.Should().ContainSingle()
.Which.Should().Be((0, 0, Player.X)); .Which.Should().Be((0, 0, Player.X));
@ -170,11 +165,8 @@ public class GameFlowTests
state.MoveCount.Should().Be(2); state.MoveCount.Should().Be(2);
var marks = new List<(int Row, int Col, Player Player)>(); var marks = new List<(int Row, int Col, Player Player)>();
using var iter2 = world2.Select<Cell, Mark>(); foreach (var it in world2.Select<Cell, Mark>())
while (iter2.MoveNext()) marks.Add((it.Item1.Row, it.Item1.Col, it.Item2.Player));
{
marks.Add((iter2.Item1.Row, iter2.Item1.Col, iter2.Item2.Player));
}
marks.Should().BeEquivalentTo([(0, 0, Player.X), (1, 0, Player.O)]); marks.Should().BeEquivalentTo([(0, 0, Player.X), (1, 0, Player.O)]);
} }

View File

@ -160,12 +160,9 @@ public class PlayTests
{ {
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
var grid = new char?[3, 3]; var grid = new char?[3, 3];
using (var iter = world.Select<Cell, Mark>()) foreach (var it in world.Select<Cell, Mark>())
{ grid[it.Item1.Row, it.Item1.Col] =
while (iter.MoveNext()) it.Item2.Player == Player.X ? 'X' : 'O';
grid[iter.Item1.Row, iter.Item1.Col] =
iter.Item2.Player == Player.X ? 'X' : 'O';
}
for (int r = 0; r < 3; r++) for (int r = 0; r < 3; r++)
{ {
@ -253,19 +250,16 @@ public class PlayTests
private static List<(int Row, int Col)> GetEmptyCells(World world) private static List<(int Row, int Col)> GetEmptyCells(World world)
{ {
var empty = new List<(int, int)>(); var empty = new List<(int, int)>();
var query = new Query<Cell>().Without<Mark>(); foreach (var it in world.Select(new Query<Cell>().Without<Mark>()))
using var iter = world.Select(query); empty.Add((it.Item1.Row, it.Item1.Col));
while (iter.MoveNext())
empty.Add((iter.Item1.Row, iter.Item1.Col));
return empty; return empty;
} }
private static bool WouldWin(World world, int row, int col, Player player) private static bool WouldWin(World world, int row, int col, Player player)
{ {
var grid = new Player[3, 3]; var grid = new Player[3, 3];
using (var iter = world.Select<Cell, Mark>()) foreach (var it in world.Select<Cell, Mark>())
while (iter.MoveNext()) grid[it.Item1.Row, it.Item1.Col] = it.Item2.Player;
grid[iter.Item1.Row, iter.Item1.Col] = iter.Item2.Player;
grid[row, col] = player; grid[row, col] = player;
for (int r = 0; r < 3; r++) for (int r = 0; r < 3; r++)

View File

@ -208,14 +208,9 @@ public class SnapshotTests
// Board: build a 3x3 grid. // Board: build a 3x3 grid.
var grid = new char?[3, 3]; var grid = new char?[3, 3];
using (var iter = world.Select<Cell, Mark>()) foreach (var it in world.Select<Cell, Mark>())
{ grid[it.Item1.Row, it.Item1.Col] =
while (iter.MoveNext()) it.Item2.Player == Player.X ? 'X' : 'O';
{
grid[iter.Item1.Row, iter.Item1.Col] =
iter.Item2.Player == Player.X ? 'X' : 'O';
}
}
int totalCells = 0; int totalCells = 0;
int markedCells = 0; int markedCells = 0;

View File

@ -41,11 +41,18 @@ public class CommandQueue
/// <summary> /// <summary>
/// Executes all queued commands in FIFO order against the given world. /// Executes all queued commands in FIFO order against the given world.
/// ///
/// Commands run inside a batching scope — structural mutations are
/// deferred and <see cref="World.GetComponent{T}"/> calls are
/// auto-tracked for modification until the drain completes.
/// Pending mutations are flushed after each command, so chained
/// commands see each other's changes within the same drain cycle.
///
/// The queue is fully drained — commands enqueued by other commands during /// The queue is fully drained — commands enqueued by other commands during
/// this call are also executed before the method returns. /// this call are also executed before the method returns.
/// </summary> /// </summary>
public void ExecuteAll(World world) public void ExecuteAll(World world)
{ {
world.BeginBatching();
int index = 0; int index = 0;
while (index < _commands.Count) while (index < _commands.Count)
{ {
@ -60,9 +67,13 @@ public class CommandQueue
{ {
_errors.Add(ex); _errors.Add(ex);
} }
// Flush after each command so chained commands see mutations.
world.FlushPendingMutations();
} }
_commands.Clear(); _commands.Clear();
world.EndBatching();
} }
/// <summary> /// <summary>