refactor(game): lock-free storage, remove /command, flatten engine wrapper
Three-stage refactor of the game-engine plumbing (game logic untouched): Stage 1 — lock-free persistence + admin serialisation. Remove the file lock from repo/fs (the .lock file, the Read/Write-vs-*Safe duality and the dead ReadSafe polling) and replace the two-step rename with a single atomic rename so concurrent reads are torn-free without a lock. Serialise the state-mutating admin writers (init/turn/banish) with one shared router LimitMiddleware, rewritten to block on the request context instead of a racy shared 100ms timer. Stage 2 — remove the obsolete immediate-command path end to end. Players submit through PUT /api/v1/order; the legacy PUT /api/v1/command path is deleted across game (route, handler, 24 command factories, Ctrl), backend (Commands handler/route, engineclient.ExecuteCommands), gateway (dispatch + executeUserGamesCommand + routing entry), the FlatBuffers/model contract (UserGamesCommand[Response]) and transcoder, plus every affected OpenAPI/README/FUNCTIONAL/ARCHITECTURE doc. The integration proxy test is converted to the order path. Stage 3 — flatten the REST->engine wrapper. Replace the executor adapter, the controller package functions and RepoController with one concrete controller.Service; drop the single-implementation Repo and Storage interfaces (repo.Repo / fs.FS are now concrete). Handlers depend on a thin handler.Engine seam and own the domain->REST projection; storage is resolved once at startup instead of per request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,12 +6,6 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MessageTypeUserGamesCommand is the authenticated gateway message type
|
||||
// used to send a batch of in-game commands to the engine through
|
||||
// `POST /api/v1/user/games/{game_id}/commands`. The signed payload is
|
||||
// a FlatBuffers `order.UserGamesCommand`.
|
||||
const MessageTypeUserGamesCommand = "user.games.command"
|
||||
|
||||
// MessageTypeUserGamesOrder is the authenticated gateway message type
|
||||
// used to validate / store a batch of in-game orders through
|
||||
// `POST /api/v1/user/games/{game_id}/orders`. The signed payload is a
|
||||
@@ -24,22 +18,12 @@ const MessageTypeUserGamesOrder = "user.games.order"
|
||||
// signed payload is a FlatBuffers `order.UserGamesOrderGet`.
|
||||
const MessageTypeUserGamesOrderGet = "user.games.order.get"
|
||||
|
||||
// UserGamesCommand is the typed payload of MessageTypeUserGamesCommand.
|
||||
// `GameID` selects the running engine container; `Commands` is the
|
||||
// player command batch executed atomically by the engine. The `Actor`
|
||||
// field present in the engine's JSON shape is rebuilt by backend from
|
||||
// the runtime player mapping — clients never carry it.
|
||||
type UserGamesCommand struct {
|
||||
// GameID identifies the running game for this batch.
|
||||
GameID uuid.UUID `json:"game_id"`
|
||||
|
||||
// Commands is the player command batch.
|
||||
Commands []DecodableCommand `json:"cmd"`
|
||||
}
|
||||
|
||||
// UserGamesOrder is the typed payload of MessageTypeUserGamesOrder.
|
||||
// Mirrors `UserGamesCommand` plus an `UpdatedAt` field that lets the
|
||||
// engine reject stale order submissions.
|
||||
// `GameID` selects the running engine container; `Commands` is the
|
||||
// player order batch; `UpdatedAt` lets the engine reject stale order
|
||||
// submissions. The `Actor` field present in the engine's JSON shape is
|
||||
// rebuilt by backend from the runtime player mapping — clients never
|
||||
// carry it.
|
||||
type UserGamesOrder struct {
|
||||
// GameID identifies the running game for this batch.
|
||||
GameID uuid.UUID `json:"game_id"`
|
||||
|
||||
@@ -4,13 +4,10 @@ import "encoding/json"
|
||||
|
||||
type Command struct {
|
||||
Actor string `json:"actor" binding:"notblank"`
|
||||
// Commands carries the engine-bound payload for either the
|
||||
// command (`PUT /api/v1/command`, immediate) or the order
|
||||
// (`PUT /api/v1/order`, validate-and-store) path. The order
|
||||
// path treats an empty array as "the player has no orders for
|
||||
// this turn" and stores it. The command handler still rejects
|
||||
// an empty array by hand because immediate execution of a
|
||||
// no-op makes no sense.
|
||||
// Commands carries the engine-bound payload for the order
|
||||
// (`PUT /api/v1/order`, validate-and-store) path. An empty array
|
||||
// means "the player has no orders for this turn" and is stored
|
||||
// as-is.
|
||||
Commands []json.RawMessage `json:"cmd"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user