Slots
A slot is a place in an actor that other actors are spawned into, in an order the scene controls.
A slot is a field that says where children of an actor can go. It holds nothing when the
actor spawns. Whoever uses the actor fills it: the scene, the actor’s own onInit, or one of
its commands. Use a slot when the actor is a container whose contents vary, such as a window,
a list or a deck. For a part that every instance has, declare a child
instead.
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, function slot<const TAccepts extends SlotAccepts>(options: {
accepts: TAccepts;
multiple?: false;
}): OnePlaceField<TAccepts> (+1 overload)
Declare a place: a named position this actor's children are hosted in.
Written as a field; the field reads as a handle once the actor spawns.
```ts
screen = slot({ accepts: LayoutActor }); // this.screen.current
letters = slot({ accepts: Letter, multiple: true }); // this.letters.all()
```slot } from "@motionactor/core";
class class LetterLetter extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Letter.char: ActorFieldSignal<string>char = signal<string>(initial: string): ActorFieldSignal<string> (+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("");
}
export class class WordWord extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Word.text: ActorFieldSignal<string>text = signal<string>(initial: string): ActorFieldSignal<string> (+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("");
Word.letters: ManyPlaceField<typeof Letter>letters = slot<typeof Letter>(options: {
accepts: typeof Letter;
multiple: true;
}): ManyPlaceField<typeof Letter> (+1 overload)
Declare a place: a named position this actor's children are hosted in.
Written as a field; the field reads as a handle once the actor spawns.
```ts
screen = slot({ accepts: LayoutActor }); // this.screen.current
letters = slot({ accepts: Letter, multiple: true }); // this.letters.all()
```slot({ accepts: typeof Letteraccepts: class LetterLetter, multiple: truemultiple: true });
Word.spelled: DerivedDefinition<string, unknown>spelled = derived<unknown, string>(fn: (this: unknown) => string): DerivedDefinition<string, unknown>derived(() =>
this.Word.letters: ManyPlaceField<typeof Letter>letters.function all(): Letter[]Children present at the reading frame, in current hosting order.all().Array<Letter>.map<string>(callbackfn: (value: Letter, index: number, array: Letter[]) => string, thisArg?: any): string[]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((letter: Letterletter) => letter: Letterletter.Letter.char: () => stringchar()).Array<string>.join(separator?: string): stringAdds all the elements of an array into a string, separated by the specified separator string.join(""),
);
Word.onInit(): voidonInit() {
for (const const char: stringchar of this.Word.text: () => stringtext()) {
this.Word.letters: ManyPlaceField<typeof Letter>letters.function spawn(overrides?: {
measuredBox?: undefined;
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
id?: undefined;
char?: SignalInputValue<string> | 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<...> | ... 1 more ... | undefined;
} | undefined;
... 18 more ...;
parent?: undefined;
} | undefined): Letter (+1 overload)
Host a child of the one declared class.spawn({ char?: SignalInputValue<string> | undefinedchar });
}
}
}
Declaring
slot({ accepts, multiple }) takes the class it accepts, or an array of classes. Spawning
anything else into it throws an error naming the slot and the class. A subclass of an accepted class is accepted, so slot({ accepts: LayoutActor }) takes any
actor that has a box.
multiple is false by default, and the slot holds one actor at a time.
The handle
When the actor spawns, the field becomes a handle on the slot.
| Handle | One | Multiple |
|---|---|---|
spawn(Class, overrides) |
Spawns into the slot; throws while it is occupied | Spawns at the end |
spawn(overrides) |
The same, constructing the accepted class | The same |
current |
The occupant, or undefined |
|
all() |
The occupants in order | |
reorder(next) |
Sets the order |
spawn(overrides) needs one accepted class to construct, so it throws when accepts is an
array or is Actor or LayoutActor itself. Pass the class then.
Reads go through the timeline. all() and current in a scene script give the occupants at
the cursor; in a derived they give the occupants at the frame being resolved, so spelled
above follows every change to the slot:
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 } from "@motionactor/core";
import { class WordWord } from "./word";
const const compiled: CompiledScene<{
word: Word;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
word: Word;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
word: Word;
}>, 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 word: Wordword = ctx: SceneContextctx.CompileContext.spawn<Word>(definition: ActorClass<Word>, overrides?: {
text?: SignalInputValue<string> | undefined;
measuredBox?: undefined;
spelled?: undefined;
onInit?: 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<...> | ... 1 more ... | undefined;
} | undefined;
... 19 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Word (+1 overload)
spawn(class WordWord, { text?: SignalInputValue<string> | undefinedtext: "stop" });
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
const [const s: Letters, const t: Lettert, const o: Lettero, const p: Letterp] = const word: Wordword.Word.letters: ManyPlaceField<typeof Letter>letters.function all(): Letter[]Children present at the reading frame, in current hosting order.all();
const word: Wordword.Word.letters: ManyPlaceField<typeof Letter>letters.function reorder(next: readonly Letter[]): voidRecord a new order from the current frame on. `next` is exactly the
children present now, reordered; a missing, extra or duplicate child
throws.reorder([const p: Letterp, const o: Lettero, const t: Lettert, const s: Letters]);
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
yield* const t: Lettert.Actor<{}>.exit(_params?: unknown): Generator<Frame, void, unknown>exit();
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
return { word: Wordword };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
word: Word;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
const compiled: CompiledScene<{
word: Word;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
word: Word;
}>
at(0).FrameState<{ ...; }>.scene: {
word: ResolvedActorOfInstance<Word> | undefined;
}
scene.word: ResolvedActorOfInstance<Word> | undefinedword?.spelled: string | undefinedspelled; // "stop"
const compiled: CompiledScene<{
word: Word;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
word: Word;
}>
at(30).FrameState<{ ...; }>.scene: {
word: ResolvedActorOfInstance<Word> | undefined;
}
scene.word: ResolvedActorOfInstance<Word> | undefinedword?.spelled: string | undefinedspelled; // "pots"
const compiled: CompiledScene<{
word: Word;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
word: Word;
}>
at(60).FrameState<{ ...; }>.scene: {
word: ResolvedActorOfInstance<Word> | undefined;
}
scene.word: ResolvedActorOfInstance<Word> | undefinedword?.spelled: string | undefinedspelled; // "pos"
reorder takes every current occupant once. Leaving one out, adding one or repeating one
throws.
Leaving a slot
An occupant leaves when it exits. exit() runs its onExit and
then despawns it, and the slot is free from that frame on. A one-actor slot can then take a
new occupant.
A deck with one multiple slot. The deck’s deal and collect stagger the cards’
enter() and exit() through this.cards.all(), never touching a card’s state. The
reorder between them changes the order all() returns, so collect takes the cards in
their new order.
Drawing a slot
Each resolved actor lists its occupants’ ids by slot in slotChildren, in order. Each
occupant has its host’s id in parentId and the slot’s name in slotName. A
renderer receives the occupants already drawn, as slots,
and places each element where the slot belongs.
Because a host only names the class it accepts, it can hold actors it knows nothing else
about. The Desktop example has windows with
slot({ accepts: LayoutActor }), and each window hosts an app it never imports.