Session.onFrame ​

Contract 2.0.0 · package sha256 a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e · generated from configurator-runtime.d.ts by services/onsite-scripter/scripts/build-contract-docs.mjs; edit the source, not this file.

Method of Session · inherited by testing.FakeSession · stable · since 2.0.0 · called as session.onFrame

ts
onFrame<R>(callback: (frame: Frame) => NotAsync<R>, options?: FrameOptions): () => void;

A per-frame hook. Synchronous: an async callback is a type error. Writes go through the Frame writer and show in the frame being drawn; they are transient (never stored, undone, counted or reported). Budget: all hooks together 3 ms per frame, averaged over the last 60 drawn frames ('frame-budget', at most once per 10 s). A hook that throws on 3 frames in a row is removed ('handler-threw' each time, then 'hook-removed'). Paused while the display is held. Lifecycle call: immediate; returns the function that removes the hook. A removal (that function, signal, or the hook removing itself in its call) releases the hook's writes from the next frame, which the runtime draws even when nothing else changes; the frame being drawn keeps them, those of the call that removed it included. So a hand-over to a resting hook shows no released pose. A look-only write outside the write queue (a rule) that a hook calls while no transaction is open, needing nothing loaded, decoded or compiled, commits before this frame is drawn and shows in it beside the Frame writes; its events dispatch after the frame. Any other write called in a hook commits as usual, in a later frame. Not for: a change that should stay or be undone, which is set (a rule) or a scene-model write. Errors: a throw records 'handler-threw'; 3 in a row remove the hook and record 'hook-removed' once (its disposer then does nothing). Over budget records 'frame-budget'.

Options ​

Option Type Default What it does
continuous boolean false Keep drawing frames while this hook lives, even with nothing else changing.
phase 'update' | 'late' 'update' 'update': before animations and the camera.
signal AbortSignal undefined Aborting removes the hook; already aborted, none is added (SignalOption).

Abort (signal) ​

This call is a creator (SignalOption.signal, the kinds of call).

A creator (collection for a new name, materials.create, on, onFrame, beforeCommit, follow, input.drag, labs.lightLinking) given a signal already aborted returns at once, already ended: a Collection or Material reading disposed: true that holds no content and no live name, or a registration that never runs and whose disposer does nothing.

It records no Problem for the abort and throws only what it would throw anyway, as the DOM's addEventListener with an aborted signal adds nothing (spec section 6.5).

Examples ​

Imports: values (has, validate, the Holo*Error classes, the pure helpers) from @holostep-configurators/runtime-client, types from @holostep-configurators/runtime-client/contract, /math and /testing names from those entries; boot from the runtime module the root's loadRuntimeAtFloor resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (examples/declaration/05-Session.onFrame-1.ts):

ts
import type { Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
const glow = session.materials.create({ emissive: '#ffaa00', emissiveStrength: 0 }, { signal });
await session.find({ tag: 'indicator' }).set({ material: glow });     // a rule: persistent
session.onFrame((frame) => {
  // transient: no commit per frame
  frame.material(glow, { emissiveStrength: 1 + Math.sin(frame.timeMs / 300) });
}, { continuous: true, signal });

Compiled under the contract's strict settings (examples/declaration/05-Session.onFrame-2.ts):

ts
import type { Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
// a cut-away reads the camera, so it runs 'late': in 'update' it lags one frame behind a move
const frontWall = session.find({ tag: 'wall-front' });              // the wall on the +Z side
// parts hung under the wall (its casings, `Placement.parent`) hide with it (spec section 6.10, R8)
session.onFrame((frame) => {
  frame.visible(frontWall, Math.abs(session.camera.state.azimuthDeg) >= 90);  // this frame's camera
}, { phase: 'late', signal });

Generic-API test (spec 3.1) ​

Rule Result
1. Neutral name pass: onFrame names no product
2. A precedent pass: Unity MonoBehaviour.LateUpdate (row G3); glTF 2.0 animations (row G52); Window.requestAnimationFrame (row K33); Unity Transform.RotateAround (row K5); Babylon.js TransformNode.setPivotPoint (row K5); Unity Object.Instantiate (row K8); and 1 more in coverage.json
3. Two apps, or labs pass: 27 apps (ledger: app-37356bf09f, app-378d190f1b, app-4f16d2f6da, app-4fd23bc869, app-510d750508, app-59c5b5751e, app-822bb9d6aa, app-8675b3225c and 10 more; fleet cross-check: app-04c2774145, app-0c773a4ee3, app-378d190f1b, app-4762caacdc, app-4f16d2f6da, app-510d750508, app-59c5b5751e, app-b362a81c85 and 13 more)
4. The cheaper fix first decided per need in Appendix B: rows G3, G35, G52, K33, K41, K5, K8 at tier labs, stable (a member only where no rule, data, asset or app code serves the need)