Modules / Game flow & UI

scenemanagerplugin + addon

Many truths in one process, a seat moved between them, and the page's one client for its scene.

Putting it in a scene

Every game is built on it, a game of one scene too, because it is what gives a connection a seat. The manager scene lists this plugin alone and holds the table of scenes. Each scene is a scene file of its own, with its own rows, world numbers, seed counter and step counter.

{
  "plugins": ["scenemanager"],
  "seats": 1,
  "prefabs": {
    "engine": { "dt": 0.016667 },
    "scenemanager": {
      "scenes": {
        "title": { "file": "title.scene.json", "suspend": true },
        "arena": { "file": "arena.scene.json", "suspend": false, "enter": { "kind": "arena", "act": "welcome" } }
      },
      "dt": 0.016667,
      "entry": "title",
      "port": 8123
    }
  },
  "actors": [{ "is": "scenemanager" }]
}

Each scene names scenemanager in its plugins, has the same prefab with an empty out and an actor for it, and an engine prefab whose width and height size the page's canvases:

{
  "plugins": ["scenemanager", "player", "ui"],
  "prefabs": {
    "engine": { "dt": 0.016667, "width": 640, "height": 360 },
    "scenemanager": { "out": {} }
  },
  "actors": [{ "is": "scenemanager" }]
}

A rule moves a seat to another scene with go, handing on any arguments:

w.call(w.find('scenemanager'), 'go', seat, 'arena', 3)

A game's files

FileWhat it holds
<game>.scene.jsonThe manager scene above.
<game>.sim.tsExports sim = { scenemanager }, with scenemanager from #plugins/scenemanager/scenemanager.sim.ts.
<scene>.scene.json, <scene>.sim.tsOne pair for each scene. The sim imports the scene file and exports it as scene beside sim. Its sim names every kind the scene makes rows of, including a kind whose prefab only the manager's file gives.
<kind>.sim.tsFor each kind in a sim that is not a plugin's: its rule, exported under the kind's name.
index.htmlRuns the manager and the game in one tab, so a second tab is a second game. It makes a canvas for the game's renderer and one for ui, each the size of the scene's engine prefab, and the page's CSS puts them in one grid cell so the UI lies over the game.
client.htmlThe same page against a server at ws://<host>:<port>, stepping nothing itself. Two seats on one game are two tabs of it. Chrome pauses a background tab, so of two tabs in one window only the one in front plays; the server keeps stepping.
<game>.server.tsServes the manager on its port and steps it by engine.dt as often as the clock owes a step.
assets/The game's art and data files. Every file the game reads is an entry of the scene's loader catalogue, world.loader.assets. An image the UI draws is an entry of its own with "for": "ui", and a file both the UI and the game draw is two entries.
plugins/<name>/, addons/<name>/Plugins and addons of the game's own, laid out as the top-level ones are. The game's own plugins are named in world.shipped.plugins.

A scene's level is a Tiled map, the tilemap entry its world.tilemap names, and nothing in the map makes a row. Everything that has a row is an actor, from the scene file's actors and then from the list its actorsFile names.

Properties

The manager's row

The scenemanager prefab of the manager scene.

FieldMeaning
scenesScene name to an entry (below).
dtSeconds each scene's step is given. Required.
entryThe name of the scene every connection enters. Required, and one of scenes.
portThe port the server listens on and client.html dials. Each game has a port no other game uses.
backlogHow many packets back a seat's changes may reach. A whole number of 1 or more, 32 when left out.

A scene's entry

FieldMeaning
fileThe scene file, ending in .scene.json, relative to the manager scene.
suspendRequired. With false the scene steps on every step of the manager. With true it steps only while a seat is on it and stops at the step it is at when the last seat leaves, carrying on from there when someone enters. As the last seat leaves, the scene is stepped once more, so whatever it does when a seat leaves is done.
enterOptional { kind, act }. A seat that enters is told to act on the first row of that kind at the start of the scene's next step, before the before of any kind. The act must be listed in the kind's open, because the seat does not possess that row.

A scene's row

The scenemanager prefab of each scene.

FieldMeaning
outSeat number to { scene, args }: the scene each seat goes to next and the arguments that go with it. Start it as {}. Seats never receive it.
namesThe names of the manager's scenes. It is written into the prefab when the scene loads, and go refuses any other name.

A scene file

A scene's prefabs are the manager's with its own over them, name by name, so a prefab every scene uses is written once, in the manager's file. Its world is made the same way, key by key, so a plugin's fixed numbers, world.<plugin>, are written once in the manager's world and a scene that gives the key has its own.

KeyMeaning
extendsThe path of a partial, <name>.partial_scene.json, relative to the scene file (see Partials).
actorsFileThe name of a <level>.actors.json beside the map, a list of actors entries. Its actors come after the scene file's own.

Partials

Scenes played on one place, such as a skirmish and its missions, share a partial: a scene file that is never a scene of its own and is not in the manager's scenes. A scene that names it in extends holds only what is its own, and is loaded as follows.

PartWhat the scene gets
world, prefabsThe partial's, with the scene's merged over them field by field at every depth, the scene's value winning. A list is replaced whole, not merged. The manager's are then under both, key by key at the top.
actorsThe partial's, then the scene's own, then those of its actorsFile.
Everything else, plugins, seats and actorsFile among themThe scene's own. The partial's are not read.

A partial that names extends itself stops the load. Importing a scene file, or reading the scene its sim exports, gives the scene's own part without its partial; child (below) gives it whole.

{
  "extends": "highlands.partial_scene.json",
  "plugins": ["loader", "scenemanager", "player", "terrain3d", "render3d"],
  "world": { "exit": "harbour" },
  "actors": [{ "is": "start", "slot": 0, "pos": [4, 0, 12] }]
}

World values written for a scene

Read from world.scenemanager. They are never sent in a packet.

SettingMeaning
.actorsThe scene's list of actors, copied whole. A rule that starts the level over spawns its cast again from these entries.
.homeThe game's folder as a url, where a mod of one of the game's own plugins is found (see Mods).

Seats

A connection is one seat on one scene at a time. It enters the entry scene when it is made and moves when that scene writes the seat's next scene into out. A seat number changes every time the seat enters a scene, the same scene included.

CaseWhat happens
A scene that names playerGets its seat from that plugin, which spawns the row the seat possesses and marks it mine for its own seat. A scene that does not, a menu say, takes a plain seat that may still send the acts its kinds list as open.
Arguments to goReach the new scene's player row as its arrive act at the start of the scene's next step. When what a seat needs there is a value the last scene chose, the player prefab holds that field as null or -1, and the row's before spawns nothing until arrive has written it.
Every seat of the scene named is takenThe slot is cleared and the seat stays where it is, as if it had not asked. The scene may send it again.
A seat sent to the scene it is onAlways goes, and comes back as a seat of another number.
A name that is not one of namesWrites nothing, and the seat stays where it is.
Messages from a scene the seat has leftAre dropped.

The wire between a page and the manager is the one link is given: loopback when the page holds the manager in the same tab, or socket across a WebSocket. Only the rows added, changed and dropped since the last packet the page confirmed cross it, so a step where nothing moved crosses as next to nothing.

Methods

Acts

Open acts of the scenemanager row. A rule calls them with w.call, and a seat may send them.

go( seat, name, ...args )

Sends a seat to the scene name once this scene's step is over, with args arriving at the new scene's player row. seat is the seat's number on this scene, the number out is keyed by, not the slot on its player row. An act passes the seat it was handed. A hook has no seat, so a player row keeps the number its arrive act was handed, for a hook to pass.

On the page

Through plugins.scenemanager. The page makes one link, which is one seat, and holds one client on it for the scene the seat is on. When the seat moves, the client is closed (every actor removed, every plugin closed, the renderers letting go of their canvases and WebGL contexts) and a new one is made for the scene the seat moved to.

go( name, ...args )

Names the next scene for this page's seat, as a seat sending go to this scene's row does. Call it after the first tick.

here : boolean

Whether the seat is still on this client's scene. It is false from the moment the link carries a packet of another scene, so a key pressed before the page has closed this client sends nothing to a scene the seat has left.

link : Link

The page's link, which outlives every client the page makes. Its store is the table the store plugin reads and writes.

Addon functions

From #addons/scenemanager/scenemanager.addon.ts.

FunctionWhat it does
load( truth, base ) : PromiseBoots one scene for each name in the table and waits for every file the scenes' catalogues name to be fetched, so the first step finds them. base is the url the scene files are relative to, the manager scene's folder: pass the page's or the server file's import.meta.url. Each manager step steps every scene whose entry says suspend: false and every scene a seat is on.
view( link, file, base, mount )Holds the page's one client. file is the manager scene and mount gives each new client its canvases. It returns input(), frame( between? ), close(), and name and client, the scene the seat is on and its client. A page calls input() and then steps the manager before frame( between ), where between is how far the clock is past the last step, in steps, which the client draws a step behind.
link( wire )One seat's link, over a wire such as loopback(accept(row)) or dial(url). Make it once per page.
accept( row )The server's side of a seat on the manager's row, for a transport to hand each page that arrives.
run( truth )Steps the manager by engine.dt as often as the clock owes a step, at most a quarter of a second owed at a time. Returns { stop() }.
child( top, name, entry, base ) : Promise<{ scene, sim }>One scene as the game runs it: its partial, the manager scene top's world and prefabs, and its actors file all applied. entry is the scene's entry in top's scenes and base the manager scene's folder. A test that reads a scene to know what to expect loads it this way.
manager( truth ), scenes( row ), scene( row, name )The manager's row, the map of scene names to their running scenes, and one scene by name.

Mods

A plugin a supports a plugin p with a mod, a file named <a>.<p>.ts, which hands on what a knows. A scene gives p a function of the mod by naming it in world.<p>.mods as { "<function>": "<a>" }. The function is new to p, or one p already has, which the mod's then overwrites. Swapping one a for another that exports the same function changes that one value.

"world": { "skydome3d": { "mods": { "land": "terrain3d" } } }
CaseWhere the mod file is
a and p are both top-level pluginsplugins/<a>/<a>.<p>.ts
a is one of the game's own pluginsBeside a, in the game's plugins/<a>/<a>.<p>.ts
Only p is the game'sBeside p, in the game's plugins/<p>/<a>.<p>.ts, because a top-level plugin names no game's

An entry may name several plugins, { "<function>": ["<a>", "<b>"] }, each giving p the same function, for a p that takes the function from every one of them, such as box3d's props. A single name is a list of one. A p that takes its function from one plugin refuses an entry that names more.

Every mod a scene names is loaded before the first step, on the server and on the page, and a mod that does not export the function it is named for stops the load. A mod is a function of what p asks it with: the server's w, or on the page the plugins it is handed, so it hands on what a's page half holds. The server loads every mod a scene names, so a mod imports only types of anything that draws. A plugin a whose page half a mod hands on must be one of the scene's plugins.

FunctionWhat it does
mod( world, p, name )The function the scene gives p as name, or undefined when the scene names none.
modsOf( world, p, name )The function of every plugin the entry names, in its order. Empty when the scene names none.
listed( scene )The order a scene's plugins tick in on the page: its plugins, with the game's own plugins in place of shipped, in their list's order.
provided( scene, p, name )Refuses a scene that names a plugin for p's mod without listing that plugin. Does nothing when the scene names none.
ahead( scene, p, name )Does what provided does, and also refuses a scene that lists any of those plugins after p. Asked by a p that reads, in the same frame, what the other plugin makes.

Used by

These use it: accounts, box2d, box3d, box3dvehicle, cover3d, cutscene, flight3d, hitscan3d, loader, ocean3d, party, predict, ragdoll3d, recorder, scatter3d, simulator, skydome3d, solids, store, terrain3d, volley3d and walker. It uses loopback, player and rewind.

In the guide