register logic
This commit is contained in:
@@ -22,8 +22,8 @@ one-to-one.
|
||||
|
||||
### No tests
|
||||
|
||||
Do not write tests, and do not add test cases to the ones that already exist.
|
||||
Correctness is checked by running the game.
|
||||
Do not write tests and do not add test files. Correctness is checked by running
|
||||
the game.
|
||||
|
||||
### Struct literals are always multi-line
|
||||
|
||||
@@ -52,15 +52,19 @@ file's name carries the structure, in the form `[category].[name].go`.
|
||||
`character`, `dialog`, `script`, `world`, `ui`, `theme`, `names`, `boot`,
|
||||
`content`.
|
||||
- `[category].manager.go` is the file that ties a category together — its
|
||||
`register…` function, its shared types, its list of entities.
|
||||
manager, its entity type, whatever the category shares.
|
||||
- Every other file in a category holds exactly one entity: one screen, one
|
||||
background, one item.
|
||||
background, one item. The file registers it itself, in an `init()`.
|
||||
|
||||
```
|
||||
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 realworld-93 + nokia-punk
|
||||
inc/theme.manager.go the theme manager and the colour helpers
|
||||
inc/theme.realworld.go realworld-93
|
||||
inc/theme.nokia_punk.go nokia-punk
|
||||
inc/world.manager.go unsaved runtime state
|
||||
inc/world.action.go custom actions and the action pump
|
||||
inc/ui.manager.go HUD layout and widget registration
|
||||
@@ -71,25 +75,130 @@ inc/character.*.go the cast, tapes included
|
||||
inc/item.*.go inventory
|
||||
inc/dialog.*.go dialogue trees
|
||||
inc/script.*.go named action sequences
|
||||
inc/screen.manager.go the deck, the scene builder
|
||||
inc/screen.manager.go the Screen type, the deck, the scene builder
|
||||
inc/screen.exit.go the connections between screens
|
||||
inc/screen.*.go one file per screen
|
||||
```
|
||||
|
||||
`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.
|
||||
|
||||
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:
|
||||
|
||||
```go
|
||||
type Character = inkwell.Character
|
||||
|
||||
var CharacterManager = NewManager(func(entity Character) string { return entity.Name })
|
||||
```
|
||||
|
||||
That is the whole of a category's manager file — an alias and one line. There
|
||||
are seven managers: `BackgroundManager`, `CharacterManager`, `DialogManager`,
|
||||
`ItemManager`, `ScriptManager`, `ScreenManager`, `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.
|
||||
|
||||
`Screen` is the one entity type that is not an alias: a screen carries exits and
|
||||
an `OnSelector` flag that inkwell's `Scene` knows nothing about.
|
||||
|
||||
### Entities register themselves
|
||||
|
||||
An entity file is a literal and nothing else. No constructor function, no list
|
||||
somewhere else to keep in step — the file hands itself to its manager in an
|
||||
`init()`:
|
||||
|
||||
```go
|
||||
package inc
|
||||
|
||||
func init() {
|
||||
BackgroundManager.Register(Background{
|
||||
Name: BgServerFarm,
|
||||
Path: "assets/bg/server_farm.png",
|
||||
Kind: inkwell.AssetImage,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Adding an entity is adding a file. Deleting one is deleting a file. Package-level
|
||||
variables are initialised before any `init()` runs, so the managers exist by the
|
||||
time the first file registers into one.
|
||||
|
||||
`init()` order is file-name order, so **registration order is alphabetical**.
|
||||
Nothing may depend on it — including the order the arrow keys walk the screens,
|
||||
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:
|
||||
|
||||
```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)
|
||||
registerScreen()
|
||||
}
|
||||
```
|
||||
|
||||
`registerScreen` is the one that needs its own function, because a screen has to
|
||||
be turned into an `inkwell.Scene` first, and because the selector's pins are
|
||||
derived from whichever screens marked themselves `OnSelector`.
|
||||
|
||||
## The world
|
||||
|
||||
There is exactly one game, so there is exactly one world: `World`, a
|
||||
package-level singleton in `world.manager.go`. Nothing takes a `*world`
|
||||
parameter and no widget holds a back-reference — `World.Do(…)`,
|
||||
`World.HUDVisible()`, `World.Slot2()` are reachable from anywhere in the
|
||||
package. `New` calls `World.attach(g)`, which binds the engine and resets the
|
||||
runtime state, so building the game twice is clean.
|
||||
|
||||
This is what lets an entity file be a literal: the tape-insert script closes
|
||||
over `World`, not over a parameter it would have had to be handed.
|
||||
|
||||
## Naming
|
||||
|
||||
One package means one namespace, so an entity's constructor carries its
|
||||
category as a prefix:
|
||||
|
||||
```go
|
||||
screenAlley() backgroundAlley()
|
||||
itemMysteryTape() characterMysteryTape()
|
||||
dialogDexTalk() scriptTapeInsert()
|
||||
screenFloor screenBuild registerScreen fillSelectorPins
|
||||
```
|
||||
|
||||
Category-level functions follow the same shape: `registerScreen`,
|
||||
`registerBackground`, `registerItem`, and so on. Only `New` and `Opts` are
|
||||
exported — they are what `main.go` needs, and nothing else leaves the package.
|
||||
Entities themselves need no name at all — they are anonymous literals inside
|
||||
their file's `init()`, and the file name says which one it is.
|
||||
|
||||
Exported names are the authoring vocabulary — what a content file spells out:
|
||||
`New` and `Opts`, `World`, the managers and the entity types they hold, the
|
||||
constants in `names.manager.go`, the colour tokens, and the action constructors
|
||||
(`TapeSay`, `Paused`, `SetMode`, `EnterScene`, `Back`, `Fn`).
|
||||
|
||||
Everything else is machinery and stays unexported: the HUD widgets
|
||||
(`tapeSlots`, `letterbox`, `hudFrame`, …), the `register…` functions, the
|
||||
runners, `screenBuild`, `exit`.
|
||||
|
||||
The engine's word for a screen is `Scene`. Ours is **screen**, because the
|
||||
wiki, the concept-art deck and the beat tables all count screens. "Scene"
|
||||
@@ -112,14 +221,12 @@ other way round.
|
||||
|
||||
## Adding a screen
|
||||
|
||||
1. `inc/screen.<name>.go` — `screen<Name>() screen`
|
||||
2. `inc/background.<name>.go` — `background<Name>() inkwell.Asset`
|
||||
3. One line in `screenDeck` (`inc/screen.manager.go`)
|
||||
4. One line in `registerBackground` (`inc/background.manager.go`)
|
||||
5. Constants in `inc/names.manager.go`: `Screen<Name>`, `Bg<Name>`
|
||||
6. A 640×380 PNG in `assets/bg/`
|
||||
1. Constants in `inc/names.manager.go`: `Screen<Name>`, `Bg<Name>`
|
||||
2. `inc/screen.<name>.go` — an `init()` registering a `Screen`
|
||||
3. `inc/background.<name>.go` — an `init()` registering a `Background`
|
||||
4. A 640×380 PNG in `assets/bg/`
|
||||
|
||||
Nothing else moves.
|
||||
Nothing else moves. There is no list to update.
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
Reference in New Issue
Block a user