From 07312f0f29fb9b0461efd83522b9bbf5342e878d Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Sun, 30 Aug 2026 09:51:17 +0200 Subject: [PATCH] Drop the duplicate Manager, use inkwell's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The engine already had the registry the game was reimplementing: inkwell.Manager[T Named], and every entity type here is an alias of an engine struct, so they all carry GetName() already. manager.manager.go and manager.interface.go are gone; a category manager is now one call to inkwell.NewManager. registerAll goes with it — Each does the hand-off — and the selector writes its derived pins back with Set, which keeps the scene in place in the registration order. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 49 ++++++++++++++------------------------- README.md | 32 ++++++++----------------- go.mod | 2 +- go.sum | 6 ++--- inc/background.manager.go | 2 +- inc/character.manager.go | 2 +- inc/content.manager.go | 10 ++++---- inc/dialog.manager.go | 2 +- inc/item.manager.go | 2 +- inc/manager.interface.go | 17 -------------- inc/manager.manager.go | 43 ---------------------------------- inc/scene.manager.go | 4 ++-- inc/scene.selector.go | 6 ++--- inc/script.manager.go | 2 +- inc/theme.manager.go | 4 ++-- 15 files changed, 47 insertions(+), 136 deletions(-) delete mode 100644 inc/manager.interface.go delete mode 100644 inc/manager.manager.go diff --git a/CLAUDE.md b/CLAUDE.md index 29c07d9..855ae52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,8 +58,6 @@ file's name carries the structure, in the form `[category].[name].go`. ``` main.go flags + inkwell.Run -inc/manager.manager.go Manager[T]: the generic registry every category uses -inc/manager.interface.go ManagerInterface and the compile-time assertions inc/boot.manager.go wiring; New(Opts) builds the game inc/names.manager.go entity names and world-state keys inc/theme.manager.go the theme manager and the colour helpers @@ -80,43 +78,30 @@ inc/scene.selector.go the map screen: its pins are derived from the graph inc/scene.*.go one file per scene ``` -`manager` is the one category with no entities of its own: it holds the registry -every other category is built from. - ## Managers Every category that owns a collection of entities has a manager, and they are -all the same generic type — `Manager[T]` in `manager.manager.go`. It keeps one -slice in registration order and one `map[string]int` beside it, so `GetByName` -is a map lookup, not a scan. +all the engine's own `inkwell.Manager[T]` — the same registry type the `*Game` +hangs its content off. The game defines no registry of its own. -Since the entity types are aliases of engine structs, no method can be attached -to them; the manager is told how to read a name instead, which is all it needs: +The entity types are aliases of engine structs, so they already carry +`GetName()` and satisfy `inkwell.Named`; the manager needs nothing else: ```go type Character = inkwell.Character -var CharacterManager = NewManager(func(entity Character) string { return entity.Name }) +var CharacterManager = inkwell.NewManager[Character]() ``` That is the whole of a category's manager file — an alias and one line. There are seven managers: `BackgroundManager`, `CharacterManager`, `DialogManager`, `ItemManager`, `ScriptManager`, `SceneManager`, `ThemeManager`. -`manager.interface.go` holds the contract they all keep, and asserts each one -against it. A new manager goes on that list. - -```go -type ManagerInterface[T any] interface { - Register(entity T) - GetByName(name string) (T, bool) - GetAll() []T -} -``` - -`Register` replaces by name and keeps the entity's position, so registering -twice is an update, never a duplicate. `GetAll` returns the slice itself, in -registration order. +The methods the game uses are `Register`, `Set`, `Get`, `All` and `Each`. +`Register` panics on a duplicate name — a second registration is a +construction-time bug, not an update — so rewriting an entity that is already +in the registry goes through `Set`, which keeps its position in the order. +`All` and `Each` both hand back the entities in registration order. Every entity type is an alias, `Scene` included. It was once a struct of our own, because inkwell's `Scene` could not carry exits; that gap was closed in the @@ -150,16 +135,16 @@ which is simply the order the files sit in. ### Handing a category to the engine -Once the game exists, `registerAll` walks a manager and gives every entity to -the engine's own manager. `registerContent` is the whole of it: +Once the game exists, `Each` walks a manager and gives every entity to the +engine's own manager. `registerContent` is the whole of it: ```go func registerContent() { - registerAll(BackgroundManager, World.G.AssetManager.Register) - registerAll(CharacterManager, World.G.CharacterManager.Register) - registerAll(ItemManager, World.G.ItemManager.Register) - registerAll(DialogManager, World.G.DialogueManager.Register) - registerAll(ScriptManager, World.G.ScriptManager.Register) + BackgroundManager.Each(World.G.AssetManager.Register) + CharacterManager.Each(World.G.CharacterManager.Register) + ItemManager.Each(World.G.ItemManager.Register) + DialogManager.Each(World.G.DialogueManager.Register) + ScriptManager.Each(World.G.ScriptManager.Register) registerScene() } ``` diff --git a/README.md b/README.md index 1142cb6..cc72224 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,6 @@ together — its manager, its shared types, its hand-off to the engine. ``` main.go flags + inkwell.Run -inc/manager.manager.go Manager[T], the generic registry -inc/manager.interface.go ManagerInterface, the contract every manager keeps inc/names.manager.go entity names and world-state keys inc/theme.*.go realworld-93 + nokia-punk inc/world.*.go unsaved runtime state, custom actions, action pump @@ -192,27 +190,17 @@ scene.*.go one file per scene: scene.alley.go, … + scene.selector.go Adding a scene means adding `scene..go` and `background..go`. Nothing else moves. -Every category owns a manager, and they are all the same generic type, -`Manager[T]` — one slice in registration order, one `map[string]int` beside it, -so a lookup by name is a map hit rather than a scan. The entity types are -aliases of engine structs and cannot carry methods, so the manager is handed a -function that reads the name instead. A category's manager file is an alias and -one line: +Every category owns a manager, and they are all the engine's own +`inkwell.Manager[T]` — the same registry the `*Game` hangs its content off, kept +in registration order and addressed by name. There is no second registry type +here: the entity types are aliases of engine structs, so they already satisfy +`inkwell.Named` and need no help reading their own name. A category's manager +file is an alias and one line: ```go type Character = inkwell.Character -var CharacterManager = NewManager(func(entity Character) string { return entity.Name }) -``` - -They all keep the same contract, asserted in `manager.interface.go`: - -```go -type ManagerInterface[T any] interface { - Register(entity T) - GetByName(name string) (T, bool) - GetAll() []T -} +var CharacterManager = inkwell.NewManager[Character]() ``` An entity file is a literal that hands itself over in an `init()`: @@ -235,9 +223,9 @@ step with the files by hand, and the deck is a thing to look at, not a thing to play through. The game's own catalogue is readable without going through the engine: -`SceneManager.GetByName("alley")` answers before a single scene has been handed -over. `registerContent` is where the hand-off happens, one `registerAll` call -per category. +`SceneManager.Get("alley")` answers before a single scene has been handed over. +`registerContent` is where the hand-off happens, one `Each` call per category — +the game's manager iterated straight into the engine manager's `Register`. There is one world, and it is a package-level singleton: `World`. Nothing takes a `*world` parameter and no widget holds a back-reference, which is what lets a diff --git a/go.mod b/go.mod index 1a049a7..df806e4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.teletypegames.org/games/realworld go 1.26.3 require ( - git.teletypegames.org/engines/inkwell v0.1.1-0.20260829220920-f9745e426624 + git.teletypegames.org/engines/inkwell v0.1.1-0.20260830075028-08f15a7e3d11 github.com/hajimehoshi/ebiten/v2 v2.9.9 ) diff --git a/go.sum b/go.sum index ee7b3b6..7591bee 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -git.teletypegames.org/engines/inkwell v0.1.0 h1:NJgT924aR3e0uLVRiTEhzLrXJY+Vnj+FArkKqMJJlVI= -git.teletypegames.org/engines/inkwell v0.1.0/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs= -git.teletypegames.org/engines/inkwell v0.1.1-0.20260829220920-f9745e426624 h1:lzKbik6RzcqIEub13X8a1J3V/MCQOPNo2XF8oOKm+Ts= -git.teletypegames.org/engines/inkwell v0.1.1-0.20260829220920-f9745e426624/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs= +git.teletypegames.org/engines/inkwell v0.1.1-0.20260830075028-08f15a7e3d11 h1:DPN74Xy0xcyelfh5xLuv6XBj6wrdzKVME1a1HyACsj4= +git.teletypegames.org/engines/inkwell v0.1.1-0.20260830075028-08f15a7e3d11/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs= github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 h1:+kz5iTT3L7uU+VhlMfTb8hHcxLO3TlaELlX8wa4XjA0= github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1/go.mod h1:lKJoeixeJwnFmYsBny4vvCJGVFc3aYDalhuDsfZzWHI= github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= diff --git a/inc/background.manager.go b/inc/background.manager.go index 0a9ef88..e870845 100644 --- a/inc/background.manager.go +++ b/inc/background.manager.go @@ -6,4 +6,4 @@ import ( type Background = inkwell.Asset -var BackgroundManager = NewManager(func(entity Background) string { return entity.Name }) +var BackgroundManager = inkwell.NewManager[Background]() diff --git a/inc/character.manager.go b/inc/character.manager.go index 4f8a38d..0e519b8 100644 --- a/inc/character.manager.go +++ b/inc/character.manager.go @@ -6,4 +6,4 @@ import ( type Character = inkwell.Character -var CharacterManager = NewManager(func(entity Character) string { return entity.Name }) +var CharacterManager = inkwell.NewManager[Character]() diff --git a/inc/content.manager.go b/inc/content.manager.go index 11ecd5e..bfddfb7 100644 --- a/inc/content.manager.go +++ b/inc/content.manager.go @@ -1,10 +1,10 @@ package inc func registerContent() { - registerAll(BackgroundManager, World.G.AssetManager.Register) - registerAll(CharacterManager, World.G.CharacterManager.Register) - registerAll(ItemManager, World.G.ItemManager.Register) - registerAll(DialogManager, World.G.DialogueManager.Register) - registerAll(ScriptManager, World.G.ScriptManager.Register) + BackgroundManager.Each(World.G.AssetManager.Register) + CharacterManager.Each(World.G.CharacterManager.Register) + ItemManager.Each(World.G.ItemManager.Register) + DialogManager.Each(World.G.DialogueManager.Register) + ScriptManager.Each(World.G.ScriptManager.Register) registerScene() } diff --git a/inc/dialog.manager.go b/inc/dialog.manager.go index 9d3236c..664addb 100644 --- a/inc/dialog.manager.go +++ b/inc/dialog.manager.go @@ -6,4 +6,4 @@ import ( type Dialog = inkwell.Dialogue -var DialogManager = NewManager(func(entity Dialog) string { return entity.Name }) +var DialogManager = inkwell.NewManager[Dialog]() diff --git a/inc/item.manager.go b/inc/item.manager.go index 7cf1497..50bf3b4 100644 --- a/inc/item.manager.go +++ b/inc/item.manager.go @@ -6,4 +6,4 @@ import ( type Item = inkwell.Item -var ItemManager = NewManager(func(entity Item) string { return entity.Name }) +var ItemManager = inkwell.NewManager[Item]() diff --git a/inc/manager.interface.go b/inc/manager.interface.go deleted file mode 100644 index fc771ee..0000000 --- a/inc/manager.interface.go +++ /dev/null @@ -1,17 +0,0 @@ -package inc - -type ManagerInterface[T any] interface { - Register(entity T) - GetByName(name string) (T, bool) - GetAll() []T -} - -var ( - _ ManagerInterface[Background] = BackgroundManager - _ ManagerInterface[Character] = CharacterManager - _ ManagerInterface[Dialog] = DialogManager - _ ManagerInterface[Item] = ItemManager - _ ManagerInterface[Script] = ScriptManager - _ ManagerInterface[Scene] = SceneManager - _ ManagerInterface[Theme] = ThemeManager -) diff --git a/inc/manager.manager.go b/inc/manager.manager.go deleted file mode 100644 index c16f128..0000000 --- a/inc/manager.manager.go +++ /dev/null @@ -1,43 +0,0 @@ -package inc - -type Manager[T any] struct { - nameOf func(T) string - entities []T - index map[string]int -} - -func NewManager[T any](nameOf func(T) string) *Manager[T] { - return &Manager[T]{ - nameOf: nameOf, - index: map[string]int{}, - } -} - -func (m *Manager[T]) Register(entity T) { - name := m.nameOf(entity) - if i, ok := m.index[name]; ok { - m.entities[i] = entity - return - } - m.index[name] = len(m.entities) - m.entities = append(m.entities, entity) -} - -func (m *Manager[T]) GetByName(name string) (T, bool) { - i, ok := m.index[name] - if !ok { - var missing T - return missing, false - } - return m.entities[i], true -} - -func (m *Manager[T]) GetAll() []T { - return m.entities -} - -func registerAll[T any](m *Manager[T], register func(T)) { - for _, entity := range m.GetAll() { - register(entity) - } -} diff --git a/inc/scene.manager.go b/inc/scene.manager.go index 829f4b3..8ecaa53 100644 --- a/inc/scene.manager.go +++ b/inc/scene.manager.go @@ -6,11 +6,11 @@ import ( type Scene = inkwell.Scene -var SceneManager = NewManager(func(entity Scene) string { return entity.Name }) +var SceneManager = inkwell.NewManager[Scene]() func registerScene() { fillSelectorPins() - registerAll(SceneManager, func(entity Scene) { + SceneManager.Each(func(entity Scene) { World.G.SceneManager.Register(sceneDefaults(entity)) }) } diff --git a/inc/scene.selector.go b/inc/scene.selector.go index 150540d..cec7db0 100644 --- a/inc/scene.selector.go +++ b/inc/scene.selector.go @@ -23,12 +23,12 @@ func init() { } func fillSelectorPins() { - selector, ok := SceneManager.GetByName(SceneSelector) + selector, ok := SceneManager.Get(SceneSelector) if !ok { return } var exits []inkwell.Exit - for _, entity := range SceneManager.GetAll() { + for _, entity := range SceneManager.All() { if !leadsToSelector(entity) { continue } @@ -39,7 +39,7 @@ func fillSelectorPins() { }) } selector.Exits = exits - SceneManager.Register(selector) + SceneManager.Set(selector) } func leadsToSelector(s Scene) bool { diff --git a/inc/script.manager.go b/inc/script.manager.go index 6e9ed36..e353e1d 100644 --- a/inc/script.manager.go +++ b/inc/script.manager.go @@ -6,4 +6,4 @@ import ( type Script = inkwell.Script -var ScriptManager = NewManager(func(entity Script) string { return entity.Name }) +var ScriptManager = inkwell.NewManager[Script]() diff --git a/inc/theme.manager.go b/inc/theme.manager.go index 768712b..f0d023c 100644 --- a/inc/theme.manager.go +++ b/inc/theme.manager.go @@ -8,7 +8,7 @@ import ( type Theme = inkwell.Theme -var ThemeManager = NewManager(func(entity Theme) string { return entity.Name }) +var ThemeManager = inkwell.NewManager[Theme]() const ( RealWorld = "realworld-93" @@ -34,5 +34,5 @@ func RGBA(hex uint32, a uint8) color.Color { } func registerTheme() { - registerAll(ThemeManager, World.G.ThemeManager.Register) + ThemeManager.Each(World.G.ThemeManager.Register) }