system

package
v0.16.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: LGPL-3.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AssembleFunc

type AssembleFunc func(ctx context.Context, resolverSource Source, raw []byte) (Definition, error)

AssembleFunc materializes raw bytes into a fully-loaded Definition: json.Unmarshal → Resolve (if implemented) → Validate (if implemented). Used both at initial load and at reconcile re-fetches so a snapshot-pinned version goes through the same hooks as a fresh load.

resolverSource is the Source the Resolver hook (if any) uses to fetch additional files. By convention this is always the local embed (never the operator), because Resolver-fetched files are heavy, designer-bundled, build-time assets that don't fit a "designer edits a row in a web UI" workflow.

func MakeAssemble

func MakeAssemble[T Definition]() AssembleFunc

MakeAssemble returns the standard assemble function for kind T: json.Unmarshal into a fresh T, run Resolve on a pointer (so mutations stick), run Validate on the value. Errors from any step propagate to the caller (LoadAll and Reconcile both panic on them).

type Definition

type Definition interface {
	// Name uniquely identifies this kind within the plugin's catalog (and, by convention,
	// matches Cardinal's component-name conventions: letters, digits, underscores).
	Name() string

	// JSONFile is the source-relative path Source.Fetch is called with at world init.
	JSONFile() string
}

Definition is the contract a config kind must satisfy to be loaded by the data plugin.

A Definition is whatever Go type the plugin unmarshals JSON into and stores in its in-memory catalog. The Name() value is the catalog key (game systems read via data.Get[T](plugin), which looks the value up under T's Name()). JSONFile() is the source-relative path the plugin hands to Source.Fetch at boot.

type AbilityData struct{ ID string; Cooldown float64 }
type Abilities struct{ Items []AbilityData }

func (Abilities) Name() string     { return "abilities" }
func (Abilities) JSONFile() string { return "abilities.json" }

Implement Name() and JSONFile() on value receivers — the plugin instantiates a zero T to read them before any pointer is taken.

type EmbedSource

type EmbedSource struct {
	FS embed.FS
}

EmbedSource serves files baked into the binary via go:embed. It is single-version by design — there is only one snapshot of the embedded files for the lifetime of a binary.

func (EmbedSource) Fetch

func (e EmbedSource) Fetch(_ context.Context, file, _ string) ([]byte, string, error)

Fetch reads file from the embedded filesystem and returns the bytes plus their sha256 hex digest. The hash argument is ignored — EmbedSource only has one version. Callers compare the returned hash to what they asked for to know whether they got the version they wanted.

type ReconcileState

type ReconcileState struct {
	cardinal.BaseSystemState
	Manifest cardinal.Exact[struct {
		Item cardinal.Ref[component.ConfigManifest]
	}]
}

ReconcileState is the system state for the data plugin's per-tick reconcile pass.

The embedded Exact search holds the ConfigManifest singleton; declaring this field is also what registers ConfigManifest with Cardinal via system-field reflection.

type Resolver

type Resolver interface {
	Resolve(ctx context.Context, source Source) error
}

Resolver is an optional interface a Definition may implement to perform a second-stage load after the primary JSON has been unmarshaled — typically to fetch additional designer-bundled files the JSON references (e.g. Tiled .tmj tilemaps that map_levels.json points at).

The Source handed to Resolve is ALWAYS the local embedded filesystem, never the operator. The reasoning: Resolver-fetched files are heavy build-time assets that don't fit a "designer edits a row in a web UI" workflow — they change when someone commits a new .tmj, which is a deploy event, not a database write. They are also NOT version-pinned in the snapshot manifest; an in-flight game resuming after a redeploy will see the current embedded files.

Resolve is called after Unmarshal and before the value is written to the catalog, so the receiver already has every field the JSON populated. Mutate the receiver in place. Returning an error panics — same failure mode as a fetch or unmarshal error.

Implement on a pointer receiver so mutations stick.

type Singleton added in v0.13.0

type Singleton interface {
	SingleObject()
}

Singleton is an optional marker a Definition may implement to declare its config is a single JSON object (e.g. {"maxPlayers":8}) rather than a collection of records: its source returns one object and its backing table holds at most one row.

SingleObject is never called — implementing it (on a value receiver, like Name/JSONFile) is the entire signal.

type Source

type Source interface {
	Fetch(ctx context.Context, file, hash string) (bytes []byte, gotHash string, err error)
}

Source supplies the raw bytes for a named file along with the content hash the source attaches to those bytes. Implementations keep the plugin dev/prod portable: tests and local dev use EmbedSource (build-time JSON), while PostgresSource reads config rows live from a database.

The hash argument selects which version to return:

  • "" — the version the source currently serves.
  • non-empty — a specific historical content hash (used on crash-restart to resume the snapshot's exact config). A versioned source that no longer holds the requested hash returns an error.

A single-version source (EmbedSource) ignores the requested hash and returns its current bytes; the caller compares the returned gotHash to the requested hash to detect that case.

func PickSource

func PickSource(fs embed.FS) Source

PickSource returns the Source the plugin should use: the embedded JSON via EmbedSource.

type State

type State struct {
	// contains filtered or unexported fields
}

State owns the data plugin's per-instance load state: registered kinds, the in-memory catalog, and the per-file manifest hashes currently reflected in that catalog.

The plugin facade (data.Plugin) holds a pointer to a State and forwards every operation here. Splitting it out keeps plugin.go a thin facade (matching the lobby / physics2d convention) and keeps all the load-and-reconcile logic colocated with the components it operates on.

func NewState

func NewState() *State

NewState builds an empty State. Call AddKind for each registered kind before LoadAll.

func (*State) AddKind

func (s *State) AddKind(name, jsonFile string, assemble AssembleFunc)

AddKind enrolls a kind into the catalog. Panics on duplicate Name or JSONFile — both are wiring bugs that would otherwise silently collide.

func (*State) Get

func (s *State) Get(name string) (Definition, bool)

Get looks up a kind in the catalog by name. Returns (value, true) on hit; (nil, false) on miss. The generic data.Get[T] wraps this and panics on miss with a typed message.

func (*State) LoadAll

func (s *State) LoadAll(ctx context.Context, primary, resolverSource Source)

LoadAll fetches every registered kind's JSON via primary, assembles it (handing resolverSource to any Resolver hook), and stores the result in the catalog. Records the per-file manifest hashes the primary source reported. Panics on any error — startup-time loading failures should be loud and immediate.

primary is the data source for each kind's JSONFile() (embed in dev, operator in prod). resolverSource is what Resolver hooks fetch additional files through — always the local embed, never the operator (see AssembleFunc doc).

Iterates jsonFile in sorted order so boot-time log output is reproducible across runs.

func (*State) MustGet

func (s *State) MustGet(name string) Definition

MustGet returns the loaded value for the kind keyed by name, panicking with a clear message if it was never registered. Used as the implementation of data.Get[T].

func (*State) Reconcile

func (s *State) Reconcile(rs *ReconcileState, primary, resolverSource Source)

Reconcile is the data plugin's per-tick reconcile pass. Runs every PreUpdate (cardinal's earliest tick hook), fast-paths the steady state, and on a post-restore mismatch attempts to re-fetch each changed file at the snapshot's hash so the world keeps running on the config it was running before the restart.

Lifecycle coverage with this one system:

  • Fresh boot: ErrSingleNoResult → create ConfigManifest from s.manifest.
  • Restart, same config: manifests match → no-op (one search + one map compare).
  • Restart, restored snapshot whose config differs: re-fetch each changed file at the snapshot's hash. Source can deliver → swap catalog atomically. Source errors (versioned source lost a version) → panic. Source returns wrong-hash content (single-version source, i.e. embed → rebuilt binary) → log a warning, keep current config, rewrite the component to s.manifest so the next snapshot is self-consistent and the warning doesn't re-fire.
  • cardinal.Reset()+Init() debug reload: wipes entities but Plugin.Register is not re-run, so the catalog persists. Next tick's reconcile sees ErrSingleNoResult and re-creates the ConfigManifest from s.manifest.
  • Restore from a pre-feature snapshot (no ConfigManifest): ErrSingleNoResult → create.

All-or-nothing: a versioned source delivers every needed historical file or it panics; a single-version source bails to the warn path on the first hash that doesn't match. No half-old/half-new catalog state.

primary is the data source for each kind's JSONFile() re-fetch at the snapshot's hash. resolverSource is what Resolver hooks fetch additional files through (always local embed).

type Validator

type Validator interface {
	Validate() error
}

Validator is an optional interface a Definition may implement to enforce post-load structural invariants — duplicate IDs, required fields, canonical enum values, cross-field rules, etc. The plugin calls Validate after Unmarshal (and after Resolve, if implemented), so the value seen by Validate is exactly what would be written to the catalog.

Returning an error panics. This is the right home for checks that the old per-kind loader systems used to enforce: fail loud at boot rather than letting a malformed JSON file produce a quietly-broken runtime.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL