@vworlds/vecs 1.0.12 → 1.0.14
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 +95 -71
- package/dist/component.d.ts +22 -70
- package/dist/component.js +4 -59
- package/dist/component.js.map +1 -1
- package/dist/dsl.d.ts +9 -5
- package/dist/dsl.js +6 -3
- package/dist/dsl.js.map +1 -1
- package/dist/entity.d.ts +80 -58
- package/dist/entity.js +245 -196
- package/dist/entity.js.map +1 -1
- package/dist/filter.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/query.d.ts +21 -21
- package/dist/query.js +17 -16
- package/dist/query.js.map +1 -1
- package/dist/system.d.ts +7 -7
- package/dist/system.js +11 -10
- package/dist/system.js.map +1 -1
- package/dist/util/array_map.js +11 -7
- package/dist/util/array_map.js.map +1 -1
- package/dist/util/bitset.d.ts +17 -3
- package/dist/util/bitset.js +47 -30
- package/dist/util/bitset.js.map +1 -1
- package/dist/world.d.ts +23 -16
- package/dist/world.js +32 -20
- package/dist/world.js.map +1 -1
- package/package.json +1 -1
package/dist/entity.js
CHANGED
|
@@ -14,18 +14,20 @@ import { Bitset } from "./util/bitset.js";
|
|
|
14
14
|
* e.set(Position, { x: 100 });
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
17
|
-
* Entities support a parent
|
|
17
|
+
* Entities support a parent-child hierarchy. `parent` and `children` form a
|
|
18
18
|
* bidirectional link maintained by {@link setParent}; the children set is
|
|
19
19
|
* created lazily. Destroying a parent recursively destroys its children.
|
|
20
20
|
*
|
|
21
21
|
* ## Deferred semantics
|
|
22
22
|
*
|
|
23
23
|
* Inside a system body or `forEach` iteration the world is in **deferred
|
|
24
|
-
* mode**: `add` / `set` / `modified` / `remove` / `destroy` /
|
|
25
|
-
* enqueue commands. The data layer (the components map and
|
|
26
|
-
* is mutated when the world drains its queue. Concretely,
|
|
24
|
+
* mode**: `add` / `attach` / `set` / `modified` / `remove` / `destroy` /
|
|
25
|
+
* `setParent` only enqueue commands. The data layer (the components map and
|
|
26
|
+
* `componentBitmask`) is mutated when the world drains its queue. Concretely,
|
|
27
|
+
* while deferred:
|
|
27
28
|
*
|
|
28
29
|
* - `entity.get(C)` returns `undefined` after `entity.add(C)` (no instance yet).
|
|
30
|
+
* - `entity.get(C)` returns `undefined` after `entity.attach(instance)` if C was absent.
|
|
29
31
|
* - `entity.get(C)` returns the previous value after `entity.set(C, props)`.
|
|
30
32
|
* - `entity.get(C)` still returns the component after `entity.remove(C)`.
|
|
31
33
|
*
|
|
@@ -42,6 +44,8 @@ export class Entity {
|
|
|
42
44
|
this.eid = eid;
|
|
43
45
|
/** @internal Maps numeric component type id to component instance. */
|
|
44
46
|
this._components = new ArrayMap();
|
|
47
|
+
/** @internal Component types with pending modified delivery. */
|
|
48
|
+
this._dirtyComponentBitmask = new Bitset();
|
|
45
49
|
/** @internal Set of queries this entity currently belongs to. */
|
|
46
50
|
this._queries = new Set();
|
|
47
51
|
/**
|
|
@@ -57,6 +61,14 @@ export class Entity {
|
|
|
57
61
|
/** @internal Set to `true` after the world has fully torn down this entity. */
|
|
58
62
|
this._destroyed = false;
|
|
59
63
|
}
|
|
64
|
+
/** Fire every `onAdd` hook registered for `meta`. */
|
|
65
|
+
_runOnAddHandlers(meta, component) {
|
|
66
|
+
meta._onAddHandlers?.forEach((handler) => handler(this, component));
|
|
67
|
+
}
|
|
68
|
+
/** Fire every `onSet` hook registered for `meta`. */
|
|
69
|
+
_runOnSetHandlers(meta, component) {
|
|
70
|
+
meta._onSetHandlers?.forEach((handler) => handler(this, component));
|
|
71
|
+
}
|
|
60
72
|
/**
|
|
61
73
|
* Re-evaluate every world query for this entity, firing `_enter` / `_exit`
|
|
62
74
|
* routing whenever membership flipped.
|
|
@@ -64,145 +76,70 @@ export class Entity {
|
|
|
64
76
|
_updateQueries() {
|
|
65
77
|
this.world.queries.forEach((q) => {
|
|
66
78
|
const belongs = q.belongs(this);
|
|
67
|
-
const isIn = this.
|
|
79
|
+
const isIn = this._queries.has(q);
|
|
68
80
|
if (belongs !== isIn) {
|
|
69
81
|
belongs ? q._enter(this) : q._exit(this);
|
|
70
82
|
}
|
|
71
83
|
});
|
|
72
84
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
80
|
-
_new(type, props) {
|
|
81
|
-
const meta = this.world.getComponentMeta(type);
|
|
82
|
-
if (meta.exclusive) {
|
|
83
|
-
for (const exclusiveType of meta.exclusive) {
|
|
84
|
-
if (this._components.has(exclusiveType)) {
|
|
85
|
-
this._remove(exclusiveType);
|
|
85
|
+
/** Store a component instance and perform the shared add-side bookkeeping. */
|
|
86
|
+
_storeComponent(meta, component, isAdd) {
|
|
87
|
+
if (meta._exclusive) {
|
|
88
|
+
for (const exclusiveMeta of meta._exclusive) {
|
|
89
|
+
if (this._components.has(exclusiveMeta.type)) {
|
|
90
|
+
this._remove(exclusiveMeta);
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this._components.set(type, c);
|
|
94
|
-
this.componentBitmask.add(type);
|
|
95
|
-
if (meta._onAddHandlers) {
|
|
96
|
-
meta._onAddHandlers.forEach((handler) => handler(c));
|
|
94
|
+
this._components.set(meta.type, component);
|
|
95
|
+
this.componentBitmask.addBit(meta.bitPtr);
|
|
96
|
+
if (isAdd) {
|
|
97
|
+
this._runOnAddHandlers(meta, component);
|
|
97
98
|
}
|
|
98
99
|
this._updateQueries();
|
|
99
|
-
return c;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* @internal Return `true` when this entity is currently tracked by `q`.
|
|
103
|
-
*/
|
|
104
|
-
_isInQuery(q) {
|
|
105
|
-
return this._queries.has(q);
|
|
106
100
|
}
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return this._components.get(type);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* @internal Reparent this entity in place, maintaining the bidirectional
|
|
117
|
-
* link. Throws if `newParent` is a descendant of this entity.
|
|
118
|
-
*
|
|
119
|
-
* Called by the world either inline (outside deferred mode) or while
|
|
120
|
-
* routing a queued `SetParent` command.
|
|
121
|
-
*/
|
|
122
|
-
_setParent(newParent) {
|
|
123
|
-
if (this._destroyed) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (newParent !== undefined) {
|
|
127
|
-
let ancestor = newParent;
|
|
128
|
-
while (ancestor !== undefined) {
|
|
129
|
-
if (ancestor === this) {
|
|
130
|
-
throw new Error(`Circular parent reference: entity ${this.eid} is already an ancestor of entity ${newParent.eid}`);
|
|
131
|
-
}
|
|
132
|
-
ancestor = ancestor._parent;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (this._parent) {
|
|
136
|
-
this._parent._children?.delete(this);
|
|
137
|
-
}
|
|
138
|
-
this._parent = newParent;
|
|
139
|
-
if (newParent) {
|
|
140
|
-
(newParent._children ?? (newParent._children = new Set())).add(this);
|
|
101
|
+
/** Perform the shared set-side hook and query update routing. */
|
|
102
|
+
_notifyComponentSet(meta, component, isUpdate) {
|
|
103
|
+
this._runOnSetHandlers(meta, component);
|
|
104
|
+
this._dirtyComponentBitmask.deleteBit(meta.bitPtr);
|
|
105
|
+
if (isUpdate) {
|
|
106
|
+
this._queries.forEach((q) => q._notifyModified(this, meta, component));
|
|
141
107
|
}
|
|
142
108
|
}
|
|
143
109
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
110
|
+
* Construct a fresh component of `type`, apply `props`, store it on this
|
|
111
|
+
* entity, fire the `onAdd` hook, and route query updates.
|
|
112
|
+
*
|
|
113
|
+
* If the component type belongs to an exclusivity group, any conflicting
|
|
114
|
+
* component already on this entity is removed first.
|
|
147
115
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const existing = this._components.get(type);
|
|
153
|
-
const c = existing ?? this._new(type, props);
|
|
116
|
+
_new(meta, props) {
|
|
117
|
+
const c = new meta.Class();
|
|
154
118
|
if (props !== undefined) {
|
|
155
|
-
|
|
156
|
-
Object.assign(c, props);
|
|
157
|
-
}
|
|
158
|
-
const setHandlers = c.meta._onSetHandlers;
|
|
159
|
-
if (setHandlers) {
|
|
160
|
-
setHandlers.forEach((handler) => handler(c));
|
|
161
|
-
}
|
|
162
|
-
c._dirty = false;
|
|
163
|
-
if (existing) {
|
|
164
|
-
this._queries.forEach((q) => q._notifyModified(c));
|
|
165
|
-
}
|
|
119
|
+
Object.assign(c, props);
|
|
166
120
|
}
|
|
121
|
+
this._storeComponent(meta, c, true);
|
|
122
|
+
return c;
|
|
167
123
|
}
|
|
168
124
|
/**
|
|
169
|
-
* @internal
|
|
170
|
-
*
|
|
125
|
+
* @internal Record that this entity is now tracked by `q`. Called by
|
|
126
|
+
* `Query._enter`.
|
|
171
127
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const c = this._components.get(type);
|
|
177
|
-
if (!c) {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
const setHandlers = c.meta._onSetHandlers;
|
|
181
|
-
if (setHandlers) {
|
|
182
|
-
setHandlers.forEach((handler) => handler(c));
|
|
183
|
-
}
|
|
184
|
-
c._dirty = false;
|
|
185
|
-
this._queries.forEach((q) => q._notifyModified(c));
|
|
128
|
+
_addQueryMembership(q) {
|
|
129
|
+
this._queries.add(q);
|
|
186
130
|
}
|
|
187
131
|
/**
|
|
188
|
-
* @internal Apply
|
|
189
|
-
*
|
|
132
|
+
* @internal Apply an `Attach` command: store the provided component instance,
|
|
133
|
+
* replacing any previous instance for this type. Events are fired as if the
|
|
134
|
+
* caller had performed a `set` operation.
|
|
190
135
|
*/
|
|
191
|
-
|
|
136
|
+
_attach(meta, component) {
|
|
192
137
|
if (this._destroyed) {
|
|
193
138
|
return;
|
|
194
139
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
this.componentBitmask.delete(type);
|
|
200
|
-
this._updateQueries();
|
|
201
|
-
this._components.delete(type);
|
|
202
|
-
const removeHandlers = c.meta._onRemoveHandlers;
|
|
203
|
-
if (removeHandlers) {
|
|
204
|
-
removeHandlers.forEach((handler) => handler(c));
|
|
205
|
-
}
|
|
140
|
+
const existing = this._components.get(meta.type);
|
|
141
|
+
this._storeComponent(meta, component, existing === undefined);
|
|
142
|
+
this._notifyComponentSet(meta, component, existing !== undefined);
|
|
206
143
|
}
|
|
207
144
|
/**
|
|
208
145
|
* @internal Apply a `Destroy` command: fire `_exit` on every query, then
|
|
@@ -221,12 +158,14 @@ export class Entity {
|
|
|
221
158
|
}
|
|
222
159
|
});
|
|
223
160
|
toExit.forEach((q) => q._exit(this));
|
|
224
|
-
this._components.forEach((c) => {
|
|
225
|
-
const
|
|
161
|
+
this._components.forEach((c, type) => {
|
|
162
|
+
const meta = this.world.getComponentMeta(type);
|
|
163
|
+
const removeHandlers = meta._onRemoveHandlers;
|
|
226
164
|
if (removeHandlers) {
|
|
227
|
-
removeHandlers.forEach((handler) => handler(c));
|
|
165
|
+
removeHandlers.forEach((handler) => handler(this, c));
|
|
228
166
|
}
|
|
229
167
|
});
|
|
168
|
+
this._dirtyComponentBitmask.clear();
|
|
230
169
|
if (this._events) {
|
|
231
170
|
this._events.emit("destroy");
|
|
232
171
|
this._events.removeAllListeners("destroy");
|
|
@@ -237,6 +176,34 @@ export class Entity {
|
|
|
237
176
|
this._parent = undefined;
|
|
238
177
|
}
|
|
239
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* @internal Look up a component instance by numeric type id.
|
|
181
|
+
*
|
|
182
|
+
* Faster than {@link get} because no class to type id resolution is needed.
|
|
183
|
+
*/
|
|
184
|
+
_get(type) {
|
|
185
|
+
return this._components.get(type);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @internal Return `true` when this entity is currently tracked by `q`.
|
|
189
|
+
*/
|
|
190
|
+
_isInQuery(q) {
|
|
191
|
+
return this._queries.has(q);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* @internal Apply a `Modified` command: fire `onSet` and route modified
|
|
195
|
+
* events to every query that watches the component type.
|
|
196
|
+
*/
|
|
197
|
+
_modified(meta) {
|
|
198
|
+
if (this._destroyed) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const c = this._components.get(meta.type);
|
|
202
|
+
if (!c) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this._notifyComponentSet(meta, c, true);
|
|
206
|
+
}
|
|
240
207
|
/**
|
|
241
208
|
* @internal Forget query `q` without firing exit callbacks. Called by
|
|
242
209
|
* {@link World} when a {@link Query.destroy} sweeps every entity.
|
|
@@ -245,11 +212,25 @@ export class Entity {
|
|
|
245
212
|
this._queries.delete(q);
|
|
246
213
|
}
|
|
247
214
|
/**
|
|
248
|
-
* @internal
|
|
249
|
-
* `
|
|
215
|
+
* @internal Apply a `Remove` command: clear the type bit, route exits,
|
|
216
|
+
* detach the component, and fire `onRemove`.
|
|
250
217
|
*/
|
|
251
|
-
|
|
252
|
-
this.
|
|
218
|
+
_remove(meta) {
|
|
219
|
+
if (this._destroyed) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const c = this._components.get(meta.type);
|
|
223
|
+
if (!c) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this._dirtyComponentBitmask.deleteBit(meta.bitPtr);
|
|
227
|
+
this.componentBitmask.deleteBit(meta.bitPtr);
|
|
228
|
+
this._updateQueries();
|
|
229
|
+
this._components.delete(meta.type);
|
|
230
|
+
const removeHandlers = meta._onRemoveHandlers;
|
|
231
|
+
if (removeHandlers) {
|
|
232
|
+
removeHandlers.forEach((handler) => handler(this, c));
|
|
233
|
+
}
|
|
253
234
|
}
|
|
254
235
|
/**
|
|
255
236
|
* @internal Record that this entity is no longer tracked by `q`. Called by
|
|
@@ -258,40 +239,66 @@ export class Entity {
|
|
|
258
239
|
_removeQueryMembership(q) {
|
|
259
240
|
this._queries.delete(q);
|
|
260
241
|
}
|
|
261
|
-
/** Parent entity in the scene hierarchy, or `undefined` for a root entity. */
|
|
262
|
-
get parent() {
|
|
263
|
-
return this._parent;
|
|
264
|
-
}
|
|
265
242
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
243
|
+
* @internal Apply a `Set` command: create the component if missing, assign
|
|
244
|
+
* `props` if provided, fire `onSet`, and route modified events to every
|
|
245
|
+
* query that watches the component type.
|
|
268
246
|
*/
|
|
269
|
-
|
|
270
|
-
|
|
247
|
+
_set(meta, props) {
|
|
248
|
+
if (this._destroyed) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const existing = this._components.get(meta.type);
|
|
252
|
+
const c = existing ?? this._new(meta, props);
|
|
253
|
+
if (props !== undefined) {
|
|
254
|
+
if (existing) {
|
|
255
|
+
Object.assign(c, props);
|
|
256
|
+
}
|
|
257
|
+
this._notifyComponentSet(meta, c, existing !== undefined);
|
|
258
|
+
}
|
|
271
259
|
}
|
|
272
260
|
/**
|
|
273
|
-
*
|
|
274
|
-
* `
|
|
261
|
+
* @internal Reparent this entity in place, maintaining the bidirectional
|
|
262
|
+
* link. Throws if `newParent` is a descendant of this entity.
|
|
275
263
|
*
|
|
276
|
-
*
|
|
264
|
+
* Called by the world either inline (outside deferred mode) or while
|
|
265
|
+
* routing a queued `SetParent` command.
|
|
277
266
|
*/
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
267
|
+
_setParent(newParent) {
|
|
268
|
+
if (this._destroyed) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (newParent !== undefined) {
|
|
272
|
+
let ancestor = newParent;
|
|
273
|
+
while (ancestor !== undefined) {
|
|
274
|
+
if (ancestor === this) {
|
|
275
|
+
throw new Error(`Circular parent reference: entity ${this.eid} is already an ancestor of entity ${newParent.eid}`);
|
|
276
|
+
}
|
|
277
|
+
ancestor = ancestor._parent;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (this._parent) {
|
|
281
|
+
this._parent._children?.delete(this);
|
|
282
|
+
}
|
|
283
|
+
this._parent = newParent;
|
|
284
|
+
if (newParent) {
|
|
285
|
+
(newParent._children ?? (newParent._children = new Set())).add(this);
|
|
281
286
|
}
|
|
282
|
-
return this._events;
|
|
283
287
|
}
|
|
284
|
-
/**
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Read-only view of direct child entities. The backing set is created lazily
|
|
290
|
+
* on the first child link; before that this getter returns a shared empty set.
|
|
291
|
+
*/
|
|
292
|
+
get children() {
|
|
293
|
+
return this._children ?? Entity._emptyChildren;
|
|
287
294
|
}
|
|
288
295
|
/**
|
|
289
296
|
* Read-only view of all components currently attached to this entity, keyed
|
|
290
297
|
* by numeric component type id.
|
|
291
298
|
*
|
|
292
299
|
* The mutating methods (`set`, `delete`, `clear`) are not exposed. Use
|
|
293
|
-
* `entity.add`, `entity.set`, and `entity.remove` to change
|
|
294
|
-
* set.
|
|
300
|
+
* `entity.add`, `entity.attach`, `entity.set`, and `entity.remove` to change
|
|
301
|
+
* the component set.
|
|
295
302
|
*
|
|
296
303
|
* ```ts
|
|
297
304
|
* entity.components.forEach((c) => console.log(c.constructor.name));
|
|
@@ -300,103 +307,145 @@ export class Entity {
|
|
|
300
307
|
get components() {
|
|
301
308
|
return this._components;
|
|
302
309
|
}
|
|
310
|
+
/** `true` when no components are currently attached to this entity. */
|
|
311
|
+
get empty() {
|
|
312
|
+
return this._components.size == 0;
|
|
313
|
+
}
|
|
303
314
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
315
|
+
* Typed event emitter for entity-level lifecycle events. Currently only the
|
|
316
|
+
* `"destroy"` event is emitted, just before the entity is fully torn down.
|
|
306
317
|
*
|
|
307
|
-
*
|
|
318
|
+
* The emitter is created lazily on first access.
|
|
308
319
|
*/
|
|
309
|
-
|
|
320
|
+
get events() {
|
|
321
|
+
if (!this._events) {
|
|
322
|
+
this._events = new Events();
|
|
323
|
+
}
|
|
324
|
+
return this._events;
|
|
325
|
+
}
|
|
326
|
+
/** Parent entity in the scene hierarchy, or `undefined` for a root entity. */
|
|
327
|
+
get parent() {
|
|
328
|
+
return this._parent;
|
|
329
|
+
}
|
|
330
|
+
add(typeOrClass) {
|
|
331
|
+
const meta = this.world.getComponentMeta(typeOrClass);
|
|
310
332
|
if (this.world.deferred) {
|
|
311
|
-
this.world._enqueue({ kind:
|
|
333
|
+
this.world._enqueue({ kind: 1 /* CommandKind.Set */, entity: this, meta, props: undefined });
|
|
312
334
|
}
|
|
313
335
|
else {
|
|
314
|
-
this.
|
|
336
|
+
this._set(meta, undefined);
|
|
315
337
|
}
|
|
338
|
+
return this;
|
|
316
339
|
}
|
|
317
340
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
341
|
+
* Attach an existing component instance to this entity and store that exact
|
|
342
|
+
* object. If a component of the same registered class already exists, it is
|
|
343
|
+
* replaced rather than assigned into.
|
|
320
344
|
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
345
|
+
* `attach` uses the instance constructor to resolve component metadata, so
|
|
346
|
+
* the constructor must already be registered in this world. The operation
|
|
347
|
+
* fires hooks and query updates like a `set` operation.
|
|
324
348
|
*
|
|
325
|
-
* @param
|
|
349
|
+
* @param component - Existing component instance to store on the entity.
|
|
326
350
|
* @returns This entity, for chaining.
|
|
327
351
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return this;
|
|
331
|
-
}
|
|
332
|
-
c._dirty = true;
|
|
352
|
+
attach(component) {
|
|
353
|
+
const meta = this.world.getComponentMeta(component.constructor);
|
|
333
354
|
if (this.world.deferred) {
|
|
334
|
-
this.world._enqueue({ kind:
|
|
355
|
+
this.world._enqueue({ kind: 6 /* CommandKind.Attach */, entity: this, meta, component });
|
|
335
356
|
}
|
|
336
357
|
else {
|
|
337
|
-
this.
|
|
358
|
+
this._attach(meta, component);
|
|
338
359
|
}
|
|
339
360
|
return this;
|
|
340
361
|
}
|
|
341
|
-
|
|
342
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Destroy this entity and recursively destroy its children.
|
|
364
|
+
*
|
|
365
|
+
* Each component fires its `onRemove` hook, the `"destroy"` event is emitted
|
|
366
|
+
* just before teardown, and the entity is unregistered from the world.
|
|
367
|
+
* After destruction the entity must not be used.
|
|
368
|
+
*/
|
|
369
|
+
destroy() {
|
|
343
370
|
if (this.world.deferred) {
|
|
344
|
-
this.world._enqueue({ kind:
|
|
371
|
+
this.world._enqueue({ kind: 4 /* CommandKind.Destroy */, entity: this });
|
|
345
372
|
}
|
|
346
373
|
else {
|
|
347
|
-
this.
|
|
374
|
+
this._destroy();
|
|
375
|
+
}
|
|
376
|
+
if (this._children) {
|
|
377
|
+
this._children.forEach((child) => {
|
|
378
|
+
child.destroy();
|
|
379
|
+
});
|
|
380
|
+
this._children.clear();
|
|
348
381
|
}
|
|
349
|
-
return this;
|
|
350
382
|
}
|
|
351
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Look up a component on this entity.
|
|
385
|
+
*
|
|
386
|
+
* @param typeOrClass - Component class or numeric type id.
|
|
387
|
+
* @returns The component instance, or `undefined` when it is not attached.
|
|
388
|
+
*/
|
|
389
|
+
get(typeOrClass) {
|
|
352
390
|
const type = this.world.getComponentType(typeOrClass);
|
|
391
|
+
return this._get(type);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Mark a component type as having changed, queueing the corresponding `onSet` / `update`
|
|
395
|
+
* notifications.
|
|
396
|
+
*
|
|
397
|
+
* Repeated calls before the world routes the modified command are coalesced via
|
|
398
|
+
* the entity's dirty component bitset.
|
|
399
|
+
*
|
|
400
|
+
* @param typeOrClass - Component class or numeric type id whose data changed.
|
|
401
|
+
* @returns This entity, for chaining.
|
|
402
|
+
*/
|
|
403
|
+
modified(typeOrClass) {
|
|
404
|
+
const meta = this.world.getComponentMeta(typeOrClass);
|
|
405
|
+
if (this._dirtyComponentBitmask.hasBit(meta.bitPtr)) {
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
this._dirtyComponentBitmask.addBit(meta.bitPtr);
|
|
353
409
|
if (this.world.deferred) {
|
|
354
|
-
this.world._enqueue({ kind:
|
|
410
|
+
this.world._enqueue({ kind: 2 /* CommandKind.Modified */, entity: this, meta });
|
|
355
411
|
}
|
|
356
412
|
else {
|
|
357
|
-
this.
|
|
413
|
+
this._modified(meta);
|
|
358
414
|
}
|
|
359
415
|
return this;
|
|
360
416
|
}
|
|
361
417
|
remove(typeOrClass) {
|
|
362
|
-
const
|
|
418
|
+
const meta = this.world.getComponentMeta(typeOrClass);
|
|
363
419
|
if (this.world.deferred) {
|
|
364
|
-
this.world._enqueue({ kind: 3 /* CommandKind.Remove */, entity: this,
|
|
420
|
+
this.world._enqueue({ kind: 3 /* CommandKind.Remove */, entity: this, meta });
|
|
365
421
|
}
|
|
366
422
|
else {
|
|
367
|
-
this._remove(
|
|
423
|
+
this._remove(meta);
|
|
368
424
|
}
|
|
369
425
|
}
|
|
370
426
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* @param typeOrClass - Component class or numeric type id.
|
|
374
|
-
* @returns The component instance, or `undefined` when it is not attached.
|
|
375
|
-
*/
|
|
376
|
-
get(typeOrClass) {
|
|
377
|
-
const type = this.world.getComponentType(typeOrClass);
|
|
378
|
-
return this._get(type);
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Destroy this entity and recursively destroy its children.
|
|
427
|
+
* Reparent this entity. In deferred mode the change is queued; outside
|
|
428
|
+
* deferred mode it executes inline.
|
|
382
429
|
*
|
|
383
|
-
*
|
|
384
|
-
* just before teardown, and the entity is unregistered from the world.
|
|
385
|
-
* After destruction the entity must not be used.
|
|
430
|
+
* @param newParent - New parent, or `undefined` to make this a root entity.
|
|
386
431
|
*/
|
|
387
|
-
|
|
432
|
+
setParent(newParent) {
|
|
388
433
|
if (this.world.deferred) {
|
|
389
|
-
this.world._enqueue({ kind:
|
|
434
|
+
this.world._enqueue({ kind: 5 /* CommandKind.SetParent */, entity: this, parent: newParent });
|
|
390
435
|
}
|
|
391
436
|
else {
|
|
392
|
-
this.
|
|
437
|
+
this._setParent(newParent);
|
|
393
438
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
this.
|
|
439
|
+
}
|
|
440
|
+
set(typeOrClass, props) {
|
|
441
|
+
const meta = this.world.getComponentMeta(typeOrClass);
|
|
442
|
+
if (this.world.deferred) {
|
|
443
|
+
this.world._enqueue({ kind: 1 /* CommandKind.Set */, entity: this, meta, props });
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
this._set(meta, props);
|
|
399
447
|
}
|
|
448
|
+
return this;
|
|
400
449
|
}
|
|
401
450
|
/** Returns `"EntityN"` where N is the entity id. */
|
|
402
451
|
toString() {
|