@gg-web-engine/core 0.0.58 → 0.0.59

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.
Files changed (52) hide show
  1. package/dist/2d/components/rendering/i-camera-2d.component.d.ts +5 -0
  2. package/dist/2d/components/rendering/i-visual-scene-2d.component.d.ts +1 -2
  3. package/dist/2d/entities/renderer-2d.entity.d.ts +2 -1
  4. package/dist/2d/gg-2d-world.d.ts +5 -1
  5. package/dist/2d/gg-2d-world.js +4 -2
  6. package/dist/2d/index.d.ts +3 -0
  7. package/dist/2d/index.js +3 -0
  8. package/dist/2d/level-loader.d.ts +101 -0
  9. package/dist/2d/level-loader.js +88 -0
  10. package/dist/2d/loader.d.ts +11 -0
  11. package/dist/2d/loader.js +10 -0
  12. package/dist/3d/components/rendering/{i-camera.component.d.ts → i-camera-3d.component.d.ts} +1 -1
  13. package/dist/3d/components/rendering/i-camera-3d.component.js +1 -0
  14. package/dist/3d/components/rendering/i-renderer-3d.component.d.ts +0 -1
  15. package/dist/3d/components/rendering/i-visual-scene-3d.component.d.ts +1 -2
  16. package/dist/3d/entities/camera-3d.entity.d.ts +24 -0
  17. package/dist/3d/entities/camera-3d.entity.js +35 -0
  18. package/dist/3d/entities/renderer-3d.entity.d.ts +0 -5
  19. package/dist/3d/entities/renderer-3d.entity.js +0 -15
  20. package/dist/3d/gg-3d-world.d.ts +2 -2
  21. package/dist/3d/index.d.ts +3 -1
  22. package/dist/3d/index.js +3 -1
  23. package/dist/3d/level-loader.d.ts +300 -0
  24. package/dist/3d/level-loader.js +267 -0
  25. package/dist/3d/loader.d.ts +44 -3
  26. package/dist/3d/loader.js +31 -3
  27. package/dist/base/blueprint/blueprint-node.d.ts +75 -0
  28. package/dist/base/blueprint/blueprint-node.js +51 -0
  29. package/dist/base/blueprint/blueprint.d.ts +123 -0
  30. package/dist/base/blueprint/blueprint.js +86 -0
  31. package/dist/base/blueprint/nodes/remove-entity.node.d.ts +26 -0
  32. package/dist/base/blueprint/nodes/remove-entity.node.js +29 -0
  33. package/dist/base/components/rendering/i-renderer.component.d.ts +1 -0
  34. package/dist/base/components/rendering/i-visual-scene.component.d.ts +2 -0
  35. package/dist/base/entities/group.entity.d.ts +17 -0
  36. package/dist/base/entities/group.entity.js +19 -0
  37. package/dist/base/entities/i-entity.d.ts +11 -0
  38. package/dist/base/entities/i-entity.js +28 -0
  39. package/dist/base/entities/i-renderer.entity.d.ts +7 -1
  40. package/dist/base/entities/i-renderer.entity.js +15 -0
  41. package/dist/base/gg-world.d.ts +13 -0
  42. package/dist/base/gg-world.js +23 -1
  43. package/dist/base/index.d.ts +5 -0
  44. package/dist/base/index.js +5 -0
  45. package/dist/base/inputs/mouse.input.js +1 -0
  46. package/dist/base/level-loader.d.ts +217 -0
  47. package/dist/base/level-loader.js +258 -0
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/package.json +1 -1
  51. package/tsconfig.json +3 -1
  52. /package/dist/{3d/components/rendering/i-camera.component.js → 2d/components/rendering/i-camera-2d.component.js} +0 -0
@@ -1,3 +1,6 @@
1
+ export * from './blueprint/blueprint';
2
+ export * from './blueprint/blueprint-node';
3
+ export * from './blueprint/nodes/remove-entity.node';
1
4
  export * from './clock/global-clock';
2
5
  export * from './clock/i-clock';
3
6
  export * from './clock/pausable-clock';
@@ -14,6 +17,7 @@ export * from './data-structures/bitmask';
14
17
  export * from './data-structures/graph';
15
18
  export * from './entities/controllers/animation-mixer';
16
19
  export * from './entities/controllers/inline-controller';
20
+ export * from './entities/group.entity';
17
21
  export * from './entities/i-entity';
18
22
  export * from './entities/i-renderer.entity';
19
23
  export * from './entities/i-renderable.entity';
@@ -36,3 +40,4 @@ export * from './math/matrix4';
36
40
  export * from './math/splines';
37
41
  export * from './pipes/gg-elastic.pipe';
38
42
  export * from './gg-world';
43
+ export * from './level-loader';
@@ -10,6 +10,7 @@ const DEFAULT_MOUSE_INPUT_OPTIONS = {
10
10
  */
11
11
  export var MouseInputState;
12
12
  (function (MouseInputState) {
13
+ //MouseInputState {
13
14
  /**
14
15
  No mouse or touch input is detected. Mouse move can still be emitted
15
16
  */
@@ -0,0 +1,217 @@
1
+ import { GgWorld, GgWorldTypeDocRepo } from './gg-world';
2
+ import { GroupEntity } from './entities/group.entity';
3
+ import { BlueprintJson, BlueprintNodeFactory } from './blueprint/blueprint';
4
+ /**
5
+ * A function that turns per-entity JSON settings into a spawned `IEntity` (e.g. a primitive body,
6
+ * a trigger, a camera). Registered against a class alias via {@link LevelLoader.registerClass}.
7
+ * May be `async`/return a `Promise` (e.g. the built-in `"Glb"` 3D class, which fetches a model) -
8
+ * {@link LevelLoader.loadLevel} awaits every generator before moving to the next entity. A
9
+ * generator that returns anything other than an `IEntity` (including `null`/`undefined`) has its
10
+ * result discarded - see {@link LevelLoader.loadLevel}.
11
+ * @template D - The position type
12
+ * @template R - The rotation type
13
+ * @template TypeDoc - The type document repository
14
+ * @template Settings - The settings object type
15
+ * @template W - The world type
16
+ */
17
+ export type EntityGenerator<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>, Settings = any, W = GgWorld<D, R, TypeDoc>> = (world: W, settings: Settings) => any;
18
+ /**
19
+ * A level/scene, serializable as a single JSON document (e.g. to be hosted as a static file and
20
+ * loaded via {@link LevelLoader.loadLevelFromUrl}).
21
+ */
22
+ export interface LevelJson {
23
+ /**
24
+ * Entities in the level
25
+ */
26
+ entities: EntityJson[];
27
+ /**
28
+ * Blueprint graphs available to this level's entities, keyed by name - referenced from an
29
+ * `EntityJson.events` entry to run a blueprint whenever the named observable on that entity
30
+ * fires. See {@link BlueprintJson} and the `gg-engine-level-json` skill's "Blueprints" section.
31
+ */
32
+ blueprints?: Record<string, BlueprintJson>;
33
+ }
34
+ /**
35
+ * JSON description of a single entity in a level. `position`/`rotation` are left untyped here
36
+ * since their shape depends on the dimensionality (`Point2`/`number` for 2D, `Point3`/`Point4`
37
+ * for 3D) of whichever `LevelLoader` subclass parses this JSON.
38
+ */
39
+ export interface EntityJson {
40
+ /**
41
+ * Class alias for the entity, matching a class registered via `registerClass`. Built-in
42
+ * primitive shapes (box/sphere/square/circle/...) all share the single `"Primitive"` alias and
43
+ * are distinguished by `shape` instead of by a per-shape class - e.g. `{ class: "Primitive",
44
+ * shape: "BOX" }` rather than `{ class: "BOX" }`. Apps register their own aliases (e.g.
45
+ * `"ShapeSpawner"`) the same way the dimensionality-specific `LevelLoader` subclasses register
46
+ * their built-ins, via `registerClass`.
47
+ */
48
+ class: string;
49
+ /**
50
+ * Shape identifier for the built-in `"Primitive"` entity class (e.g. `"Box"`, `"Circle"`) - see
51
+ * the dimensionality-specific `LevelLoader` subclass (`Gg2dLevelLoader`/`Gg3dLevelLoader`) for
52
+ * the supported values. Ignored for any other `class`.
53
+ */
54
+ shape?: string;
55
+ /**
56
+ * Position of the entity
57
+ */
58
+ position?: any;
59
+ /**
60
+ * Rotation of the entity
61
+ */
62
+ rotation?: any;
63
+ /**
64
+ * Name of the entity. `loadLevel` sets the generator's returned `IEntity`'s `.name` to this
65
+ * (overriding whatever default the generator gave it), so it can be found afterwards with
66
+ * `GgWorld.getEntityByName`/`IEntity.getChildEntityByName`. Moot if the generator doesn't return
67
+ * an `IEntity` - that result is discarded (with a console warning) before naming is applied.
68
+ */
69
+ name?: string;
70
+ /**
71
+ * Configuration for the entity, passed to its generator alongside position/rotation/name
72
+ */
73
+ config?: any;
74
+ /**
75
+ * Maps an observable property name on this entity's generated `IEntity` (e.g. `Trigger3dEntity`'s
76
+ * `"onEntityEntered"`) to what should run whenever that observable fires - see
77
+ * {@link EntityEventBinding}. `loadLevel` subscribes to the observable and triggers a fresh
78
+ * `Blueprint` instance (via its `"in"` entry point) with whatever value it emits, each time it
79
+ * fires - see `LevelLoader.loadLevel` and the `gg-engine-level-json` skill's "Blueprints"
80
+ * section. Silently ignored (with a console warning) if the binding can't be resolved to a
81
+ * blueprint, or the named property isn't an `Observable`.
82
+ */
83
+ events?: Record<string, EntityEventBinding>;
84
+ }
85
+ /**
86
+ * What an `EntityJson.events` entry runs. Either:
87
+ * - a plain `string` - first tried as a key into the level's top-level `blueprints` map (a named,
88
+ * possibly multi-node graph); if not found there, tried as a blueprint node type alias
89
+ * registered via `registerBlueprintNode` (e.g. the built-in `"RemoveEntity"`) instead, with no
90
+ * settings - shorthand for the single-node form below with `settings` omitted.
91
+ * - `{ type, settings? }` - a single built-in/registered blueprint node used directly as the
92
+ * handler, with inline `settings`, no `blueprints` entry needed at all - e.g.
93
+ * `{ "type": "RemoveEntity", "settings": { "dispose": true } }`. Only node types registered with
94
+ * a default input pin (every built-in one is - see `registerBlueprintNode`) support this form;
95
+ * others require a full graph declared in `blueprints` instead, addressing the desired input pin
96
+ * explicitly via `inputs`.
97
+ */
98
+ export type EntityEventBinding = string | {
99
+ type: string;
100
+ settings?: Record<string, any>;
101
+ };
102
+ /**
103
+ * Base class for level loaders: parses a {@link LevelJson} document into world entities by
104
+ * dispatching each `EntityJson.class` to a generator function registered with {@link registerClass}.
105
+ *
106
+ * A generator is required to return an `IEntity`. Every `IEntity` a generator produces is parented
107
+ * under one {@link GroupEntity} per `loadLevel`/`loadLevelFromUrl` call (added to the world
108
+ * immediately, and handed back once loading completes) - so a whole level can be torn down in one
109
+ * shot with `world.removeEntity(level, true)`, which cascades removal/disposal to every child, and
110
+ * any named entity can be found afterwards with `level.getChildEntityByName(name)`. If a generator
111
+ * returns anything other than an `IEntity` (including `null`/`undefined`), `loadLevel` logs a
112
+ * `console.warn` and skips that entity - it's never parented, named, or tracked.
113
+ * @template D - The position type
114
+ * @template R - The rotation type
115
+ * @template TypeDoc - The type document repository
116
+ */
117
+ export declare abstract class LevelLoader<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>> {
118
+ protected readonly world: GgWorld<D, R, TypeDoc>;
119
+ /**
120
+ * Map of class aliases to generator functions
121
+ */
122
+ protected generators: Map<string, EntityGenerator<D, R, TypeDoc, any, any>>;
123
+ /**
124
+ * Map of blueprint node type aliases to node factory functions - see {@link registerBlueprintNode}.
125
+ */
126
+ protected blueprintNodes: Map<string, BlueprintNodeFactory<D, R, TypeDoc>>;
127
+ /**
128
+ * Map of blueprint node type aliases to their default input pin name, for node types registered
129
+ * with one - see {@link registerBlueprintNode}.
130
+ */
131
+ protected blueprintNodeDefaultInputs: Map<string, string>;
132
+ /**
133
+ * Constructor
134
+ * @param world - The world instance
135
+ */
136
+ constructor(world: GgWorld<D, R, TypeDoc>);
137
+ /**
138
+ * Register a generator function for a class alias
139
+ * @param classAlias - The class alias
140
+ * @param generator - The generator function
141
+ */
142
+ registerClass<Settings, W = any>(classAlias: string, generator: EntityGenerator<D, R, TypeDoc, Settings, W>): void;
143
+ /**
144
+ * Register a {@link BlueprintNode} factory for a node type alias, so a `BlueprintJson`'s
145
+ * `nodes` can reference it by `type` (e.g. the built-in `"RemoveEntity"`, registered by every
146
+ * `LevelLoader` out of the box). Same pattern as {@link registerClass}, one level down (node
147
+ * types within a blueprint graph, rather than entity classes within a level).
148
+ * @param typeAlias - The node type alias
149
+ * @param factory - Builds a node instance from its baked-in settings
150
+ * @param defaultInputPin - This node type's sole "trigger me" input pin name, if it has one
151
+ * canonical one (e.g. `"RemoveEntity"`'s `"entity"`). Enables the node type to be used directly
152
+ * as an `EntityJson.events` binding (`{ "eventName": "TypeAlias" }` or
153
+ * `{ "eventName": { "type": "TypeAlias", "settings": {...} } }`) without declaring a full
154
+ * `BlueprintJson` graph in `blueprints` - see {@link EntityEventBinding}. Omit for a node type
155
+ * with zero or multiple input pins, or one with no single obviously-correct default; it remains
156
+ * usable from a full graph either way.
157
+ */
158
+ registerBlueprintNode(typeAlias: string, factory: BlueprintNodeFactory<D, R, TypeDoc>, defaultInputPin?: string): void;
159
+ /**
160
+ * Load a level from an already-parsed JSON document. Every `IEntity` the level's entities
161
+ * produce is parented under - and, on failure, torn down along with - the returned
162
+ * {@link GroupEntity}, already added to the world.
163
+ * @param levelJson - The level JSON
164
+ * @param levelName - Optional name for the returned group entity (e.g. so a debugger/console
165
+ * listing entities by name shows something more meaningful than the default auto-generated one)
166
+ * @returns The level's root group entity
167
+ */
168
+ loadLevel(levelJson: LevelJson, levelName?: string): Promise<GroupEntity<D, R, TypeDoc>>;
169
+ /**
170
+ * Resolve `eventBinding` (see {@link EntityEventBinding}) to a `BlueprintJson`, instantiate a
171
+ * fresh `Blueprint` from it, and subscribe it to `entity[eventName]` so every value that
172
+ * observable emits triggers the blueprint's `"in"` entry point. Wrapped in a
173
+ * `BlueprintBindingEntity` so the subscription (and the blueprint's own node state) is torn down
174
+ * automatically once that entity is disposed - the caller parents the returned entity under the
175
+ * level's group for that reason.
176
+ * @param entity - The entity carrying the observable property
177
+ * @param eventName - Name of the observable property on `entity`
178
+ * @param eventBinding - What to run - a `blueprints` name, a bare node type alias, or `{ type,
179
+ * settings? }`
180
+ * @param blueprints - The level's top-level blueprint map, if any
181
+ * @returns The binding entity to parent under the level, or `undefined` if `eventBinding`
182
+ * couldn't be resolved or the named property isn't an `Observable` (both logged via
183
+ * `console.warn`)
184
+ */
185
+ private bindEvent;
186
+ /**
187
+ * Turn an `EntityEventBinding` into a `BlueprintJson` to run. An object form (`{ type,
188
+ * settings? }`) always builds a single-node inline graph via {@link inlineNodeBlueprint}. A
189
+ * string form is tried first as a key into `blueprints` (a named, possibly multi-node graph),
190
+ * then - if not found there - as a bare node type alias, same as the object form with no
191
+ * settings.
192
+ * @param eventName - Name of the observable property being bound, for warning messages
193
+ * @param eventBinding - The binding to resolve
194
+ * @param blueprints - The level's top-level blueprint map, if any
195
+ * @returns The resolved graph, or `undefined` (logged via `console.warn`) if it couldn't be
196
+ */
197
+ private resolveEventBlueprint;
198
+ /**
199
+ * Build a single-node `BlueprintJson` wrapping one blueprint node type, wired so the node's
200
+ * registered default input pin (see {@link registerBlueprintNode}) is reachable as `"in"` - what
201
+ * powers the `EntityEventBinding` shorthand that skips declaring a `blueprints` entry entirely.
202
+ * @param eventName - Name of the observable property being bound, for warning messages
203
+ * @param nodeType - The blueprint node type alias
204
+ * @param settings - Settings to bake into the node, if any
205
+ * @returns The single-node graph, or `undefined` (logged via `console.warn`) if `nodeType` isn't
206
+ * registered, or was registered without a default input pin
207
+ */
208
+ private inlineNodeBlueprint;
209
+ /**
210
+ * Fetch a level JSON document hosted at `url` and load it, so a whole level/scene can be
211
+ * shipped and consumed as a single static JSON file.
212
+ * @param url - URL (or path) of the level JSON document
213
+ * @param levelName - Optional name for the returned group entity, see {@link loadLevel}
214
+ * @returns The level's root group entity
215
+ */
216
+ loadLevelFromUrl(url: string, levelName?: string): Promise<GroupEntity<D, R, TypeDoc>>;
217
+ }
@@ -0,0 +1,258 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { GroupEntity } from './entities/group.entity';
11
+ import { IEntity, TickOrder } from './entities/i-entity';
12
+ import { Blueprint } from './blueprint/blueprint';
13
+ import { RemoveEntityBlueprintNode } from './blueprint/nodes/remove-entity.node';
14
+ /**
15
+ * Base class for level loaders: parses a {@link LevelJson} document into world entities by
16
+ * dispatching each `EntityJson.class` to a generator function registered with {@link registerClass}.
17
+ *
18
+ * A generator is required to return an `IEntity`. Every `IEntity` a generator produces is parented
19
+ * under one {@link GroupEntity} per `loadLevel`/`loadLevelFromUrl` call (added to the world
20
+ * immediately, and handed back once loading completes) - so a whole level can be torn down in one
21
+ * shot with `world.removeEntity(level, true)`, which cascades removal/disposal to every child, and
22
+ * any named entity can be found afterwards with `level.getChildEntityByName(name)`. If a generator
23
+ * returns anything other than an `IEntity` (including `null`/`undefined`), `loadLevel` logs a
24
+ * `console.warn` and skips that entity - it's never parented, named, or tracked.
25
+ * @template D - The position type
26
+ * @template R - The rotation type
27
+ * @template TypeDoc - The type document repository
28
+ */
29
+ export class LevelLoader {
30
+ /**
31
+ * Constructor
32
+ * @param world - The world instance
33
+ */
34
+ constructor(world) {
35
+ this.world = world;
36
+ /**
37
+ * Map of class aliases to generator functions
38
+ */
39
+ this.generators = new Map();
40
+ /**
41
+ * Map of blueprint node type aliases to node factory functions - see {@link registerBlueprintNode}.
42
+ */
43
+ this.blueprintNodes = new Map();
44
+ /**
45
+ * Map of blueprint node type aliases to their default input pin name, for node types registered
46
+ * with one - see {@link registerBlueprintNode}.
47
+ */
48
+ this.blueprintNodeDefaultInputs = new Map();
49
+ this.registerBlueprintNode('RemoveEntity', (w, settings) => new RemoveEntityBlueprintNode(w, settings), 'entity');
50
+ }
51
+ /**
52
+ * Register a generator function for a class alias
53
+ * @param classAlias - The class alias
54
+ * @param generator - The generator function
55
+ */
56
+ registerClass(classAlias, generator) {
57
+ this.generators.set(classAlias, generator);
58
+ }
59
+ /**
60
+ * Register a {@link BlueprintNode} factory for a node type alias, so a `BlueprintJson`'s
61
+ * `nodes` can reference it by `type` (e.g. the built-in `"RemoveEntity"`, registered by every
62
+ * `LevelLoader` out of the box). Same pattern as {@link registerClass}, one level down (node
63
+ * types within a blueprint graph, rather than entity classes within a level).
64
+ * @param typeAlias - The node type alias
65
+ * @param factory - Builds a node instance from its baked-in settings
66
+ * @param defaultInputPin - This node type's sole "trigger me" input pin name, if it has one
67
+ * canonical one (e.g. `"RemoveEntity"`'s `"entity"`). Enables the node type to be used directly
68
+ * as an `EntityJson.events` binding (`{ "eventName": "TypeAlias" }` or
69
+ * `{ "eventName": { "type": "TypeAlias", "settings": {...} } }`) without declaring a full
70
+ * `BlueprintJson` graph in `blueprints` - see {@link EntityEventBinding}. Omit for a node type
71
+ * with zero or multiple input pins, or one with no single obviously-correct default; it remains
72
+ * usable from a full graph either way.
73
+ */
74
+ registerBlueprintNode(typeAlias, factory, defaultInputPin) {
75
+ this.blueprintNodes.set(typeAlias, factory);
76
+ if (defaultInputPin !== undefined) {
77
+ this.blueprintNodeDefaultInputs.set(typeAlias, defaultInputPin);
78
+ }
79
+ else {
80
+ this.blueprintNodeDefaultInputs.delete(typeAlias);
81
+ }
82
+ }
83
+ /**
84
+ * Load a level from an already-parsed JSON document. Every `IEntity` the level's entities
85
+ * produce is parented under - and, on failure, torn down along with - the returned
86
+ * {@link GroupEntity}, already added to the world.
87
+ * @param levelJson - The level JSON
88
+ * @param levelName - Optional name for the returned group entity (e.g. so a debugger/console
89
+ * listing entities by name shows something more meaningful than the default auto-generated one)
90
+ * @returns The level's root group entity
91
+ */
92
+ loadLevel(levelJson, levelName) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ const level = new GroupEntity();
95
+ if (levelName !== undefined) {
96
+ level.name = levelName;
97
+ }
98
+ this.world.addEntity(level);
99
+ try {
100
+ for (const entityJson of levelJson.entities) {
101
+ const { class: classAlias, shape, position, rotation, name, config, events } = entityJson;
102
+ const generator = this.generators.get(classAlias);
103
+ if (!generator) {
104
+ console.warn(`No generator registered for class alias "${classAlias}"`);
105
+ continue;
106
+ }
107
+ const settings = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config !== null && config !== void 0 ? config : {})), (shape !== undefined ? { shape } : {})), (position !== undefined ? { position } : {})), (rotation !== undefined ? { rotation } : {})), (name !== undefined ? { name } : {}));
108
+ const entity = yield generator(this.world, settings);
109
+ if (!(entity instanceof IEntity)) {
110
+ console.warn(`Generator for class alias "${classAlias}" did not return an IEntity - skipping`);
111
+ continue;
112
+ }
113
+ if (name !== undefined) {
114
+ entity.name = name;
115
+ }
116
+ // addChildren reparents the entity under level regardless of whether a generator already
117
+ // self-added it to the world (e.g. addPrimitiveRigidBody does) - safe either way.
118
+ level.addChildren(entity);
119
+ if (events) {
120
+ for (const [eventName, eventBinding] of Object.entries(events)) {
121
+ const bindingEntity = this.bindEvent(entity, eventName, eventBinding, levelJson.blueprints);
122
+ if (bindingEntity) {
123
+ level.addChildren(bindingEntity);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ catch (e) {
130
+ // Don't leave a partially-loaded level (and its already-spawned entities) behind if a
131
+ // generator throws partway through - the caller never gets `level` back to clean it up itself.
132
+ this.world.removeEntity(level, true);
133
+ throw e;
134
+ }
135
+ return level;
136
+ });
137
+ }
138
+ /**
139
+ * Resolve `eventBinding` (see {@link EntityEventBinding}) to a `BlueprintJson`, instantiate a
140
+ * fresh `Blueprint` from it, and subscribe it to `entity[eventName]` so every value that
141
+ * observable emits triggers the blueprint's `"in"` entry point. Wrapped in a
142
+ * `BlueprintBindingEntity` so the subscription (and the blueprint's own node state) is torn down
143
+ * automatically once that entity is disposed - the caller parents the returned entity under the
144
+ * level's group for that reason.
145
+ * @param entity - The entity carrying the observable property
146
+ * @param eventName - Name of the observable property on `entity`
147
+ * @param eventBinding - What to run - a `blueprints` name, a bare node type alias, or `{ type,
148
+ * settings? }`
149
+ * @param blueprints - The level's top-level blueprint map, if any
150
+ * @returns The binding entity to parent under the level, or `undefined` if `eventBinding`
151
+ * couldn't be resolved or the named property isn't an `Observable` (both logged via
152
+ * `console.warn`)
153
+ */
154
+ bindEvent(entity, eventName, eventBinding, blueprints) {
155
+ const blueprintJson = this.resolveEventBlueprint(eventName, eventBinding, blueprints);
156
+ if (!blueprintJson) {
157
+ return undefined;
158
+ }
159
+ const observable = entity[eventName];
160
+ if (!observable || typeof observable.subscribe !== 'function') {
161
+ console.warn(`Entity has no observable property "${eventName}" to bind a blueprint to`);
162
+ return undefined;
163
+ }
164
+ const blueprint = new Blueprint(this.world, blueprintJson, this.blueprintNodes);
165
+ return new BlueprintBindingEntity(blueprint, observable);
166
+ }
167
+ /**
168
+ * Turn an `EntityEventBinding` into a `BlueprintJson` to run. An object form (`{ type,
169
+ * settings? }`) always builds a single-node inline graph via {@link inlineNodeBlueprint}. A
170
+ * string form is tried first as a key into `blueprints` (a named, possibly multi-node graph),
171
+ * then - if not found there - as a bare node type alias, same as the object form with no
172
+ * settings.
173
+ * @param eventName - Name of the observable property being bound, for warning messages
174
+ * @param eventBinding - The binding to resolve
175
+ * @param blueprints - The level's top-level blueprint map, if any
176
+ * @returns The resolved graph, or `undefined` (logged via `console.warn`) if it couldn't be
177
+ */
178
+ resolveEventBlueprint(eventName, eventBinding, blueprints) {
179
+ if (typeof eventBinding === 'object') {
180
+ return this.inlineNodeBlueprint(eventName, eventBinding.type, eventBinding.settings);
181
+ }
182
+ const named = blueprints === null || blueprints === void 0 ? void 0 : blueprints[eventBinding];
183
+ if (named) {
184
+ return named;
185
+ }
186
+ if (this.blueprintNodes.has(eventBinding)) {
187
+ return this.inlineNodeBlueprint(eventName, eventBinding, undefined);
188
+ }
189
+ console.warn(`No blueprint or blueprint node type named "${eventBinding}" found for event "${eventName}" - skipping`);
190
+ return undefined;
191
+ }
192
+ /**
193
+ * Build a single-node `BlueprintJson` wrapping one blueprint node type, wired so the node's
194
+ * registered default input pin (see {@link registerBlueprintNode}) is reachable as `"in"` - what
195
+ * powers the `EntityEventBinding` shorthand that skips declaring a `blueprints` entry entirely.
196
+ * @param eventName - Name of the observable property being bound, for warning messages
197
+ * @param nodeType - The blueprint node type alias
198
+ * @param settings - Settings to bake into the node, if any
199
+ * @returns The single-node graph, or `undefined` (logged via `console.warn`) if `nodeType` isn't
200
+ * registered, or was registered without a default input pin
201
+ */
202
+ inlineNodeBlueprint(eventName, nodeType, settings) {
203
+ if (!this.blueprintNodes.has(nodeType)) {
204
+ console.warn(`No blueprint node type registered for "${nodeType}" (event "${eventName}") - skipping`);
205
+ return undefined;
206
+ }
207
+ const inputPin = this.blueprintNodeDefaultInputs.get(nodeType);
208
+ if (!inputPin) {
209
+ console.warn(`Blueprint node type "${nodeType}" has no default input pin registered - event "${eventName}" must ` +
210
+ `reference a full graph declared in "blueprints" instead, addressing the desired pin explicitly`);
211
+ return undefined;
212
+ }
213
+ return {
214
+ nodes: [{ id: 'n1', type: nodeType, settings }],
215
+ inputs: { in: { node: 'n1', pin: inputPin } },
216
+ };
217
+ }
218
+ /**
219
+ * Fetch a level JSON document hosted at `url` and load it, so a whole level/scene can be
220
+ * shipped and consumed as a single static JSON file.
221
+ * @param url - URL (or path) of the level JSON document
222
+ * @param levelName - Optional name for the returned group entity, see {@link loadLevel}
223
+ * @returns The level's root group entity
224
+ */
225
+ loadLevelFromUrl(url, levelName) {
226
+ return __awaiter(this, void 0, void 0, function* () {
227
+ const response = yield fetch(url);
228
+ if (!response.ok) {
229
+ throw new Error(`Failed to load level JSON from "${url}": ${response.status} ${response.statusText}`);
230
+ }
231
+ const levelJson = yield response.json();
232
+ return this.loadLevel(levelJson, levelName);
233
+ });
234
+ }
235
+ }
236
+ /**
237
+ * Plain do-nothing `IEntity` that owns one event-to-blueprint binding created by
238
+ * `LevelLoader.bindEvent`: subscribes to the bound observable on construction, and unsubscribes
239
+ * plus disposes the `Blueprint` on `dispose()`. Parented under the level's group entity like any
240
+ * other level-produced entity, so `world.removeEntity(level, true)` tears the binding down along
241
+ * with the rest of the level - there is nothing else app code needs to do to clean it up.
242
+ * @template D - The position type
243
+ * @template R - The rotation type
244
+ * @template TypeDoc - The type document repository
245
+ */
246
+ class BlueprintBindingEntity extends IEntity {
247
+ constructor(blueprint, observable) {
248
+ super();
249
+ this.blueprint = blueprint;
250
+ this.tickOrder = TickOrder.CONTROLLERS;
251
+ this.subscription = observable.subscribe(value => this.blueprint.trigger('in', value));
252
+ }
253
+ dispose() {
254
+ this.subscription.unsubscribe();
255
+ this.blueprint.dispose();
256
+ super.dispose();
257
+ }
258
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.0.58";
1
+ export declare const VERSION = "0.0.59";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.0.58';
1
+ export const VERSION = '0.0.59';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gg-web-engine/core",
3
- "version": "0.0.58",
3
+ "version": "0.0.59",
4
4
  "description": "An attempt to create open source game engine for browser",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/tsconfig.json CHANGED
@@ -2,7 +2,9 @@
2
2
  "extends": "./../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "baseUrl": "./src/",
5
- "outDir": "./dist/"
5
+ "outDir": "./dist/",
6
+ "rootDir": "./src/",
7
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
6
8
  },
7
9
  "include": ["*.ts", "**/*.ts"],
8
10
  "exclude": ["**/*.spec.ts", "node_modules", "dist/**/*", "test/**/*"]