Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview

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 derived fields,
  • another actor’s fields, read through a reference the actor holds,
  • a slot’s occupants, through all() or current,
  • runtime.frame(), runtime.timeSeconds() and runtime.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: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.round(x: number): number
Returns a supplied numeric expression rounded to the nearest integer.
@paramx The value to be 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.

Was this page helpful?