@moku-labs/game 0.0.1 → 0.0.2
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 +424 -37
- package/dist/assets.d.mts +161 -0
- package/dist/assets.mjs +4729 -0
- package/dist/component-DGg5DqKK.mjs +44 -0
- package/dist/control.d.mts +162 -0
- package/dist/control.mjs +758 -0
- package/dist/define-sFoO3y6X.d.mts +263 -0
- package/dist/headless-CvamCUcR.mjs +186 -0
- package/dist/headless-KaSWcd0s.d.mts +181 -0
- package/dist/index.d.mts +1204 -50
- package/dist/index.mjs +20978 -2010
- package/dist/inspect.d.mts +109 -0
- package/dist/inspect.mjs +519 -0
- package/dist/jsx-dev-runtime.d.mts +2 -0
- package/dist/jsx-dev-runtime.mjs +2 -0
- package/dist/jsx-runtime.d.mts +2 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/memory-CvgdnsQO.mjs +259 -0
- package/dist/registry-DlpRCibU.mjs +384 -0
- package/dist/runtime-DRlwxkIv.mjs +182 -0
- package/dist/runtime-DiOTkDZz.d.mts +77 -0
- package/dist/session-DmAxY6Ll.mjs +145 -0
- package/dist/testing.d.mts +23 -111
- package/dist/testing.mjs +12 -289
- package/dist/types-BxkNNYul.d.mts +2840 -0
- package/dist/types-CTPS9GBu.d.mts +742 -0
- package/dist/types-DD-QrG_z.d.mts +588 -0
- package/dist/types-DWILGrPn.d.mts +622 -0
- package/dist/types-DYLnSgMI.d.mts +570 -0
- package/dist/types-JNc_UQBo.d.mts +2896 -0
- package/dist/types-yg_ywtT-.d.mts +1859 -0
- package/dist/visual-BDUHSRvf.mjs +734 -0
- package/package.json +26 -4
- package/dist/registry-DWV5C0Mf.mjs +0 -666
- package/dist/types-BfsmUzLC.d.mts +0 -1908
|
@@ -0,0 +1,2840 @@
|
|
|
1
|
+
import { D as Hint, I as Events$2, K as Snapshot, L as Json, M as Time, P as Api$3, R as Root, X as Api$5, k as Api$2, lt as Require, nt as Api$6, st as Config$2, t as Api$4 } from "./types-JNc_UQBo.mjs";
|
|
2
|
+
import { Log } from "@moku-labs/common/browser";
|
|
3
|
+
import { PluginCtx } from "@moku-labs/core";
|
|
4
|
+
|
|
5
|
+
//#region src/plugins/world/ecs/types.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Entity id: `generation * 2 ** 20 + index`, a safe integer. A freed index comes back with a
|
|
8
|
+
* higher generation, so an id is never silently reused.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const entity: Entity = 1_048_576; // generation 1, index 0
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
type Entity = number;
|
|
16
|
+
/**
|
|
17
|
+
* Who owns an entity. Every `spawn` names one, so `despawnOwnedBy` can free exactly one owner's
|
|
18
|
+
* entities and `snapshot()` can say where an entity came from.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const owner: Owner = { kind: "projection", name: "board.items" };
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
type Owner = {
|
|
26
|
+
kind: "projection" | "node" | "plugin";
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* One entity as `snapshot()` reads it: its id and the two halves of the id, its owner, every
|
|
31
|
+
* component value that is plain JSON, and the names of the ones that are not.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const entity: EntitySnapshot = {
|
|
36
|
+
* id: 1_048_576, index: 0, generation: 1, owner: { kind: "projection", name: "board.items" },
|
|
37
|
+
* components: { Layer: { name: "items" } }, skipped: ["Display"]
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
type EntitySnapshot = {
|
|
42
|
+
id: Entity;
|
|
43
|
+
index: number;
|
|
44
|
+
generation: number;
|
|
45
|
+
owner: Owner;
|
|
46
|
+
components: Record<string, Json>;
|
|
47
|
+
skipped: string[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The whole world as plain JSON: the effective mode, every entity sorted by index and every
|
|
51
|
+
* resource that is JSON.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const empty: WorldSnapshot = { mode: "live", entities: [], resources: {} };
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
type WorldSnapshot = {
|
|
59
|
+
mode: WorldMode;
|
|
60
|
+
entities: EntitySnapshot[];
|
|
61
|
+
resources: Record<string, Json>;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The four phases systems run in, in frame order.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const phase: WorldPhase = "animate";
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
type WorldPhase = "input" | "animate" | "layout" | "sync";
|
|
72
|
+
/**
|
|
73
|
+
* World mode. `"paused"` and `"fast"` skip the phases `input`, `animate` and `layout`; phase
|
|
74
|
+
* `sync` runs in every mode.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const mode: WorldMode = "fast";
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
type WorldMode = "live" | "paused" | "fast";
|
|
82
|
+
/**
|
|
83
|
+
* One component value ready to be written: the storage key and the frozen data.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const value: ComponentValue<{ level: number }> = { type: Item, value: { level: 2 } };
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
type ComponentValue<Value extends object> = {
|
|
91
|
+
readonly type: ComponentType<Value>;
|
|
92
|
+
readonly value: Readonly<Value>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* A tag value. A tag carries no data and is stored as `true`.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const held: TagValue = { type: Held, value: true };
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
type TagValue = {
|
|
103
|
+
readonly type: TagType;
|
|
104
|
+
readonly value: true;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Any component or tag value, as `spawn` and `view` return them.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* const values: AnyComponentValue[] = [Held()];
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
type AnyComponentValue = {
|
|
115
|
+
readonly type: AnyComponentType;
|
|
116
|
+
readonly value: object | true;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* A component type whose value shape is erased to a plain record: what the projection writes when
|
|
120
|
+
* it only knows the component by name.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* const erased: AnyComponent = component("Transform", { x: 0, y: 0 }) as AnyComponent;
|
|
125
|
+
* erased.componentName; // "Transform"
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
type AnyComponent = ComponentType<Record<string, unknown>>;
|
|
129
|
+
/**
|
|
130
|
+
* A component type made by `component()`: callable to build a value, and carrying the storage
|
|
131
|
+
* name plus the defaults that give the TypeScript type, the JSON shape and the inspector schema.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const Item: ComponentType<{ kind: string; level: number }> = component("Item", {
|
|
136
|
+
* kind: "",
|
|
137
|
+
* level: 1
|
|
138
|
+
* });
|
|
139
|
+
* Item({ level: 2 }); // { type: Item, value: { kind: "", level: 2 } }
|
|
140
|
+
* Item.owned; // []: a projection view writes every field
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
type ComponentType<Value extends object> = {
|
|
144
|
+
(patch?: Partial<Value>): ComponentValue<Value>;
|
|
145
|
+
readonly componentName: string;
|
|
146
|
+
readonly defaults: Readonly<Value>;
|
|
147
|
+
readonly kind: "component"; /** Fields a plugin writes and a projection view never does, so a view never corrects them. */
|
|
148
|
+
readonly owned: readonly string[];
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* A tag type made by `tag()`: a component with no data.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const Held: TagType = tag("Held");
|
|
156
|
+
* Held(); // { type: Held, value: true }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
type TagType = {
|
|
160
|
+
(): TagValue;
|
|
161
|
+
readonly componentName: string;
|
|
162
|
+
readonly kind: "tag";
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* What the world stores about a component or tag type, independent of its data type.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const registered: AnyComponentType = { componentName: "Held", kind: "tag" };
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
type AnyComponentType = {
|
|
173
|
+
readonly componentName: string;
|
|
174
|
+
readonly kind: "component" | "tag";
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* What the ECS needs from a component type to read and write its store: the name and the
|
|
178
|
+
* defaults, never the call signature. A component bound to a game's keys by a `…For` binder
|
|
179
|
+
* (`Narrowed`) is the same object with a narrower call, so it passes here unchanged.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const ref: ComponentHandle<{ x: number }> = component("Pos", { x: 0 });
|
|
184
|
+
* ref.defaults; // { x: 0 }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
type ComponentHandle<Value extends object> = {
|
|
188
|
+
readonly componentName: string;
|
|
189
|
+
readonly defaults: Readonly<Value>;
|
|
190
|
+
readonly kind: "component";
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* A component type whose call signature takes a narrower patch: the same object, the same
|
|
194
|
+
* store, the compiler refuses what the game's generated keys do not know.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const Sprite: Narrowed<{ texture: string }, { texture?: "board.cell" }> = component("Sprite", { texture: "" });
|
|
199
|
+
* Sprite({ texture: "board.cell" }).value.texture; // "board.cell"
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
type Narrowed<Value extends object, Patch> = Omit<ComponentType<Value>, never> & ((patch?: Patch) => ComponentValue<Value>);
|
|
203
|
+
/**
|
|
204
|
+
* A resource type made by `resource()`: one mutable object per world, created from a deep clone
|
|
205
|
+
* of the defaults on first read.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* const Pointer: ResourceType<{ x: number; y: number }> = resource("Pointer", { x: 0, y: 0 });
|
|
210
|
+
* Pointer.resourceName; // "Pointer"
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
type ResourceType<Value extends object> = {
|
|
214
|
+
readonly resourceName: string;
|
|
215
|
+
readonly defaults: Readonly<Value>;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* A query term marked as written by `mut()`. Its only effect is change detection.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* const written: Mut<{ x: number }> = mut(Transform);
|
|
223
|
+
* written.kind; // "mut"
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
type Mut<Value extends object> = {
|
|
227
|
+
readonly kind: "mut";
|
|
228
|
+
readonly of: ComponentHandle<Value>;
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* A `mut()` term with its data type erased, as the world stores it.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const term: AnyMut = { kind: "mut", of: { componentName: "Transform", kind: "component" } };
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
type AnyMut = {
|
|
239
|
+
readonly kind: "mut";
|
|
240
|
+
readonly of: AnyComponentType;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* One term of a query: a component type, a tag type, or a `mut()` of a component type.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* const terms: QueryTerm[] = [mut(Transform), Sprite, Held];
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
type QueryTerm = AnyComponentType | AnyMut;
|
|
251
|
+
/**
|
|
252
|
+
* The value a query yields for one term: writable for `mut()`, `true` for a tag, read-only for a
|
|
253
|
+
* plain component.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* type Written = ValueOf<Mut<{ x: number }>>; // { x: number }
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
type ValueOf<Term> = Term extends Mut<infer Value> ? Value : Term extends TagType ? true : Term extends ComponentHandle<infer Value> ? Readonly<Value> : never;
|
|
261
|
+
/**
|
|
262
|
+
* The value part of a query row, one entry per term, in term order.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* type Values = QueryValues<[TagType]>; // [true]
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
type QueryValues<Terms extends readonly QueryTerm[]> = { -readonly [Index in keyof Terms]: ValueOf<Terms[Index]> };
|
|
270
|
+
/**
|
|
271
|
+
* One row a query yields: the entity, then one value per term.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* type Row = QueryTuple<[ComponentType<{ x: number }>]>; // [Entity, Readonly<{ x: number }>]
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
type QueryTuple<Terms extends readonly QueryTerm[]> = [Entity, ...QueryValues<Terms>];
|
|
279
|
+
/**
|
|
280
|
+
* What a system receives next to its entities: the world, the mutable resources, the frozen model
|
|
281
|
+
* snapshot of the frame and the frame's `Time`. A system may read pure game rules; it never
|
|
282
|
+
* writes the model.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* const run = (entities: Iterable<[Entity]>, ctx: SystemContext): void => {
|
|
287
|
+
* ctx.res(Pointer).x = 0;
|
|
288
|
+
* ctx.time.delta; // 16
|
|
289
|
+
* };
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
type SystemContext = {
|
|
293
|
+
readonly world: EcsApi;
|
|
294
|
+
res<Value extends object>(resource: ResourceType<Value>): Value;
|
|
295
|
+
readonly snapshot: Snapshot;
|
|
296
|
+
readonly time: Readonly<Time>;
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* A system definition, typed over its query tuple.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* const bob: SystemDefinition<[ComponentType<{ y: number }>]> = {
|
|
304
|
+
* name: "bob",
|
|
305
|
+
* phase: "animate",
|
|
306
|
+
* query: [Transform],
|
|
307
|
+
* run: entities => {
|
|
308
|
+
* for (const [, transform] of entities) transform.y; // read-only
|
|
309
|
+
* }
|
|
310
|
+
* };
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
type SystemDefinition<Terms extends readonly QueryTerm[]> = {
|
|
314
|
+
readonly name: string;
|
|
315
|
+
readonly phase: WorldPhase;
|
|
316
|
+
readonly query: Terms;
|
|
317
|
+
run(entities: Iterable<QueryTuple<Terms>>, ctx: SystemContext): void;
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* A system with its query tuple erased, as the world stores it.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* const entry: AnySystem = { name: "bob", phase: "animate", query: [], run: () => {} };
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
type AnySystem = {
|
|
328
|
+
readonly name: string;
|
|
329
|
+
readonly phase: WorldPhase;
|
|
330
|
+
readonly query: readonly QueryTerm[];
|
|
331
|
+
run(entities: Iterable<readonly unknown[]>, ctx: SystemContext): void;
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* One registered system and the frame it was registered in. A system registered during a frame
|
|
335
|
+
* runs from the next frame on.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* const entry: SystemEntry = { definition: bobSystem, frame: 12 };
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
type SystemEntry = {
|
|
343
|
+
readonly definition: AnySystem;
|
|
344
|
+
readonly frame: number;
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* A structural change queued while a phase runs. Applied in call order when the phase ends.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* const command: Command = { kind: "remove", entity: 1_048_576, component: "Held" };
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
type Command = {
|
|
355
|
+
kind: "attach";
|
|
356
|
+
entity: Entity;
|
|
357
|
+
components: readonly AnyComponentValue[];
|
|
358
|
+
} | {
|
|
359
|
+
kind: "despawn";
|
|
360
|
+
entity: Entity;
|
|
361
|
+
} | {
|
|
362
|
+
kind: "despawnOwnedBy";
|
|
363
|
+
owner: Owner;
|
|
364
|
+
} | {
|
|
365
|
+
kind: "add";
|
|
366
|
+
entity: Entity;
|
|
367
|
+
value: AnyComponentValue;
|
|
368
|
+
} | {
|
|
369
|
+
kind: "remove";
|
|
370
|
+
entity: Entity;
|
|
371
|
+
component: string;
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* A structural listener registered with `onAdded` or `onRemoved`.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* const hook: StructuralHook = (entity, value) => void entity + String(value);
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
type StructuralHook = (entity: Entity, value: unknown) => void;
|
|
382
|
+
/**
|
|
383
|
+
* The frozen model snapshot of one frame, kept so four phases read it once.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* const cached: FrameSnapshot = { frame: 12, snapshot: { player: {}, session: {}, rng: rngState } };
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
type FrameSnapshot = {
|
|
391
|
+
frame: number;
|
|
392
|
+
snapshot: Snapshot;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* ecs module state.
|
|
396
|
+
*/
|
|
397
|
+
type EcsState = {
|
|
398
|
+
generations: number[];
|
|
399
|
+
free: number[];
|
|
400
|
+
owners: Map<Entity, Owner>; /** Key is `"kind:name"` of the owner. */
|
|
401
|
+
byOwner: Map<string, Set<Entity>>;
|
|
402
|
+
types: Map<string, AnyComponentType>;
|
|
403
|
+
stores: Map<string, Map<Entity, object | true>>;
|
|
404
|
+
resources: Map<string, object>;
|
|
405
|
+
systems: Record<WorldPhase, SystemEntry[]>;
|
|
406
|
+
running: WorldPhase | undefined;
|
|
407
|
+
commands: Command[];
|
|
408
|
+
changed: Map<string, Set<Entity>>;
|
|
409
|
+
added: Map<string, StructuralHook[]>;
|
|
410
|
+
removed: Map<string, StructuralHook[]>;
|
|
411
|
+
ownerLeft: Array<(owner: Owner) => void>;
|
|
412
|
+
mode: WorldMode;
|
|
413
|
+
offFrame: Array<() => void>;
|
|
414
|
+
frameSnapshot: FrameSnapshot | undefined;
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* ecs module API, `app.world.ecs`: the screen as data. Plain-object components in one store per
|
|
418
|
+
* component type, generational entity ids, a mandatory owner, a command buffer applied between
|
|
419
|
+
* phases, coarse change detection and fixed phases driven by `time`.
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```ts
|
|
423
|
+
* // A test owns its entities and reads them back.
|
|
424
|
+
* const entity = app.world.ecs.spawn({ kind: "plugin", name: "test" }, [Transform({ x: 10 })]);
|
|
425
|
+
* app.world.ecs.get(entity, Transform); // { x: 10, y: 0, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
type EcsApi = {
|
|
429
|
+
/**
|
|
430
|
+
* Registers a system at the end of its phase list. A system registered during a frame runs
|
|
431
|
+
* from the next frame on.
|
|
432
|
+
*
|
|
433
|
+
* @param definition - The system, built with `system()`.
|
|
434
|
+
* @returns The remover; calling it twice is a no-op.
|
|
435
|
+
* @throws {Error} When a system of that name is already registered.
|
|
436
|
+
* @example
|
|
437
|
+
* ```ts
|
|
438
|
+
* // `text` registers its layout system when the plugin starts.
|
|
439
|
+
* const world = ctx.require(worldPlugin);
|
|
440
|
+
* const off = world.ecs.system(measureLabels); // runs from the next frame
|
|
441
|
+
*
|
|
442
|
+
* off(); // the plugin stops
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
system(definition: AnySystem): () => void;
|
|
446
|
+
/**
|
|
447
|
+
* Creates an entity. The id is reserved and returned at once, also inside a phase, where the
|
|
448
|
+
* components are attached when the phase ends.
|
|
449
|
+
*
|
|
450
|
+
* @param owner - Who owns the entity. Required by the type.
|
|
451
|
+
* @param components - Component and tag values the entity starts with.
|
|
452
|
+
* @returns The new entity id.
|
|
453
|
+
* @example
|
|
454
|
+
* ```ts
|
|
455
|
+
* // `ui` spawns a popup entity it owns, so closing the node frees it in one call.
|
|
456
|
+
* const entity = app.world.ecs.spawn({ kind: "node", name: "reward" }, [
|
|
457
|
+
* Transform({ x: 540, y: 960 })
|
|
458
|
+
* ]);
|
|
459
|
+
* app.world.ecs.has(entity, Transform); // true
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
spawn(owner: Owner, components: readonly AnyComponentValue[]): Entity;
|
|
463
|
+
/**
|
|
464
|
+
* Removes every component of an entity, each firing `onRemoved`, frees the index and bumps the
|
|
465
|
+
* generation. A stale id is a no-op.
|
|
466
|
+
*
|
|
467
|
+
* @param entity - The entity to remove.
|
|
468
|
+
* @example
|
|
469
|
+
* ```ts
|
|
470
|
+
* // `ui` closes the popup it spawned. Asking again answers with the empty world.
|
|
471
|
+
* app.world.ecs.despawn(popup);
|
|
472
|
+
* app.world.ecs.has(popup, Transform); // false
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
despawn(entity: Entity): void;
|
|
476
|
+
/**
|
|
477
|
+
* Despawns exactly the entities of one owner and tells `projection` that the owner left.
|
|
478
|
+
*
|
|
479
|
+
* @param owner - The owner whose entities go.
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* // A test cleans up after itself without touching the projections of the game.
|
|
483
|
+
* app.world.ecs.despawnOwnedBy({ kind: "plugin", name: "test" });
|
|
484
|
+
* app.world.ecs.snapshot(); // { mode: "live", entities: [], resources: {} }
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
despawnOwnedBy(owner: Owner): void;
|
|
488
|
+
/**
|
|
489
|
+
* Reads a component value. A stale id or a missing component gives `undefined`.
|
|
490
|
+
*
|
|
491
|
+
* @param entity - The entity to read.
|
|
492
|
+
* @param component - The component type.
|
|
493
|
+
* @returns The stored value, read-only, or `undefined`.
|
|
494
|
+
* @example
|
|
495
|
+
* ```ts
|
|
496
|
+
* // `input` asks whether the entity under the finger may be dragged.
|
|
497
|
+
* const world = ctx.require(worldPlugin);
|
|
498
|
+
* world.ecs.get(hit, Draggable); // { payload: { id: "i7" } }
|
|
499
|
+
* world.ecs.get(hit, DropTarget); // undefined: this item takes no drop
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
get<Value extends object>(entity: Entity, component: ComponentHandle<Value>): Readonly<Value> | undefined;
|
|
503
|
+
/**
|
|
504
|
+
* Shallow-merges a patch into a stored component and marks the entity changed for it. Never
|
|
505
|
+
* queued: a `set` inside a phase is visible to the next system of that phase.
|
|
506
|
+
*
|
|
507
|
+
* @param entity - The entity to write.
|
|
508
|
+
* @param component - The component type.
|
|
509
|
+
* @param patch - The fields to overwrite.
|
|
510
|
+
* @throws {Error} When the entity is stale or does not carry the component.
|
|
511
|
+
* @example
|
|
512
|
+
* ```ts
|
|
513
|
+
* // `input` moves the held view with the finger.
|
|
514
|
+
* const world = ctx.require(worldPlugin);
|
|
515
|
+
* world.ecs.set(held, Transform, { x: 420, y: 810 });
|
|
516
|
+
* world.ecs.get(held, Transform); // { x: 420, y: 810, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
set<Value extends object>(entity: Entity, component: ComponentHandle<Value>, patch: Partial<Value>): void;
|
|
520
|
+
/**
|
|
521
|
+
* Adds a component value. On an entity that already carries it the value is replaced and the
|
|
522
|
+
* entity is marked changed; no `onAdded` fires.
|
|
523
|
+
*
|
|
524
|
+
* @param entity - The entity to write.
|
|
525
|
+
* @param value - The component or tag value, built by calling its type.
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* // `text` gives the label entity its display object once the atlas is there.
|
|
529
|
+
* const world = ctx.require(worldPlugin);
|
|
530
|
+
* world.ecs.add(label, Display({ object: bitmapText }));
|
|
531
|
+
* world.ecs.has(label, Display); // true
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
add(entity: Entity, value: AnyComponentValue): void;
|
|
535
|
+
/**
|
|
536
|
+
* Removes a component. Fires `onRemoved` when the entity carried it.
|
|
537
|
+
*
|
|
538
|
+
* @param entity - The entity to write.
|
|
539
|
+
* @param component - The component or tag type to drop.
|
|
540
|
+
* @example
|
|
541
|
+
* ```ts
|
|
542
|
+
* // `text` drops the display object when the label leaves the screen.
|
|
543
|
+
* const world = ctx.require(worldPlugin);
|
|
544
|
+
* world.ecs.remove(label, Display);
|
|
545
|
+
* world.ecs.has(label, Display); // false
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
remove(entity: Entity, component: AnyComponentType): void;
|
|
549
|
+
/**
|
|
550
|
+
* Tells whether an entity carries a component or tag. False for a stale id.
|
|
551
|
+
*
|
|
552
|
+
* @param entity - The entity to read.
|
|
553
|
+
* @param component - The component or tag type.
|
|
554
|
+
* @returns True when the entity carries it.
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* // `input` never hit-tests a view that is on its way out.
|
|
558
|
+
* const world = ctx.require(worldPlugin);
|
|
559
|
+
* world.ecs.has(candidate, Exiting); // true while the merge animation plays
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
has(entity: Entity, component: AnyComponentType): boolean;
|
|
563
|
+
/**
|
|
564
|
+
* Adds a tag. Structural and idempotent.
|
|
565
|
+
*
|
|
566
|
+
* @param entity - The entity to write.
|
|
567
|
+
* @param tagType - The tag type.
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* // `input` marks the view under the finger while the drag runs.
|
|
571
|
+
* const world = ctx.require(worldPlugin);
|
|
572
|
+
* world.ecs.tag(dragged, Held);
|
|
573
|
+
* world.ecs.has(dragged, Held); // true
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
tag(entity: Entity, tagType: TagType): void;
|
|
577
|
+
/**
|
|
578
|
+
* Removes a tag. Structural and idempotent.
|
|
579
|
+
*
|
|
580
|
+
* @param entity - The entity to write.
|
|
581
|
+
* @param tagType - The tag type.
|
|
582
|
+
* @example
|
|
583
|
+
* ```ts
|
|
584
|
+
* // `input` releases the view on pointer up.
|
|
585
|
+
* const world = ctx.require(worldPlugin);
|
|
586
|
+
* world.ecs.untag(dragged, Held);
|
|
587
|
+
* world.ecs.has(dragged, Held); // false
|
|
588
|
+
* ```
|
|
589
|
+
*/
|
|
590
|
+
untag(entity: Entity, tagType: TagType): void;
|
|
591
|
+
/**
|
|
592
|
+
* Walks the store of the FIRST term in insertion order and keeps the entities that carry every
|
|
593
|
+
* term. Deterministic. A `mut()` term marks every yielded entity changed for that component.
|
|
594
|
+
*
|
|
595
|
+
* @param terms - Component types, tag types and `mut()` terms.
|
|
596
|
+
* @returns The matching rows: the entity and one value per term.
|
|
597
|
+
* @example
|
|
598
|
+
* ```ts
|
|
599
|
+
* // `renderer.sync` walks every sprite that has a place on the screen.
|
|
600
|
+
* for (const [entity, sprite, transform] of app.world.ecs.query(Sprite, Transform)) {
|
|
601
|
+
* display.get(entity)?.position.set(transform.x, transform.y);
|
|
602
|
+
* sprite.texture; // "board.item-red-1"
|
|
603
|
+
* }
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
query<const Terms extends readonly QueryTerm[]>(...terms: Terms): Iterable<QueryTuple<Terms>>;
|
|
607
|
+
/**
|
|
608
|
+
* Returns the one mutable value of a resource, created from a deep clone of its defaults on
|
|
609
|
+
* first read.
|
|
610
|
+
*
|
|
611
|
+
* @param resourceType - The resource type.
|
|
612
|
+
* @returns The mutable resource value.
|
|
613
|
+
* @example
|
|
614
|
+
* ```ts
|
|
615
|
+
* // `input` writes the pointer once per frame; every game system reads the same object.
|
|
616
|
+
* const world = ctx.require(worldPlugin);
|
|
617
|
+
* const pointer = world.ecs.resource(Pointer);
|
|
618
|
+
* pointer.down = true;
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
resource<Value extends object>(resourceType: ResourceType<Value>): Value;
|
|
622
|
+
/**
|
|
623
|
+
* Registers a listener for the moment a component is added, called when the structural change
|
|
624
|
+
* is applied. A throwing listener is logged and the others still run.
|
|
625
|
+
*
|
|
626
|
+
* @param component - The component type to watch.
|
|
627
|
+
* @param fn - Called with the entity and the stored value.
|
|
628
|
+
* @returns The remover; calling it twice is a no-op.
|
|
629
|
+
* @example
|
|
630
|
+
* ```ts
|
|
631
|
+
* // `renderer.sync` creates a Pixi object for every new sprite.
|
|
632
|
+
* const world = ctx.require(worldPlugin);
|
|
633
|
+
* const off = world.ecs.onAdded(Sprite, (entity, sprite) => {
|
|
634
|
+
* objects.set(entity, makeSprite(sprite.texture));
|
|
635
|
+
* });
|
|
636
|
+
*
|
|
637
|
+
* off(); // the renderer stops
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
onAdded<Value extends object>(component: ComponentHandle<Value>, fn: (entity: Entity, value: Readonly<Value>) => void): () => void;
|
|
641
|
+
/**
|
|
642
|
+
* Registers a listener for the moment a tag is added: `tag` on an entity without it, or a spawn
|
|
643
|
+
* that carries it. A tag has no value, so the listener gets the entity only.
|
|
644
|
+
*
|
|
645
|
+
* @param tagType - The tag type to watch.
|
|
646
|
+
* @param fn - Called with the entity that got the tag.
|
|
647
|
+
* @returns The remover; calling it twice is a no-op.
|
|
648
|
+
* @example
|
|
649
|
+
* ```ts
|
|
650
|
+
* // `ui` shows the pressed look while `input` holds the `Pressed` tag on a button.
|
|
651
|
+
* const world = ctx.require(worldPlugin);
|
|
652
|
+
* const off = world.ecs.onAdded(Pressed, entity => markPointer(entity, "pressed", true));
|
|
653
|
+
*
|
|
654
|
+
* world.ecs.tag(button, Pressed); // markPointer(button, "pressed", true)
|
|
655
|
+
* off(); // ui stops
|
|
656
|
+
* ```
|
|
657
|
+
*/
|
|
658
|
+
onAdded(tagType: TagType, fn: (entity: Entity) => void): () => void;
|
|
659
|
+
/**
|
|
660
|
+
* Registers a listener for the moment a component is removed.
|
|
661
|
+
*
|
|
662
|
+
* @param component - The component type to watch.
|
|
663
|
+
* @param fn - Called with the entity and the value it had.
|
|
664
|
+
* @returns The remover; calling it twice is a no-op.
|
|
665
|
+
* @example
|
|
666
|
+
* ```ts
|
|
667
|
+
* // `renderer.sync` destroys the Pixi object of a sprite that left.
|
|
668
|
+
* const world = ctx.require(worldPlugin);
|
|
669
|
+
* const off = world.ecs.onRemoved(Sprite, entity => {
|
|
670
|
+
* objects.get(entity)?.destroy();
|
|
671
|
+
* objects.delete(entity);
|
|
672
|
+
* });
|
|
673
|
+
*
|
|
674
|
+
* off(); // the renderer stops
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
onRemoved<Value extends object>(component: ComponentHandle<Value>, fn: (entity: Entity, value: Readonly<Value>) => void): () => void;
|
|
678
|
+
/**
|
|
679
|
+
* Registers a listener for the moment a tag is removed: `untag` on an entity that had it, or a
|
|
680
|
+
* despawn. The listener gets the entity only.
|
|
681
|
+
*
|
|
682
|
+
* @param tagType - The tag type to watch.
|
|
683
|
+
* @param fn - Called with the entity that lost the tag.
|
|
684
|
+
* @returns The remover; calling it twice is a no-op.
|
|
685
|
+
* @example
|
|
686
|
+
* ```ts
|
|
687
|
+
* // `ui` drops the pressed look when `input` releases the button.
|
|
688
|
+
* const world = ctx.require(worldPlugin);
|
|
689
|
+
* const off = world.ecs.onRemoved(Pressed, entity => markPointer(entity, "pressed", false));
|
|
690
|
+
*
|
|
691
|
+
* world.ecs.untag(button, Pressed); // markPointer(button, "pressed", false)
|
|
692
|
+
* off(); // ui stops
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
onRemoved(tagType: TagType, fn: (entity: Entity) => void): () => void;
|
|
696
|
+
/**
|
|
697
|
+
* The coarse change set of the frame, cleared in `time` phase `signals` after every `sync`
|
|
698
|
+
* system ran.
|
|
699
|
+
*
|
|
700
|
+
* @param component - The component type to ask about.
|
|
701
|
+
* @returns The entities marked changed for it this frame.
|
|
702
|
+
* @example
|
|
703
|
+
* ```ts
|
|
704
|
+
* // `renderer.sync` moves only what moved.
|
|
705
|
+
* for (const entity of app.world.ecs.changed(Transform)) {
|
|
706
|
+
* const transform = app.world.ecs.get(entity, Transform);
|
|
707
|
+
* display.get(entity)?.position.set(transform?.x ?? 0, transform?.y ?? 0);
|
|
708
|
+
* }
|
|
709
|
+
* ```
|
|
710
|
+
*/
|
|
711
|
+
changed(component: AnyComponentType): Iterable<Entity>;
|
|
712
|
+
/**
|
|
713
|
+
* The effective mode: `"fast"` while the flow walks fast, otherwise the stored one.
|
|
714
|
+
*
|
|
715
|
+
* @returns The mode the frame runs in.
|
|
716
|
+
* @example
|
|
717
|
+
* ```ts
|
|
718
|
+
* // A test checks that a fast walk froze the tweens.
|
|
719
|
+
* app.flow.setMode("fast");
|
|
720
|
+
* app.world.ecs.mode(); // "fast", even though setMode was never called on the world
|
|
721
|
+
* ```
|
|
722
|
+
*/
|
|
723
|
+
mode(): WorldMode;
|
|
724
|
+
/**
|
|
725
|
+
* Stores the world mode. `"fast"` finishes every motion and flushes every despawn queue at once.
|
|
726
|
+
*
|
|
727
|
+
* @param mode - The new stored mode.
|
|
728
|
+
* @example
|
|
729
|
+
* ```ts
|
|
730
|
+
* // A headless test wants the final picture without stepping frames.
|
|
731
|
+
* app.world.ecs.setMode("fast");
|
|
732
|
+
* app.world.ecs.mode(); // "fast"
|
|
733
|
+
* ```
|
|
734
|
+
*/
|
|
735
|
+
setMode(mode: WorldMode): void;
|
|
736
|
+
/**
|
|
737
|
+
* The component type behind a name. A type registers itself on first use, so a name nobody
|
|
738
|
+
* wrote, read or queried yet is unknown.
|
|
739
|
+
*
|
|
740
|
+
* @param name - The storage name of the component.
|
|
741
|
+
* @returns The type, or `undefined` when the world never met it.
|
|
742
|
+
* @example
|
|
743
|
+
* ```ts
|
|
744
|
+
* // `text` resolves the component a label binds its number to.
|
|
745
|
+
* const world = ctx.require(worldPlugin);
|
|
746
|
+
* const type = world.ecs.typeOf("Coins"); // the Coins component, registered when it was written
|
|
747
|
+
* const pose = type === undefined ? undefined : world.ecs.get(counter, type);
|
|
748
|
+
*
|
|
749
|
+
* pose?.amount; // 120
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
typeOf(name: string): AnyComponent | undefined;
|
|
753
|
+
/**
|
|
754
|
+
* The whole world as plain JSON, sorted by entity index. A component value that is not JSON is
|
|
755
|
+
* left out and its name is listed in `skipped`.
|
|
756
|
+
*
|
|
757
|
+
* @returns The world as JSON: the mode, the entities and the resources.
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* // A test asserts what the board holds after the first reconcile.
|
|
761
|
+
* app.world.ecs.snapshot();
|
|
762
|
+
* // { mode: "live", entities: [{ id: 1048576, index: 0, generation: 1,
|
|
763
|
+
* // owner: { kind: "projection", name: "board.items" },
|
|
764
|
+
* // components: { Layer: { name: "items" } }, skipped: [] }], resources: {} }
|
|
765
|
+
* ```
|
|
766
|
+
*/
|
|
767
|
+
snapshot(): WorldSnapshot;
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* ecs methods injected into `projection`. Not public.
|
|
771
|
+
*/
|
|
772
|
+
type EcsInternal = {
|
|
773
|
+
/**
|
|
774
|
+
* Registers a listener for `despawnOwnedBy`, so `projection` can drop the views of an owner
|
|
775
|
+
* that left.
|
|
776
|
+
*
|
|
777
|
+
* @param fn - Called with the owner whose entities were despawned.
|
|
778
|
+
* @returns The remover.
|
|
779
|
+
*/
|
|
780
|
+
onOwnerLeft(fn: (owner: Owner) => void): () => void;
|
|
781
|
+
/**
|
|
782
|
+
* The owner of an entity, which is also the liveness check: a stale id has none. `projection`
|
|
783
|
+
* asks before it hands out a view handle for an entity another plugin owns.
|
|
784
|
+
*
|
|
785
|
+
* @param entity - The entity to ask about.
|
|
786
|
+
* @returns The owner, or `undefined` when the id is stale.
|
|
787
|
+
*/
|
|
788
|
+
ownerOf(entity: Entity): Owner | undefined;
|
|
789
|
+
/**
|
|
790
|
+
* Runs the systems of one phase, then flushes the command buffer.
|
|
791
|
+
*
|
|
792
|
+
* @param phase - The phase to run.
|
|
793
|
+
* @param time - The frame's `Time`.
|
|
794
|
+
*/
|
|
795
|
+
runPhase(phase: WorldPhase, time: Readonly<Time>): void;
|
|
796
|
+
/**
|
|
797
|
+
* Applies the queued structural commands in call order.
|
|
798
|
+
*/
|
|
799
|
+
flush(): void;
|
|
800
|
+
/**
|
|
801
|
+
* Clears every change set. Called in `time` phase `signals`.
|
|
802
|
+
*/
|
|
803
|
+
clearChanges(): void;
|
|
804
|
+
/**
|
|
805
|
+
* Drops every entity, component, resource, system and hook.
|
|
806
|
+
*/
|
|
807
|
+
clear(): void;
|
|
808
|
+
};
|
|
809
|
+
//#endregion
|
|
810
|
+
//#region src/plugins/world/projection/types.d.ts
|
|
811
|
+
/**
|
|
812
|
+
* How a layer orders the entities drawn in it.
|
|
813
|
+
*
|
|
814
|
+
* @example
|
|
815
|
+
* ```ts
|
|
816
|
+
* const sort: LayerSort = "y";
|
|
817
|
+
* ```
|
|
818
|
+
*/
|
|
819
|
+
type LayerSort = "none" | "y" | "order";
|
|
820
|
+
/**
|
|
821
|
+
* One named layer of a scene. The order of the list is draw order.
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```ts
|
|
825
|
+
* const layer: LayerSpec = { name: "items", sort: "y" };
|
|
826
|
+
* ```
|
|
827
|
+
*/
|
|
828
|
+
type LayerSpec = {
|
|
829
|
+
name: string;
|
|
830
|
+
sort: LayerSort;
|
|
831
|
+
};
|
|
832
|
+
/**
|
|
833
|
+
* Easing of a tween: one of the named curves the driver knows, or a function that gets the
|
|
834
|
+
* normalised time and returns the eased fraction.
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* ```ts
|
|
838
|
+
* const ease: Ease = "outBack";
|
|
839
|
+
* ```
|
|
840
|
+
*/
|
|
841
|
+
type Ease = "linear" | "in" | "out" | "inOut" | "inCubic" | "outCubic" | "inOutCubic" | "inBack" | "outBack" | ((t: number) => number);
|
|
842
|
+
/**
|
|
843
|
+
* What every motion returns: it can be finished, cancelled and asked whether it still runs.
|
|
844
|
+
* V3 `anim` hands out the same shape.
|
|
845
|
+
*
|
|
846
|
+
* @example
|
|
847
|
+
* ```ts
|
|
848
|
+
* const handle: MotionHandle = view.toRest(Transform);
|
|
849
|
+
* handle.active(); // true until the last frame wrote the exact target
|
|
850
|
+
* ```
|
|
851
|
+
*/
|
|
852
|
+
type MotionHandle = {
|
|
853
|
+
finish(): void;
|
|
854
|
+
cancel(): void;
|
|
855
|
+
active(): boolean;
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
* What a motion hook returns. `void` means the hook wrote the component directly.
|
|
859
|
+
*
|
|
860
|
+
* @example
|
|
861
|
+
* ```ts
|
|
862
|
+
* const motion: Motion = undefined; // the hook called view.set and is already done
|
|
863
|
+
* ```
|
|
864
|
+
*/
|
|
865
|
+
type Motion = MotionHandle | void;
|
|
866
|
+
/**
|
|
867
|
+
* Only the numeric fields of a component value: what a tween can interpolate.
|
|
868
|
+
*
|
|
869
|
+
* @example
|
|
870
|
+
* ```ts
|
|
871
|
+
* type Numbers = NumericFields<{ x: number; texture: string }>; // { x: number }
|
|
872
|
+
* ```
|
|
873
|
+
*/
|
|
874
|
+
type NumericFields<Value extends object> = { [Key in keyof Value as Value[Key] extends number ? Key : never]: Value[Key] };
|
|
875
|
+
/**
|
|
876
|
+
* One keyframe segment of a track: the fraction of the track's `ms` it ends at (`0..1`), the
|
|
877
|
+
* curve it eases with (the track's `ease` when left out) and the numeric fields it reaches. A
|
|
878
|
+
* field it does not name holds where the segment before left it.
|
|
879
|
+
*
|
|
880
|
+
* @example
|
|
881
|
+
* ```ts
|
|
882
|
+
* // The swing of a popup board: past the rest pose at 42 % of the track, then home.
|
|
883
|
+
* const segments: TrackSegment[] = [
|
|
884
|
+
* { at: 0.42, ease: "out", to: { y: 974, rotation: 0.087 } },
|
|
885
|
+
* { at: 1, ease: "inOut", to: { y: 960, rotation: 0 } }
|
|
886
|
+
* ];
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
type TrackSegment = {
|
|
890
|
+
at: number;
|
|
891
|
+
ease?: Ease;
|
|
892
|
+
to: Record<string, number>;
|
|
893
|
+
};
|
|
894
|
+
/**
|
|
895
|
+
* Options of `ViewHandle.tween`. An additive tween adds its delta over the value the absolute
|
|
896
|
+
* writer of the field holds, instead of owning the field. `segments` turns the tween into a
|
|
897
|
+
* keyframe walk: the segments run one after another over `ms` on one clock, and `to` is where the
|
|
898
|
+
* last one ends. `repeat` runs the walk again from its first segment when it ends: a number
|
|
899
|
+
* counts the extra runs, `"forever"` never ends. The world passes it to the driver unchanged;
|
|
900
|
+
* without a driver the target is written once.
|
|
901
|
+
*
|
|
902
|
+
* @example
|
|
903
|
+
* ```ts
|
|
904
|
+
* const options: TweenOptions = { ms: 350, ease: "out", delayMs: 40 };
|
|
905
|
+
* const walk: TweenOptions = { ms: 1000, segments: [{ at: 0.42, to: { y: 974 } }, { at: 1, to: { y: 960 } }] };
|
|
906
|
+
* const sway: TweenOptions = { ms: 2400, additive: true, repeat: "forever", segments: [{ at: 0.5, to: { rotation: 0.05 } }, { at: 1, to: { rotation: 0 } }] };
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
909
|
+
type TweenOptions = {
|
|
910
|
+
ms: number;
|
|
911
|
+
ease?: Ease;
|
|
912
|
+
delayMs?: number;
|
|
913
|
+
additive?: boolean;
|
|
914
|
+
segments?: readonly TrackSegment[];
|
|
915
|
+
repeat?: number | "forever";
|
|
916
|
+
};
|
|
917
|
+
/**
|
|
918
|
+
* Options of `ViewHandle.toRest`. Defaults: the plugin's `settleMs` and ease `"out"`. `delayMs`
|
|
919
|
+
* lets a hook wait, for example for a flight to land, before the view comes home.
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* ```ts
|
|
923
|
+
* const options: RestOptions = { ms: 200, delayMs: 120 };
|
|
924
|
+
* ```
|
|
925
|
+
*/
|
|
926
|
+
type RestOptions = {
|
|
927
|
+
ms?: number;
|
|
928
|
+
ease?: Ease;
|
|
929
|
+
delayMs?: number;
|
|
930
|
+
};
|
|
931
|
+
/**
|
|
932
|
+
* What the driver is told about one track: how long it runs, how it eases, how long it waits,
|
|
933
|
+
* whether it adds to the field or owns it, and the keyframe segments it walks. A track with
|
|
934
|
+
* segments walks them over `ms` on one clock and claims every field a segment names; its target
|
|
935
|
+
* `to` is the last segment's target. A track with `repeat` starts its walk again when it ends
|
|
936
|
+
* (a number counts the extra runs, `"forever"` never ends); the instant driver ignores it and
|
|
937
|
+
* writes the end pose once.
|
|
938
|
+
*
|
|
939
|
+
* @example
|
|
940
|
+
* ```ts
|
|
941
|
+
* const options: TrackOptions = { ms: 250, ease: "outBack", delayMs: 0, additive: false };
|
|
942
|
+
* const walk: TrackOptions = { ms: 1000, segments: [{ at: 0.42, to: { y: 974 } }, { at: 1, to: { y: 960 } }] };
|
|
943
|
+
* const twice: TrackOptions = { ms: 400, ease: "inOut", delayMs: 0, additive: true, repeat: 1 };
|
|
944
|
+
* ```
|
|
945
|
+
*/
|
|
946
|
+
type TrackOptions = {
|
|
947
|
+
ms: number;
|
|
948
|
+
ease?: Ease;
|
|
949
|
+
delayMs?: number;
|
|
950
|
+
additive?: boolean;
|
|
951
|
+
segments?: readonly TrackSegment[];
|
|
952
|
+
repeat?: number | "forever";
|
|
953
|
+
};
|
|
954
|
+
/**
|
|
955
|
+
* The tween engine behind `ViewHandle.tween`, `toRest` and `all`. `anim` installs one with
|
|
956
|
+
* `setDriver`; without it the world writes every target at once.
|
|
957
|
+
*
|
|
958
|
+
* A track reads its start values when its delay ends, writes the exact target on its last frame
|
|
959
|
+
* and goes through `ecs.set`, so `changed()` sees it. Muted fields are never written, also not on
|
|
960
|
+
* `finish()`.
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```ts
|
|
964
|
+
* // `anim` hands the world its own tween core in `onStart`.
|
|
965
|
+
* const driver: TweenDriver = { track: startTrack, cancelAll: cancelTracksOf };
|
|
966
|
+
* const off = ctx.require(worldPlugin).projection.setDriver(driver);
|
|
967
|
+
*
|
|
968
|
+
* off(); // `anim` stopped: the world writes instantly again
|
|
969
|
+
* ```
|
|
970
|
+
*/
|
|
971
|
+
type TweenDriver = {
|
|
972
|
+
/**
|
|
973
|
+
* Starts one track on one component of one entity.
|
|
974
|
+
*
|
|
975
|
+
* @param entity - The entity to animate.
|
|
976
|
+
* @param component - The component to animate.
|
|
977
|
+
* @param to - The numeric target fields.
|
|
978
|
+
* @param options - Duration, easing, delay and the additive flag.
|
|
979
|
+
* @param muted - The fields another writer owns, read at every write.
|
|
980
|
+
* @returns The motion handle of the track.
|
|
981
|
+
*/
|
|
982
|
+
track(entity: Entity, component: AnyComponent, to: Record<string, number>, options: TrackOptions, muted: () => ReadonlySet<string>): MotionHandle;
|
|
983
|
+
/**
|
|
984
|
+
* Ends every track of one entity. Called when a view despawns and on every flush.
|
|
985
|
+
*
|
|
986
|
+
* @param entity - The entity whose tracks end.
|
|
987
|
+
*/
|
|
988
|
+
cancelAll(entity: Entity): void;
|
|
989
|
+
};
|
|
990
|
+
/**
|
|
991
|
+
* One node of an element description, as `ui` builds it. The world never imports `ui`: it knows
|
|
992
|
+
* the shape structurally, carries it in `Tree` and hands it back unchanged.
|
|
993
|
+
*
|
|
994
|
+
* @example
|
|
995
|
+
* ```ts
|
|
996
|
+
* const node: DescriptionNode = { type: "box", props: { gap: 8 }, children: [] };
|
|
997
|
+
* ```
|
|
998
|
+
*/
|
|
999
|
+
type DescriptionNode = {
|
|
1000
|
+
type: string;
|
|
1001
|
+
key?: string;
|
|
1002
|
+
props: object;
|
|
1003
|
+
children: readonly unknown[];
|
|
1004
|
+
};
|
|
1005
|
+
/**
|
|
1006
|
+
* What one `view(item)` call returns: the components of the item, or one description node that
|
|
1007
|
+
* the world wraps as `Tree`.
|
|
1008
|
+
*
|
|
1009
|
+
* @example
|
|
1010
|
+
* ```ts
|
|
1011
|
+
* const components: ViewOutput = [Level({ level: 2 })];
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
type ViewOutput = readonly AnyComponentValue[] | DescriptionNode;
|
|
1015
|
+
/**
|
|
1016
|
+
* What a motion hook is handed: the one view it animates. A view never holds a reference to
|
|
1017
|
+
* another view — `peer` answers with the other key's item, never with its entity.
|
|
1018
|
+
*
|
|
1019
|
+
* @example
|
|
1020
|
+
* ```ts
|
|
1021
|
+
* const enter = (view: ViewHandle<Item>, item: Item): Motion => {
|
|
1022
|
+
* view.set(Transform, { scale: 0 });
|
|
1023
|
+
* return view.toRest(Transform, { ms: 250 });
|
|
1024
|
+
* };
|
|
1025
|
+
* ```
|
|
1026
|
+
*/
|
|
1027
|
+
type ViewHandle<Item> = {
|
|
1028
|
+
readonly entity: Entity;
|
|
1029
|
+
readonly key: string;
|
|
1030
|
+
get<Value extends object>(component: ComponentType<Value>): Readonly<Value> | undefined;
|
|
1031
|
+
rest<Value extends object>(component: ComponentType<Value>): Readonly<Value> | undefined;
|
|
1032
|
+
set<Value extends object>(component: ComponentType<Value>, patch: Partial<Value>): void;
|
|
1033
|
+
tween<Value extends object>(component: ComponentType<Value>, to: Partial<NumericFields<Value>>, options: TweenOptions): MotionHandle;
|
|
1034
|
+
toRest<Value extends object>(component: ComponentType<Value>, options?: RestOptions): MotionHandle;
|
|
1035
|
+
all(handles: readonly Motion[]): MotionHandle;
|
|
1036
|
+
peer(key: string): Item | undefined;
|
|
1037
|
+
};
|
|
1038
|
+
/**
|
|
1039
|
+
* One `motion.change` hook, keyed by component name. `previous` and `next` are both passed, so a
|
|
1040
|
+
* rollback that lowers a level can pick another motion than a level-up.
|
|
1041
|
+
*
|
|
1042
|
+
* @example
|
|
1043
|
+
* ```ts
|
|
1044
|
+
* const slide: ChangeHook<Item> = view => view.toRest(Transform, { ms: 350 });
|
|
1045
|
+
* ```
|
|
1046
|
+
*/
|
|
1047
|
+
type ChangeHook<Item> = (view: ViewHandle<Item>, previous: Item, next: Item, hint?: Hint) => Motion;
|
|
1048
|
+
/**
|
|
1049
|
+
* The `change` table of a projection: one hook per component name.
|
|
1050
|
+
*
|
|
1051
|
+
* @example
|
|
1052
|
+
* ```ts
|
|
1053
|
+
* const change: ChangeHooks<Item> = { Transform: view => view.toRest(Transform) };
|
|
1054
|
+
* ```
|
|
1055
|
+
*/
|
|
1056
|
+
type ChangeHooks<Item> = {
|
|
1057
|
+
readonly [component: string]: ChangeHook<Item>;
|
|
1058
|
+
};
|
|
1059
|
+
/**
|
|
1060
|
+
* The motion hooks of a projection. Every one is optional; without a hook the component diff is
|
|
1061
|
+
* written directly. `loop` starts what moves a view for its whole life (an additive sway, a spin):
|
|
1062
|
+
* it is played wherever `enter` plays, next to it, and its motion is kept apart from the view's
|
|
1063
|
+
* other motions, so a change never cancels it; it ends when the view dies.
|
|
1064
|
+
*
|
|
1065
|
+
* @example
|
|
1066
|
+
* ```ts
|
|
1067
|
+
* const motion: ProjectionMotion<Item> = {
|
|
1068
|
+
* change: { Transform: view => view.toRest(Transform, { ms: 350 }) }
|
|
1069
|
+
* };
|
|
1070
|
+
* // An order card that sways forever once it entered.
|
|
1071
|
+
* const swaying: ProjectionMotion<Item> = {
|
|
1072
|
+
* loop: view =>
|
|
1073
|
+
* view.tween(Transform, { rotation: 0.05 }, { ms: 1200, additive: true, repeat: "forever" })
|
|
1074
|
+
* };
|
|
1075
|
+
* ```
|
|
1076
|
+
*/
|
|
1077
|
+
type ProjectionMotion<Item> = {
|
|
1078
|
+
enter?(view: ViewHandle<Item>, item: Item, hint?: Hint): Motion;
|
|
1079
|
+
loop?(view: ViewHandle<Item>): Motion;
|
|
1080
|
+
exit?(view: ViewHandle<Item>, item: Item, hint?: Hint): Motion;
|
|
1081
|
+
readonly change?: ChangeHooks<Item>;
|
|
1082
|
+
settle?(view: ViewHandle<Item>, components: readonly string[]): Motion;
|
|
1083
|
+
};
|
|
1084
|
+
/**
|
|
1085
|
+
* The motion hooks with the item type erased, as the world stores them.
|
|
1086
|
+
*
|
|
1087
|
+
* @example
|
|
1088
|
+
* ```ts
|
|
1089
|
+
* const stored: AnyProjectionMotion = { change: {} };
|
|
1090
|
+
* ```
|
|
1091
|
+
*/
|
|
1092
|
+
type AnyProjectionMotion = {
|
|
1093
|
+
enter?(view: ViewHandle<unknown>, item: unknown, hint?: Hint): Motion;
|
|
1094
|
+
loop?(view: ViewHandle<unknown>): Motion;
|
|
1095
|
+
exit?(view: ViewHandle<unknown>, item: unknown, hint?: Hint): Motion;
|
|
1096
|
+
readonly change?: ChangeHooks<never>;
|
|
1097
|
+
settle?(view: ViewHandle<unknown>, components: readonly string[]): Motion;
|
|
1098
|
+
};
|
|
1099
|
+
/**
|
|
1100
|
+
* One projection: keyed items of the model turned into entities. `Item` is inferred from the
|
|
1101
|
+
* return of `from`; `layer` and `lift` keep their literal types so a scene can check them.
|
|
1102
|
+
*
|
|
1103
|
+
* @example
|
|
1104
|
+
* ```ts
|
|
1105
|
+
* const spec: ProjectionSpec<Item, "items", "lifted", Player, Session> = {
|
|
1106
|
+
* name: "board.items",
|
|
1107
|
+
* layer: "items",
|
|
1108
|
+
* lift: "lifted",
|
|
1109
|
+
* from: player => player.board.items,
|
|
1110
|
+
* key: item => item.id,
|
|
1111
|
+
* view: item => [Item({ level: item.level })]
|
|
1112
|
+
* };
|
|
1113
|
+
* ```
|
|
1114
|
+
*/
|
|
1115
|
+
type ProjectionSpec<Item, LayerName extends string, LiftName extends string, Player, Session> = {
|
|
1116
|
+
readonly name: string;
|
|
1117
|
+
readonly layer: LayerName;
|
|
1118
|
+
readonly lift?: LiftName;
|
|
1119
|
+
from(player: Player, session: Session): readonly Item[] | Item; /** Omitted when `from` returns one plain object: the key is then the name of the projection. */
|
|
1120
|
+
key?(item: Item): string;
|
|
1121
|
+
view(item: Item): ViewOutput;
|
|
1122
|
+
readonly motion?: ProjectionMotion<Item>;
|
|
1123
|
+
};
|
|
1124
|
+
/**
|
|
1125
|
+
* A projection spec with its item and layer types erased, as the world stores it.
|
|
1126
|
+
*
|
|
1127
|
+
* @example
|
|
1128
|
+
* ```ts
|
|
1129
|
+
* const stored: AnyProjectionSpec = {
|
|
1130
|
+
* name: "board.items",
|
|
1131
|
+
* layer: "items",
|
|
1132
|
+
* from: () => [],
|
|
1133
|
+
* key: () => "",
|
|
1134
|
+
* view: () => []
|
|
1135
|
+
* };
|
|
1136
|
+
* ```
|
|
1137
|
+
*/
|
|
1138
|
+
type AnyProjectionSpec = {
|
|
1139
|
+
readonly name: string;
|
|
1140
|
+
readonly layer: string;
|
|
1141
|
+
readonly lift?: string;
|
|
1142
|
+
from(player: Json, session: Json): unknown;
|
|
1143
|
+
key?(item: unknown): string;
|
|
1144
|
+
view(item: unknown): ViewOutput;
|
|
1145
|
+
readonly motion?: AnyProjectionMotion;
|
|
1146
|
+
};
|
|
1147
|
+
/**
|
|
1148
|
+
* Why a reconcile runs. Merged over the frame; the cause table decides play or direct.
|
|
1149
|
+
*
|
|
1150
|
+
* @example
|
|
1151
|
+
* ```ts
|
|
1152
|
+
* const cause: Cause = "edge";
|
|
1153
|
+
* ```
|
|
1154
|
+
*/
|
|
1155
|
+
type Cause = "edge" | "rollback" | "restore" | "load" | "mount" | "rerun";
|
|
1156
|
+
/**
|
|
1157
|
+
* One live or exiting view: the entity of one key of one projection, its rest pose and the
|
|
1158
|
+
* motions that still run on it.
|
|
1159
|
+
*
|
|
1160
|
+
* @example
|
|
1161
|
+
* ```ts
|
|
1162
|
+
* const view: View = {
|
|
1163
|
+
* entity: 1_048_576,
|
|
1164
|
+
* projection: "board.items",
|
|
1165
|
+
* key: "i7",
|
|
1166
|
+
* item: { id: "i7" },
|
|
1167
|
+
* rest: new Map(),
|
|
1168
|
+
* handles: [],
|
|
1169
|
+
* lifted: false,
|
|
1170
|
+
* dropWhenStill: false,
|
|
1171
|
+
* exiting: false
|
|
1172
|
+
* };
|
|
1173
|
+
* ```
|
|
1174
|
+
*/
|
|
1175
|
+
type View$1 = {
|
|
1176
|
+
entity: Entity;
|
|
1177
|
+
projection: string;
|
|
1178
|
+
key: string;
|
|
1179
|
+
item: unknown; /** The last output of `view(item)`, by component name: what the picture equals at rest. */
|
|
1180
|
+
rest: Map<string, AnyComponentValue>;
|
|
1181
|
+
handles: MotionHandle[];
|
|
1182
|
+
lifted: boolean; /** `lift(false)` is waiting for the last motion to end. */
|
|
1183
|
+
dropWhenStill: boolean;
|
|
1184
|
+
exiting: boolean;
|
|
1185
|
+
};
|
|
1186
|
+
/**
|
|
1187
|
+
* One mounted projection: its owner, the live views by key, the despawn queue and the items of
|
|
1188
|
+
* the last reconcile.
|
|
1189
|
+
*
|
|
1190
|
+
* @example
|
|
1191
|
+
* ```ts
|
|
1192
|
+
* const mounted: Mounted = {
|
|
1193
|
+
* owner: { kind: "projection", name: "board.items" },
|
|
1194
|
+
* live: new Map(),
|
|
1195
|
+
* queue: [],
|
|
1196
|
+
* items: new Map()
|
|
1197
|
+
* };
|
|
1198
|
+
* ```
|
|
1199
|
+
*/
|
|
1200
|
+
type Mounted = {
|
|
1201
|
+
owner: Owner;
|
|
1202
|
+
live: Map<string, View$1>;
|
|
1203
|
+
queue: View$1[];
|
|
1204
|
+
items: Map<string, unknown>;
|
|
1205
|
+
};
|
|
1206
|
+
/**
|
|
1207
|
+
* One track the projection started on the driver. The world keeps the component name and the
|
|
1208
|
+
* handle, so it knows which components are driven without knowing how the driver runs them.
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```ts
|
|
1212
|
+
* const track: Track = { entity: 1_048_576, component: "Transform", handle: flight };
|
|
1213
|
+
* track.handle.active(); // true while the flight runs
|
|
1214
|
+
* ```
|
|
1215
|
+
*/
|
|
1216
|
+
type Track = {
|
|
1217
|
+
entity: Entity;
|
|
1218
|
+
component: string;
|
|
1219
|
+
handle: MotionHandle;
|
|
1220
|
+
};
|
|
1221
|
+
/**
|
|
1222
|
+
* The rest pose of one entity, by component name: the last output of `view(item)` for a view, or
|
|
1223
|
+
* what `setRest` recorded for an element another plugin owns.
|
|
1224
|
+
*/
|
|
1225
|
+
type RestPose = Map<string, AnyComponentValue>;
|
|
1226
|
+
/**
|
|
1227
|
+
* Where a keyed element lives: a plugin above registered it with `registerKey`, so `entityOf`
|
|
1228
|
+
* and `keyOf` answer for it next to the views.
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```ts
|
|
1232
|
+
* const key: ProjectionKey = { projection: "hud", key: "coins" };
|
|
1233
|
+
* ```
|
|
1234
|
+
*/
|
|
1235
|
+
type ProjectionKey = {
|
|
1236
|
+
projection: string;
|
|
1237
|
+
key: string;
|
|
1238
|
+
};
|
|
1239
|
+
/**
|
|
1240
|
+
* What the next reconcile has to do: why it runs, which roots changed and whether `view` is
|
|
1241
|
+
* forced for every item.
|
|
1242
|
+
*
|
|
1243
|
+
* @example
|
|
1244
|
+
* ```ts
|
|
1245
|
+
* const dirty: Dirty = { causes: ["edge"], roots: new Set(["player"]), force: false };
|
|
1246
|
+
* ```
|
|
1247
|
+
*/
|
|
1248
|
+
type Dirty = {
|
|
1249
|
+
causes: Cause[];
|
|
1250
|
+
roots: Set<Root>;
|
|
1251
|
+
force: boolean;
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* projection module state.
|
|
1255
|
+
*/
|
|
1256
|
+
type ProjectionState = {
|
|
1257
|
+
specs: Map<string, AnyProjectionSpec>;
|
|
1258
|
+
layers: readonly LayerSpec[];
|
|
1259
|
+
mounted: Map<string, Mounted>;
|
|
1260
|
+
byEntity: Map<Entity, View$1>;
|
|
1261
|
+
dirty: Dirty | undefined;
|
|
1262
|
+
hints: Hint[]; /** The tracks the projection started, with the handle that says whether they still run. */
|
|
1263
|
+
tracks: Track[]; /** The tween engine `anim` installed, or `undefined`: then every target is written at once. */
|
|
1264
|
+
driver: TweenDriver | undefined; /** The rest pose of an entity that is not a view, recorded by its owner through `setRest`. */
|
|
1265
|
+
rests: Map<Entity, RestPose>; /** Projection name to key to the entity a plugin above registered under it. */
|
|
1266
|
+
keys: Map<string, Map<string, Entity>>; /** The reverse of `keys`, so `keyOf` answers for a registered element too. */
|
|
1267
|
+
keysByEntity: Map<Entity, ProjectionKey>; /** Entity to component name to the fields another writer owns. */
|
|
1268
|
+
mutes: Map<Entity, Map<string, Set<string>>>;
|
|
1269
|
+
offHints: Array<() => void>;
|
|
1270
|
+
};
|
|
1271
|
+
/**
|
|
1272
|
+
* projection module API, `app.world.projection`: the bridge from the model to entities.
|
|
1273
|
+
*
|
|
1274
|
+
* @example
|
|
1275
|
+
* ```ts
|
|
1276
|
+
* // A test mounts the board and asks which entity carries one model key.
|
|
1277
|
+
* app.world.projection.mount(["board.items"], { kind: "plugin", name: "test" });
|
|
1278
|
+
* app.world.projection.entityOf("board.items", "i7"); // 1048576
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
type ProjectionApi = {
|
|
1282
|
+
/**
|
|
1283
|
+
* Stores a projection spec. Nothing is drawn until `mount`.
|
|
1284
|
+
*
|
|
1285
|
+
* @param spec - The projection, built with `projection()`.
|
|
1286
|
+
* @throws {Error} When a projection of that name is already registered.
|
|
1287
|
+
* @example
|
|
1288
|
+
* ```ts
|
|
1289
|
+
* // A test registers one projection by hand instead of composing a feature.
|
|
1290
|
+
* app.world.projection.register(boardItems);
|
|
1291
|
+
* app.world.projection.entityOf("board.items", "i7"); // undefined: nothing is mounted yet
|
|
1292
|
+
* ```
|
|
1293
|
+
*/
|
|
1294
|
+
register(spec: AnyProjectionSpec): void;
|
|
1295
|
+
/**
|
|
1296
|
+
* Stores the layer list of the scene. The order of the list is draw order; every call stores a
|
|
1297
|
+
* new frozen array.
|
|
1298
|
+
*
|
|
1299
|
+
* @param list - The layers, in draw order.
|
|
1300
|
+
* @example
|
|
1301
|
+
* ```ts
|
|
1302
|
+
* // `scenes` switches to the board scene.
|
|
1303
|
+
* const world = ctx.require(worldPlugin);
|
|
1304
|
+
* world.projection.setLayers([
|
|
1305
|
+
* { name: "board", sort: "none" },
|
|
1306
|
+
* { name: "items", sort: "y" },
|
|
1307
|
+
* { name: "lifted", sort: "none" }
|
|
1308
|
+
* ]);
|
|
1309
|
+
* ```
|
|
1310
|
+
*/
|
|
1311
|
+
setLayers(list: ReadonlyArray<LayerSpec>): void;
|
|
1312
|
+
/**
|
|
1313
|
+
* The current layer list, empty before the first `setLayers`. A new list is a new reference, so
|
|
1314
|
+
* a reader compares by identity.
|
|
1315
|
+
*
|
|
1316
|
+
* @returns The layers, in draw order.
|
|
1317
|
+
* @example
|
|
1318
|
+
* ```ts
|
|
1319
|
+
* // `renderer.sync` rebuilds its containers only when the scene really changed.
|
|
1320
|
+
* const current = app.world.projection.layers();
|
|
1321
|
+
* if (current !== lastLayers) rebuildContainers(current); // [{ name: "board", sort: "none" }, ...]
|
|
1322
|
+
* ```
|
|
1323
|
+
*/
|
|
1324
|
+
layers(): ReadonlyArray<LayerSpec>;
|
|
1325
|
+
/**
|
|
1326
|
+
* Marks projections mounted under an owner and reconciles them at once, direct: a scene brings
|
|
1327
|
+
* its own transition. Nothing is mounted when the call throws.
|
|
1328
|
+
*
|
|
1329
|
+
* @param names - Names of the projections to mount.
|
|
1330
|
+
* @param owner - Who owns the entities of these projections.
|
|
1331
|
+
* @throws {Error} For an unknown name, or a `layer` or `lift` the scene does not declare.
|
|
1332
|
+
* @example
|
|
1333
|
+
* ```ts
|
|
1334
|
+
* // `scenes` mounts the projections of the board scene it just switched to.
|
|
1335
|
+
* const world = ctx.require(worldPlugin);
|
|
1336
|
+
* world.projection.mount(["board.cells", "board.items"], { kind: "plugin", name: "scenes" });
|
|
1337
|
+
* world.projection.entityOf("board.items", "i7"); // 1048576, the picture is already there
|
|
1338
|
+
* ```
|
|
1339
|
+
*/
|
|
1340
|
+
mount(names: readonly string[], owner: Owner): void;
|
|
1341
|
+
/**
|
|
1342
|
+
* Unmounts projections: every motion of their views is finished, and the live views and the
|
|
1343
|
+
* despawn queue are despawned in the same call.
|
|
1344
|
+
*
|
|
1345
|
+
* @param names - Names of the projections to unmount.
|
|
1346
|
+
* @example
|
|
1347
|
+
* ```ts
|
|
1348
|
+
* // `scenes` leaves the board scene.
|
|
1349
|
+
* const world = ctx.require(worldPlugin);
|
|
1350
|
+
* world.projection.unmount(["board.cells", "board.items"]);
|
|
1351
|
+
* world.projection.entityOf("board.items", "i7"); // undefined
|
|
1352
|
+
* ```
|
|
1353
|
+
*/
|
|
1354
|
+
unmount(names: readonly string[]): void;
|
|
1355
|
+
/**
|
|
1356
|
+
* A drop with no commit: cancels the view's motions and plays settle for every loose component.
|
|
1357
|
+
* In mode `paused` or `fast` the rest pose is written at once. No-op for an entity that is not
|
|
1358
|
+
* a live view.
|
|
1359
|
+
*
|
|
1360
|
+
* @param entity - The entity of the view that was dropped.
|
|
1361
|
+
* @example
|
|
1362
|
+
* ```ts
|
|
1363
|
+
* // `input` released the finger over nothing, so the gate refused the answer.
|
|
1364
|
+
* const world = ctx.require(worldPlugin);
|
|
1365
|
+
* world.projection.settle(held); // the item glides back to its cell over settleMs
|
|
1366
|
+
* ```
|
|
1367
|
+
*/
|
|
1368
|
+
settle(entity: Entity): void;
|
|
1369
|
+
/**
|
|
1370
|
+
* Hands a set of fields to another writer. Tracks never write them, also not on `finish()`.
|
|
1371
|
+
*
|
|
1372
|
+
* @param entity - The entity of the view.
|
|
1373
|
+
* @param component - The component whose fields are taken over.
|
|
1374
|
+
* @param fields - The field names the caller owns.
|
|
1375
|
+
* @returns The remover; safe after the entity left, and a no-op when called twice.
|
|
1376
|
+
* @example
|
|
1377
|
+
* ```ts
|
|
1378
|
+
* // `input` owns the position of the held view while the finger drags it.
|
|
1379
|
+
* const world = ctx.require(worldPlugin);
|
|
1380
|
+
* const release = world.projection.mute(held, Transform, ["x", "y"]);
|
|
1381
|
+
*
|
|
1382
|
+
* release(); // the finger let go
|
|
1383
|
+
* ```
|
|
1384
|
+
*/
|
|
1385
|
+
mute(entity: Entity, component: ComponentType<object>, fields: readonly string[]): () => void;
|
|
1386
|
+
/**
|
|
1387
|
+
* Moves a view into or out of its projection's `lift` layer. `lift(entity, false)` on a view
|
|
1388
|
+
* that still moves takes effect when its last motion ends, so a settling view stays above the
|
|
1389
|
+
* board until it is home.
|
|
1390
|
+
*
|
|
1391
|
+
* @param entity - The entity of the view.
|
|
1392
|
+
* @param on - True to lift, false to drop back.
|
|
1393
|
+
* @example
|
|
1394
|
+
* ```ts
|
|
1395
|
+
* // `input` lifts the item the finger picked up so it draws above the board.
|
|
1396
|
+
* const world = ctx.require(worldPlugin);
|
|
1397
|
+
* world.projection.lift(held, true);
|
|
1398
|
+
*
|
|
1399
|
+
* world.projection.lift(held, false); // on release: takes effect when the settle motion ends
|
|
1400
|
+
* ```
|
|
1401
|
+
*/
|
|
1402
|
+
lift(entity: Entity, on: boolean): void;
|
|
1403
|
+
/**
|
|
1404
|
+
* The projection and key of an entity. Also answers for a view in the despawn queue and for an
|
|
1405
|
+
* element registered with `registerKey`.
|
|
1406
|
+
*
|
|
1407
|
+
* @param entity - The entity to ask about.
|
|
1408
|
+
* @returns The projection name and the key, or `undefined`.
|
|
1409
|
+
* @example
|
|
1410
|
+
* ```ts
|
|
1411
|
+
* // `renderer.sync` labels its display objects for the inspector.
|
|
1412
|
+
* app.world.projection.keyOf(1_048_576); // { projection: "board.items", key: "i7" }
|
|
1413
|
+
* app.world.projection.keyOf(42); // undefined: not a view
|
|
1414
|
+
* ```
|
|
1415
|
+
*/
|
|
1416
|
+
keyOf(entity: Entity): ProjectionKey | undefined;
|
|
1417
|
+
/**
|
|
1418
|
+
* The rest value of one component of an entity: what a view holds when nothing animates it, or
|
|
1419
|
+
* what `setRest` recorded for an element. Read-only and owner-free, so a plugin that owns no
|
|
1420
|
+
* entity can aim at one.
|
|
1421
|
+
*
|
|
1422
|
+
* @param entity - A projection view or a registered element.
|
|
1423
|
+
* @param component - The component whose rest value is asked.
|
|
1424
|
+
* @returns The rest value, or `undefined` when the entity has none recorded.
|
|
1425
|
+
* @example
|
|
1426
|
+
* ```ts
|
|
1427
|
+
* // `anim` builds a coin flight toward the counter: `at(target)` is the counter's rest pose.
|
|
1428
|
+
* const world = ctx.require(worldPlugin);
|
|
1429
|
+
* const counter = world.projection.entityOf("hud", "coins") ?? 0;
|
|
1430
|
+
*
|
|
1431
|
+
* world.projection.restOf(counter, Transform); // { x: 40, y: 120, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
|
|
1432
|
+
* ```
|
|
1433
|
+
*/
|
|
1434
|
+
restOf<Value extends object>(entity: Entity, component: ComponentType<Value>): Value | undefined;
|
|
1435
|
+
/**
|
|
1436
|
+
* The entity of one key of one projection: a live view, never the despawn queue, or an element
|
|
1437
|
+
* a plugin above registered under that key.
|
|
1438
|
+
*
|
|
1439
|
+
* @param projection - Name of the projection.
|
|
1440
|
+
* @param key - The model key.
|
|
1441
|
+
* @returns The entity, or `undefined`.
|
|
1442
|
+
* @example
|
|
1443
|
+
* ```ts
|
|
1444
|
+
* // `input` resolves the target of a scripted drag.
|
|
1445
|
+
* const world = ctx.require(worldPlugin);
|
|
1446
|
+
* world.projection.entityOf("board.items", "i7"); // 1048576
|
|
1447
|
+
* world.projection.entityOf("board.items", "gone"); // undefined
|
|
1448
|
+
* ```
|
|
1449
|
+
*/
|
|
1450
|
+
entityOf(projection: string, key: string): Entity | undefined;
|
|
1451
|
+
/**
|
|
1452
|
+
* The live view entities of a mounted projection, in the order of its model keys. A view that
|
|
1453
|
+
* plays its exit is left out, and so is an element registered with `registerKey`. Every call
|
|
1454
|
+
* hands out a new array.
|
|
1455
|
+
*
|
|
1456
|
+
* @param name - Name of the projection.
|
|
1457
|
+
* @returns The entities, or `[]` when the projection is not mounted.
|
|
1458
|
+
* @example
|
|
1459
|
+
* ```ts
|
|
1460
|
+
* // `ui` hosts the board inside a slot and parents every board item to it on reconcile.
|
|
1461
|
+
* const world = ctx.require(worldPlugin);
|
|
1462
|
+
* world.projection.entitiesOf("board.items"); // [1048580, 1048581]
|
|
1463
|
+
* world.projection.entitiesOf("shop.items"); // []: not mounted
|
|
1464
|
+
* ```
|
|
1465
|
+
*/
|
|
1466
|
+
entitiesOf(name: string): readonly Entity[];
|
|
1467
|
+
/**
|
|
1468
|
+
* Installs the tween engine every `ViewHandle.tween`, `toRest` and `all` then runs on. Without
|
|
1469
|
+
* one the world writes every target at once and hands back an inactive handle.
|
|
1470
|
+
*
|
|
1471
|
+
* @param driver - The engine that runs the tracks.
|
|
1472
|
+
* @returns The remover; it puts the instant writes back.
|
|
1473
|
+
* @example
|
|
1474
|
+
* ```ts
|
|
1475
|
+
* // `anim` installs its tween core when it starts and takes it back when it stops.
|
|
1476
|
+
* const world = ctx.require(worldPlugin);
|
|
1477
|
+
* ctx.state.removeDriver = world.projection.setDriver(createDriver(ctx, ctx.state));
|
|
1478
|
+
*
|
|
1479
|
+
* ctx.state.removeDriver(); // onStop: motions are instant again
|
|
1480
|
+
* ```
|
|
1481
|
+
*/
|
|
1482
|
+
setDriver(driver: TweenDriver): () => void;
|
|
1483
|
+
/**
|
|
1484
|
+
* A view handle for an entity that is not a projection view, so an element another plugin owns
|
|
1485
|
+
* can be animated the same way. `set`, `get`, `tween`, `toRest` and `all` work; `rest` answers
|
|
1486
|
+
* what `setRest` recorded; `peer` answers `undefined`.
|
|
1487
|
+
*
|
|
1488
|
+
* @param entity - The entity of the element.
|
|
1489
|
+
* @param owner - Who is asking; without it only the kind of the owner is checked.
|
|
1490
|
+
* @returns The handle, or `undefined` for a projection view and for a foreign entity.
|
|
1491
|
+
* @example
|
|
1492
|
+
* ```ts
|
|
1493
|
+
* // `ui` plays the `motion` prop of an element it owns.
|
|
1494
|
+
* const world = ctx.require(worldPlugin);
|
|
1495
|
+
* const handle = world.projection.viewOf(button, { kind: "plugin", name: "ui" });
|
|
1496
|
+
*
|
|
1497
|
+
* handle?.tween(Transform, { scale: 1.1 }, { ms: 120, ease: "outBack" });
|
|
1498
|
+
* ```
|
|
1499
|
+
*/
|
|
1500
|
+
viewOf(entity: Entity, owner: Owner): ViewHandle<unknown> | undefined;
|
|
1501
|
+
/**
|
|
1502
|
+
* Records what one component of an element looks like at rest, so `toRest` knows where home is.
|
|
1503
|
+
*
|
|
1504
|
+
* @param entity - The entity of the element.
|
|
1505
|
+
* @param component - The component whose rest value is recorded.
|
|
1506
|
+
* @param value - The value the element holds when nothing animates.
|
|
1507
|
+
* @example
|
|
1508
|
+
* ```ts
|
|
1509
|
+
* // `ui` laid the coin counter out and tells the world where it belongs.
|
|
1510
|
+
* const world = ctx.require(worldPlugin);
|
|
1511
|
+
* world.projection.setRest(counter, Transform, { x: 40, y: 120, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } });
|
|
1512
|
+
*
|
|
1513
|
+
* world.projection.viewOf(counter, { kind: "plugin", name: "ui" })?.rest(Transform); // { x: 40, y: 120, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
|
|
1514
|
+
* ```
|
|
1515
|
+
*/
|
|
1516
|
+
setRest<Value extends object>(entity: Entity, component: ComponentType<Value>, value: Value): void;
|
|
1517
|
+
/**
|
|
1518
|
+
* Gives an entity a projection and key of its own, so `entityOf` and `keyOf` answer for it next
|
|
1519
|
+
* to the live views. That is how an element becomes a target for `anim`, `input` and `guide`.
|
|
1520
|
+
*
|
|
1521
|
+
* @param projection - Name the element is addressed under.
|
|
1522
|
+
* @param key - Key the element is addressed under.
|
|
1523
|
+
* @param entity - The entity of the element.
|
|
1524
|
+
* @returns The remover; it drops the key again.
|
|
1525
|
+
* @throws {Error} When a live view of that projection already holds the key.
|
|
1526
|
+
* @example
|
|
1527
|
+
* ```ts
|
|
1528
|
+
* // `ui` publishes the coin counter of the HUD.
|
|
1529
|
+
* const world = ctx.require(worldPlugin);
|
|
1530
|
+
* const drop = world.projection.registerKey("hud", "coins", counter);
|
|
1531
|
+
*
|
|
1532
|
+
* world.projection.entityOf("hud", "coins"); // the entity of the counter
|
|
1533
|
+
* drop(); // the element left the screen
|
|
1534
|
+
* ```
|
|
1535
|
+
*/
|
|
1536
|
+
registerKey(projection: string, key: string, entity: Entity): () => void;
|
|
1537
|
+
/**
|
|
1538
|
+
* Marks every mounted projection dirty and forces `view` for every item, so the next frame
|
|
1539
|
+
* rebuilds the picture directly.
|
|
1540
|
+
*
|
|
1541
|
+
* @example
|
|
1542
|
+
* ```ts
|
|
1543
|
+
* // `i18n` switched the locale: every label has to be projected again.
|
|
1544
|
+
* const world = ctx.require(worldPlugin);
|
|
1545
|
+
* world.projection.rerunAll(); // the next frame writes the new strings with no motion
|
|
1546
|
+
* ```
|
|
1547
|
+
*/
|
|
1548
|
+
rerunAll(): void;
|
|
1549
|
+
};
|
|
1550
|
+
/**
|
|
1551
|
+
* projection methods the plugin root drives from the frame and the hooks. Not public.
|
|
1552
|
+
*/
|
|
1553
|
+
type ProjectionInternal = {
|
|
1554
|
+
/**
|
|
1555
|
+
* Records a commit. In fast mode it reconciles at once, direct.
|
|
1556
|
+
*
|
|
1557
|
+
* @param roots - The state roots the commit touched.
|
|
1558
|
+
* @param cause - Why the commit happened.
|
|
1559
|
+
*/
|
|
1560
|
+
markDirty(roots: readonly Root[], cause: Cause): void;
|
|
1561
|
+
/**
|
|
1562
|
+
* Reconciles when the world is dirty. Runs at the start of `time` phase `input`.
|
|
1563
|
+
*/
|
|
1564
|
+
reconcileIfDirty(): void;
|
|
1565
|
+
/**
|
|
1566
|
+
* Empties the hint buffer. Runs once per frame, reconcile or not.
|
|
1567
|
+
*/
|
|
1568
|
+
dropHints(): void;
|
|
1569
|
+
/**
|
|
1570
|
+
* Buffers one released hint.
|
|
1571
|
+
*
|
|
1572
|
+
* @param hint - The hint `flow.fx` released after a commit.
|
|
1573
|
+
*/
|
|
1574
|
+
pushHint(hint: Hint): void;
|
|
1575
|
+
/**
|
|
1576
|
+
* Sweeps the views whose motions ended: the despawn queue, the convergence check and the
|
|
1577
|
+
* pending drops. The tracks themselves are advanced by the driver.
|
|
1578
|
+
*/
|
|
1579
|
+
sweep(): void;
|
|
1580
|
+
/**
|
|
1581
|
+
* Finishes every motion and flushes every despawn queue. Called when the mode turns `fast`.
|
|
1582
|
+
*/
|
|
1583
|
+
flushAll(): void;
|
|
1584
|
+
/**
|
|
1585
|
+
* Drops the views of a projection whose owner despawned its entities.
|
|
1586
|
+
*
|
|
1587
|
+
* @param owner - The owner that left.
|
|
1588
|
+
*/
|
|
1589
|
+
ownerLeft(owner: Owner): void;
|
|
1590
|
+
/**
|
|
1591
|
+
* Drops every view, track, mute and spec.
|
|
1592
|
+
*/
|
|
1593
|
+
clear(): void;
|
|
1594
|
+
};
|
|
1595
|
+
declare namespace types_d_exports$1 {
|
|
1596
|
+
export { AnyComponentType, AnyComponentValue, AnyProjectionSpec, AnySystem, Api$1 as Api, Cause, ChangeHook, ComponentHandle, ComponentType, ComponentValue, Config$1 as Config, Deps$1 as Deps, DescriptionNode, Ease, EcsApi, EcsModule, Entity, EntitySnapshot, Events$1 as Events, KernelSlice$1 as KernelSlice, LayerSort, LayerSpec, ModelCommitted, Motion, MotionHandle, Mut, Narrowed, NumericFields, Owner, ProjectionApi, ProjectionKey, ProjectionModule, ProjectionMotion, ProjectionSpec, QueryTerm, QueryTuple, ResourceType, RestOptions, State$1 as State, SystemContext, SystemDefinition, TagType, TrackOptions, TrackSegment, TweenDriver, TweenOptions, ViewHandle, ViewOutput, WorldCtx, WorldMode, WorldPhase, WorldSnapshot };
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* world plugin events.
|
|
1600
|
+
*
|
|
1601
|
+
* @example
|
|
1602
|
+
* ```ts
|
|
1603
|
+
* // A dev build counts what one reconcile did.
|
|
1604
|
+
* createPlugin("reconcileLog", {
|
|
1605
|
+
* depends: [worldPlugin],
|
|
1606
|
+
* hooks: ctx => ({ "world:reconciled": counts => ctx.log.debug("reconciled", counts) })
|
|
1607
|
+
* }); // one merge logs { mode: "play", entered: 0, changed: 1, exited: 1, ... }
|
|
1608
|
+
* ```
|
|
1609
|
+
*/
|
|
1610
|
+
type Events$1 = {
|
|
1611
|
+
/** Dev only (config `reconciledEvent`). Counts of one reconcile. */"world:reconciled": {
|
|
1612
|
+
mode: "play" | "direct";
|
|
1613
|
+
projections: number;
|
|
1614
|
+
entered: number;
|
|
1615
|
+
changed: number;
|
|
1616
|
+
exited: number;
|
|
1617
|
+
revived: number;
|
|
1618
|
+
queued: number;
|
|
1619
|
+
hintsRouted: number;
|
|
1620
|
+
hintsDropped: number;
|
|
1621
|
+
};
|
|
1622
|
+
};
|
|
1623
|
+
/**
|
|
1624
|
+
* world plugin config.
|
|
1625
|
+
*
|
|
1626
|
+
* @example
|
|
1627
|
+
* ```ts
|
|
1628
|
+
* createApp({ pluginConfigs: { world: { settleMs: 250, reconciledEvent: true } } });
|
|
1629
|
+
* ```
|
|
1630
|
+
*/
|
|
1631
|
+
type Config$1 = {
|
|
1632
|
+
/** Duration of the built-in settle motion, in game milliseconds. */settleMs: number; /** Emit `world:reconciled` after every reconcile. Off in production; tests and the editor turn it on. */
|
|
1633
|
+
reconciledEvent: boolean;
|
|
1634
|
+
};
|
|
1635
|
+
/**
|
|
1636
|
+
* world plugin state: one branch per module.
|
|
1637
|
+
*/
|
|
1638
|
+
type State$1 = {
|
|
1639
|
+
ecs: EcsState;
|
|
1640
|
+
projection: ProjectionState;
|
|
1641
|
+
};
|
|
1642
|
+
/**
|
|
1643
|
+
* world plugin API, `app.world`, grouped by module.
|
|
1644
|
+
*
|
|
1645
|
+
* @example
|
|
1646
|
+
* ```ts
|
|
1647
|
+
* app.world.ecs.mode(); // "live"
|
|
1648
|
+
* app.world.projection.entityOf("board.items", "i7"); // 1048576
|
|
1649
|
+
* ```
|
|
1650
|
+
*/
|
|
1651
|
+
type Api$1 = {
|
|
1652
|
+
ecs: EcsApi;
|
|
1653
|
+
projection: ProjectionApi;
|
|
1654
|
+
};
|
|
1655
|
+
/**
|
|
1656
|
+
* Resolved dependency APIs.
|
|
1657
|
+
*/
|
|
1658
|
+
type Deps$1 = {
|
|
1659
|
+
time: Api$2;
|
|
1660
|
+
model: Api$3;
|
|
1661
|
+
flow: Api$4;
|
|
1662
|
+
};
|
|
1663
|
+
/**
|
|
1664
|
+
* What the kernel context offers before the deps are attached.
|
|
1665
|
+
*
|
|
1666
|
+
* `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
|
|
1667
|
+
* row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
|
|
1668
|
+
* kernel's own, world-typed one. No member of the context is cast.
|
|
1669
|
+
*/
|
|
1670
|
+
type KernelSlice$1 = PluginCtx<Config$1, State$1, Events$1> & {
|
|
1671
|
+
readonly global: object;
|
|
1672
|
+
readonly log: Log.LogApi;
|
|
1673
|
+
readonly require: Require;
|
|
1674
|
+
};
|
|
1675
|
+
/**
|
|
1676
|
+
* Domain context shared by the two modules.
|
|
1677
|
+
*/
|
|
1678
|
+
type WorldCtx = KernelSlice$1 & {
|
|
1679
|
+
readonly deps: Deps$1;
|
|
1680
|
+
};
|
|
1681
|
+
/**
|
|
1682
|
+
* Payload of the one event world listens to.
|
|
1683
|
+
*/
|
|
1684
|
+
type ModelCommitted = Events$2["model:committed"];
|
|
1685
|
+
/**
|
|
1686
|
+
* Both halves of the `ecs` module: what a game calls and what `projection` gets injected.
|
|
1687
|
+
*/
|
|
1688
|
+
type EcsModule = EcsApi & EcsInternal;
|
|
1689
|
+
/**
|
|
1690
|
+
* Both halves of the `projection` module: what a game calls and what the frame drives.
|
|
1691
|
+
*/
|
|
1692
|
+
type ProjectionModule = ProjectionApi & ProjectionInternal;
|
|
1693
|
+
//#endregion
|
|
1694
|
+
//#region src/plugins/renderer/host/types.d.ts
|
|
1695
|
+
/**
|
|
1696
|
+
* host module state.
|
|
1697
|
+
*/
|
|
1698
|
+
type HostState = {
|
|
1699
|
+
/** The Pixi module object, once `config.loadPixi()` resolved. */pixi: PixiModule | undefined;
|
|
1700
|
+
app: PixiApplication | undefined;
|
|
1701
|
+
canvas: HTMLCanvasElement | undefined; /** Where the canvas was appended. Read by `viewport` for its observer and its measurements. */
|
|
1702
|
+
mount: HTMLElement | undefined;
|
|
1703
|
+
kind: RendererKind;
|
|
1704
|
+
ready: boolean; /** True from the moment a device is lost until the restore attempt ends. */
|
|
1705
|
+
restoring: boolean; /** `viewport` and `sync` subscribe here; run once, after the first successful init. */
|
|
1706
|
+
onReady: Array<() => void>; /** Run after a restore created a new application, where `onReady` must not run twice. */
|
|
1707
|
+
onRestore: Array<() => void>; /** Run before the lost application is destroyed, while its display objects are still alive. */
|
|
1708
|
+
onLoss: Array<() => void>;
|
|
1709
|
+
cleanups: Array<() => void>;
|
|
1710
|
+
unsupported: HTMLElement | undefined;
|
|
1711
|
+
};
|
|
1712
|
+
/**
|
|
1713
|
+
* host module API, `app.renderer.host`. Everything a caller needs to know about the one Pixi
|
|
1714
|
+
* application: whether it draws, what it draws with, and which canvas it draws on.
|
|
1715
|
+
*
|
|
1716
|
+
* @example
|
|
1717
|
+
* ```ts
|
|
1718
|
+
* // A game shows a DOM fallback while the renderer is inert or on the unsupported screen.
|
|
1719
|
+
* if (!app.renderer.host.ready()) showFallback();
|
|
1720
|
+
* app.renderer.host.kind(); // "webgpu"
|
|
1721
|
+
* ```
|
|
1722
|
+
*/
|
|
1723
|
+
type HostApi = {
|
|
1724
|
+
/**
|
|
1725
|
+
* Tells whether the renderer draws. False while inert, while the device is lost and on the
|
|
1726
|
+
* unsupported-device screen.
|
|
1727
|
+
*
|
|
1728
|
+
* @returns True after a successful init.
|
|
1729
|
+
* @example
|
|
1730
|
+
* ```ts
|
|
1731
|
+
* // `assets` skips the texture upload of a bundle while nothing can draw it.
|
|
1732
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
1733
|
+
* if (!renderer.host.ready()) return; // headless: the bundle stays on disk
|
|
1734
|
+
* ```
|
|
1735
|
+
*/
|
|
1736
|
+
ready(): boolean;
|
|
1737
|
+
/**
|
|
1738
|
+
* The backend Pixi chose, read once from the application after init.
|
|
1739
|
+
*
|
|
1740
|
+
* @returns `"webgpu"`, `"webgl"`, or `"none"` while inert or unsupported.
|
|
1741
|
+
* @example
|
|
1742
|
+
* ```ts
|
|
1743
|
+
* // A game picks the WGSL or the GLSL source of its own filter.
|
|
1744
|
+
* const source = app.renderer.host.kind() === "webgpu" ? wgsl : glsl; // "webgpu" in Chrome
|
|
1745
|
+
* ```
|
|
1746
|
+
*/
|
|
1747
|
+
kind(): RendererKind;
|
|
1748
|
+
/**
|
|
1749
|
+
* The canvas of the Pixi application. A WebGPU restore makes a NEW canvas, so a caller that
|
|
1750
|
+
* holds listeners compares this with its stored canvas every frame.
|
|
1751
|
+
*
|
|
1752
|
+
* @returns The canvas, or `undefined` while inert and on the unsupported screen.
|
|
1753
|
+
* @example
|
|
1754
|
+
* ```ts
|
|
1755
|
+
* // `input` re-attaches its pointer listeners when a restore replaced the canvas.
|
|
1756
|
+
* const canvas = ctx.require(rendererPlugin).host.canvas();
|
|
1757
|
+
* if (canvas !== undefined && canvas !== attached) attachPointerListeners(canvas);
|
|
1758
|
+
* ```
|
|
1759
|
+
*/
|
|
1760
|
+
canvas(): HTMLCanvasElement | undefined;
|
|
1761
|
+
/**
|
|
1762
|
+
* The Pixi module the renderer loaded lazily, so a plugin above draws with the same Pixi and
|
|
1763
|
+
* still imports none of it.
|
|
1764
|
+
*
|
|
1765
|
+
* @returns The module once `ready()`, `undefined` while inert, lost or unsupported.
|
|
1766
|
+
* @example
|
|
1767
|
+
* ```ts
|
|
1768
|
+
* // `text` builds the display object of a label out of the module the renderer loaded.
|
|
1769
|
+
* const pixi = ctx.require(rendererPlugin).host.pixi();
|
|
1770
|
+
* if (pixi === undefined) return; // headless: nothing is drawn
|
|
1771
|
+
*
|
|
1772
|
+
* const line = new pixi.BitmapText({ text: "+5", style: { fontFamily: "hud.body" } });
|
|
1773
|
+
* ```
|
|
1774
|
+
*/
|
|
1775
|
+
pixi(): PixiModule | undefined;
|
|
1776
|
+
};
|
|
1777
|
+
/**
|
|
1778
|
+
* The textures the GPU holds: how many sources, and their estimated bytes.
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```ts
|
|
1782
|
+
* const usage: TextureUsage = { count: 2, bytes: 5_242_880 };
|
|
1783
|
+
* ```
|
|
1784
|
+
*/
|
|
1785
|
+
type TextureUsage = {
|
|
1786
|
+
count: number;
|
|
1787
|
+
bytes: number;
|
|
1788
|
+
};
|
|
1789
|
+
/**
|
|
1790
|
+
* host methods injected into `viewport`, `sync` and `monitor`, and driven by the plugin root. Not
|
|
1791
|
+
* public.
|
|
1792
|
+
*/
|
|
1793
|
+
type HostInternal = {
|
|
1794
|
+
/**
|
|
1795
|
+
* Subscribes to the first successful init. Callbacks run in subscription order: `viewport`
|
|
1796
|
+
* first, `sync` second.
|
|
1797
|
+
*
|
|
1798
|
+
* @param fn - Called once, after `ready` turned true.
|
|
1799
|
+
*/
|
|
1800
|
+
onReady(fn: () => void): void;
|
|
1801
|
+
/**
|
|
1802
|
+
* Subscribes to a successful restore, where a new application and a new canvas exist but the
|
|
1803
|
+
* one-time registrations of `onReady` must not run again.
|
|
1804
|
+
*
|
|
1805
|
+
* @param fn - Called after every successful restore.
|
|
1806
|
+
*/
|
|
1807
|
+
onRestore(fn: () => void): void;
|
|
1808
|
+
/**
|
|
1809
|
+
* Subscribes to the moment just before a lost application is destroyed. This is the last point
|
|
1810
|
+
* at which a display object the game owns can be saved out of the tree.
|
|
1811
|
+
*
|
|
1812
|
+
* @param fn - Called before every `destroy` of a lost application.
|
|
1813
|
+
*/
|
|
1814
|
+
onLoss(fn: () => void): void;
|
|
1815
|
+
/**
|
|
1816
|
+
* The stage of the application. `sync` hangs its root container here.
|
|
1817
|
+
*
|
|
1818
|
+
* @returns The stage, or `undefined` while inert.
|
|
1819
|
+
*/
|
|
1820
|
+
stage(): PixiContainer | undefined;
|
|
1821
|
+
/**
|
|
1822
|
+
* Where the canvas was appended. `viewport` observes and measures it.
|
|
1823
|
+
*
|
|
1824
|
+
* @returns The mount element, or `undefined` while inert.
|
|
1825
|
+
*/
|
|
1826
|
+
mount(): HTMLElement | undefined;
|
|
1827
|
+
/**
|
|
1828
|
+
* Resizes the renderer to a CSS-pixel size. A no-op while nothing draws.
|
|
1829
|
+
*
|
|
1830
|
+
* @param width - New CSS width of the canvas.
|
|
1831
|
+
* @param height - New CSS height of the canvas.
|
|
1832
|
+
*/
|
|
1833
|
+
resize(width: number, height: number): void;
|
|
1834
|
+
/**
|
|
1835
|
+
* Draws one frame. A no-op while nothing draws, so a lost device costs nothing.
|
|
1836
|
+
*/
|
|
1837
|
+
render(): void;
|
|
1838
|
+
/**
|
|
1839
|
+
* Draws the stage into a PNG over the whole canvas. A failed read is logged.
|
|
1840
|
+
*
|
|
1841
|
+
* @returns A PNG data URL, or `undefined` while nothing draws or when Pixi could not read it.
|
|
1842
|
+
*/
|
|
1843
|
+
extract(): Promise<string | undefined>;
|
|
1844
|
+
/**
|
|
1845
|
+
* The texture sources the GPU holds and their estimated memory, read at call time.
|
|
1846
|
+
*
|
|
1847
|
+
* @returns The usage; zero while nothing draws.
|
|
1848
|
+
*/
|
|
1849
|
+
textures(): TextureUsage;
|
|
1850
|
+
/**
|
|
1851
|
+
* Runs the init sequence: resolve the mount, load Pixi, create the application, append the
|
|
1852
|
+
* canvas, then run the `onReady` callbacks. A failure shows the unsupported-device screen.
|
|
1853
|
+
*
|
|
1854
|
+
* @returns Resolves when the application is up, or when the plugin decided to stay inert.
|
|
1855
|
+
*/
|
|
1856
|
+
init(): Promise<void>;
|
|
1857
|
+
};
|
|
1858
|
+
//#endregion
|
|
1859
|
+
//#region src/plugins/renderer/viewport/types.d.ts
|
|
1860
|
+
/**
|
|
1861
|
+
* The orientation a game is designed for, read from the framework config.
|
|
1862
|
+
*
|
|
1863
|
+
* @example
|
|
1864
|
+
* ```ts
|
|
1865
|
+
* const orientation: Orientation = "portrait";
|
|
1866
|
+
* ```
|
|
1867
|
+
*/
|
|
1868
|
+
type Orientation = Config$2["orientation"];
|
|
1869
|
+
/**
|
|
1870
|
+
* A point in reference units.
|
|
1871
|
+
*
|
|
1872
|
+
* @example
|
|
1873
|
+
* ```ts
|
|
1874
|
+
* const point: Point = { x: 540, y: 960 };
|
|
1875
|
+
* ```
|
|
1876
|
+
*/
|
|
1877
|
+
type Point = {
|
|
1878
|
+
x: number;
|
|
1879
|
+
y: number;
|
|
1880
|
+
};
|
|
1881
|
+
/**
|
|
1882
|
+
* A rectangle in CSS pixels inside the canvas.
|
|
1883
|
+
*
|
|
1884
|
+
* @example
|
|
1885
|
+
* ```ts
|
|
1886
|
+
* const frame: Rect = { x: 555, y: 0, width: 810, height: 1080 };
|
|
1887
|
+
* ```
|
|
1888
|
+
*/
|
|
1889
|
+
type Rect = {
|
|
1890
|
+
x: number;
|
|
1891
|
+
y: number;
|
|
1892
|
+
width: number;
|
|
1893
|
+
height: number;
|
|
1894
|
+
};
|
|
1895
|
+
/**
|
|
1896
|
+
* The part of the frame a notch, a home bar or a rounded corner covers, in reference units.
|
|
1897
|
+
*
|
|
1898
|
+
* @example
|
|
1899
|
+
* ```ts
|
|
1900
|
+
* const safeArea: SafeArea = { top: 47, right: 0, bottom: 34, left: 0 };
|
|
1901
|
+
* ```
|
|
1902
|
+
*/
|
|
1903
|
+
type SafeArea = {
|
|
1904
|
+
top: number;
|
|
1905
|
+
right: number;
|
|
1906
|
+
bottom: number;
|
|
1907
|
+
left: number;
|
|
1908
|
+
};
|
|
1909
|
+
/**
|
|
1910
|
+
* What a game lays its board out in: the drawn frame in reference units.
|
|
1911
|
+
*
|
|
1912
|
+
* @example
|
|
1913
|
+
* ```ts
|
|
1914
|
+
* const size: ViewportSize = {
|
|
1915
|
+
* width: 1080, height: 1920, scale: 0.5625, orientation: "portrait",
|
|
1916
|
+
* safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
|
|
1917
|
+
* };
|
|
1918
|
+
* ```
|
|
1919
|
+
*/
|
|
1920
|
+
type ViewportSize = {
|
|
1921
|
+
width: number;
|
|
1922
|
+
height: number;
|
|
1923
|
+
scale: number;
|
|
1924
|
+
orientation: Orientation;
|
|
1925
|
+
safeArea: SafeArea;
|
|
1926
|
+
};
|
|
1927
|
+
/**
|
|
1928
|
+
* viewport module state.
|
|
1929
|
+
*/
|
|
1930
|
+
type ViewportState = {
|
|
1931
|
+
/** The drawn rectangle inside the canvas, in CSS pixels. Bars fill the rest. */frame: Rect; /** CSS pixels per reference unit. */
|
|
1932
|
+
scale: number;
|
|
1933
|
+
/**
|
|
1934
|
+
* The frame in reference units: the short side is at least `referenceSide`, the long side inside
|
|
1935
|
+
* the safe area at least `referenceLong`.
|
|
1936
|
+
*/
|
|
1937
|
+
reference: {
|
|
1938
|
+
width: number;
|
|
1939
|
+
height: number;
|
|
1940
|
+
};
|
|
1941
|
+
safeArea: SafeArea; /** The observer saw a new size; the `render` callback applies it before drawing. */
|
|
1942
|
+
resizePending: boolean; /** The hidden element whose padding carries `env(safe-area-inset-*)`. */
|
|
1943
|
+
probe: HTMLElement | undefined;
|
|
1944
|
+
cleanups: Array<() => void>;
|
|
1945
|
+
};
|
|
1946
|
+
/**
|
|
1947
|
+
* viewport module API, `app.renderer.viewport`. The map between the window and the reference
|
|
1948
|
+
* space. Both sides fit: the short side holds `referenceSide`, the long side inside the safe area
|
|
1949
|
+
* holds `referenceLong`, and the smaller scale wins, so a wide screen gets a wider reference space.
|
|
1950
|
+
*
|
|
1951
|
+
* @example
|
|
1952
|
+
* ```ts
|
|
1953
|
+
* // A portrait game in a 1920x1080 window: an 810x1080 frame with bars left and right, and the
|
|
1954
|
+
* // 1920 reference long side decides the scale.
|
|
1955
|
+
* app.renderer.viewport.size(); // { width: 1440, height: 1920, scale: 0.5625, ... }
|
|
1956
|
+
* app.renderer.viewport.toReference(960, 540); // { x: 720, y: 960 }
|
|
1957
|
+
* ```
|
|
1958
|
+
*/
|
|
1959
|
+
type ViewportApi = {
|
|
1960
|
+
/**
|
|
1961
|
+
* Maps a pointer event to reference units: `(client − canvas rect − frame offset) / scale`.
|
|
1962
|
+
* The canvas rectangle is read at call time, so a scrolled or animated page still answers right.
|
|
1963
|
+
*
|
|
1964
|
+
* @param clientX - `event.clientX` of a pointer event.
|
|
1965
|
+
* @param clientY - `event.clientY` of a pointer event.
|
|
1966
|
+
* @returns The point in reference units. Inert: the input unchanged.
|
|
1967
|
+
* @example
|
|
1968
|
+
* ```ts
|
|
1969
|
+
* // `input` turns every pointer event into reference units before it hit-tests.
|
|
1970
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
1971
|
+
* const point = renderer.viewport.toReference(event.clientX, event.clientY);
|
|
1972
|
+
* renderer.sync.hitTest(point.x, point.y, isLiveDraggable);
|
|
1973
|
+
* ```
|
|
1974
|
+
*/
|
|
1975
|
+
toReference(clientX: number, clientY: number): Point;
|
|
1976
|
+
/**
|
|
1977
|
+
* Maps a point in reference units to client CSS pixels, the coordinates of `event.clientX`:
|
|
1978
|
+
* `canvas rect + frame offset + point × scale`. The inverse of `toReference`; the canvas
|
|
1979
|
+
* rectangle is read at call time.
|
|
1980
|
+
*
|
|
1981
|
+
* @param point - A point in reference units.
|
|
1982
|
+
* @returns The point in client CSS pixels, a fresh object. Inert: the same numbers, reference
|
|
1983
|
+
* units, since there is no canvas to place them on.
|
|
1984
|
+
* @example
|
|
1985
|
+
* ```ts
|
|
1986
|
+
* // The `game.rect` source of the editor places Home's Play plank on the page. A 390x844
|
|
1987
|
+
* // phone, the canvas at the top left: the scale is 390 / 1080.
|
|
1988
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
1989
|
+
* renderer.viewport.toScreen({ x: 540, y: 960 }); // { x: 195, y: 346.666… }
|
|
1990
|
+
* ```
|
|
1991
|
+
*/
|
|
1992
|
+
toScreen(point: Point): Point;
|
|
1993
|
+
/**
|
|
1994
|
+
* The drawn frame in reference units, as a fresh object.
|
|
1995
|
+
*
|
|
1996
|
+
* @returns Width, height, scale, designed orientation and the safe area.
|
|
1997
|
+
* @example
|
|
1998
|
+
* ```ts
|
|
1999
|
+
* // A board keeps its bottom row above the home bar of the phone.
|
|
2000
|
+
* const { height, safeArea } = app.renderer.viewport.size();
|
|
2001
|
+
* const bottom = height - safeArea.bottom; // 1920 - 34
|
|
2002
|
+
* ```
|
|
2003
|
+
*/
|
|
2004
|
+
size(): ViewportSize;
|
|
2005
|
+
};
|
|
2006
|
+
/**
|
|
2007
|
+
* viewport methods injected into `sync` and driven by the plugin root. Not public.
|
|
2008
|
+
*/
|
|
2009
|
+
type ViewportInternal = {
|
|
2010
|
+
/**
|
|
2011
|
+
* Writes the frame offset and the scale onto the root container, so root-local coordinates ARE
|
|
2012
|
+
* reference coordinates.
|
|
2013
|
+
*
|
|
2014
|
+
* @param root - The root container of `sync`.
|
|
2015
|
+
*/
|
|
2016
|
+
apply(root: PixiContainer): void;
|
|
2017
|
+
/**
|
|
2018
|
+
* Creates the safe-area probe and the resize observer, and measures once.
|
|
2019
|
+
*/
|
|
2020
|
+
start(): void;
|
|
2021
|
+
/**
|
|
2022
|
+
* Applies a pending resize: one per frame, before drawing.
|
|
2023
|
+
*
|
|
2024
|
+
* @returns True when the frame changed, so `sync` writes the root transform again.
|
|
2025
|
+
*/
|
|
2026
|
+
applyPending(): boolean;
|
|
2027
|
+
};
|
|
2028
|
+
//#endregion
|
|
2029
|
+
//#region src/plugins/renderer/sync/types.d.ts
|
|
2030
|
+
/**
|
|
2031
|
+
* Which component gave an entity its display object. `"adapter"` is a component a plugin above
|
|
2032
|
+
* registered with `displays.provide`.
|
|
2033
|
+
*
|
|
2034
|
+
* @example
|
|
2035
|
+
* ```ts
|
|
2036
|
+
* const kind: ViewKind = "NineSlice";
|
|
2037
|
+
* ```
|
|
2038
|
+
*/
|
|
2039
|
+
type ViewKind = "Sprite" | "NineSlice" | "Shape" | "Display" | "adapter";
|
|
2040
|
+
/**
|
|
2041
|
+
* How a component of a plugin above becomes a display object. The renderer parents, orders and
|
|
2042
|
+
* frees the object like a sprite; the plugin decides what it is.
|
|
2043
|
+
*
|
|
2044
|
+
* @example
|
|
2045
|
+
* ```ts
|
|
2046
|
+
* const adapter: DisplayAdapter<{ resolved: string }> = {
|
|
2047
|
+
* create: value => buildRuns(value.resolved),
|
|
2048
|
+
* update: (object, previous, next) => {
|
|
2049
|
+
* if (previous.resolved !== next.resolved) rebuild(object, next.resolved);
|
|
2050
|
+
* },
|
|
2051
|
+
* destroy: object => (object as Container).destroy({ children: true, texture: false })
|
|
2052
|
+
* };
|
|
2053
|
+
* ```
|
|
2054
|
+
*/
|
|
2055
|
+
type DisplayAdapter<Value extends object = object> = {
|
|
2056
|
+
/**
|
|
2057
|
+
* Builds the display object of one entity. Called on the first pass after the component
|
|
2058
|
+
* appeared, and never while the renderer is inert.
|
|
2059
|
+
*
|
|
2060
|
+
* @param value - The component value.
|
|
2061
|
+
* @param entity - The entity it sits on.
|
|
2062
|
+
* @returns A Pixi container.
|
|
2063
|
+
*/
|
|
2064
|
+
create(value: Readonly<Value>, entity: Entity): unknown;
|
|
2065
|
+
/**
|
|
2066
|
+
* Writes a changed component value onto the object the adapter built.
|
|
2067
|
+
*
|
|
2068
|
+
* @param object - What `create` returned.
|
|
2069
|
+
* @param previous - The value of the last pass.
|
|
2070
|
+
* @param next - The value now.
|
|
2071
|
+
*/
|
|
2072
|
+
update(object: unknown, previous: Readonly<Value>, next: Readonly<Value>): void;
|
|
2073
|
+
/**
|
|
2074
|
+
* Frees the object. Called when the component or the entity leaves, and when the renderer stops.
|
|
2075
|
+
*
|
|
2076
|
+
* @param object - What `create` returned.
|
|
2077
|
+
*/
|
|
2078
|
+
destroy(object: unknown): void;
|
|
2079
|
+
};
|
|
2080
|
+
/**
|
|
2081
|
+
* One registration of `displays.provide`: the component, its adapter and the world hooks that
|
|
2082
|
+
* watch the component.
|
|
2083
|
+
*/
|
|
2084
|
+
type DisplayEntry = {
|
|
2085
|
+
component: ComponentHandle<object>;
|
|
2086
|
+
adapter: DisplayAdapter; /** The `onAdded` and `onRemoved` removers of this registration. */
|
|
2087
|
+
removers: Array<() => void>;
|
|
2088
|
+
};
|
|
2089
|
+
/**
|
|
2090
|
+
* The rectangle a hit test checks, in the local space of the view: anchor already applied.
|
|
2091
|
+
*
|
|
2092
|
+
* @example
|
|
2093
|
+
* ```ts
|
|
2094
|
+
* const box: HitBox = { x: -32, y: -32, width: 64, height: 64 };
|
|
2095
|
+
* ```
|
|
2096
|
+
*/
|
|
2097
|
+
type HitBox = {
|
|
2098
|
+
x: number;
|
|
2099
|
+
y: number;
|
|
2100
|
+
width: number;
|
|
2101
|
+
height: number;
|
|
2102
|
+
};
|
|
2103
|
+
/**
|
|
2104
|
+
* What `sync` knows about one drawn entity.
|
|
2105
|
+
*
|
|
2106
|
+
* @example
|
|
2107
|
+
* ```ts
|
|
2108
|
+
* const view: View = {
|
|
2109
|
+
* object: sprite, kind: "Sprite", poolKey: "Sprite:board.cell", layer: "items",
|
|
2110
|
+
* textureKey: "board.cell", wrapper: undefined, placeholder: false, mask: undefined,
|
|
2111
|
+
* display: undefined, value: undefined, drawScale: { x: 1, y: 1 }, frameKey: "",
|
|
2112
|
+
* outline: undefined, hitBox: { x: -32, y: -32, width: 64, height: 64 }
|
|
2113
|
+
* };
|
|
2114
|
+
* ```
|
|
2115
|
+
*/
|
|
2116
|
+
type View = {
|
|
2117
|
+
object: PixiContainer;
|
|
2118
|
+
kind: ViewKind; /** `kind + ":" + texture key`, the pool this object goes back to. */
|
|
2119
|
+
poolKey: string; /** Name of the layer container the object hangs in, or `""` while it is parented. */
|
|
2120
|
+
layer: string;
|
|
2121
|
+
textureKey: string; /** The container that holds this entity's children, created when it is first named a parent. */
|
|
2122
|
+
wrapper: PixiContainer | undefined; /** No provider answered: the view draws the 64x64 magenta square until a bundle arrives. */
|
|
2123
|
+
placeholder: boolean; /** The rectangle a `clip: true` shape masks its children with. */
|
|
2124
|
+
mask: PixiGraphics | undefined; /** The registration that built the object, for a component a plugin above draws its own way. */
|
|
2125
|
+
display: DisplayEntry | undefined; /** A copy of the component value the adapter last saw, so `update` gets an honest `previous`. */
|
|
2126
|
+
value: Readonly<object> | undefined;
|
|
2127
|
+
/**
|
|
2128
|
+
* How much the object is stretched from its texture to the box it draws, per axis: box over
|
|
2129
|
+
* texture for a sized sprite, 64 for the 1x1 placeholder, 1 for everything else.
|
|
2130
|
+
*/
|
|
2131
|
+
drawScale: Point; /** The crop a `"cover"` sprite shows, as its `frames` key; `""` when it shows none. */
|
|
2132
|
+
frameKey: string; /** The debug outline of a nine-slice, in its wrapper; `undefined` while debug is off. */
|
|
2133
|
+
outline: PixiGraphics | undefined;
|
|
2134
|
+
hitBox: HitBox;
|
|
2135
|
+
};
|
|
2136
|
+
/**
|
|
2137
|
+
* The crop of a `"cover"` sprite: a texture that shows the part of `base` covering one box.
|
|
2138
|
+
* Cached by texture key and box size, shared by the entities in `users`, and freed when the last
|
|
2139
|
+
* of them lets go or when its base is destroyed.
|
|
2140
|
+
*
|
|
2141
|
+
* @example
|
|
2142
|
+
* ```ts
|
|
2143
|
+
* const frame: CoverFrame = { base: meadow, texture: meadowCut, users: new Set([1_048_576]) };
|
|
2144
|
+
* ```
|
|
2145
|
+
*/
|
|
2146
|
+
type CoverFrame = {
|
|
2147
|
+
base: PixiTexture;
|
|
2148
|
+
texture: PixiTexture;
|
|
2149
|
+
users: Set<Entity>;
|
|
2150
|
+
};
|
|
2151
|
+
/**
|
|
2152
|
+
* One layer of the scene: its container and the sort rule its children follow.
|
|
2153
|
+
*
|
|
2154
|
+
* @example
|
|
2155
|
+
* ```ts
|
|
2156
|
+
* const entry: LayerEntry = { container: itemsContainer, sort: "y" };
|
|
2157
|
+
* ```
|
|
2158
|
+
*/
|
|
2159
|
+
type LayerEntry = {
|
|
2160
|
+
container: PixiContainer | undefined;
|
|
2161
|
+
sort: LayerSort;
|
|
2162
|
+
};
|
|
2163
|
+
/**
|
|
2164
|
+
* A texture source `assets` registered.
|
|
2165
|
+
*
|
|
2166
|
+
* @example
|
|
2167
|
+
* ```ts
|
|
2168
|
+
* const provider: TextureProvider = key => loaded.get(key);
|
|
2169
|
+
* ```
|
|
2170
|
+
*/
|
|
2171
|
+
type TextureProvider = (key: string) => PixiTexture | undefined;
|
|
2172
|
+
/**
|
|
2173
|
+
* Nine-slice borders in pixels: left, top, right, bottom.
|
|
2174
|
+
*
|
|
2175
|
+
* @example
|
|
2176
|
+
* ```ts
|
|
2177
|
+
* const borders: NineBorders = [12, 12, 12, 12];
|
|
2178
|
+
* ```
|
|
2179
|
+
*/
|
|
2180
|
+
type NineBorders = readonly [number, number, number, number];
|
|
2181
|
+
/**
|
|
2182
|
+
* Options of `textures.create`.
|
|
2183
|
+
*
|
|
2184
|
+
* @example
|
|
2185
|
+
* ```ts
|
|
2186
|
+
* const options: CreateTextureOptions = { nine: [12, 12, 12, 12] };
|
|
2187
|
+
* ```
|
|
2188
|
+
*/
|
|
2189
|
+
type CreateTextureOptions = {
|
|
2190
|
+
nine?: NineBorders;
|
|
2191
|
+
};
|
|
2192
|
+
/**
|
|
2193
|
+
* The texture registry `assets` drives, `app.renderer.sync.textures`. The renderer makes and
|
|
2194
|
+
* destroys Pixi textures on request; `assets` owns when that happens.
|
|
2195
|
+
*
|
|
2196
|
+
* @example
|
|
2197
|
+
* ```ts
|
|
2198
|
+
* // `assets` decoded a file, hands the bitmap over and answers for the key from then on.
|
|
2199
|
+
* const texture = app.renderer.sync.textures.create(bitmap);
|
|
2200
|
+
* app.renderer.sync.textures.invalidate(["board.cell"]);
|
|
2201
|
+
* ```
|
|
2202
|
+
*/
|
|
2203
|
+
type TexturesApi = {
|
|
2204
|
+
/**
|
|
2205
|
+
* Adds a provider to the chain. The newest provider is asked first.
|
|
2206
|
+
*
|
|
2207
|
+
* @param fn - Answers a texture for an asset key, or `undefined`.
|
|
2208
|
+
* @returns The remover; calling it twice is a no-op.
|
|
2209
|
+
* @example
|
|
2210
|
+
* ```ts
|
|
2211
|
+
* // `assets` publishes its loaded bundles in onStart and takes them back in onStop.
|
|
2212
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2213
|
+
* const off = renderer.sync.textures.provide(key => ctx.state.loaded.get(key));
|
|
2214
|
+
*
|
|
2215
|
+
* off(); // `assets` stops
|
|
2216
|
+
* ```
|
|
2217
|
+
*/
|
|
2218
|
+
provide(fn: TextureProvider): () => void;
|
|
2219
|
+
/**
|
|
2220
|
+
* Makes a Pixi texture from a decoded image, so `assets` never imports Pixi.
|
|
2221
|
+
*
|
|
2222
|
+
* @param image - The decoded image.
|
|
2223
|
+
* @param options - `nine` is left, top, right and bottom in pixels; it becomes the texture's
|
|
2224
|
+
* default nine-slice borders.
|
|
2225
|
+
* @returns The new texture.
|
|
2226
|
+
* @throws {Error} When the renderer does not draw.
|
|
2227
|
+
* @example
|
|
2228
|
+
* ```ts
|
|
2229
|
+
* // `assets` uploads one file of a bundle it just decoded.
|
|
2230
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2231
|
+
* const bitmap = await createImageBitmap(await response.blob());
|
|
2232
|
+
* const texture = renderer.sync.textures.create(bitmap, { nine: [12, 12, 12, 12] });
|
|
2233
|
+
* ```
|
|
2234
|
+
*/
|
|
2235
|
+
create(image: ImageBitmap | HTMLImageElement, options?: CreateTextureOptions): PixiTexture;
|
|
2236
|
+
/**
|
|
2237
|
+
* Destroys a texture and its source. Destroying the same texture twice is a no-op.
|
|
2238
|
+
*
|
|
2239
|
+
* @param texture - The texture to free.
|
|
2240
|
+
* @example
|
|
2241
|
+
* ```ts
|
|
2242
|
+
* // `assets` unloads a bundle: first the views let go, then the GPU memory is freed.
|
|
2243
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2244
|
+
* renderer.sync.textures.invalidate(["board.cell"]);
|
|
2245
|
+
* renderer.sync.textures.destroy(texture);
|
|
2246
|
+
* ```
|
|
2247
|
+
*/
|
|
2248
|
+
destroy(texture: PixiTexture): void;
|
|
2249
|
+
/**
|
|
2250
|
+
* Marks asset keys stale. Every view that uses one resolves again in the next pass, and the
|
|
2251
|
+
* pooled objects of these keys are destroyed at once.
|
|
2252
|
+
*
|
|
2253
|
+
* @param keys - The asset keys a bundle brought or took away.
|
|
2254
|
+
* @example
|
|
2255
|
+
* ```ts
|
|
2256
|
+
* // A lazy bundle arrived: the magenta placeholders become the art on the next frame.
|
|
2257
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2258
|
+
* renderer.sync.textures.invalidate(["board.chain-1", "board.chain-2"]);
|
|
2259
|
+
* ```
|
|
2260
|
+
*/
|
|
2261
|
+
invalidate(keys: readonly string[]): void;
|
|
2262
|
+
};
|
|
2263
|
+
/**
|
|
2264
|
+
* The display registry a plugin above extends, `app.renderer.sync.displays`. `Sprite`,
|
|
2265
|
+
* `NineSlice` and `Shape` are built in; everything else arrives as an adapter.
|
|
2266
|
+
*
|
|
2267
|
+
* @example
|
|
2268
|
+
* ```ts
|
|
2269
|
+
* // `text` teaches the renderer to draw its own component, and takes it back in onStop.
|
|
2270
|
+
* const off = ctx.require(rendererPlugin).sync.displays.provide(Text, textAdapter);
|
|
2271
|
+
* ```
|
|
2272
|
+
*/
|
|
2273
|
+
type DisplaysApi = {
|
|
2274
|
+
/**
|
|
2275
|
+
* Registers how a component becomes a display object. The renderer calls `create` on the first
|
|
2276
|
+
* pass after the component appeared, `update` on every change, and `destroy` when it leaves.
|
|
2277
|
+
*
|
|
2278
|
+
* @param component - The component the plugin owns.
|
|
2279
|
+
* @param adapter - How that component is built, written and freed.
|
|
2280
|
+
* @returns The remover; calling it twice is a no-op. The views built with it stay until their
|
|
2281
|
+
* entities leave or the renderer stops.
|
|
2282
|
+
* @example
|
|
2283
|
+
* ```ts
|
|
2284
|
+
* // `text` draws its labels with BitmapText, and stops drawing them when it stops.
|
|
2285
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2286
|
+
* const off = renderer.sync.displays.provide(Text, createTextAdapter(ctx));
|
|
2287
|
+
*
|
|
2288
|
+
* off(); // `text` stops: the renderer builds no new label
|
|
2289
|
+
* ```
|
|
2290
|
+
*/
|
|
2291
|
+
provide<Value extends object>(component: ComponentHandle<Value>, adapter: DisplayAdapter<Value>): () => void;
|
|
2292
|
+
};
|
|
2293
|
+
/**
|
|
2294
|
+
* The bitmap fonts of the one Pixi application, `app.renderer.sync.fonts`. `assets` loads the
|
|
2295
|
+
* files, `text` hands them over, the renderer owns the installed font.
|
|
2296
|
+
*
|
|
2297
|
+
* @example
|
|
2298
|
+
* ```ts
|
|
2299
|
+
* // `text` installs the font of a bundle that just landed, once.
|
|
2300
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2301
|
+
* const font = ctx.require(assetsPlugin).font("hud.body");
|
|
2302
|
+
*
|
|
2303
|
+
* if (font !== undefined && !renderer.sync.fonts.installed("hud.body")) {
|
|
2304
|
+
* renderer.sync.fonts.install("hud.body", font.fnt, font.texture);
|
|
2305
|
+
* }
|
|
2306
|
+
* ```
|
|
2307
|
+
*/
|
|
2308
|
+
type FontsApi = {
|
|
2309
|
+
/**
|
|
2310
|
+
* Installs a BMFont file and its page texture under an asset key, so a `BitmapText` with that
|
|
2311
|
+
* key as its `fontFamily` draws with it.
|
|
2312
|
+
*
|
|
2313
|
+
* @param key - The asset key of the font.
|
|
2314
|
+
* @param fnt - The `.fnt` file: BMFont text, BMFont XML or BMFont JSON.
|
|
2315
|
+
* @param texture - The page texture `assets` uploaded.
|
|
2316
|
+
* @throws {Error} When the renderer does not draw, which is every headless run.
|
|
2317
|
+
* @example
|
|
2318
|
+
* ```ts
|
|
2319
|
+
* // `text` installs the boot font after the renderer came up.
|
|
2320
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2321
|
+
* const font = ctx.require(assetsPlugin).font("hud.body");
|
|
2322
|
+
*
|
|
2323
|
+
* if (renderer.host.ready() && font !== undefined) {
|
|
2324
|
+
* renderer.sync.fonts.install("hud.body", font.fnt, font.texture);
|
|
2325
|
+
* }
|
|
2326
|
+
* ```
|
|
2327
|
+
*/
|
|
2328
|
+
install(key: string, fnt: string, texture: PixiTexture): void;
|
|
2329
|
+
/**
|
|
2330
|
+
* Tells whether a font key is installed in this application.
|
|
2331
|
+
*
|
|
2332
|
+
* @param key - The asset key of the font.
|
|
2333
|
+
* @returns True when the font is there.
|
|
2334
|
+
* @example
|
|
2335
|
+
* ```ts
|
|
2336
|
+
* // `text` measures with the real advance table only once the font is in the renderer.
|
|
2337
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2338
|
+
* renderer.sync.fonts.installed("hud.body"); // false before the boot bundle landed
|
|
2339
|
+
* ```
|
|
2340
|
+
*/
|
|
2341
|
+
installed(key: string): boolean;
|
|
2342
|
+
};
|
|
2343
|
+
/**
|
|
2344
|
+
* The debug switches of the renderer.
|
|
2345
|
+
*
|
|
2346
|
+
* @example
|
|
2347
|
+
* ```ts
|
|
2348
|
+
* const switches: DebugSwitches = { nineSlice: true };
|
|
2349
|
+
* ```
|
|
2350
|
+
*/
|
|
2351
|
+
type DebugSwitches = {
|
|
2352
|
+
nineSlice: boolean;
|
|
2353
|
+
};
|
|
2354
|
+
/**
|
|
2355
|
+
* The debug drawing of the renderer, `app.renderer.sync.debug`. The switch outlines every
|
|
2356
|
+
* nine-slice at once; a single panel asks for its own outline with `NineSlice.debug` (`ui`:
|
|
2357
|
+
* `style.debug`).
|
|
2358
|
+
*
|
|
2359
|
+
* @example
|
|
2360
|
+
* ```ts
|
|
2361
|
+
* // Check where every panel of the open popup is cut: cyan lines, red where corners overlap.
|
|
2362
|
+
* app.renderer.sync.debug.nineSlice(true);
|
|
2363
|
+
* app.renderer.sync.debug.state(); // { nineSlice: true }
|
|
2364
|
+
* ```
|
|
2365
|
+
*/
|
|
2366
|
+
type DebugApi = {
|
|
2367
|
+
/**
|
|
2368
|
+
* Turns the outline of every nine-slice on or off. The outline strokes the bounds of the panel
|
|
2369
|
+
* and the four lines Pixi cuts its texture at: cyan, red when the corners overlap or the texture
|
|
2370
|
+
* is missing. A nine-slice whose own `debug` is true keeps its outline while the switch is off.
|
|
2371
|
+
* The views are drawn again on the next pass.
|
|
2372
|
+
*
|
|
2373
|
+
* @param on - True to outline every nine-slice.
|
|
2374
|
+
* @example
|
|
2375
|
+
* ```ts
|
|
2376
|
+
* // A dev build binds a key: "d" shows where the settings popup cuts its parchment and tabs.
|
|
2377
|
+
* globalThis.addEventListener("keydown", event => {
|
|
2378
|
+
* if (event.key === "d") app.renderer.sync.debug.nineSlice(true);
|
|
2379
|
+
* });
|
|
2380
|
+
* // After the next frame the parchment 1000x1040 with insets 72/76 shows lines at x 72 and
|
|
2381
|
+
* // 928, y 76 and 964.
|
|
2382
|
+
* ```
|
|
2383
|
+
*/
|
|
2384
|
+
nineSlice(on: boolean): void;
|
|
2385
|
+
/**
|
|
2386
|
+
* What the debug switches are set to, as a fresh object.
|
|
2387
|
+
*
|
|
2388
|
+
* @returns The switches.
|
|
2389
|
+
* @example
|
|
2390
|
+
* ```ts
|
|
2391
|
+
* // An e2e test makes sure no outline is drawn before it compares a screenshot.
|
|
2392
|
+
* app.renderer.sync.debug.state(); // { nineSlice: false }
|
|
2393
|
+
* ```
|
|
2394
|
+
*/
|
|
2395
|
+
state(): DebugSwitches;
|
|
2396
|
+
};
|
|
2397
|
+
/**
|
|
2398
|
+
* sync module API, `app.renderer.sync`. The one owner of every display object: it builds them
|
|
2399
|
+
* from components, sorts them inside the named layers of the scene and answers hit tests.
|
|
2400
|
+
*
|
|
2401
|
+
* @example
|
|
2402
|
+
* ```ts
|
|
2403
|
+
* // `input` turns a pointer position into the entity under the finger.
|
|
2404
|
+
* app.renderer.sync.hitTest(540, 300, entity => !app.world.ecs.has(entity, Exiting)); // 1048576
|
|
2405
|
+
* ```
|
|
2406
|
+
*/
|
|
2407
|
+
type SyncApi = {
|
|
2408
|
+
/**
|
|
2409
|
+
* The topmost entity whose hit box holds the point and which `accept` allows. Pure component
|
|
2410
|
+
* math: Pixi world matrices are one frame behind at input time.
|
|
2411
|
+
*
|
|
2412
|
+
* @param x - Reference x.
|
|
2413
|
+
* @param y - Reference y.
|
|
2414
|
+
* @param accept - Filter of the caller: the first accepted entity wins.
|
|
2415
|
+
* @returns The entity, or `undefined` when nothing was hit. Inert: always `undefined`.
|
|
2416
|
+
* @example
|
|
2417
|
+
* ```ts
|
|
2418
|
+
* // `input` never picks a view that is on its way out.
|
|
2419
|
+
* const renderer = ctx.require(rendererPlugin);
|
|
2420
|
+
* const world = ctx.require(worldPlugin);
|
|
2421
|
+
* renderer.sync.hitTest(540, 300, entity => !world.ecs.has(entity, Exiting)); // 1048576
|
|
2422
|
+
* ```
|
|
2423
|
+
*/
|
|
2424
|
+
hitTest(x: number, y: number, accept: (entity: Entity) => boolean): Entity | undefined;
|
|
2425
|
+
/**
|
|
2426
|
+
* The texture registry `assets` drives.
|
|
2427
|
+
*/
|
|
2428
|
+
textures: TexturesApi;
|
|
2429
|
+
/**
|
|
2430
|
+
* The display registry a plugin above extends with its own component.
|
|
2431
|
+
*/
|
|
2432
|
+
displays: DisplaysApi;
|
|
2433
|
+
/**
|
|
2434
|
+
* The bitmap fonts of the one application.
|
|
2435
|
+
*/
|
|
2436
|
+
fonts: FontsApi;
|
|
2437
|
+
/**
|
|
2438
|
+
* The debug drawing: the nine-slice outline.
|
|
2439
|
+
*/
|
|
2440
|
+
debug: DebugApi;
|
|
2441
|
+
/**
|
|
2442
|
+
* The Pixi object of an entity, for debugging and for the plugins that draw their own thing.
|
|
2443
|
+
*
|
|
2444
|
+
* @param entity - The entity to ask about.
|
|
2445
|
+
* @returns The display object, or `undefined` when the entity draws nothing.
|
|
2446
|
+
* @example
|
|
2447
|
+
* ```ts
|
|
2448
|
+
* // An e2e test reads the label the renderer wrote, to find the view in the Pixi tree.
|
|
2449
|
+
* const object = app.renderer.sync.displayOf(entity);
|
|
2450
|
+
* (object as { label: string }).label; // "Sprite#1048576 board.items:i5"
|
|
2451
|
+
* ```
|
|
2452
|
+
*/
|
|
2453
|
+
displayOf(entity: Entity): unknown | undefined;
|
|
2454
|
+
};
|
|
2455
|
+
/**
|
|
2456
|
+
* sync methods driven by the plugin root and by a device restore. Not public.
|
|
2457
|
+
*/
|
|
2458
|
+
type SyncInternal = {
|
|
2459
|
+
/**
|
|
2460
|
+
* Creates the root container, registers the world hooks and the `renderer.sync` system, and
|
|
2461
|
+
* builds the views of the entities that already exist.
|
|
2462
|
+
*/
|
|
2463
|
+
start(): void;
|
|
2464
|
+
/**
|
|
2465
|
+
* Drops every view, rebuilds the root, the layers and every view. Used after a restore, where
|
|
2466
|
+
* the old application and all its objects are gone.
|
|
2467
|
+
*/
|
|
2468
|
+
rebuildAll(): void;
|
|
2469
|
+
/**
|
|
2470
|
+
* One pass over the change sets: removed, layers, added, changed, invalidated keys.
|
|
2471
|
+
*/
|
|
2472
|
+
pass(): void;
|
|
2473
|
+
/**
|
|
2474
|
+
* Lets go of the whole tree while it is still alive: the objects the game owns are detached,
|
|
2475
|
+
* the pooled ones are destroyed. Called just before a lost application is destroyed.
|
|
2476
|
+
*/
|
|
2477
|
+
forget(): void;
|
|
2478
|
+
/**
|
|
2479
|
+
* The root container, so the plugin root can re-apply the viewport transform after a resize.
|
|
2480
|
+
*
|
|
2481
|
+
* @returns The root, or `undefined` while inert.
|
|
2482
|
+
*/
|
|
2483
|
+
root(): PixiContainer | undefined;
|
|
2484
|
+
/**
|
|
2485
|
+
* How many display objects `sync` holds, for the counters of `monitor`.
|
|
2486
|
+
*
|
|
2487
|
+
* @returns The views of entities and the objects waiting in the pools.
|
|
2488
|
+
*/
|
|
2489
|
+
counts(): SyncCounts;
|
|
2490
|
+
};
|
|
2491
|
+
/**
|
|
2492
|
+
* The display objects `sync` holds: views of entities and pooled objects.
|
|
2493
|
+
*
|
|
2494
|
+
* @example
|
|
2495
|
+
* ```ts
|
|
2496
|
+
* const counts: SyncCounts = { views: 180, pooled: 24 };
|
|
2497
|
+
* ```
|
|
2498
|
+
*/
|
|
2499
|
+
type SyncCounts = {
|
|
2500
|
+
views: number;
|
|
2501
|
+
pooled: number;
|
|
2502
|
+
};
|
|
2503
|
+
/**
|
|
2504
|
+
* sync module state.
|
|
2505
|
+
*/
|
|
2506
|
+
type SyncState = {
|
|
2507
|
+
/** The container the viewport transform is written on. Its children are the layers. */root: PixiContainer | undefined; /** The list last read from `world.projection.layers()`, compared by reference. */
|
|
2508
|
+
layerList: ReadonlyArray<LayerSpec> | undefined;
|
|
2509
|
+
layers: Map<string, LayerEntry>;
|
|
2510
|
+
views: Map<Entity, View>; /** The back reference from a display object to its entity. */
|
|
2511
|
+
entityOf: Map<object, Entity>;
|
|
2512
|
+
pools: Map<string, PixiContainer[]>;
|
|
2513
|
+
pooled: number;
|
|
2514
|
+
providers: TextureProvider[]; /** The components a plugin above draws its own way, in registration order. */
|
|
2515
|
+
adapters: DisplayEntry[]; /** The bitmap fonts installed in this application, by asset key. */
|
|
2516
|
+
fonts: Map<string, PixiBitmapFont>; /** The global Pixi cache the fonts were registered in, so `onStop` can take them out again. */
|
|
2517
|
+
fontCache: PixiModule["Cache"] | undefined; /** Texture key to the entities that use it, for `invalidate`. */
|
|
2518
|
+
byKey: Map<string, Set<Entity>>; /** The crops of `"cover"` sprites, by `key@widthxheight`. */
|
|
2519
|
+
frames: Map<string, CoverFrame>;
|
|
2520
|
+
invalidated: Set<string>; /** Keys already reported missing, so one key warns once. */
|
|
2521
|
+
warned: Set<string>;
|
|
2522
|
+
added: Set<Entity>;
|
|
2523
|
+
removed: Set<Entity>; /** Entities whose `Parent` was removed: `world` marks no change then, only `onRemoved` fires. */
|
|
2524
|
+
reparented: Set<Entity>; /** The debug switches, from `config.debug` and `debug.nineSlice`. */
|
|
2525
|
+
debug: DebugSwitches; /** The nine-slice switch moved: the next pass writes every nine-slice again. */
|
|
2526
|
+
outlinesStale: boolean;
|
|
2527
|
+
cleanups: Array<() => void>;
|
|
2528
|
+
};
|
|
2529
|
+
//#endregion
|
|
2530
|
+
//#region src/plugins/renderer/monitor/types.d.ts
|
|
2531
|
+
/**
|
|
2532
|
+
* The counters of the renderer, for a stats panel or a frame-budget check. Plain numbers, so the
|
|
2533
|
+
* object goes through `JSON.stringify` as it is.
|
|
2534
|
+
*
|
|
2535
|
+
* @example
|
|
2536
|
+
* ```ts
|
|
2537
|
+
* const stats: RenderStats = { fps: 60, frameMs: 3.4, textures: 12, textureMb: 41.25, views: 180, pooled: 24 };
|
|
2538
|
+
* ```
|
|
2539
|
+
*/
|
|
2540
|
+
type RenderStats = {
|
|
2541
|
+
/**
|
|
2542
|
+
* Frames drawn per second, measured over the last full second of `clock` time. 0 before the
|
|
2543
|
+
* first second, while inert, and when no frame was drawn in the last second (a paused clock).
|
|
2544
|
+
*/
|
|
2545
|
+
fps: number;
|
|
2546
|
+
/**
|
|
2547
|
+
* Mean milliseconds of `clock` time one frame took over that second, CPU side: from the
|
|
2548
|
+
* renderer's callback in phase `input` to the end of `render`.
|
|
2549
|
+
*/
|
|
2550
|
+
frameMs: number; /** Live texture sources on the GPU: Pixi's `renderer.texture.managedTextures`, empty slots skipped. */
|
|
2551
|
+
textures: number; /** Estimated GPU memory of those sources in MiB: 4 bytes per pixel, every mip level. */
|
|
2552
|
+
textureMb: number; /** Display objects `sync` holds for entities. */
|
|
2553
|
+
views: number; /** Display objects waiting in the pools. */
|
|
2554
|
+
pooled: number;
|
|
2555
|
+
/**
|
|
2556
|
+
* Not counted, so always absent. Under WebGPU, Pixi 8.21 batches sprites and graphics straight
|
|
2557
|
+
* onto the native render pass encoder (`encoder.renderPassEncoder.drawIndexed`), past
|
|
2558
|
+
* `renderer.encoder.draw`, so no one place in Pixi sees every draw.
|
|
2559
|
+
*/
|
|
2560
|
+
drawCalls?: number;
|
|
2561
|
+
};
|
|
2562
|
+
/**
|
|
2563
|
+
* monitor module state: the open measuring window, the last closed one, and the captures that
|
|
2564
|
+
* wait for a frame.
|
|
2565
|
+
*/
|
|
2566
|
+
type MonitorState = {
|
|
2567
|
+
/** Clock time at the start of the frame being drawn; `undefined` between frames. */frameStart: number | undefined; /** Clock time at the start of the last frame, the far end of the next interval. */
|
|
2568
|
+
lastStart: number | undefined; /** Clock milliseconds between frame starts, summed over the open window. */
|
|
2569
|
+
windowMs: number; /** Intervals between frame starts in the open window. */
|
|
2570
|
+
intervals: number; /** Clock milliseconds spent inside frames in the open window. */
|
|
2571
|
+
workMs: number; /** Frames finished in the open window. */
|
|
2572
|
+
frames: number; /** Frames per second of the last closed window. */
|
|
2573
|
+
fps: number; /** Mean frame work of the last closed window, in milliseconds. */
|
|
2574
|
+
frameMs: number; /** Callers of `capture()` waiting for the next drawn frame. */
|
|
2575
|
+
captures: Array<(url: string | undefined) => void>;
|
|
2576
|
+
};
|
|
2577
|
+
/**
|
|
2578
|
+
* monitor module API, on `app.renderer` itself: the counters of the renderer and, in a dev build,
|
|
2579
|
+
* a picture of the frame. What the editor's `game.render` source and `game.capture` command read.
|
|
2580
|
+
*
|
|
2581
|
+
* @example
|
|
2582
|
+
* ```ts
|
|
2583
|
+
* // A headless test: nothing is drawn, so every counter is 0 and there is no picture.
|
|
2584
|
+
* app.renderer.stats(); // { fps: 0, frameMs: 0, textures: 0, textureMb: 0, views: 0, pooled: 0 }
|
|
2585
|
+
* await app.renderer.capture(); // undefined
|
|
2586
|
+
* ```
|
|
2587
|
+
*/
|
|
2588
|
+
type MonitorApi = {
|
|
2589
|
+
/**
|
|
2590
|
+
* The counters of the renderer, as a fresh object. The frame timing is kept each frame without
|
|
2591
|
+
* an allocation; the rest is read at call time from `sync` and Pixi. There is no `drawCalls`:
|
|
2592
|
+
* Pixi 8.21 has no one draw point under WebGPU.
|
|
2593
|
+
*
|
|
2594
|
+
* @returns Frames per second, frame work, GPU textures and their memory, views, pooled objects.
|
|
2595
|
+
* Inert: all 0.
|
|
2596
|
+
* @example
|
|
2597
|
+
* ```ts
|
|
2598
|
+
* // The editor's stats panel reads it every frame. Here the game drew one sprite for 51 frames,
|
|
2599
|
+
* // 20 ms apart with 4 ms of work each, and its texture is not uploaded yet.
|
|
2600
|
+
* app.renderer.stats(); // { fps: 50, frameMs: 4, textures: 0, textureMb: 0, views: 1, pooled: 0 }
|
|
2601
|
+
* ```
|
|
2602
|
+
*/
|
|
2603
|
+
stats(): RenderStats;
|
|
2604
|
+
/**
|
|
2605
|
+
* A PNG of the whole canvas, bars included, taken right after the next frame is drawn, so the
|
|
2606
|
+
* picture shows the state the game just reached; at once while the clock is paused, since no
|
|
2607
|
+
* frame comes then. Dev builds only: `__MOKU_GAME_DEV__` must be `true`.
|
|
2608
|
+
*
|
|
2609
|
+
* @returns A `data:image/png;base64,…` URL. `undefined` in a production build, while inert, lost
|
|
2610
|
+
* or unsupported, and when Pixi could not read the frame (logged with `ctx.log.error`).
|
|
2611
|
+
* @example
|
|
2612
|
+
* ```ts
|
|
2613
|
+
* // The editor's capture button, on the dev page that set globalThis.__MOKU_GAME_DEV__ = true.
|
|
2614
|
+
* const url = await app.renderer.capture(); // "data:image/png;base64,iVBORw0KGgo…"
|
|
2615
|
+
* // The same call in a production build.
|
|
2616
|
+
* await app.renderer.capture(); // undefined
|
|
2617
|
+
* ```
|
|
2618
|
+
*/
|
|
2619
|
+
capture(): Promise<string | undefined>;
|
|
2620
|
+
};
|
|
2621
|
+
/**
|
|
2622
|
+
* monitor methods the frame drives. Not public.
|
|
2623
|
+
*/
|
|
2624
|
+
type MonitorInternal = {
|
|
2625
|
+
/**
|
|
2626
|
+
* Marks the start of a frame: closes an interval of the measuring window.
|
|
2627
|
+
*/
|
|
2628
|
+
begin(): void;
|
|
2629
|
+
/**
|
|
2630
|
+
* Marks the end of a drawn frame: adds its work to the window, and hands the frame to the
|
|
2631
|
+
* captures that wait for it.
|
|
2632
|
+
*/
|
|
2633
|
+
end(): void;
|
|
2634
|
+
};
|
|
2635
|
+
/**
|
|
2636
|
+
* What `monitor` gets injected: the modules it reads its counters from.
|
|
2637
|
+
*/
|
|
2638
|
+
type MonitorDeps = {
|
|
2639
|
+
host: HostApi & HostInternal;
|
|
2640
|
+
sync: SyncApi & SyncInternal;
|
|
2641
|
+
};
|
|
2642
|
+
declare namespace types_d_exports {
|
|
2643
|
+
export { Api, AspectRange, BitmapFontData, Config, CoverFrame, CreateTextureOptions, DebugApi, DebugSwitches, Deps, DisplayAdapter, DisplayEntry, DisplaysApi, Events, FontsApi, HitBox, HostApi, HostInternal, HostModule, HostState, KernelSlice, LayerEntry, Modules, MonitorApi, MonitorDeps, MonitorInternal, MonitorModule, MonitorState, NineBorders, Orientation, PixiApplication, PixiBitmapFont, PixiContainer, PixiGraphics, PixiModule, PixiNineSliceSprite, PixiSprite, PixiTexture, Point, Rect, RenderStats, RendererCtx, RendererKind, SafeArea, State, SyncApi, SyncCounts, SyncInternal, SyncModule, SyncState, TeardownScope, TextureProvider, TextureUsage, TexturesApi, View, ViewKind, ViewportApi, ViewportInternal, ViewportModule, ViewportSize, ViewportState };
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* The part of Pixi the engine uses: the classes `sync` builds views from (a `Rectangle` frames the
|
|
2647
|
+
* crop of a `"cover"` sprite), the bitmap-font pieces `fonts.install` needs, and the `BitmapText`
|
|
2648
|
+
* a plugin above builds through `host.pixi()`. The module object never arrives through a static
|
|
2649
|
+
* import: `config.loadPixi()` returns it, so a game without a screen carries no Pixi in its bundle.
|
|
2650
|
+
*
|
|
2651
|
+
* @example
|
|
2652
|
+
* ```ts
|
|
2653
|
+
* const pixi: PixiModule = await import("pixi.js");
|
|
2654
|
+
* pixi.Texture.WHITE.width; // 1
|
|
2655
|
+
* ```
|
|
2656
|
+
*/
|
|
2657
|
+
type PixiModule = Pick<typeof import("pixi.js"), "Application" | "BitmapFont" | "BitmapText" | "Cache" | "Container" | "Graphics" | "NineSliceSprite" | "Rectangle" | "Sprite" | "Texture" | "bitmapFontTextParser" | "bitmapFontXMLStringParser">;
|
|
2658
|
+
/**
|
|
2659
|
+
* One Pixi application: renderer, canvas and stage.
|
|
2660
|
+
*/
|
|
2661
|
+
type PixiApplication = InstanceType<PixiModule["Application"]>;
|
|
2662
|
+
/**
|
|
2663
|
+
* A Pixi display object. `Sprite` and `NineSliceSprite` are containers too, so every view is one.
|
|
2664
|
+
*/
|
|
2665
|
+
type PixiContainer = InstanceType<PixiModule["Container"]>;
|
|
2666
|
+
/**
|
|
2667
|
+
* A Pixi sprite.
|
|
2668
|
+
*/
|
|
2669
|
+
type PixiSprite = InstanceType<PixiModule["Sprite"]>;
|
|
2670
|
+
/**
|
|
2671
|
+
* A Pixi nine-slice sprite.
|
|
2672
|
+
*/
|
|
2673
|
+
type PixiNineSliceSprite = InstanceType<PixiModule["NineSliceSprite"]>;
|
|
2674
|
+
/**
|
|
2675
|
+
* A Pixi graphics object: what a `Shape` component is drawn with, and what clips its children.
|
|
2676
|
+
*/
|
|
2677
|
+
type PixiGraphics = InstanceType<PixiModule["Graphics"]>;
|
|
2678
|
+
/**
|
|
2679
|
+
* A Pixi texture. `assets` owns its lifetime; the renderer only makes and destroys it on request.
|
|
2680
|
+
*/
|
|
2681
|
+
type PixiTexture = InstanceType<PixiModule["Texture"]>;
|
|
2682
|
+
/**
|
|
2683
|
+
* A bitmap font the renderer installed for a font asset key. One per key.
|
|
2684
|
+
*/
|
|
2685
|
+
type PixiBitmapFont = InstanceType<PixiModule["BitmapFont"]>;
|
|
2686
|
+
/**
|
|
2687
|
+
* The parsed shape of a `.fnt` file: what `BitmapFont` is built from.
|
|
2688
|
+
*/
|
|
2689
|
+
type BitmapFontData = ConstructorParameters<PixiModule["BitmapFont"]>[0]["data"];
|
|
2690
|
+
/**
|
|
2691
|
+
* Which backend the one application drew with, or `"none"` while it draws nothing.
|
|
2692
|
+
*
|
|
2693
|
+
* @example
|
|
2694
|
+
* ```ts
|
|
2695
|
+
* const kind: RendererKind = "webgpu";
|
|
2696
|
+
* ```
|
|
2697
|
+
*/
|
|
2698
|
+
type RendererKind = "webgpu" | "webgl" | "none";
|
|
2699
|
+
/**
|
|
2700
|
+
* renderer plugin events.
|
|
2701
|
+
*
|
|
2702
|
+
* @example
|
|
2703
|
+
* ```ts
|
|
2704
|
+
* // A game reports a lost GPU to its analytics; the pause itself is handled by `lifecycle`.
|
|
2705
|
+
* createPlugin("gpuReport", {
|
|
2706
|
+
* depends: [rendererPlugin],
|
|
2707
|
+
* hooks: ctx => ({ "renderer:device-lost": ({ kind }) => ctx.log.warn("gpu-lost", { kind }) })
|
|
2708
|
+
* }); // a lost WebGPU device logs { kind: "webgpu" }
|
|
2709
|
+
* ```
|
|
2710
|
+
*/
|
|
2711
|
+
type Events = {
|
|
2712
|
+
/** The GPU device or the WebGL context was lost; the game is paused until the restore lands. */"renderer:device-lost": {
|
|
2713
|
+
kind: "webgpu" | "webgl";
|
|
2714
|
+
reason: string;
|
|
2715
|
+
};
|
|
2716
|
+
};
|
|
2717
|
+
/**
|
|
2718
|
+
* The two numbers that bound the shape of the drawn frame: allowed long side / short side.
|
|
2719
|
+
*
|
|
2720
|
+
* @example
|
|
2721
|
+
* ```ts
|
|
2722
|
+
* const aspect: AspectRange = { min: 4 / 3, max: 21 / 9 };
|
|
2723
|
+
* ```
|
|
2724
|
+
*/
|
|
2725
|
+
type AspectRange = {
|
|
2726
|
+
min: number;
|
|
2727
|
+
max: number;
|
|
2728
|
+
};
|
|
2729
|
+
/**
|
|
2730
|
+
* renderer plugin config.
|
|
2731
|
+
*
|
|
2732
|
+
* @example
|
|
2733
|
+
* ```ts
|
|
2734
|
+
* createApp({
|
|
2735
|
+
* plugins: [...screen],
|
|
2736
|
+
* pluginConfigs: { renderer: { mount: "#game", background: 0x101018, maxResolution: 2 } }
|
|
2737
|
+
* });
|
|
2738
|
+
* // A debug build of the fixture outlines every nine-slice from the first frame.
|
|
2739
|
+
* createApp({ pluginConfigs: { renderer: { mount: "#game", debug: { nineSlice: true } } } });
|
|
2740
|
+
* ```
|
|
2741
|
+
*/
|
|
2742
|
+
type Config = {
|
|
2743
|
+
/** Where the canvas goes: a selector or an element. `undefined` keeps the plugin inert. */mount: string | HTMLElement | undefined; /** Clear colour. Also the colour of the bars around the frame. */
|
|
2744
|
+
background: number; /** Antialias the whole canvas. Off by default: pixel art and sprites do not need it. */
|
|
2745
|
+
antialias: boolean; /** Cap of `devicePixelRatio`. Memory grows with its square. */
|
|
2746
|
+
maxResolution: number; /** Passed to Pixi. Pixi falls back to WebGL by itself. */
|
|
2747
|
+
preference: "webgpu" | "webgl"; /** Allowed long side / short side of the frame. A window outside it gets bars. */
|
|
2748
|
+
aspect: AspectRange; /** Display objects kept in all pools together. */
|
|
2749
|
+
poolLimit: number; /** Text of the unsupported-device screen. */
|
|
2750
|
+
unsupportedMessage: string; /** Loader seam. Tests pass a fake module. */
|
|
2751
|
+
loadPixi: () => Promise<PixiModule>; /** Debug drawing at start. `app.renderer.sync.debug` switches it while the game runs. */
|
|
2752
|
+
debug: DebugSwitches;
|
|
2753
|
+
};
|
|
2754
|
+
/**
|
|
2755
|
+
* renderer plugin state: one branch per module.
|
|
2756
|
+
*/
|
|
2757
|
+
type State = {
|
|
2758
|
+
host: HostState;
|
|
2759
|
+
viewport: ViewportState;
|
|
2760
|
+
sync: SyncState;
|
|
2761
|
+
monitor: MonitorState;
|
|
2762
|
+
};
|
|
2763
|
+
/**
|
|
2764
|
+
* renderer plugin API, `app.renderer`: grouped by module, and the two members of `monitor`,
|
|
2765
|
+
* `stats()` and `capture()`, on the plugin itself.
|
|
2766
|
+
*
|
|
2767
|
+
* @example
|
|
2768
|
+
* ```ts
|
|
2769
|
+
* app.renderer.host.kind(); // "webgpu"
|
|
2770
|
+
* app.renderer.viewport.size().width; // 1080, reference units
|
|
2771
|
+
* app.renderer.sync.hitTest(540, 300, () => true); // 1048576
|
|
2772
|
+
* app.renderer.stats().views; // 180
|
|
2773
|
+
* ```
|
|
2774
|
+
*/
|
|
2775
|
+
type Api = {
|
|
2776
|
+
host: HostApi;
|
|
2777
|
+
viewport: ViewportApi;
|
|
2778
|
+
sync: SyncApi;
|
|
2779
|
+
} & MonitorApi;
|
|
2780
|
+
/**
|
|
2781
|
+
* Resolved dependency APIs. `clock` is the time source of the frame counters.
|
|
2782
|
+
*/
|
|
2783
|
+
type Deps = {
|
|
2784
|
+
time: Api$2;
|
|
2785
|
+
lifecycle: Api$5;
|
|
2786
|
+
world: Api$1;
|
|
2787
|
+
clock: Api$6;
|
|
2788
|
+
};
|
|
2789
|
+
/**
|
|
2790
|
+
* What the kernel context offers before the deps are attached.
|
|
2791
|
+
*
|
|
2792
|
+
* `emit` is the kernel's own: `index.ts` writes `events` with an annotated `register`
|
|
2793
|
+
* (core spec `14-EVENT-REGISTRATION.md` row 8), so the plugin's own event reaches the pre-typed
|
|
2794
|
+
* `api` factory and no member of this context is cast.
|
|
2795
|
+
*/
|
|
2796
|
+
type KernelSlice = PluginCtx<Config, State, Events> & {
|
|
2797
|
+
readonly global: Readonly<Config$2>;
|
|
2798
|
+
readonly log: Log.LogApi;
|
|
2799
|
+
readonly require: Require;
|
|
2800
|
+
};
|
|
2801
|
+
/**
|
|
2802
|
+
* Domain context shared by the three modules.
|
|
2803
|
+
*/
|
|
2804
|
+
type RendererCtx = KernelSlice & {
|
|
2805
|
+
readonly deps: Deps;
|
|
2806
|
+
};
|
|
2807
|
+
/**
|
|
2808
|
+
* Both halves of the `host` module: what a game calls and what the two modules above get injected.
|
|
2809
|
+
*/
|
|
2810
|
+
type HostModule = HostApi & HostInternal;
|
|
2811
|
+
/**
|
|
2812
|
+
* Both halves of the `viewport` module: what a game calls and what `sync` and the frame drive.
|
|
2813
|
+
*/
|
|
2814
|
+
type ViewportModule = ViewportApi & ViewportInternal;
|
|
2815
|
+
/**
|
|
2816
|
+
* Both halves of the `sync` module: what `input` and `assets` call and what the frame drives.
|
|
2817
|
+
*/
|
|
2818
|
+
type SyncModule = SyncApi & SyncInternal;
|
|
2819
|
+
/**
|
|
2820
|
+
* Both halves of the `monitor` module: what a tool calls and what the frame drives.
|
|
2821
|
+
*/
|
|
2822
|
+
type MonitorModule = MonitorApi & MonitorInternal;
|
|
2823
|
+
/**
|
|
2824
|
+
* The four modules in injection order.
|
|
2825
|
+
*/
|
|
2826
|
+
type Modules = {
|
|
2827
|
+
host: HostModule;
|
|
2828
|
+
viewport: ViewportModule;
|
|
2829
|
+
sync: SyncModule;
|
|
2830
|
+
monitor: MonitorModule;
|
|
2831
|
+
};
|
|
2832
|
+
/**
|
|
2833
|
+
* What `onStop` receives: the frozen config and the plugin state, nothing else.
|
|
2834
|
+
*/
|
|
2835
|
+
type TeardownScope = {
|
|
2836
|
+
readonly config: Readonly<Config>;
|
|
2837
|
+
readonly state: State;
|
|
2838
|
+
};
|
|
2839
|
+
//#endregion
|
|
2840
|
+
export { ComponentHandle as A, SystemDefinition as B, ProjectionSpec as C, AnyComponent as D, ViewHandle as E, Mut as F, Narrowed as I, Owner as L, ComponentValue as M, Entity as N, AnyComponentType as O, EntitySnapshot as P, QueryTerm as R, ProjectionMotion as S, TrackSegment as T, TagType as V, LayerSort as _, types_d_exports as a, MotionHandle as b, Point as c, Config$1 as d, State$1 as f, Ease as g, DescriptionNode as h, State as i, ComponentType as j, AnyComponentValue as k, ViewportSize as l, ChangeHook as m, Config as n, RenderStats as o, types_d_exports$1 as p, PixiTexture as r, DebugSwitches as s, Api as t, Api$1 as u, LayerSpec as v, TrackOptions as w, NumericFields as x, Motion as y, ResourceType as z };
|