Plugin lifecycle
Plugins export free functions the host calls at load and unload:
export function onLoad(prev?: State) {
// first load: prev is undefined
// hot-reload: prev is the value returned from the previous onUnload
}
export function onUnload(): State {
return { reloads: (prevReloads ?? 0) + 1, trackedPawn };
} Hot reload handoff
On a same-id file-watch Reload, the old instance’s onUnload() return is serialized (JSON + EntityRef revival) and passed to the new onLoad(prev).
- Primitives, strings, arrays, and nested objects round-trip.
EntityRefvalues revive live and serial-gated in the new context (isValid() === falseif the entity died in the gap).- Carry 64-bit values as decimal strings —
bigintcannot beJSON.stringify‘d and would drop the whole handoff.
Vanished (delete the .s2sp) clears any pending handoff. A later re-add is a fresh onLoad(undefined).
Map changes
Plugins persist across changelevel — onLoad does not re-fire per map. Subscribe to Server.onMapStart (or poll Server.mapName on a frame throttle) for map-aware work:
import { Server } from '@s2script/server';
Server.onMapStart((mapName) => {
console.log('map start', mapName);
}); Teardown
The host ledger owns persistent resources (commands, event subs, DB handles, websockets, imported interfaces). Unload walks the ledger in reverse-dependency order — plugins should still return useful state from onUnload, but cleanup does not depend on the plugin running correctly.