Modules / Infrastructure

recorderaddon

A game under the editor's clock: every step rebuilt by booting again and replaying.

Using it

The recorder runs a game's manager scene under a clock someone else holds, the editor's. It boots the manager, loads its scenes and opens one seat, as a page does, and writes down everything that reaches the truths from outside: every envelope the seat sends and every edit made to a live row. Any step is rebuilt by booting again from the scene files and replaying that journal up to it, which is how time goes back. The die's counter and the next row id are inside the truth, where nothing outside can read or set them, and replaying the same inputs reproduces them exactly. The addon has no settings in a scene.

import { recorder } from '#addons/recorder/recorder.addon.ts'

recorder( file, sim, base, from = [], end = 0 ) : Promise<Recorder>

file and sim are the manager scene and its sim, and base is the url its scene files are relative to. from is a journal to start from and end the step it ends at; the recorder starts at step 0. The manager scene needs an engine prefab with a numeric dt, the seconds a step takes.

put( into, path, value )

Writes value at path, a list of keys and indexes, inside into. The edits the recorder applies use it. The path may not be empty, and every key before the last must lead to an object.

Recording

Each journal entry happens at a step: the manager's step counter when it was made, after the step before it and before that step. An envelope sent at step k is in the truth's inbox when step k runs, as a page's is. An edit made at step k is on the row when step k runs. Arriving at step k, by stepping or by seeking, applies every entry at k in the order they were made, so a paused truth shows the edits made at it.

The recording ends at end. Stepping below it plays the recording back. When the seat sends an envelope below it, the seat takes over: every entry after the current step is dropped and the recording goes on live from there. An edit below the end is kept with the rest of the recording, so the steps after it replay with it.

A step that throws leaves its truth half stepped. The recorder keeps the error as failure, rebuilds the step before it, and stays there. An entry a rebuild cannot apply, because an authored edit left out the row it names or an envelope names a visit the seat is not on, is skipped and kept in skipped.

Entries

EntryMeaning
{ at, up }An envelope the seat sent up the wire, at step at.
{ at, set: { scene, id, path, value } }Sets the field at path of row id in the named scene to value.
{ at, spawn: { scene, kind, data } }Spawns a row of kind with data in the named scene.
{ at, end: { scene, id } }Ends row id in the named scene.

An edit is an entry without its at. A tail is { end, entries }: the step a recording ends at and the entries after some step. A failure is { at, message, stack? }.

Properties of the recorder

On the value recorder() resolves to.

truth : Truth

The manager's truth as it stands now. It is a new one after every rebuild.

n : number

The manager's current step.

end : number

The step the recording ends at. It grows as the recorder steps past it.

dt : number

Seconds a step takes, from the manager scene's engine prefab.

journal : readonly Entry[]

Every entry of the recording, in the order made.

failure : Failure | undefined

The last step that threw, until the next step or seek.

skipped : readonly Entry[]

The entries the last rebuild could not apply.

Methods

step() : Promise | undefined

Steps once. Returns undefined when the step went through. When the step throws, it returns a promise that resolves once the step before it has been rebuilt, with failure set.

seek( k ) : Promise

Rebuilds step k, held between 0 and end, by booting again and replaying the journal.

rerun() : Promise

The same step, rebuilt from the scene files as they are now.

restart() : Promise

Step 0 of an empty recording.

cut()

Ends the recording here: every entry after the current step is dropped.

splice( at, tail ) : Promise<Tail>

Swaps the recording after step at for tail: every entry after it is dropped, tail's are put in their place, and the recording ends where tail says. A truth past at is rebuilt from the recording as it now is. Returns the tail it replaced, so the same call puts it back, and an end set by hand is taken back by splicing the old tail in.

edit( edit ) : Entry

Makes an edit to a live row from this step on, and returns its entry, which unedit takes back. It fails if the edit cannot be made, for instance because the row does not exist.

unedit( entry ) : Promise

Takes an edit out of the journal and rebuilds this step without it. Fails if the entry is not in the journal.

redo( entry ) : Promise

Puts an edit that unedit took out back, and rebuilds this step with it.

refresh()

Sends the rows of the seat's scene to the view again, as they are now.

on( f ) : () => void

Calls f after every step, seek and edit. The return value stops it.

link is the seat the page's view holds, the same one for the recorder's whole life. Every truth the recorder boots gets a new connection behind it, and the view keeps its client across them: the link works out each packet's born and ended itself, against the rows it last handed the view, so a row the client already draws is not set up twice and a row the new truth does not have is removed. A row whose id now names another kind is removed and born again. A rebuild hands the view one packet, of the step it stops at. At step 0 no step has sent the seat one, and the view is handed nothing, as a page is.

link.hear( fn )

Sets the function that receives the packets, and sends it the latest one.

link.send( up )

Sends an envelope up as the seat. Below end it first cuts the recording, then records the envelope at the current step.

link.close()

Lets the recorder go. Its seat leaves every truth, and no truth is stepped again, not even by the seat's leaving, which would step a suspend: true scene it was the last one in; so a truth whose next step throws is closed as cleanly as any other.

Works with

  • scenemanager
    The manager scene the recorder boots, loads and opens a seat on.
  • loopback
    The wire between the recorder's seat and its truths.

In the guide