From cbb7edd472c362997f49e8e7b38dcf0e0cb0cebe Mon Sep 17 00:00:00 2001 From: hypercross Date: Tue, 21 Jul 2026 13:57:11 +0800 Subject: [PATCH] refactor: simplify entity lookups and clean up state updates - Replace manual entity iteration with `world.FindEntity()` - Remove redundant `MarkModified` calls - Add safety counters to prevent infinite loops in tests and systems - Call `FlushPendingMutations` after card transfers to ensure consistency - Rename `Run` to `RunImpl` in several systems --- Game.Blackjack.Tests/GameFlowTests.cs | 19 +++++++--------- Game.Blackjack.Tests/PlayTests.cs | 10 ++++----- Game.Blackjack.Tests/SnapshotTests.cs | 8 +------ Game.Blackjack/Commands/HitCommand.cs | 22 ++++++++----------- Game.Blackjack/Commands/NewRoundCommand.cs | 10 +++------ Game.Blackjack/Commands/PlaceBetCommand.cs | 1 - Game.Blackjack/Commands/StandCommand.cs | 1 - Game.Blackjack/Systems/DealSystem.cs | 3 +-- Game.Blackjack/Systems/DealerSystem.cs | 7 +++--- Game.Blackjack/Systems/DeckSetupSystem.cs | 7 +++--- Game.Blackjack/Systems/HandUtil.cs | 13 +---------- .../Systems/PlayerBustCheckSystem.cs | 3 +-- 12 files changed, 35 insertions(+), 69 deletions(-) diff --git a/Game.Blackjack.Tests/GameFlowTests.cs b/Game.Blackjack.Tests/GameFlowTests.cs index c53e04a..96ca1ee 100644 --- a/Game.Blackjack.Tests/GameFlowTests.cs +++ b/Game.Blackjack.Tests/GameFlowTests.cs @@ -232,13 +232,7 @@ public class GameFlowTests private static Entity FindEntity(World world) where T : struct { - using var iter = world.Select(); - while (iter.MoveNext()) - { - if (iter.CurrentEntity != World.SingletonEntity) - return iter.CurrentEntity; - } - return Entity.Null; + return world.FindEntity(); } private static int CountCardsInHand(World world, Entity handEntity) @@ -251,11 +245,14 @@ public class GameFlowTests private static List<(Suit, Rank)> GetAllCards(World world) { var cards = new List<(Suit, Rank)>(); - var query = world.Query().With().Build(); - world.ForEach(query, (Entity e, ref Card card) => + using (var iter = world.Select()) { - cards.Add((card.Suit, card.Rank)); - }); + while (iter.MoveNext()) + { + var card = iter.Val1; + cards.Add((card.Suit, card.Rank)); + } + } cards.Sort((a, b) => { int cmp = a.Item1.CompareTo(b.Item1); diff --git a/Game.Blackjack.Tests/PlayTests.cs b/Game.Blackjack.Tests/PlayTests.cs index cf98df0..bf175e6 100644 --- a/Game.Blackjack.Tests/PlayTests.cs +++ b/Game.Blackjack.Tests/PlayTests.cs @@ -199,8 +199,10 @@ public class PlayTests // Player turn: agent decides hit/stand. int roundHits = 0; - while (world.ReadSingleton().Phase == GamePhase.PlayerTurn) + int playerTurnSafety = 0; + while (world.ReadSingleton().Phase == GamePhase.PlayerTurn && playerTurnSafety < 52) { + playerTurnSafety++; var total = HandUtil.CalculateHand(world, new PlayerHand()); var decision = agent.Decide(world); log.Decisions.Add($"R{totalRounds + 1} Hand={total}, Decision={decision}"); @@ -282,11 +284,7 @@ public class PlayTests private static Entity FindEntity(World world) where T : struct { - using var iter = world.Select(); - while (iter.MoveNext()) - if (iter.CurrentEntity != World.SingletonEntity) - return iter.CurrentEntity; - return Entity.Null; + return world.FindEntity(); } private static List GetHandCards(World world, Entity hand) diff --git a/Game.Blackjack.Tests/SnapshotTests.cs b/Game.Blackjack.Tests/SnapshotTests.cs index 39daf9c..b53310e 100644 --- a/Game.Blackjack.Tests/SnapshotTests.cs +++ b/Game.Blackjack.Tests/SnapshotTests.cs @@ -248,12 +248,6 @@ public class SnapshotTests private static Entity FindEntity(World world) where T : struct { - using var iter = world.Select(); - while (iter.MoveNext()) - { - if (iter.CurrentEntity != World.SingletonEntity) - return iter.CurrentEntity; - } - return Entity.Null; + return world.FindEntity(); } } diff --git a/Game.Blackjack/Commands/HitCommand.cs b/Game.Blackjack/Commands/HitCommand.cs index ebbad27..15b4d85 100644 --- a/Game.Blackjack/Commands/HitCommand.cs +++ b/Game.Blackjack/Commands/HitCommand.cs @@ -11,7 +11,7 @@ public struct HitCommand : ICommand { public void Execute(World world) { - ref var state = ref world.GetSingleton(); + var state = world.ReadSingleton(); if (state.Phase != GamePhase.PlayerTurn) return; @@ -25,9 +25,8 @@ public struct HitCommand : ICommand internal static void DrawCard(World world) where THand : struct { - var singletonEntity = World.SingletonEntity; - var deckEntity = FindEntity(world, singletonEntity); - var handEntity = FindEntity(world, singletonEntity); + var deckEntity = world.FindEntity(); + var handEntity = world.FindEntity(); if (deckEntity == Entity.Null || handEntity == Entity.Null) return; @@ -42,17 +41,14 @@ public struct HitCommand : ICommand // Remove from deck, add to hand. world.RemoveComponent(cardEntity); world.AddComponent(cardEntity, new Holds { Target = handEntity }); + + // Flush so subsequent DrawCard calls see the updated state. + world.FlushPendingMutations(); } - internal static Entity FindEntity(World world, Entity singletonEntity) + internal static Entity FindEntity(World world) where T : struct { - using var iter = world.Select(); - while (iter.MoveNext()) - { - if (iter.CurrentEntity != singletonEntity) - return iter.CurrentEntity; - } - return Entity.Null; + return world.FindEntity(); } -} \ No newline at end of file +} diff --git a/Game.Blackjack/Commands/NewRoundCommand.cs b/Game.Blackjack/Commands/NewRoundCommand.cs index fd705e0..390cd86 100644 --- a/Game.Blackjack/Commands/NewRoundCommand.cs +++ b/Game.Blackjack/Commands/NewRoundCommand.cs @@ -17,28 +17,25 @@ public struct NewRoundCommand : ICommand return; // Clear hands from previous round. - var singletonEntity = World.SingletonEntity; var handEntities = new List(); using (var iter = world.Select()) { while (iter.MoveNext()) { - if (iter.CurrentEntity != singletonEntity) - handEntities.Add(iter.CurrentEntity); + handEntities.Add(iter.Entity); } } using (var iter = world.Select()) { while (iter.MoveNext()) { - if (iter.CurrentEntity != singletonEntity) - handEntities.Add(iter.CurrentEntity); + handEntities.Add(iter.Entity); } } foreach (var hand in handEntities) { var cards = world.GetSources(hand); - var deckEntity = HitCommand.FindEntity(world, singletonEntity); + var deckEntity = world.FindEntity(); foreach (var card in cards) { world.RemoveComponent(card); @@ -49,6 +46,5 @@ public struct NewRoundCommand : ICommand state.RoundNumber++; state.Phase = GamePhase.Betting; state.Result = RoundResult.None; - world.MarkModified(World.SingletonEntity); } } \ No newline at end of file diff --git a/Game.Blackjack/Commands/PlaceBetCommand.cs b/Game.Blackjack/Commands/PlaceBetCommand.cs index 5cbfe02..38b63cd 100644 --- a/Game.Blackjack/Commands/PlaceBetCommand.cs +++ b/Game.Blackjack/Commands/PlaceBetCommand.cs @@ -24,6 +24,5 @@ public struct PlaceBetCommand : ICommand state.CurrentBet = Amount; state.Chips -= Amount; state.Phase = GamePhase.Dealing; - world.MarkModified(World.SingletonEntity); } } \ No newline at end of file diff --git a/Game.Blackjack/Commands/StandCommand.cs b/Game.Blackjack/Commands/StandCommand.cs index 0d8f146..806d0a4 100644 --- a/Game.Blackjack/Commands/StandCommand.cs +++ b/Game.Blackjack/Commands/StandCommand.cs @@ -17,6 +17,5 @@ public struct StandCommand : ICommand return; state.Phase = GamePhase.DealerTurn; - world.MarkModified(World.SingletonEntity); } } \ No newline at end of file diff --git a/Game.Blackjack/Systems/DealSystem.cs b/Game.Blackjack/Systems/DealSystem.cs index 6acce10..b2570b8 100644 --- a/Game.Blackjack/Systems/DealSystem.cs +++ b/Game.Blackjack/Systems/DealSystem.cs @@ -8,7 +8,7 @@ namespace Game.Blackjack; /// public class DealSystem : ISystem { - public void Run(World world) + public void RunImpl(World world) { var state = world.ReadSingleton(); if (state.Phase != GamePhase.Dealing) @@ -23,7 +23,6 @@ public class DealSystem : ISystem HitCommand.DrawCard(world); mutableState.Phase = GamePhase.PlayerTurn; - world.MarkModified(World.SingletonEntity); } } diff --git a/Game.Blackjack/Systems/DealerSystem.cs b/Game.Blackjack/Systems/DealerSystem.cs index c8f65a2..795111e 100644 --- a/Game.Blackjack/Systems/DealerSystem.cs +++ b/Game.Blackjack/Systems/DealerSystem.cs @@ -8,7 +8,7 @@ namespace Game.Blackjack; /// public class DealerSystem : ISystem { - public void Run(World world) + public void RunImpl(World world) { var state = world.ReadSingleton(); if (state.Phase != GamePhase.DealerTurn) @@ -18,10 +18,12 @@ public class DealerSystem : ISystem // Dealer must hit on 16 and below, stand on 17+. int dealerTotal = HandUtil.CalculateHand(world, DealerHandTag.Instance); - while (dealerTotal < 17) + int safety = 0; + while (dealerTotal < 17 && safety < 52) { HitCommand.DrawCard(world); dealerTotal = HandUtil.CalculateHand(world, DealerHandTag.Instance); + safety++; } int playerTotal = HandUtil.CalculateHand(world, PlayerHandTag.Instance); @@ -49,6 +51,5 @@ public class DealerSystem : ISystem mutableState.Chips += mutableState.CurrentBet; } - world.MarkModified(World.SingletonEntity); } } \ No newline at end of file diff --git a/Game.Blackjack/Systems/DeckSetupSystem.cs b/Game.Blackjack/Systems/DeckSetupSystem.cs index 27229d3..c76a73c 100644 --- a/Game.Blackjack/Systems/DeckSetupSystem.cs +++ b/Game.Blackjack/Systems/DeckSetupSystem.cs @@ -9,18 +9,17 @@ namespace Game.Blackjack; /// public class DeckSetupSystem : ISystem { - public void Run(World world) + public void RunImpl(World world) { var state = world.ReadSingleton(); if (state.Phase != GamePhase.Dealing) return; // Only create deck entities if they don't exist yet. - var singletonEntity = World.SingletonEntity; bool hasDeck = false; using (var iter = world.Select()) { - hasDeck = iter.MoveNext() && iter.CurrentEntity != singletonEntity; + hasDeck = iter.MoveNext(); } if (!hasDeck) @@ -50,7 +49,7 @@ public class DeckSetupSystem : ISystem // Shuffle the deck using mulberry32 with the current seed. ref var mutableState = ref world.GetSingleton(); - var deckEntity2 = HitCommand.FindEntity(world, singletonEntity); + var deckEntity2 = world.FindEntity(); var cards = world.GetSources(deckEntity2).ToArray(); Shuffle(world, deckEntity2, cards, ref mutableState.Seed); } diff --git a/Game.Blackjack/Systems/HandUtil.cs b/Game.Blackjack/Systems/HandUtil.cs index 32273ba..d7e973b 100644 --- a/Game.Blackjack/Systems/HandUtil.cs +++ b/Game.Blackjack/Systems/HandUtil.cs @@ -22,18 +22,7 @@ public static class HandUtil where T : struct { // Find the hand entity. - Entity handEntity = Entity.Null; - using (var iter = world.Select()) - { - while (iter.MoveNext()) - { - if (iter.CurrentEntity != World.SingletonEntity) - { - handEntity = iter.CurrentEntity; - break; - } - } - } + var handEntity = world.FindEntity(); if (handEntity == Entity.Null) return 0; diff --git a/Game.Blackjack/Systems/PlayerBustCheckSystem.cs b/Game.Blackjack/Systems/PlayerBustCheckSystem.cs index 4cc6051..0a41287 100644 --- a/Game.Blackjack/Systems/PlayerBustCheckSystem.cs +++ b/Game.Blackjack/Systems/PlayerBustCheckSystem.cs @@ -8,7 +8,7 @@ namespace Game.Blackjack; /// public class PlayerBustCheckSystem : ISystem { - public void Run(World world) + public void RunImpl(World world) { var state = world.ReadSingleton(); if (state.Phase != GamePhase.PlayerTurn) @@ -21,6 +21,5 @@ public class PlayerBustCheckSystem : ISystem ref var mutableState = ref world.GetSingleton(); mutableState.Phase = GamePhase.RoundOver; mutableState.Result = RoundResult.PlayerBust; - world.MarkModified(World.SingletonEntity); } } \ No newline at end of file