From 5d7eb14911a3dffaa9a04369b84767e3c6729849 Mon Sep 17 00:00:00 2001 From: hypercross Date: Tue, 21 Jul 2026 10:09:40 +0800 Subject: [PATCH] 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 --- Game.TicTacToe.Tests/GameFlowTests.cs | 28 ++++++++++----------------- Game.TicTacToe.Tests/PlayTests.cs | 20 +++++++------------ Game.TicTacToe.Tests/SnapshotTests.cs | 11 +++-------- OECS/CommandQueue.cs | 11 +++++++++++ 4 files changed, 31 insertions(+), 39 deletions(-) diff --git a/Game.TicTacToe.Tests/GameFlowTests.cs b/Game.TicTacToe.Tests/GameFlowTests.cs index ba861cb..5140c7c 100644 --- a/Game.TicTacToe.Tests/GameFlowTests.cs +++ b/Game.TicTacToe.Tests/GameFlowTests.cs @@ -12,10 +12,9 @@ public class GameFlowTests { var (world, _) = SetupGame(); - var query = new Query().Without(); var emptyCount = 0; - using var iter = world.Select(query); - while (iter.MoveNext()) emptyCount++; + foreach (var _ in world.Select(new Query().Without())) + emptyCount++; emptyCount.Should().Be(9); } @@ -29,13 +28,12 @@ public class GameFlowTests group.RunLogical(); var markedCount = 0; - using var iter = world.Select(); - while (iter.MoveNext()) + foreach (var it in world.Select()) { markedCount++; - iter.Item1.Row.Should().Be(0); - iter.Item1.Col.Should().Be(0); - iter.Item2.Player.Should().Be(Player.X); + it.Item1.Row.Should().Be(0); + it.Item1.Col.Should().Be(0); + it.Item2.Player.Should().Be(Player.X); } 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. var marks = new List<(int Row, int Col, Player Player)>(); - using var iter3 = world.Select(); - while (iter3.MoveNext()) - { - marks.Add((iter3.Item1.Row, iter3.Item1.Col, iter3.Item2.Player)); - } + foreach (var it in world.Select()) + marks.Add((it.Item1.Row, it.Item1.Col, it.Item2.Player)); marks.Should().ContainSingle() .Which.Should().Be((0, 0, Player.X)); @@ -170,11 +165,8 @@ public class GameFlowTests state.MoveCount.Should().Be(2); var marks = new List<(int Row, int Col, Player Player)>(); - using var iter2 = world2.Select(); - while (iter2.MoveNext()) - { - marks.Add((iter2.Item1.Row, iter2.Item1.Col, iter2.Item2.Player)); - } + foreach (var it in world2.Select()) + marks.Add((it.Item1.Row, it.Item1.Col, it.Item2.Player)); marks.Should().BeEquivalentTo([(0, 0, Player.X), (1, 0, Player.O)]); } diff --git a/Game.TicTacToe.Tests/PlayTests.cs b/Game.TicTacToe.Tests/PlayTests.cs index 115d103..1a7f548 100644 --- a/Game.TicTacToe.Tests/PlayTests.cs +++ b/Game.TicTacToe.Tests/PlayTests.cs @@ -160,12 +160,9 @@ public class PlayTests { var sb = new System.Text.StringBuilder(); var grid = new char?[3, 3]; - using (var iter = world.Select()) - { - while (iter.MoveNext()) - grid[iter.Item1.Row, iter.Item1.Col] = - iter.Item2.Player == Player.X ? 'X' : 'O'; - } + foreach (var it in world.Select()) + grid[it.Item1.Row, it.Item1.Col] = + it.Item2.Player == Player.X ? 'X' : 'O'; for (int r = 0; r < 3; r++) { @@ -253,19 +250,16 @@ public class PlayTests private static List<(int Row, int Col)> GetEmptyCells(World world) { var empty = new List<(int, int)>(); - var query = new Query().Without(); - using var iter = world.Select(query); - while (iter.MoveNext()) - empty.Add((iter.Item1.Row, iter.Item1.Col)); + foreach (var it in world.Select(new Query().Without())) + empty.Add((it.Item1.Row, it.Item1.Col)); return empty; } private static bool WouldWin(World world, int row, int col, Player player) { var grid = new Player[3, 3]; - using (var iter = world.Select()) - while (iter.MoveNext()) - grid[iter.Item1.Row, iter.Item1.Col] = iter.Item2.Player; + foreach (var it in world.Select()) + grid[it.Item1.Row, it.Item1.Col] = it.Item2.Player; grid[row, col] = player; for (int r = 0; r < 3; r++) diff --git a/Game.TicTacToe.Tests/SnapshotTests.cs b/Game.TicTacToe.Tests/SnapshotTests.cs index d30c5ce..2673cab 100644 --- a/Game.TicTacToe.Tests/SnapshotTests.cs +++ b/Game.TicTacToe.Tests/SnapshotTests.cs @@ -208,14 +208,9 @@ public class SnapshotTests // Board: build a 3x3 grid. var grid = new char?[3, 3]; - using (var iter = world.Select()) - { - while (iter.MoveNext()) - { - grid[iter.Item1.Row, iter.Item1.Col] = - iter.Item2.Player == Player.X ? 'X' : 'O'; - } - } + foreach (var it in world.Select()) + grid[it.Item1.Row, it.Item1.Col] = + it.Item2.Player == Player.X ? 'X' : 'O'; int totalCells = 0; int markedCells = 0; diff --git a/OECS/CommandQueue.cs b/OECS/CommandQueue.cs index 1f52e2a..ea725a9 100644 --- a/OECS/CommandQueue.cs +++ b/OECS/CommandQueue.cs @@ -41,11 +41,18 @@ public class CommandQueue /// /// Executes all queued commands in FIFO order against the given world. /// + /// Commands run inside a batching scope — structural mutations are + /// deferred and 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 /// this call are also executed before the method returns. /// public void ExecuteAll(World world) { + world.BeginBatching(); int index = 0; while (index < _commands.Count) { @@ -60,9 +67,13 @@ public class CommandQueue { _errors.Add(ex); } + + // Flush after each command so chained commands see mutations. + world.FlushPendingMutations(); } _commands.Clear(); + world.EndBatching(); } ///