From 4a9402c02e591c2d2dd1ae354b70775bdbbfafa5 Mon Sep 17 00:00:00 2001 From: Jeremy Randolph Date: Tue, 16 Apr 2024 15:31:27 -0700 Subject: [PATCH] Remove recovery example, rename tests --- cardinal/init_test.go | 6 +-- cardinal/main.go | 1 - cardinal/recover_test.go | 83 ------------------------------ cardinal/system/default_spanwer.go | 7 ++- cardinal/system/player_spawner.go | 23 ++------- cardinal/system/recover.go | 36 ------------- cardinal/system_test.go | 18 +++---- 7 files changed, 22 insertions(+), 152 deletions(-) delete mode 100644 cardinal/recover_test.go delete mode 100644 cardinal/system/recover.go diff --git a/cardinal/init_test.go b/cardinal/init_test.go index ee92cde..0adc81d 100644 --- a/cardinal/init_test.go +++ b/cardinal/init_test.go @@ -12,9 +12,9 @@ import ( "github.com/argus-labs/starter-game-template/cardinal/component" ) -// TestDefaultPlayersAreCreatedOnInit ensures a set of default players are created in the SpawnDefaultPlayersSystem. -// These players should only be created on tick 0. -func TestDefaultPlayersAreCreatedOnInit(t *testing.T) { +// TestInitSystem_SpawnDefaultPlayersSystem_DefaultPlayersAreSpawned ensures a set of default players are created in the +// SpawnDefaultPlayersSystem. These players should only be created on tick 0. +func TestInitSystem_SpawnDefaultPlayersSystem_DefaultPlayersAreSpawned(t *testing.T) { tf := testutils.NewTestFixture(t, nil) MustInitWorld(tf.World) diff --git a/cardinal/main.go b/cardinal/main.go index f3dda01..592f869 100644 --- a/cardinal/main.go +++ b/cardinal/main.go @@ -51,7 +51,6 @@ func MustInitWorld(w *cardinal.World) { // For example, you may want to run the attack system before the regen system // so that the player's HP is subtracted (and player killed if it reaches 0) before HP is regenerated. Must(cardinal.RegisterSystems(w, - system.RecoverPlayersSystem, system.AttackSystem, system.RegenSystem, system.PlayerSpawnerSystem, diff --git a/cardinal/recover_test.go b/cardinal/recover_test.go deleted file mode 100644 index 4d905db..0000000 --- a/cardinal/recover_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "pkg.world.dev/world-engine/cardinal" - "pkg.world.dev/world-engine/cardinal/testutils" - - comp "github.com/argus-labs/starter-game-template/cardinal/component" - "github.com/argus-labs/starter-game-template/cardinal/msg" - "github.com/argus-labs/starter-game-template/cardinal/system" -) - -// TestRecoveryOfNonECSState ensures data can be successfully saved and recovered across Cardinal restarts. -// When Cardinal restarts (and the ECS DB is still populated), in-memory go objects will be wiped out. These in-memory -// objects must be rebuilt using the data inside of ECS to ensure consistent behavior across cardinal restarts. -// NOTE: It's perfectly fine to use Cardinal's ECS storage to keep track of all your game state; this System and -// related test is included in the starter-game-template as an example for how to do in-memory object recovery and -// properly test it. -func TestRecoveryOfNonECSState(t *testing.T) { - tf := testutils.NewTestFixture(t, nil) - MustInitWorld(tf.World) - - tf.DoTick() - - // Make some players. - for i := 0; i < 10; i++ { - tf.AddTransaction(getCreateMsgID(t, tf.World), msg.CreatePlayerMsg{ - Nickname: fmt.Sprintf("player-%d", i), - }) - - tf.DoTick() - } - - wCtx := cardinal.NewReadOnlyWorldContext(tf.World) - // Make sure we can find those 10 players in our non-ecs data structure. - for i := 0; i < 10; i++ { - target := fmt.Sprintf("player-%d", i) - id, ok := system.PlayerNameToID[target] - if !ok { - t.Fatalf("failed to find player %q in non-ecs storage", target) - } - p, err := cardinal.GetComponent[comp.Player](wCtx, id) - if err != nil { - t.Fatalf("failed to get player %q: %v", target, err) - } - if p.Nickname != target { - t.Fatalf("player nickname does not match: got %q want %q", p.Nickname, target) - } - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - // Simulate Cardinal Restart: Save the now-populated redis DB for use in another text fixture. // - // In addition, clear the in-memory go object (PlayerNameToID) to simulate a freshly restarted // - // Cardinal process. // - // ////////////////////////////////////////////////////////////////////////////////////////////// - originalRedis := tf.Redis - system.PlayerNameToID = nil - - // Create a new test fixture using the redis DB from the original test fixture. - tf = testutils.NewTestFixture(t, originalRedis) - MustInitWorld(tf.World) - - // The recover system must be given a chance to run. - tf.DoTick() - wCtx = cardinal.NewReadOnlyWorldContext(tf.World) - // Make sure we can STILL find those 10 players in our non-ecs data structure. - for i := 0; i < 10; i++ { - target := fmt.Sprintf("player-%d", i) - id, ok := system.PlayerNameToID[target] - if !ok { - t.Fatalf("failed to find player %q in non-ecs storage", target) - } - p, err := cardinal.GetComponent[comp.Player](wCtx, id) - if err != nil { - t.Fatalf("failed to get player %q: %v", target, err) - } - if p.Nickname != target { - t.Fatalf("player lookup does not match expectation: got %q want %q", p.Nickname, target) - } - } -} diff --git a/cardinal/system/default_spanwer.go b/cardinal/system/default_spanwer.go index c250c5b..f135dcb 100644 --- a/cardinal/system/default_spanwer.go +++ b/cardinal/system/default_spanwer.go @@ -4,6 +4,8 @@ import ( "fmt" "pkg.world.dev/world-engine/cardinal" + + comp "github.com/argus-labs/starter-game-template/cardinal/component" ) // SpawnDefaultPlayersSystem creates 10 players with nicknames "default-[0-9]". This System is registered as an @@ -11,7 +13,10 @@ import ( func SpawnDefaultPlayersSystem(world cardinal.WorldContext) error { for i := 0; i < 10; i++ { name := fmt.Sprintf("default-%d", i) - _, err := createPlayer(world, name) + _, err := cardinal.Create(world, + comp.Player{Nickname: name}, + comp.Health{HP: InitialHP}, + ) if err != nil { return err } diff --git a/cardinal/system/player_spawner.go b/cardinal/system/player_spawner.go index d31cc5a..0f8df2d 100644 --- a/cardinal/system/player_spawner.go +++ b/cardinal/system/player_spawner.go @@ -5,7 +5,6 @@ import ( "pkg.world.dev/world-engine/cardinal" "pkg.world.dev/world-engine/cardinal/message" - "pkg.world.dev/world-engine/cardinal/types" comp "github.com/argus-labs/starter-game-template/cardinal/component" "github.com/argus-labs/starter-game-template/cardinal/msg" @@ -21,7 +20,10 @@ func PlayerSpawnerSystem(world cardinal.WorldContext) error { return cardinal.EachMessage[msg.CreatePlayerMsg, msg.CreatePlayerResult]( world, func(create message.TxData[msg.CreatePlayerMsg]) (msg.CreatePlayerResult, error) { - id, err := createPlayer(world, create.Msg.Nickname) + id, err := cardinal.Create(world, + comp.Player{Nickname: create.Msg.Nickname}, + comp.Health{HP: InitialHP}, + ) if err != nil { return msg.CreatePlayerResult{}, fmt.Errorf("error creating player: %w", err) } @@ -36,20 +38,3 @@ func PlayerSpawnerSystem(world cardinal.WorldContext) error { return msg.CreatePlayerResult{Success: true}, nil }) } - -// createPlayer creates a player with the given name, and sets the player's HP to the InitialHP value. -// It also updates the PlayerNameToID global variable that maintains a mapping of player names to Entity IDs. -func createPlayer(world cardinal.WorldContext, name string) (types.EntityID, error) { - id, err := cardinal.Create(world, - comp.Player{Nickname: name}, - comp.Health{HP: InitialHP}, - ) - if err != nil { - return 0, err - } - if PlayerNameToID == nil { - PlayerNameToID = map[string]types.EntityID{} - } - PlayerNameToID[name] = id - return id, nil -} diff --git a/cardinal/system/recover.go b/cardinal/system/recover.go deleted file mode 100644 index 3ae7385..0000000 --- a/cardinal/system/recover.go +++ /dev/null @@ -1,36 +0,0 @@ -package system - -import ( - "pkg.world.dev/world-engine/cardinal" - "pkg.world.dev/world-engine/cardinal/search/filter" - "pkg.world.dev/world-engine/cardinal/types" - - comp "github.com/argus-labs/starter-game-template/cardinal/component" -) - -// PlayerNameToID maps a player's name to the relevant entity ID. This information is stored in a global variable which -// is NOT a part of the ECS data model. Care must be taken to 1) ensure the data remains consistent with the data -// contained in the ECS data model and 2) the data is recovered from ECS when Cardinal restarts. -var PlayerNameToID map[string]types.EntityID - -// RecoverPlayersSystem demonstrates a recovery system pattern. The system will be executed every game tick, however -// the main logic of the system will only be executed once each time Cardinal restarts. This pattern can be used to -// maintain non-ECS state. This recovery step is required to ensure the in-memory data (which is wiped out when Cardinal -// restarts) is maintained across cardinal restarts. These recovery systems should be registered BEFORE any other -// systems that may depend on the in-memory information. -func RecoverPlayersSystem(world cardinal.WorldContext) error { - if PlayerNameToID != nil { - return nil - } - // Ensure this recovery step only happens once each time Cardinal is started. - PlayerNameToID = map[string]types.EntityID{} - - return cardinal.NewSearch(world, filter.Contains(comp.Player{})).Each(func(id types.EntityID) bool { - player, err := cardinal.GetComponent[comp.Player](world, id) - if err != nil { - panic(err) - } - PlayerNameToID[player.Nickname] = id - return true - }) -} diff --git a/cardinal/system_test.go b/cardinal/system_test.go index c23d012..b2b6192 100644 --- a/cardinal/system_test.go +++ b/cardinal/system_test.go @@ -18,9 +18,9 @@ const ( createMsgName = "game.create-player" ) -// TestErrorWhenAttackTargetDoesNotExist ensures the attack message results in an error when the given target does -// not exist. Note, message errors are stored in receipts; they are NOT returned from the relevant system. -func TestErrorWhenAttackTargetDoesNotExist(t *testing.T) { +// TestSystem_AttackSystem_ErrorWhenTargetDoesNotExist ensures the attack message results in an error when the given +// target does not exist. Note, message errors are stored in receipts; they are NOT returned from the relevant system. +func TestSystem_AttackSystem_ErrorWhenTargetDoesNotExist(t *testing.T) { tf := testutils.NewTestFixture(t, nil) MustInitWorld(tf.World) @@ -36,9 +36,9 @@ func TestErrorWhenAttackTargetDoesNotExist(t *testing.T) { } } -// TestCanCreatePlayer ensure the CreatePlayer message can be used to create a new player with the default amount of -// health. cardinal.NewSearch is used to find the newly created player. -func TestCanCreatePlayers(t *testing.T) { +// TestSystem_PlayerSpawnerSystem_CanCreatePlayer ensures the CreatePlayer message can be used to create a new player +// with the default amount of health. cardinal.NewSearch is used to find the newly created player. +func TestSystem_PlayerSpawnerSystem_CanCreatePlayer(t *testing.T) { tf := testutils.NewTestFixture(t, nil) MustInitWorld(tf.World) @@ -75,9 +75,9 @@ func TestCanCreatePlayers(t *testing.T) { } } -// TestAttackingTargetReducesTheirHealth ensures an attack message can find an existing target the reduce the target's -// health. -func TestAttackingTargetReducesTheirHealth(t *testing.T) { +// TestSystem_AttackSystem_AttackingTargetReducesTheirHealth ensures an attack message can find an existing target the +// reduce the target's health. +func TestSystem_AttackSystem_AttackingTargetReducesTheirHealth(t *testing.T) { tf := testutils.NewTestFixture(t, nil) MustInitWorld(tf.World)