diff options
| author | historia <[not public]> | 2026-09-15 15:35:52 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-15 15:35:52 -0400 |
| commit | 0b0b25222b3131ada79ff78c28eb8bf07d977deb (patch) | |
| tree | 2ceaef5203595044f66a8f00064cd8e017f8f48d /cmd | |
| parent | 396c3facb8287b9a34076cae8f3d7b300d366d99 (diff) | |
| download | thehouseoficarus-0b0b25222b3131ada79ff78c28eb8bf07d977deb.tar.gz | |
feat: single-owner game state, transactional lifecycle, durable money on server stop
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/thoi/main.go | 36 |
1 files changed, 29 insertions, 7 deletions
diff --git a/cmd/thoi/main.go b/cmd/thoi/main.go index cc4785d..2031cb6 100644 --- a/cmd/thoi/main.go +++ b/cmd/thoi/main.go @@ -51,7 +51,9 @@ func main() { // body — the canonical per-tick system sequence — is enforced and tested in // internal/game/tick_systems.go and documented in // building_guide/tick_order.md. Reordering it is a breaking change. - g.Ticks.Subscribe(1, func() bool { + // SubscribeTicks executes the body on the game's owner goroutine so the + // whole tick sequence serializes against session input and admin tasks. + g.SubscribeTicks(1, func() bool { g.RunTickSystems() return true }) @@ -62,8 +64,23 @@ func main() { } g.SetHub(server.Hub()) + // Admin mutation callbacks execute on the game's owner goroutine: the + // admin HTTP goroutine must not touch live players or world state. + // MigrateRoomIDs is the complete room-ID migration (players, Hub + // membership, casino room keys, world state, mob instances). + adminRewriteRoomIDs := g.MigrateRoomIDs + adminRebuildCraftIndex := func() { + g.RunOnOwner(func() { g.RebuildCraftIndex() }) + } + adminEvictRoom := func(roomID int) { + g.RunOnOwner(func() { g.EvictCasinoRoom(roomID) }) + } + adminReloadCourses := func() { + g.RunOnOwner(func() { g.CourseStore.ReloadAll() }) + } + if cfg.AdminHTTP.Enabled { - adminSrv, err := admin.NewServer(cfg, false, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, g.CourseStore.ReloadAll, g.RewriteRoomIDs, g.RebuildCraftIndex, *configPath) + adminSrv, err := admin.NewServer(cfg, false, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, adminReloadCourses, adminRewriteRoomIDs, adminEvictRoom, adminRebuildCraftIndex, *configPath) if err != nil { log.Fatalf("failed to start admin HTTP server: %v", err) } @@ -76,7 +93,7 @@ func main() { } if cfg.AdminHTTPS.Enabled { - adminSrv, err := admin.NewServer(cfg, true, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, g.CourseStore.ReloadAll, g.RewriteRoomIDs, g.RebuildCraftIndex, *configPath) + adminSrv, err := admin.NewServer(cfg, true, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, adminReloadCourses, adminRewriteRoomIDs, adminEvictRoom, adminRebuildCraftIndex, *configPath) if err != nil { log.Fatalf("failed to start admin HTTPS server: %v", err) } @@ -105,14 +122,19 @@ func main() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + // One shutdown request handler: signals and the in-game shutdown + // command both converge on RequestShutdown, which drains work, + // resolves outstanding wagers, saves owned state, then closes the + // transports. ListenAndServe returns after Shutdown, and main returns + // normally (no os.Exit; a final-save failure is logged and the exit + // code reports it via the transport error path). + g.SetTransportStop(server.Shutdown) go func() { <-sigCh - log.Println("Shutting down...") - g.Ticks.Stop() - os.Exit(0) + g.RequestShutdown("signal") }() - if err := server.ListenAndServe(g.HandleSession); err != nil { + if err := server.ListenAndServe(g.HandleSessionOnOwner); err != nil { log.Fatalf("server error: %v", err) } } |
