refactor: simplify entity lookups and clean up state updates

- Replace manual entity iteration with `world.FindEntity<T>()`
- Remove redundant `MarkModified<GameState>` 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
This commit is contained in:
2026-07-21 13:57:11 +08:00
parent 4cdfe9c957
commit cbb7edd472
12 changed files with 35 additions and 69 deletions
+1 -2
View File
@@ -8,7 +8,7 @@ namespace Game.Blackjack;
/// </summary>
public class DealSystem : ISystem
{
public void Run(World world)
public void RunImpl(World world)
{
var state = world.ReadSingleton<GameState>();
if (state.Phase != GamePhase.Dealing)
@@ -23,7 +23,6 @@ public class DealSystem : ISystem
HitCommand.DrawCard<DealerHand>(world);
mutableState.Phase = GamePhase.PlayerTurn;
world.MarkModified<GameState>(World.SingletonEntity);
}
}
+4 -3
View File
@@ -8,7 +8,7 @@ namespace Game.Blackjack;
/// </summary>
public class DealerSystem : ISystem
{
public void Run(World world)
public void RunImpl(World world)
{
var state = world.ReadSingleton<GameState>();
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<DealerHand>(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<GameState>(World.SingletonEntity);
}
}
+3 -4
View File
@@ -9,18 +9,17 @@ namespace Game.Blackjack;
/// </summary>
public class DeckSetupSystem : ISystem
{
public void Run(World world)
public void RunImpl(World world)
{
var state = world.ReadSingleton<GameState>();
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<Deck>())
{
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<GameState>();
var deckEntity2 = HitCommand.FindEntity<Deck>(world, singletonEntity);
var deckEntity2 = world.FindEntity<Deck>();
var cards = world.GetSources<InDeck>(deckEntity2).ToArray();
Shuffle(world, deckEntity2, cards, ref mutableState.Seed);
}
+1 -12
View File
@@ -22,18 +22,7 @@ public static class HandUtil
where T : struct
{
// Find the hand entity.
Entity handEntity = Entity.Null;
using (var iter = world.Select<T>())
{
while (iter.MoveNext())
{
if (iter.CurrentEntity != World.SingletonEntity)
{
handEntity = iter.CurrentEntity;
break;
}
}
}
var handEntity = world.FindEntity<T>();
if (handEntity == Entity.Null)
return 0;
@@ -8,7 +8,7 @@ namespace Game.Blackjack;
/// </summary>
public class PlayerBustCheckSystem : ISystem
{
public void Run(World world)
public void RunImpl(World world)
{
var state = world.ReadSingleton<GameState>();
if (state.Phase != GamePhase.PlayerTurn)
@@ -21,6 +21,5 @@ public class PlayerBustCheckSystem : ISystem
ref var mutableState = ref world.GetSingleton<GameState>();
mutableState.Phase = GamePhase.RoundOver;
mutableState.Result = RoundResult.PlayerBust;
world.MarkModified<GameState>(World.SingletonEntity);
}
}