derived
A read-only value computed from other reads each time a frame resolves.
A derived is a field computed from other values. It has no setter and stores nothing: each
time a frame resolves, the body runs against that frame’s values and its result is the
field’s value for the frame.
The scene tweens one signal, elapsed. remaining reads elapsed, label reads
remaining, and progress reads elapsed again. Scrubbing backwards gives the same numbers
as playing forwards, because every frame computes the chain from that frame’s elapsed.
What a body can read
Inside a body, every read returns its value at the frame being resolved:
- the actor’s signals, channels and other
derivedfields, - another actor’s fields, read through a reference the actor holds,
- a slot’s occupants, through
all()orcurrent, runtime.frame(),runtime.timeSeconds()andruntime.viewport().
The body has to be a function of those reads. Date.now(), Math.random() or a counter kept
in a module gives a different answer when the same frame is resolved twice, and the
difference shows up when someone seeks. Writing a signal from a body throws
Cannot set signal "x" during signal resolution.
Computing for the renderer
A renderer that draws finished numbers stays simple. Geometry, transforms and labels belong in
derived fields, and the renderer reads them:
import { function derived<TThis, T>(fn: (this: TThis) => T): DerivedDefinition<T, TThis>derived, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
export class class MeterMeter extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Meter.level: ActorFieldSignal<number>level = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0);
Meter.fillWidth: DerivedDefinition<number, unknown>fillWidth = derived<unknown, number>(fn: (this: unknown) => number): DerivedDefinition<number, unknown>derived(() => this.Meter.level: () => numberlevel() * this.LayoutActor<{}>.localLayout(): ResolvedLayoutBoxlocalLayout().Rect.width: numberwidth);
Meter.caption: DerivedDefinition<string, unknown>caption = derived<unknown, string>(fn: (this: unknown) => string): DerivedDefinition<string, unknown>derived(() => `${var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.round(x: number): numberReturns a supplied numeric expression rounded to the nearest integer.round(this.Meter.level: () => numberlevel() * 100)}%`);
}
localLayout() is the meter’s resolved box, so fillWidth follows its layout. The Desktop example
builds a whole dock this way: icon centres, window transforms and z-order are a chain of
derived fields, and the renderer maps over the last one.
As a spawn input
A spawn input can be a derived. The field then follows the computation instead of starting
from a fixed value:
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, function derived<TThis, T>(fn: (this: TThis) => T): DerivedDefinition<T, TThis>derived, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
class class DotDot extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Dot.x: ActorFieldSignal<number>x = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0);
}
const const compiled: CompiledScene<{
shadow: Dot;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
shadow: Dot;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
shadow: Dot;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const leader: Dotleader = ctx: SceneContextctx.CompileContext.spawn<Dot>(definition: ActorClass<Dot>, overrides?: {
measuredBox?: undefined;
x?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Dot (+1 overload)
spawn(class DotDot);
const const shadow: Dotshadow = ctx: SceneContextctx.CompileContext.spawn<Dot>(definition: ActorClass<Dot>, overrides?: {
measuredBox?: undefined;
x?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Dot (+1 overload)
spawn(class DotDot, {
x?: SignalInputValue<number> | undefinedx: derived<any, number>(fn: (this: any) => number): DerivedDefinition<number, any>derived(() => const leader: Dotleader.Dot.x: () => numberx() * 0.5),
});
yield* const leader: Dotleader.Dot.x: ActorFieldSignal<number>x.LiveSignal<number>.tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(200, 1);
return { shadow: Dotshadow };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
shadow: Dot;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
const compiled: CompiledScene<{
shadow: Dot;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
shadow: Dot;
}>
at(30).FrameState<{ ...; }>.scene: {
shadow: ResolvedActorOfInstance<Dot> | undefined;
}
scene.shadow: ResolvedActorOfInstance<Dot> | undefinedshadow?.x: number | undefinedx; // 100
To follow a cell as it is, pass it uncalled. The field binds to it, live and read-only, the same
as derived(() => leader.x()):
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
class class DotDot extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Dot.x: ActorFieldSignal<number>x = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0);
}
const const compiled: CompiledScene<{
echo: Dot;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
echo: Dot;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
echo: Dot;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const leader: Dotleader = ctx: SceneContextctx.CompileContext.spawn<Dot>(definition: ActorClass<Dot>, overrides?: {
measuredBox?: undefined;
x?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Dot (+1 overload)
spawn(class DotDot);
const const echo: Dotecho = ctx: SceneContextctx.CompileContext.spawn<Dot>(definition: ActorClass<Dot>, overrides?: {
measuredBox?: undefined;
x?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Dot (+1 overload)
spawn(class DotDot, { x?: SignalInputValue<number> | undefinedx: const leader: Dotleader.Dot.x: ActorFieldSignal<number>x });
yield* const leader: Dotleader.Dot.x: ActorFieldSignal<number>x.LiveSignal<number>.tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(200, 1);
return { echo: Dotecho };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
echo: Dot;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
const compiled: CompiledScene<{
echo: Dot;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
echo: Dot;
}>
at(30).FrameState<{ ...; }>.scene: {
echo: ResolvedActorOfInstance<Dot> | undefined;
}
scene.echo: ResolvedActorOfInstance<Dot> | undefinedecho?.x: number | undefinedx; // 200
derive does the same from the middle of a scene.
On a typed field (signal.of(type)), each value the computation produces is checked against the
type, never decoded: a cell holding "#00ff00" cannot feed a colorType field, which holds a
Color. A value that fails throws TypedFieldValueError, naming the actor, field, type and value.
Writing the body
Write the body as an arrow function. this is the actor, and inside a resolve it reads that
frame’s values. TypeScript infers the type from the body.
A derived declared inside a group cannot read the group it belongs to.
TypeScript reports that the group references itself in its own initializer. Declare the
derived next to the group instead, or have it read signals declared outside the group.