diff options
| author | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
| commit | 153acb7f644d755c45533b7c5311e3ffa47e8973 (patch) | |
| tree | 7645e6f7b65bc3830b078e3b9336d2e6b199d584 /cmd/thoi/main.go | |
| parent | 87d08f0151e643951769e323995f1c5ed89ee200 (diff) | |
| download | thehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz | |
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'cmd/thoi/main.go')
| -rw-r--r-- | cmd/thoi/main.go | 26 |
1 files changed, 21 insertions, 5 deletions
diff --git a/cmd/thoi/main.go b/cmd/thoi/main.go index a07fbcc..cff6eb6 100644 --- a/cmd/thoi/main.go +++ b/cmd/thoi/main.go @@ -42,8 +42,6 @@ func main() { } g := game.New(dataDir, &cfg.DefaultColors, &cfg.ConstantColors, cfg.StartupValidation, cfg.StartingRoom) - g.Ticks.Start(cfg.TickLength) - defer g.Ticks.Stop() // The master per-tick subscriber. Registered as engine subscription ID 1, // the engine fires it before any dynamically-registered combat/aggro/respawn @@ -63,6 +61,12 @@ func main() { log.Fatalf("failed to start server: %v", err) } g.SetHub(server.Hub()) + // Start the tick engine only after the hub exists: every tick system reads + // g.Hub on the owner goroutine, so SetHub must happen-before the first + // tick. Starting earlier left a window (net.NewServer generates its RSA + // keys in between) in which a tick could observe a nil hub. + g.Ticks.Start(cfg.TickLength) + defer g.Ticks.Stop() // The entire editor API request is one owner task, including file commits, // runtime migration, undo history and reloads. These callbacks already run @@ -77,6 +81,9 @@ func main() { if err != nil { log.Fatalf("failed to start admin HTTP server: %v", err) } + // The raw file editor must refuse a file whose character/account is + // online; wired before ListenAndServe so the first request sees it. + adminSrv.SetPlayerFileOnlineCheck(g.AdminPlayerFileOnline) go func() { if err := adminSrv.ListenAndServe(); err != nil { log.Printf("admin HTTP server error: %v", err) @@ -90,6 +97,9 @@ func main() { if err != nil { log.Fatalf("failed to start admin HTTPS server: %v", err) } + // Same online-file refusal as the HTTP editor: both servers expose + // the same raw file API. + adminSrv.SetPlayerFileOnlineCheck(g.AdminPlayerFileOnline) go func() { if err := adminSrv.ListenAndServe(); err != nil { log.Printf("admin HTTPS server error: %v", err) @@ -118,9 +128,8 @@ func main() { // 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). + // transports. ListenAndServe returns after Shutdown; main then reports + // a failed final save with a non-zero exit (see below). g.SetTransportStop(server.Shutdown) go func() { <-sigCh @@ -130,4 +139,11 @@ func main() { if err := server.ListenAndServe(g.HandleSessionOnOwner); err != nil { log.Fatalf("server error: %v", err) } + // The transports close inside the shutdown sequence, so a clean return + // from ListenAndServe is where the final save is known to have finished. + // It needs its own report: the transport error path can only say that + // the server stopped, never that online state failed to persist. + if err := g.ShutdownSaveError(); err != nil { + log.Fatalf("shutdown: final save failed: %v", err) + } } |
