From 69235cac5b3da5ff0dc20310e7474cabd8e63ba3 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 15 Sep 2026 20:14:02 -0400 Subject: feat: route all html editor API mutations through single owner task --- building_guide/concurrency.md | 58 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) (limited to 'building_guide/concurrency.md') diff --git a/building_guide/concurrency.md b/building_guide/concurrency.md index ff8c049..984a61d 100644 --- a/building_guide/concurrency.md +++ b/building_guide/concurrency.md @@ -43,6 +43,61 @@ inline on the caller's goroutine (legacy behavior), which is fine for synthetic tests but production Games always have the owner (started by `game.New`). +## Admin editor isolation + +All `/api/` routes run through one boundary in `internal/admin/owner.go`: + +1. Authenticate, read the request body (maximum 8 MiB) on the HTTP goroutine. +2. Submit one synchronous `Game.RunAdminOnOwner` task. The **whole handler** + runs there: validation against current files, plan construction, file + commits, runtime migration/eviction, store reloads, and undo history. +3. Write the buffered response on the HTTP goroutine after the task finishes. + +Reads use the same boundary so editor API reads cannot observe half an edit. +Neither slow uploads nor slow downloads hold the owner. File I/O does: large +bulk edits can briefly pause gameplay. This deliberately avoids a second +transaction scheduler, fine-grained locks, or optimistic plan retries. Use a +maintenance window for large world reorganizations. + +`RunAdminOnOwner` rejects requests during shutdown with HTTP 503, including +requests queued before shutdown but not yet started. It never executes on the +HTTP goroutine after the owner stops. A disconnected request is skipped if it +has not started; once started, its edit completes even if the browser leaves. +A failed/lost HTTP response is not proof that the edit did not commit. + +### Endpoint inventory and callback contract + +| API family (all behind the same boundary) | Mutations / live dependencies | +| --- | --- | +| `/rooms`, `/rooms/{id}`, `/rooms/new-empty` | Room create/update/delete, room index and runtime room state | +| `/rooms/link`, `/rooms/insert`, `/rooms/remove`, `/rooms/move`, `/rooms/rename`, `/rooms/swap` | Multi-file room plans, course changes, room-ID migration or eviction where applicable | +| `/rooms/bulk-edit`, `/rooms/bulk-delete`, `/rooms/resolve-duplicate` | Bulk room changes, indexes and runtime cleanup | +| `/room-dirs/create`, `/room-dirs/rename` | Room file organization and indexes | +| `/items`, `/mobs`, `/drops`, `/hazards` and their ID routes | Definitions plus existing cache/index invalidation; items also rebuild crafting | +| `/objects`, `/techs`, `/modules`, `/triggers` and their ID routes | Definition files; existing manual-reload semantics remain unchanged | +| Entity `/rename`, `/move`, `/dirs` routes | Definition file organization, undo history, existing cache invalidation | +| `/courses`, `/courses/{id}` | Course files, exit reconciliation, course store reload | +| `/files`, `/files/rename` | Raw file data and history; NOT a semantic room-ID migration or automatic cache reload | +| `/colors`, `/colors/reset` | Persisted config and live color maps | +| `/undo/undo`, `/undo/redo` | File images, room-ID migration, index/cache rebuilds, history together | +| Remaining API routes | Read-only queries; serialized to get coherent snapshots | + +Register new endpoints on `apiMux`, never outside this boundary. Do not wrap +individual callbacks in `RunOnOwner`: they are **already on the owner**. +`cmd/thoi/main.go` wires direct course reload, craft rebuild, casino eviction, +and `MigrateRoomIDsOnOwner` callbacks. `UndoStack`'s mutex protects history, +not gameplay: its apply path and the following rebuild must remain in the +same enclosing API task. Login password rehash saves and history clearing +also run on the owner, with expensive password hashing outside it. + +This guarantees **isolation from gameplay**, not new crash-atomicity or +rollback guarantees for every editor operation. Existing file transaction +and error behavior is unchanged. Raw Files edits and external filesystem +writes still bypass semantic validation/migrations; use structured room +operations for live renumbering, and stop the game for external bulk edits. +Some definition editors still need the in-game `reload` command; serialization +does not add missing cache invalidations or migrate existing spawned entities. + ## Durable money (casino wallet) Every wager and payout is journaled before the balance mutation @@ -135,7 +190,8 @@ Signals (SIGINT/SIGTERM) and the in-game `shutdown` command converge on Live room-ID changes (admin rename, swapid) execute as one mutation plan: the renamed room's file (id field, filename, self-loop references) plus every other room whose references moved, applied as a single journal transaction, -followed by the full runtime migration (`Game.MigrateRoomIDs`): player +followed within the same owner task by the full runtime migration +(`Game.MigrateRoomIDsOnOwner` for the editor): player state, Hub room buckets, casino room keys, world room-keyed state (ground items, seeded markers, object states), and live mob instances. Undo/redo store the whole plan (all before/after images plus the idMap) and apply the -- cgit v1.2.3