effect-motion 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/Camera.d.ts +49 -0
- package/dist/Camera.js +33 -0
- package/dist/Entity.d.ts +40 -0
- package/dist/Entity.js +32 -0
- package/dist/Fonts.d.ts +31 -0
- package/dist/Fonts.js +11 -0
- package/dist/Instance.d.ts +16 -0
- package/dist/Instance.js +18 -0
- package/dist/Motion.d.ts +74 -0
- package/dist/Motion.js +125 -0
- package/dist/Phaser.d.ts +65 -0
- package/dist/Phaser.js +170 -0
- package/dist/Physics.d.ts +78 -0
- package/dist/Physics.js +117 -0
- package/dist/Renderer.d.ts +69 -0
- package/dist/Renderer.js +90 -0
- package/dist/Runner.d.ts +360 -0
- package/dist/Runner.js +257 -0
- package/dist/Scene.d.ts +241 -0
- package/dist/Scene.js +454 -0
- package/dist/Time.d.ts +38 -0
- package/dist/Time.js +43 -0
- package/dist/Timing.d.ts +96 -0
- package/dist/Timing.js +151 -0
- package/dist/demo.d.ts +186 -0
- package/dist/demo.js +76 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/particles/Particle.d.ts +91 -0
- package/dist/particles/Particle.js +1 -0
- package/dist/particles/ParticleField.d.ts +170 -0
- package/dist/particles/ParticleField.js +69 -0
- package/dist/particles/Prng.d.ts +28 -0
- package/dist/particles/Prng.js +43 -0
- package/dist/particles/constructors.d.ts +78 -0
- package/dist/particles/constructors.js +15 -0
- package/dist/particles/index.d.ts +6 -0
- package/dist/particles/index.js +6 -0
- package/dist/particles/overLife.d.ts +16 -0
- package/dist/particles/overLife.js +21 -0
- package/dist/particles/render.d.ts +13 -0
- package/dist/particles/render.js +33 -0
- package/dist/particles/simulate.d.ts +39 -0
- package/dist/particles/simulate.js +81 -0
- package/dist/particles/step.d.ts +24 -0
- package/dist/particles/step.js +167 -0
- package/dist/shapes/Circle.d.ts +37 -0
- package/dist/shapes/Circle.js +9 -0
- package/dist/shapes/Ellipse.d.ts +41 -0
- package/dist/shapes/Ellipse.js +10 -0
- package/dist/shapes/Group.d.ts +150 -0
- package/dist/shapes/Group.js +82 -0
- package/dist/shapes/Layer.d.ts +9 -0
- package/dist/shapes/Layer.js +23 -0
- package/dist/shapes/Line.d.ts +47 -0
- package/dist/shapes/Line.js +30 -0
- package/dist/shapes/Path.d.ts +38 -0
- package/dist/shapes/Path.js +13 -0
- package/dist/shapes/Rect.d.ts +41 -0
- package/dist/shapes/Rect.js +10 -0
- package/dist/shapes/Shape2D.d.ts +40 -0
- package/dist/shapes/Shape2D.js +41 -0
- package/dist/shapes/Square.d.ts +37 -0
- package/dist/shapes/Square.js +11 -0
- package/dist/shapes/Text.d.ts +60 -0
- package/dist/shapes/Text.js +24 -0
- package/dist/shapes/index.d.ts +10 -0
- package/dist/shapes/index.js +10 -0
- package/dist/svg/SvgDomRenderer.d.ts +28 -0
- package/dist/svg/SvgDomRenderer.js +50 -0
- package/dist/svg/SvgNode.d.ts +13 -0
- package/dist/svg/SvgNode.js +18 -0
- package/dist/svg/SvgRenderer.d.ts +22 -0
- package/dist/svg/SvgRenderer.js +20 -0
- package/dist/svg/camera.d.ts +21 -0
- package/dist/svg/camera.js +43 -0
- package/dist/svg/index.d.ts +6 -0
- package/dist/svg/index.js +6 -0
- package/dist/svg/layers.d.ts +18 -0
- package/dist/svg/layers.js +13 -0
- package/dist/svg/shapes.d.ts +1089 -0
- package/dist/svg/shapes.js +119 -0
- package/package.json +51 -0
package/dist/Runner.d.ts
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import type * as Cause from "effect/Cause";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import type * as Fiber from "effect/Fiber";
|
|
6
|
+
import type * as Schema from "effect/Schema";
|
|
7
|
+
import { type CameraState } from "./Camera";
|
|
8
|
+
import type * as Entity from "./Entity";
|
|
9
|
+
import * as Instance from "./Instance";
|
|
10
|
+
import { Group } from "./shapes/Group";
|
|
11
|
+
export declare const TypeId: "~motion/SceneRunner";
|
|
12
|
+
/** conventional id of the implicit root group every instance attaches to */
|
|
13
|
+
export declare const ROOT_ID = "root";
|
|
14
|
+
export type Seed = number | string;
|
|
15
|
+
/** the fixed default: scenes are deterministic even with no seed set */
|
|
16
|
+
export declare const defaultSeed: Seed;
|
|
17
|
+
export type Settings = {
|
|
18
|
+
frameRate: number;
|
|
19
|
+
/** output resolution — carried on every frame so renderers can size themselves */
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
/** canvas background — carried on every frame so renderers can paint it (default a near-black, not pure #000) */
|
|
23
|
+
backgroundColor: string;
|
|
24
|
+
/**
|
|
25
|
+
* seeds the scene's pseudo-random service (effect's Random via
|
|
26
|
+
* withSeed); the fixed default keeps default-constructed scenes
|
|
27
|
+
* byte-identical across runs. Note: the generator algorithm belongs
|
|
28
|
+
* to effect, so upgrading effect may change seeded sequences.
|
|
29
|
+
*/
|
|
30
|
+
seed: Seed;
|
|
31
|
+
/**
|
|
32
|
+
* hard cap on frames a scene may produce (default 36_000 — 10 minutes
|
|
33
|
+
* at 60fps): a scene that exceeds it dies instead of hanging the
|
|
34
|
+
* consumer. Set to `Infinity` to declare an intentionally infinite
|
|
35
|
+
* scene.
|
|
36
|
+
*/
|
|
37
|
+
maxFrames: number;
|
|
38
|
+
};
|
|
39
|
+
export type GroupInstance = Instance.Of<typeof Group>;
|
|
40
|
+
/**
|
|
41
|
+
* A child in a polymorphic `children` list: a plain string (→ a `Text`),
|
|
42
|
+
* an already-created `Instance`, or an `Effect` that resolves to one (a
|
|
43
|
+
* not-yet-yielded `instantiate` — yielded internally so JSX children need
|
|
44
|
+
* no `yield*`). Normalized to a stored child id in list order.
|
|
45
|
+
*/
|
|
46
|
+
export type Child = string | Instance.Instance | Effect.Effect<Instance.Instance, unknown, unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* Builtin, engine-owned instance properties — namespaced with `$` and
|
|
49
|
+
* held BESIDE entity data, never in the entity schema. Every entity gets
|
|
50
|
+
* them uniformly. `$visible` defaults to `true`.
|
|
51
|
+
*/
|
|
52
|
+
export interface BuiltinProps {
|
|
53
|
+
readonly $visible?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The input accepted by `instantiate`: an entity's own make-input, plus
|
|
57
|
+
* builtin props ($visible), plus — when the entity has a `children` field
|
|
58
|
+
* — a polymorphic `children` list (strings/instances/effects) in place of
|
|
59
|
+
* the stored `Array<string>` of ids.
|
|
60
|
+
*/
|
|
61
|
+
export type InstantiateProps<MakeInput> = Omit<MakeInput, "children"> & BuiltinProps & ("children" extends keyof MakeInput ? {
|
|
62
|
+
readonly children?: ReadonlyArray<Child>;
|
|
63
|
+
} : Record<never, never>);
|
|
64
|
+
/**
|
|
65
|
+
* The ambient mount parent for `instantiate` — provided per scene
|
|
66
|
+
* evaluation (`Scene.play({ parent })`); `null` means the runner root.
|
|
67
|
+
*/
|
|
68
|
+
export declare const CurrentParent: Context.Reference<Instance.Instance<"shapes/Group", Schema.Struct<{
|
|
69
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
70
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
71
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
72
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
73
|
+
readonly a: Schema.Number;
|
|
74
|
+
readonly b: Schema.Number;
|
|
75
|
+
readonly c: Schema.Number;
|
|
76
|
+
readonly d: Schema.Number;
|
|
77
|
+
readonly e: Schema.Number;
|
|
78
|
+
readonly f: Schema.Number;
|
|
79
|
+
}>>;
|
|
80
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
81
|
+
}>, {
|
|
82
|
+
"~position": Entity.TraitLens<Schema.Struct.ReadonlySide<{
|
|
83
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
84
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
85
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
86
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
87
|
+
readonly a: Schema.Number;
|
|
88
|
+
readonly b: Schema.Number;
|
|
89
|
+
readonly c: Schema.Number;
|
|
90
|
+
readonly d: Schema.Number;
|
|
91
|
+
readonly e: Schema.Number;
|
|
92
|
+
readonly f: Schema.Number;
|
|
93
|
+
}>>;
|
|
94
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
95
|
+
}, "Type">, Entity.Position>;
|
|
96
|
+
"~opacity": Entity.TraitLens<Schema.Struct.ReadonlySide<{
|
|
97
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
98
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
99
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
100
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
101
|
+
readonly a: Schema.Number;
|
|
102
|
+
readonly b: Schema.Number;
|
|
103
|
+
readonly c: Schema.Number;
|
|
104
|
+
readonly d: Schema.Number;
|
|
105
|
+
readonly e: Schema.Number;
|
|
106
|
+
readonly f: Schema.Number;
|
|
107
|
+
}>>;
|
|
108
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
109
|
+
}, "Type">, number>;
|
|
110
|
+
}> | null>;
|
|
111
|
+
/**
|
|
112
|
+
* A branch of animation as the runner tracks it: its fiber and its
|
|
113
|
+
* SEMANTIC end (`finished` resolves at `Scene.finish` or completion,
|
|
114
|
+
* whichever comes first).
|
|
115
|
+
*/
|
|
116
|
+
export interface BranchEntry {
|
|
117
|
+
readonly fiber: Fiber.Fiber<unknown, unknown>;
|
|
118
|
+
readonly finished: Effect.Effect<void>;
|
|
119
|
+
}
|
|
120
|
+
declare const Runner_base: Context.ServiceClass<Runner, "Runner", {
|
|
121
|
+
root: Instance.Instance<"shapes/Group", Schema.Struct<{
|
|
122
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
123
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
124
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
125
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
126
|
+
readonly a: Schema.Number;
|
|
127
|
+
readonly b: Schema.Number;
|
|
128
|
+
readonly c: Schema.Number;
|
|
129
|
+
readonly d: Schema.Number;
|
|
130
|
+
readonly e: Schema.Number;
|
|
131
|
+
readonly f: Schema.Number;
|
|
132
|
+
}>>;
|
|
133
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
134
|
+
}>, {
|
|
135
|
+
"~position": Entity.TraitLens<Schema.Struct.ReadonlySide<{
|
|
136
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
137
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
138
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
139
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
140
|
+
readonly a: Schema.Number;
|
|
141
|
+
readonly b: Schema.Number;
|
|
142
|
+
readonly c: Schema.Number;
|
|
143
|
+
readonly d: Schema.Number;
|
|
144
|
+
readonly e: Schema.Number;
|
|
145
|
+
readonly f: Schema.Number;
|
|
146
|
+
}>>;
|
|
147
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
148
|
+
}, "Type">, Entity.Position>;
|
|
149
|
+
"~opacity": Entity.TraitLens<Schema.Struct.ReadonlySide<{
|
|
150
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
151
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
152
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
153
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
154
|
+
readonly a: Schema.Number;
|
|
155
|
+
readonly b: Schema.Number;
|
|
156
|
+
readonly c: Schema.Number;
|
|
157
|
+
readonly d: Schema.Number;
|
|
158
|
+
readonly e: Schema.Number;
|
|
159
|
+
readonly f: Schema.Number;
|
|
160
|
+
}>>;
|
|
161
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
162
|
+
}, "Type">, number>;
|
|
163
|
+
}>;
|
|
164
|
+
instantiate: <Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>, MakeInput>(entity: Entity.Entity<Name, Data, Traits, MakeInput>, props: InstantiateProps<MakeInput>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, unknown, Entity.Entity<Name, Data, Traits, MakeInput> | Runner>;
|
|
165
|
+
appendChild: (parent: GroupInstance, child: Instance.Instance) => void;
|
|
166
|
+
removeChild: (parent: GroupInstance, child: Instance.Instance) => void;
|
|
167
|
+
settings: {
|
|
168
|
+
frameRate: number;
|
|
169
|
+
width: number;
|
|
170
|
+
height: number;
|
|
171
|
+
backgroundColor: string;
|
|
172
|
+
seed: Seed;
|
|
173
|
+
maxFrames: number;
|
|
174
|
+
};
|
|
175
|
+
getDataUnsafe: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>) => Data["Type"] | null;
|
|
176
|
+
setDataUnsafe: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>, data: unknown) => void;
|
|
177
|
+
state: Effect.Effect<{
|
|
178
|
+
instances: {
|
|
179
|
+
[x: string]: {
|
|
180
|
+
data: unknown;
|
|
181
|
+
entity: Entity.AnyEntity;
|
|
182
|
+
$visible: boolean;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
root: string;
|
|
186
|
+
frameRate: number;
|
|
187
|
+
width: number;
|
|
188
|
+
height: number;
|
|
189
|
+
backgroundColor: string;
|
|
190
|
+
camera: CameraState;
|
|
191
|
+
}, never, never>;
|
|
192
|
+
camera: Instance.Instance<"Camera", Schema.Struct<{
|
|
193
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
194
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
195
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
196
|
+
}>, {
|
|
197
|
+
readonly "~position": {
|
|
198
|
+
get: (data: Schema.Struct.ReadonlySide<{
|
|
199
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
200
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
201
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
202
|
+
}, "Type">) => {
|
|
203
|
+
x: number;
|
|
204
|
+
y: number;
|
|
205
|
+
};
|
|
206
|
+
set: (data: Schema.Struct.ReadonlySide<{
|
|
207
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
208
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
209
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
210
|
+
}, "Type">, value: Entity.Position) => Schema.Struct.ReadonlySide<{
|
|
211
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
212
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
213
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
214
|
+
}, "Type">;
|
|
215
|
+
};
|
|
216
|
+
}>;
|
|
217
|
+
setCamera: (instance: Instance.Instance) => void;
|
|
218
|
+
destroy: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>) => void;
|
|
219
|
+
phaser: {
|
|
220
|
+
register: (n: number) => void;
|
|
221
|
+
deregister: (n: number) => void;
|
|
222
|
+
arriveAndAwaitAdvance: Effect.Effect<void, never, never>;
|
|
223
|
+
awaitAdvance: Effect.Effect<number, never, never>;
|
|
224
|
+
snapshotUnsafe: () => {
|
|
225
|
+
phase: number;
|
|
226
|
+
parties: number;
|
|
227
|
+
arrived: number;
|
|
228
|
+
state: "idle" | "pending" | "running";
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
forks: Set<BranchEntry>;
|
|
232
|
+
backgrounds: Set<BranchEntry>;
|
|
233
|
+
countAwaited: (n: number) => void;
|
|
234
|
+
awaitedCount: () => number;
|
|
235
|
+
recordFailure: (cause: Cause.Cause<unknown>) => void;
|
|
236
|
+
failureCause: () => Cause.Cause<unknown> | undefined;
|
|
237
|
+
}> & {
|
|
238
|
+
readonly make: (settings?: Partial<Settings> | undefined) => Effect.Effect<{
|
|
239
|
+
root: Instance.Instance<"shapes/Group", Schema.Struct<{
|
|
240
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
241
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
242
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
243
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
244
|
+
readonly a: Schema.Number;
|
|
245
|
+
readonly b: Schema.Number;
|
|
246
|
+
readonly c: Schema.Number;
|
|
247
|
+
readonly d: Schema.Number;
|
|
248
|
+
readonly e: Schema.Number;
|
|
249
|
+
readonly f: Schema.Number;
|
|
250
|
+
}>>;
|
|
251
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
252
|
+
}>, {
|
|
253
|
+
"~position": Entity.TraitLens<Schema.Struct.ReadonlySide<{
|
|
254
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
255
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
256
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
257
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
258
|
+
readonly a: Schema.Number;
|
|
259
|
+
readonly b: Schema.Number;
|
|
260
|
+
readonly c: Schema.Number;
|
|
261
|
+
readonly d: Schema.Number;
|
|
262
|
+
readonly e: Schema.Number;
|
|
263
|
+
readonly f: Schema.Number;
|
|
264
|
+
}>>;
|
|
265
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
266
|
+
}, "Type">, Entity.Position>;
|
|
267
|
+
"~opacity": Entity.TraitLens<Schema.Struct.ReadonlySide<{
|
|
268
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
269
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
270
|
+
opacity: Schema.withConstructorDefault<Schema.Number>;
|
|
271
|
+
transform: Schema.withConstructorDefault<Schema.Struct<{
|
|
272
|
+
readonly a: Schema.Number;
|
|
273
|
+
readonly b: Schema.Number;
|
|
274
|
+
readonly c: Schema.Number;
|
|
275
|
+
readonly d: Schema.Number;
|
|
276
|
+
readonly e: Schema.Number;
|
|
277
|
+
readonly f: Schema.Number;
|
|
278
|
+
}>>;
|
|
279
|
+
children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
|
|
280
|
+
}, "Type">, number>;
|
|
281
|
+
}>;
|
|
282
|
+
instantiate: <Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>, MakeInput>(entity: Entity.Entity<Name, Data, Traits, MakeInput>, props: InstantiateProps<MakeInput>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, unknown, Entity.Entity<Name, Data, Traits, MakeInput> | Runner>;
|
|
283
|
+
appendChild: (parent: GroupInstance, child: Instance.Instance) => void;
|
|
284
|
+
removeChild: (parent: GroupInstance, child: Instance.Instance) => void;
|
|
285
|
+
settings: {
|
|
286
|
+
frameRate: number;
|
|
287
|
+
width: number;
|
|
288
|
+
height: number;
|
|
289
|
+
backgroundColor: string;
|
|
290
|
+
seed: Seed;
|
|
291
|
+
maxFrames: number;
|
|
292
|
+
};
|
|
293
|
+
getDataUnsafe: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>) => Data["Type"] | null;
|
|
294
|
+
setDataUnsafe: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>, data: unknown) => void;
|
|
295
|
+
state: Effect.Effect<{
|
|
296
|
+
instances: {
|
|
297
|
+
[x: string]: {
|
|
298
|
+
data: unknown;
|
|
299
|
+
entity: Entity.AnyEntity;
|
|
300
|
+
$visible: boolean;
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
root: string;
|
|
304
|
+
frameRate: number;
|
|
305
|
+
width: number;
|
|
306
|
+
height: number;
|
|
307
|
+
backgroundColor: string;
|
|
308
|
+
camera: CameraState;
|
|
309
|
+
}, never, never>;
|
|
310
|
+
camera: Instance.Instance<"Camera", Schema.Struct<{
|
|
311
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
312
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
313
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
314
|
+
}>, {
|
|
315
|
+
readonly "~position": {
|
|
316
|
+
get: (data: Schema.Struct.ReadonlySide<{
|
|
317
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
318
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
319
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
320
|
+
}, "Type">) => {
|
|
321
|
+
x: number;
|
|
322
|
+
y: number;
|
|
323
|
+
};
|
|
324
|
+
set: (data: Schema.Struct.ReadonlySide<{
|
|
325
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
326
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
327
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
328
|
+
}, "Type">, value: Entity.Position) => Schema.Struct.ReadonlySide<{
|
|
329
|
+
x: Schema.withConstructorDefault<Schema.Number>;
|
|
330
|
+
y: Schema.withConstructorDefault<Schema.Number>;
|
|
331
|
+
zoom: Schema.withConstructorDefault<Schema.Number>;
|
|
332
|
+
}, "Type">;
|
|
333
|
+
};
|
|
334
|
+
}>;
|
|
335
|
+
setCamera: (instance: Instance.Instance) => void;
|
|
336
|
+
destroy: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>) => void;
|
|
337
|
+
phaser: {
|
|
338
|
+
register: (n: number) => void;
|
|
339
|
+
deregister: (n: number) => void;
|
|
340
|
+
arriveAndAwaitAdvance: Effect.Effect<void, never, never>;
|
|
341
|
+
awaitAdvance: Effect.Effect<number, never, never>;
|
|
342
|
+
snapshotUnsafe: () => {
|
|
343
|
+
phase: number;
|
|
344
|
+
parties: number;
|
|
345
|
+
arrived: number;
|
|
346
|
+
state: "idle" | "pending" | "running";
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
forks: Set<BranchEntry>;
|
|
350
|
+
backgrounds: Set<BranchEntry>;
|
|
351
|
+
countAwaited: (n: number) => void;
|
|
352
|
+
awaitedCount: () => number;
|
|
353
|
+
recordFailure: (cause: Cause.Cause<unknown>) => void;
|
|
354
|
+
failureCause: () => Cause.Cause<unknown> | undefined;
|
|
355
|
+
}, never, never>;
|
|
356
|
+
};
|
|
357
|
+
export declare class Runner extends Runner_base {
|
|
358
|
+
}
|
|
359
|
+
export declare const layer: Layer.Layer<Runner, never, never>;
|
|
360
|
+
export {};
|
package/dist/Runner.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { Camera, IDENTITY } from "./Camera";
|
|
5
|
+
import * as Instance from "./Instance";
|
|
6
|
+
import * as Phaser from "./Phaser";
|
|
7
|
+
import { Group } from "./shapes/Group";
|
|
8
|
+
import { Text } from "./shapes/Text";
|
|
9
|
+
export const TypeId = "~motion/SceneRunner";
|
|
10
|
+
/** conventional id of the implicit root group every instance attaches to */
|
|
11
|
+
export const ROOT_ID = "root";
|
|
12
|
+
/** the fixed default: scenes are deterministic even with no seed set */
|
|
13
|
+
export const defaultSeed = "effect-motion";
|
|
14
|
+
/**
|
|
15
|
+
* The ambient mount parent for `instantiate` — provided per scene
|
|
16
|
+
* evaluation (`Scene.play({ parent })`); `null` means the runner root.
|
|
17
|
+
*/
|
|
18
|
+
export const CurrentParent = Context.Reference("motion/Runner/CurrentParent", { defaultValue: () => null });
|
|
19
|
+
export class Runner extends Context.Service()("Runner", {
|
|
20
|
+
make: Effect.fnUntraced(function* (settings = {}) {
|
|
21
|
+
const instances = {};
|
|
22
|
+
// each instance's current parent group id (or null = detached / root).
|
|
23
|
+
// Tracked so appendChild detaches from the old parent in O(1) rather
|
|
24
|
+
// than scanning the tree. The root is not tracked (it has no parent).
|
|
25
|
+
const parentOf = {};
|
|
26
|
+
const phaser = yield* Phaser.Phaser.make;
|
|
27
|
+
// concurrent branches spawned by Scene.fork / Scene.play /
|
|
28
|
+
// Scene.background. `forks` hold the scene's end hostage until their
|
|
29
|
+
// SEMANTIC end; a branch that finishes (or completes) is demoted to
|
|
30
|
+
// `backgrounds`, which are interrupted at scene end.
|
|
31
|
+
const forks = new Set();
|
|
32
|
+
const backgrounds = new Set();
|
|
33
|
+
// root party + un-finished forks — the work the scene's end must wait
|
|
34
|
+
// for. Kept as a synchronous counter (updated in the same finalizers
|
|
35
|
+
// that release phaser parties) so the frame consumer can decide
|
|
36
|
+
// "scene over" without depending on the scene fiber being scheduled.
|
|
37
|
+
let awaited = 0;
|
|
38
|
+
// first NON-finished branch failure; failures in a tail (after
|
|
39
|
+
// Scene.finish) are deliberately not reported
|
|
40
|
+
let failure;
|
|
41
|
+
let idCounter = 0;
|
|
42
|
+
const generateId = (name) => {
|
|
43
|
+
return `${name}_${idCounter++}`;
|
|
44
|
+
};
|
|
45
|
+
const setDataUnsafe = (instance, data) => {
|
|
46
|
+
// preserve $visible across data updates; new instances default visible
|
|
47
|
+
// ($visible is set explicitly by instantiate when overridden)
|
|
48
|
+
const prev = instances[instance.id];
|
|
49
|
+
instances[instance.id] = {
|
|
50
|
+
data: instance.entity.make(data),
|
|
51
|
+
entity: instance.entity,
|
|
52
|
+
$visible: prev?.$visible ?? true,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const setVisibleUnsafe = (id, visible) => {
|
|
56
|
+
const entry = instances[id];
|
|
57
|
+
if (entry !== undefined) {
|
|
58
|
+
instances[id] = { ...entry, $visible: visible };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const getDataUnsafe = (instance) => {
|
|
62
|
+
return instances[instance.id]?.data ?? null;
|
|
63
|
+
};
|
|
64
|
+
const resolvedSettings = {
|
|
65
|
+
...settings,
|
|
66
|
+
frameRate: settings.frameRate ?? 60,
|
|
67
|
+
width: settings.width ?? 500,
|
|
68
|
+
height: settings.height ?? 300,
|
|
69
|
+
backgroundColor: settings.backgroundColor ?? "#16161d",
|
|
70
|
+
seed: settings.seed ?? defaultSeed,
|
|
71
|
+
maxFrames: settings.maxFrames ?? 36_000,
|
|
72
|
+
};
|
|
73
|
+
// the root group: never rendered itself, holds the top level
|
|
74
|
+
const root = Instance.make(Group, ROOT_ID);
|
|
75
|
+
setDataUnsafe(root, {});
|
|
76
|
+
// the active camera: an ordinary instance (so the animators drive it),
|
|
77
|
+
// never registered with a sink so it never draws. A default identity
|
|
78
|
+
// camera is present from the start, so `depth`/zoom work with no author
|
|
79
|
+
// ceremony; `setCamera` swaps which instance is active.
|
|
80
|
+
const camera = Instance.make(Camera, "camera");
|
|
81
|
+
setDataUnsafe(camera, {});
|
|
82
|
+
let activeCameraId = camera.id;
|
|
83
|
+
const cameraState = () => {
|
|
84
|
+
const data = instances[activeCameraId]?.data;
|
|
85
|
+
// a destroyed active camera falls back to identity rather than dying:
|
|
86
|
+
// the view is not scene-critical state
|
|
87
|
+
return data ?? IDENTITY;
|
|
88
|
+
};
|
|
89
|
+
// append `id` to a group's children and record it as the child's parent
|
|
90
|
+
const attach = (parent, id) => {
|
|
91
|
+
const data = getDataUnsafe(parent);
|
|
92
|
+
if (data === null) {
|
|
93
|
+
throw new Error(`Runner: parent group "${parent.id}" was destroyed`);
|
|
94
|
+
}
|
|
95
|
+
setDataUnsafe(parent, { ...data, children: [...data.children, id] });
|
|
96
|
+
parentOf[id] = parent.id;
|
|
97
|
+
};
|
|
98
|
+
// remove `id` from its current parent's children (O(1) via parentOf),
|
|
99
|
+
// leaving it detached. No-op if already detached or parent is gone.
|
|
100
|
+
const detach = (id) => {
|
|
101
|
+
const parentId = parentOf[id];
|
|
102
|
+
if (parentId == null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const parentEntry = instances[parentId];
|
|
106
|
+
const children = parentEntry?.data
|
|
107
|
+
?.children;
|
|
108
|
+
if (parentEntry !== undefined && Array.isArray(children)) {
|
|
109
|
+
setDataUnsafe({ id: parentId, entity: parentEntry.entity }, {
|
|
110
|
+
...parentEntry.data,
|
|
111
|
+
children: children.filter((c) => c !== id),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
parentOf[id] = null;
|
|
115
|
+
};
|
|
116
|
+
// move `child` under `parent`: detach from its current parent first
|
|
117
|
+
// (so it is never double-referenced), then attach.
|
|
118
|
+
const appendChild = (parent, child) => {
|
|
119
|
+
if (instances[child.id] === undefined) {
|
|
120
|
+
throw new Error(`Runner: child "${child.id}" was destroyed`);
|
|
121
|
+
}
|
|
122
|
+
detach(child.id);
|
|
123
|
+
attach(parent, child.id);
|
|
124
|
+
};
|
|
125
|
+
const removeChild = (parent, child) => {
|
|
126
|
+
if (parentOf[child.id] === parent.id) {
|
|
127
|
+
detach(child.id);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
// normalize a polymorphic children list into stored child ids, in
|
|
131
|
+
// order: a string → a Text; an Instance → its id; otherwise an
|
|
132
|
+
// Effect<Instance> yielded here (JSX children need no yield*).
|
|
133
|
+
const normalizeChildren = (children) => Effect.gen(function* () {
|
|
134
|
+
const ids = [];
|
|
135
|
+
for (const child of children) {
|
|
136
|
+
if (typeof child === "string") {
|
|
137
|
+
const child$ = yield* self.instantiate(Text, { text: child });
|
|
138
|
+
ids.push(child$.id);
|
|
139
|
+
}
|
|
140
|
+
else if (Instance.isInstance(child)) {
|
|
141
|
+
ids.push(child.id);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const resolved = yield* child;
|
|
145
|
+
ids.push(resolved.id);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return ids;
|
|
149
|
+
});
|
|
150
|
+
const self = {
|
|
151
|
+
root,
|
|
152
|
+
instantiate: Effect.fnUntraced(function* (entity, props) {
|
|
153
|
+
// peel off builtin ($visible) and polymorphic children before the
|
|
154
|
+
// schema constructs the data — neither is an entity-data field
|
|
155
|
+
const { $visible, children, ...rest } = props;
|
|
156
|
+
// children are born (via normalizeChildren) attached to their
|
|
157
|
+
// ambient parent — reparent them into THIS instance below
|
|
158
|
+
const childIds = children === undefined
|
|
159
|
+
? undefined
|
|
160
|
+
: yield* normalizeChildren(children);
|
|
161
|
+
const dataInput = childIds === undefined ? rest : { ...rest, children: childIds };
|
|
162
|
+
const id = generateId(entity.name);
|
|
163
|
+
const instance = Instance.make(entity, id);
|
|
164
|
+
setDataUnsafe(instance, dataInput);
|
|
165
|
+
if ($visible === false) {
|
|
166
|
+
setVisibleUnsafe(id, false);
|
|
167
|
+
}
|
|
168
|
+
// cameras are view state, not tree nodes: they live in `instances`
|
|
169
|
+
// so the animators drive them, but must NOT be mounted into the
|
|
170
|
+
// render tree (no sink renders a Camera — the renderer would die on
|
|
171
|
+
// the unknown entity). Everything else mounts under the ambient
|
|
172
|
+
// parent (Scene.play), defaulting to root.
|
|
173
|
+
if (entity.name !== Camera.name) {
|
|
174
|
+
const ambient = yield* CurrentParent;
|
|
175
|
+
attach(ambient ?? root, id);
|
|
176
|
+
}
|
|
177
|
+
// adopt listed children: they were attached to the ambient parent
|
|
178
|
+
// at birth; detach them there and record this instance as parent
|
|
179
|
+
// (their ids already live in this instance's `children` data)
|
|
180
|
+
if (childIds !== undefined) {
|
|
181
|
+
for (const childId of childIds) {
|
|
182
|
+
detach(childId);
|
|
183
|
+
parentOf[childId] = id;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return instance;
|
|
187
|
+
}),
|
|
188
|
+
// move `child` under `parent` (detaching from its current parent
|
|
189
|
+
// first, so it is never double-referenced). Instances are born
|
|
190
|
+
// attached to the ambient parent; this reparents them.
|
|
191
|
+
appendChild,
|
|
192
|
+
// detach `child` from `parent` (no-op unless currently its child),
|
|
193
|
+
// leaving it detached from the tree (still alive, just unmounted)
|
|
194
|
+
removeChild,
|
|
195
|
+
settings: resolvedSettings,
|
|
196
|
+
getDataUnsafe,
|
|
197
|
+
setDataUnsafe,
|
|
198
|
+
state: Effect.sync(() => {
|
|
199
|
+
// the active camera lives in `instances` so the animators drive it,
|
|
200
|
+
// but it is view state, not a renderable instance — omit it from the
|
|
201
|
+
// frame's instance map (its data is surfaced separately as `camera`)
|
|
202
|
+
const { [activeCameraId]: _camera, ...renderable } = instances;
|
|
203
|
+
return {
|
|
204
|
+
instances: renderable,
|
|
205
|
+
root: ROOT_ID,
|
|
206
|
+
frameRate: resolvedSettings.frameRate,
|
|
207
|
+
width: resolvedSettings.width,
|
|
208
|
+
height: resolvedSettings.height,
|
|
209
|
+
backgroundColor: resolvedSettings.backgroundColor,
|
|
210
|
+
camera: cameraState(),
|
|
211
|
+
};
|
|
212
|
+
}),
|
|
213
|
+
// the default identity camera (animate it, or swap via setCamera)
|
|
214
|
+
camera,
|
|
215
|
+
// swap the active camera to another instance; its live data becomes
|
|
216
|
+
// the view on every subsequent frame
|
|
217
|
+
setCamera: (instance) => {
|
|
218
|
+
activeCameraId = instance.id;
|
|
219
|
+
},
|
|
220
|
+
destroy: (instance) => {
|
|
221
|
+
// O(1) detach from the tracked parent, then drop the instance
|
|
222
|
+
detach(instance.id);
|
|
223
|
+
delete instances[instance.id];
|
|
224
|
+
delete parentOf[instance.id];
|
|
225
|
+
// backstop scan: stays correct even after manual reparenting via
|
|
226
|
+
// raw data updates (which bypass parentOf tracking)
|
|
227
|
+
for (const [id, entry] of Object.entries(instances)) {
|
|
228
|
+
const children = entry.data.children;
|
|
229
|
+
if (Array.isArray(children) && children.includes(instance.id)) {
|
|
230
|
+
instances[id] = {
|
|
231
|
+
entity: entry.entity,
|
|
232
|
+
$visible: entry.$visible,
|
|
233
|
+
data: entry.entity.make({
|
|
234
|
+
...entry.data,
|
|
235
|
+
children: children.filter((child) => child !== instance.id),
|
|
236
|
+
}),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
phaser,
|
|
242
|
+
forks,
|
|
243
|
+
backgrounds,
|
|
244
|
+
countAwaited: (n) => {
|
|
245
|
+
awaited += n;
|
|
246
|
+
},
|
|
247
|
+
awaitedCount: () => awaited,
|
|
248
|
+
recordFailure: (cause) => {
|
|
249
|
+
failure = failure ?? cause;
|
|
250
|
+
},
|
|
251
|
+
failureCause: () => failure,
|
|
252
|
+
};
|
|
253
|
+
return self;
|
|
254
|
+
}),
|
|
255
|
+
}) {
|
|
256
|
+
}
|
|
257
|
+
export const layer = Layer.effect(Runner, Runner.make());
|