@woosh/meep-engine 2.39.15 → 2.39.18

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 (29) hide show
  1. package/core/binary/int32_to_binary_string.js +18 -0
  2. package/core/cache/Cache.js +1 -1
  3. package/core/geom/3d/decompose_matrix_4_array.js +3 -1
  4. package/core/geom/3d/ray/ray3_array_apply_matrix4.js +15 -1
  5. package/editor/tools/v2/TransformControls.d.ts +15 -0
  6. package/editor/tools/v2/TransformControls.js +1782 -0
  7. package/editor/tools/v2/prototypeTransformControls.js +80 -0
  8. package/engine/asset/AssetManager.js +82 -23
  9. package/engine/ecs/parent/EntityNode.js +41 -13
  10. package/engine/ecs/terrain/ecs/Terrain.js +20 -21
  11. package/engine/ecs/terrain/ecs/layers/TerrainLayers.js +8 -3
  12. package/engine/ecs/transform/Transform.d.ts +2 -0
  13. package/engine/ecs/transform/Transform.js +6 -0
  14. package/engine/ecs/transform-attachment/TransformAttachment.js +13 -0
  15. package/engine/ecs/transform-attachment/TransformAttachmentSystem.js +138 -16
  16. package/engine/graphics/FrameRunner.js +8 -2
  17. package/engine/graphics/ecs/mesh-v2/DrawMode.js +4 -0
  18. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +77 -0
  19. package/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +10 -0
  20. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +2 -30
  21. package/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.js +11 -0
  22. package/engine/graphics/ecs/mesh-v2/render/adapters/InstancedRendererAdapter.js +8 -32
  23. package/engine/graphics/ecs/mesh-v2/render/optimization/RuntimeDrawMethodOptimizer.js +6 -0
  24. package/engine/graphics/geometry/buffered/makeGeometryIndexed.js +23 -0
  25. package/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +16 -4
  26. package/engine/graphics/geometry/buffered/query/GeometryVisitor.js +8 -1
  27. package/engine/graphics/geometry/buffered/query/RaycastNearestHitComputingVisitor.js +14 -5
  28. package/engine/graphics/geometry/bvh/buffered/BinaryBVHFromBufferGeometry.js +56 -0
  29. package/package.json +1 -1
@@ -0,0 +1,80 @@
1
+ import { EngineHarness } from "../../../engine/EngineHarness.js";
2
+ import EntityBuilder from "../../../engine/ecs/EntityBuilder.js";
3
+ import { ShadedGeometry } from "../../../engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
4
+ import { BoxBufferGeometry, MeshStandardMaterial } from "three";
5
+ import { Transform } from "../../../engine/ecs/transform/Transform.js";
6
+ import { ShadedGeometrySystem } from "../../../engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js";
7
+ import { TransformControls } from "./TransformControls.js";
8
+ import { Camera } from "../../../engine/graphics/ecs/camera/Camera.js";
9
+ import { TransformAttachmentSystem } from "../../../engine/ecs/transform-attachment/TransformAttachmentSystem.js";
10
+ import InputController from "../../../engine/input/ecs/components/InputController.js";
11
+ import InputControllerSystem from "../../../engine/input/ecs/systems/InputControllerSystem.js";
12
+
13
+ const harness = new EngineHarness();
14
+
15
+ /**
16
+ *
17
+ * @param {Engine} engine
18
+ */
19
+ async function main(engine) {
20
+ EngineHarness.buildBasics({
21
+ engine,
22
+ cameraController: false
23
+ });
24
+
25
+
26
+ const ecd = engine.entityManager.dataset;
27
+
28
+ // create something to drag
29
+ const cube_entity = new EntityBuilder()
30
+ .add(ShadedGeometry.from(
31
+ new BoxBufferGeometry(1, 1, 1),
32
+ new MeshStandardMaterial({
33
+ color: 0xFFAAAA
34
+ })
35
+ ))
36
+ .add(Transform.fromJSON({
37
+ position: {
38
+ x: 10,
39
+ y: 0,
40
+ z: 10
41
+ }
42
+ }))
43
+ .build(ecd);
44
+
45
+ const camera = ecd.getAnyComponent(Camera);
46
+
47
+ const controls = new TransformControls(camera.component.object, engine.gameView.el);
48
+
49
+
50
+ controls.build(ecd); // add controls to the scene
51
+ controls.attach(cube_entity); // make controls target the cube
52
+
53
+ new EntityBuilder()
54
+ .add(new InputController([{
55
+ path: 'keyboard/keys/w/down',
56
+ listener() {
57
+ controls.mode = "translate"
58
+ }
59
+ }, {
60
+ path: 'keyboard/keys/e/down',
61
+ listener() {
62
+ controls.mode = "scale"
63
+ }
64
+ }, {
65
+ path: 'keyboard/keys/r/down',
66
+ listener() {
67
+ controls.mode = "rotate"
68
+ }
69
+ }]))
70
+ .build(ecd);
71
+
72
+ }
73
+
74
+ harness.initialize({
75
+ configuration(config, engine) {
76
+ config.addSystem(new ShadedGeometrySystem(engine));
77
+ config.addSystem(new TransformAttachmentSystem(engine));
78
+ config.addSystem(new InputControllerSystem(engine.devices));
79
+ }
80
+ }).then(main);
@@ -14,29 +14,58 @@ import { array_remove_element } from "../../core/collection/array/array_remove_e
14
14
  import { AssetDescription } from "./AssetDescription.js";
15
15
  import { noop } from "../../core/function/Functions.js";
16
16
 
17
- /**
18
- *
19
- * @param {function(asset:Asset)} successCallback
20
- * @param {function(error:*)} failureCallback
21
- * @param {function(loaded:number, total:number):void} progressCallback
22
- * @constructor
23
- */
24
- function AssetRequest(successCallback, failureCallback, progressCallback) {
25
- this.successCallback = successCallback;
26
- this.failureCallback = failureCallback;
27
- this.pogressCallback = progressCallback;
17
+ class AssetRequest {
18
+ /**
19
+ *
20
+ * @param {function(asset:Asset)} successCallback
21
+ * @param {function(error:*)} failureCallback
22
+ * @param {function(loaded:number, total:number):void} progressCallback
23
+ * @constructor
24
+ */
25
+ constructor(successCallback, failureCallback, progressCallback) {
26
+ this.successCallback = successCallback;
27
+ this.failureCallback = failureCallback;
28
+ this.pogressCallback = progressCallback;
29
+ }
28
30
  }
29
31
 
30
-
31
32
  /**
32
- *
33
- * @param {AssetDescription} description
34
- * @constructor
33
+ * @enum {number}
35
34
  */
36
- function PendingAsset(description) {
37
- this.description = description;
38
- this.requests = [];
39
- this.progress = new BoundedValue(0, 0);
35
+ const AssetLoadState = {
36
+ Initial: 0,
37
+ Queued: 1,
38
+ Loading: 2,
39
+ Succeeded: 3,
40
+ Failed: 4
41
+ };
42
+
43
+
44
+ class PendingAsset {
45
+ /**
46
+ *
47
+ * @param {AssetDescription} description
48
+ * @constructor
49
+ */
50
+ constructor(description) {
51
+ this.description = description;
52
+ /**
53
+ *
54
+ * @type {AssetRequest[]}
55
+ */
56
+ this.requests = [];
57
+ /**
58
+ *
59
+ * @type {BoundedValue}
60
+ */
61
+ this.progress = new BoundedValue(0, 0);
62
+
63
+ /**
64
+ *
65
+ * @type {AssetLoadState|number}
66
+ */
67
+ this.state = AssetLoadState.Initial;
68
+ }
40
69
  }
41
70
 
42
71
 
@@ -232,6 +261,19 @@ export class AssetManager {
232
261
  * @param {Asset} loaded_asset
233
262
  */
234
263
  const success = async (loaded_asset) => {
264
+ if (pendingAsset.state === AssetLoadState.Succeeded) {
265
+ // incorrect state
266
+ console.warn(`Asset already resolved, duplicate success invocation. Ignored. AD:${assetDescription}`);
267
+ return;
268
+ }
269
+
270
+ if (pendingAsset.state === AssetLoadState.Failed) {
271
+ // incorrect state
272
+ console.error(`Asset already failed. Unexpected resolution signal. AD:${assetDescription}`);
273
+ }
274
+
275
+ pendingAsset.state = AssetLoadState.Succeeded;
276
+
235
277
  let asset = loaded_asset;
236
278
 
237
279
  // apply transform chain
@@ -256,25 +298,42 @@ export class AssetManager {
256
298
 
257
299
  //register asset
258
300
  assets.set(assetDescription, asset);
259
- requests.forEach(function (request) {
301
+
302
+ // process callbacks
303
+ for (let i = 0; i < requests.length; i++) {
304
+ const request = requests[i];
260
305
  try {
261
306
  request.successCallback(asset);
262
307
  } catch (e) {
263
308
  console.error("Failed to execute asset success callback", e);
264
309
  }
265
- });
310
+ }
311
+
266
312
  //clear callbacks
267
313
  requestMap.delete(assetDescription);
268
314
  }
269
315
 
270
316
  function failure(error) {
271
- requests.forEach(function (request) {
317
+ if (pendingAsset.state === AssetLoadState.Failed) {
318
+ console.warn(`Asset already failed, this is a redundant invocation. AD: ${assetDescription}`);
319
+ return;
320
+ }
321
+
322
+ if (pendingAsset.state === AssetLoadState.Succeeded) {
323
+ // incorrect state
324
+ console.error(`Asset already resolved. Unexpected failure signal. AD:${assetDescription}`);
325
+ }
326
+
327
+ pendingAsset.state = AssetLoadState.Failed;
328
+
329
+ for (let i = 0; i < requests.length; i++) {
330
+ const request = requests[i];
272
331
  try {
273
332
  request.failureCallback(error);
274
333
  } catch (e) {
275
334
  console.error("Failed to execute asset failure callback", e);
276
335
  }
277
- });
336
+ }
278
337
  //clear callbacks
279
338
  requestMap.delete(assetDescription);
280
339
 
@@ -219,16 +219,24 @@ export class EntityNode {
219
219
  addChild(node) {
220
220
  const added = array_push_if_unique(this.__children, node);
221
221
 
222
- if (added) {
223
- if (node.parent !== null) {
224
- // has a parent already, detach
225
- node.parent.removeChild(node);
226
- }
222
+ if (!added) {
223
+ // already contain this child
224
+ return false;
225
+ }
226
+
227
+ if (node.parent !== null) {
228
+ // has a parent already, detach
229
+ node.parent.removeChild(node);
230
+ }
227
231
 
228
- node.parent = this;
232
+ node.parent = this;
233
+
234
+ // live mode
235
+ if (this.__entity.isBuilt) {
236
+ // TODO attach node live
229
237
  }
230
238
 
231
- return added;
239
+ return true;
232
240
  }
233
241
 
234
242
  /**
@@ -239,13 +247,21 @@ export class EntityNode {
239
247
  removeChild(node) {
240
248
  const removed = array_remove_first(this.__children, node);
241
249
 
242
- if (removed) {
243
- assert.equal(node.parent, this, 'node is a child, but parent property points to something else instead of this node');
244
-
245
- node.parent = null;
250
+ if (!removed) {
251
+ // not present
252
+ return false;
246
253
  }
247
254
 
248
- return removed;
255
+ assert.equal(node.parent, this, 'node is a child, but parent property points to something else instead of this node');
256
+
257
+ node.parent = null;
258
+
259
+
260
+ return true;
261
+ }
262
+
263
+ get isBuilt() {
264
+ return this.__entity.isBuilt;
249
265
  }
250
266
 
251
267
  /**
@@ -278,6 +294,7 @@ export class EntityNode {
278
294
  const parent_entity_id = parent_entity_builder.entity;
279
295
  parent_entity.entity = parent_entity_id;
280
296
 
297
+ assert.ok(parent.entity.hasComponent(Transform), "parent node must have a transform but doesn't. Transform is required for attachment transform hierarchy to work correctly");
281
298
 
282
299
  const attachment = this.__safe_get_attachment();
283
300
 
@@ -306,7 +323,12 @@ export class EntityNode {
306
323
  }
307
324
 
308
325
  destroy() {
309
- assert.ok(this.__entity.isBuilt, 'Not built');
326
+ if (!this.__entity.isBuilt) {
327
+ // not built
328
+
329
+ //TODO check for dangling listeners
330
+ return;
331
+ }
310
332
 
311
333
  // remove listeners
312
334
  this.__transform.position.onChanged.remove(this.__transform_sync_down, this);
@@ -328,3 +350,9 @@ export class EntityNode {
328
350
  this.on.destroyed.send0();
329
351
  }
330
352
  }
353
+
354
+ /**
355
+ * @readonly
356
+ * @type {boolean}
357
+ */
358
+ EntityNode.prototype.isEntityNode = true;
@@ -36,6 +36,7 @@ import { IllegalStateException } from "../../../../core/fsm/exceptions/IllegalSt
36
36
  import { buildLightTexture } from "./BuildLightTexture.js";
37
37
  import { promiseSamplerHeight } from "./PromiseSamplerHeight.js";
38
38
  import { loadLegacyTerrainSplats } from "./splat/loadLegacyTerrainSplats.js";
39
+ import { WHITE_PIXEL_DATA_URL } from "../../../graphics/WHITE_PIXEL_DATA_URL.js";
39
40
 
40
41
  let idCounter = 0;
41
42
 
@@ -350,7 +351,7 @@ class Terrain {
350
351
  overlay.tileImage.onChanged.add(this.updateTileImage, this);
351
352
  }
352
353
 
353
- updateTileImage() {
354
+ async updateTileImage() {
354
355
  const tileImageURL = this.overlay.tileImage.getValue();
355
356
 
356
357
  const assetManager = this.__assetManager;
@@ -359,27 +360,25 @@ class Terrain {
359
360
  return;
360
361
  }
361
362
 
362
- assetManager.promise(tileImageURL, GameAssetType.Texture)
363
- .then((asset) => {
363
+ const asset = await assetManager.promise(tileImageURL, GameAssetType.Texture);
364
364
 
365
- if (tileImageURL !== this.overlay.tileImage.getValue()) {
366
- //url has changed, abort
367
- return;
368
- }
365
+ if (tileImageURL !== this.overlay.tileImage.getValue()) {
366
+ //url has changed, abort
367
+ return;
368
+ }
369
369
 
370
- /**
371
- *
372
- * @type {Texture}
373
- */
374
- const texture = asset.create();
370
+ /**
371
+ *
372
+ * @type {Texture}
373
+ */
374
+ const texture = asset.create();
375
375
 
376
- texture.minFilter = LinearFilter;
377
- texture.magFilter = LinearFilter;
376
+ texture.minFilter = LinearFilter;
377
+ texture.magFilter = LinearFilter;
378
378
 
379
- texture.generateMipmaps = false;
379
+ texture.generateMipmaps = false;
380
380
 
381
- this.material.uniforms.diffuseGridOverlaySprite.value = texture;
382
- });
381
+ this.material.uniforms.diffuseGridOverlaySprite.value = texture;
383
382
  }
384
383
 
385
384
  update(timeDelta) {
@@ -710,7 +709,7 @@ class Terrain {
710
709
  }
711
710
 
712
711
  /**
713
- *
712
+ * @deprecated
714
713
  * @param {AssetManager} assetManager
715
714
  */
716
715
  buildFromLegacy(assetManager) {
@@ -969,9 +968,9 @@ class Terrain {
969
968
  this.splat.fromJSON(splat);
970
969
 
971
970
  if (overlayTileImage !== undefined) {
972
-
973
- this.overlay.baseTileImage(overlayTileImage);
974
-
971
+ this.overlay.baseTileImage = overlayTileImage;
972
+ } else {
973
+ this.overlay.baseTileImage = WHITE_PIXEL_DATA_URL;
975
974
  }
976
975
 
977
976
 
@@ -234,9 +234,14 @@ export class TerrainLayers {
234
234
 
235
235
  const layerIndex = i;
236
236
 
237
- promise.then(() => {
238
- this.writeLayerDataIntoTexture(layerIndex);
239
- });
237
+ promise.then(
238
+ () => {
239
+ this.writeLayerDataIntoTexture(layerIndex);
240
+ },
241
+ (reason) => {
242
+ console.error(`Failed to load layer [${layerIndex}]"${layer.textureDiffuseURL}". Reason: ${reason}`);
243
+ }
244
+ );
240
245
 
241
246
  promises.push(promise);
242
247
  }
@@ -31,4 +31,6 @@ export class Transform {
31
31
  fromMatrix4(matrix: ArrayLike<number>): void
32
32
 
33
33
  multiplyTransforms(a: Transform, b: Transform): void
34
+
35
+ makeIdentity():void
34
36
  }
@@ -285,6 +285,12 @@ export class Transform {
285
285
  toMatrix4(result) {
286
286
  compose_matrix4_array(result, this.position, this.rotation, this.scale);
287
287
  }
288
+
289
+ makeIdentity() {
290
+ this.position.copy(Vector3.zero);
291
+ this.rotation.copy(Quaternion.identity);
292
+ this.scale.copy(Vector3.one);
293
+ }
288
294
  }
289
295
 
290
296
  /**
@@ -1,4 +1,5 @@
1
1
  import { Transform } from "../transform/Transform.js";
2
+ import { int32_to_binary_string } from "../../../core/binary/int32_to_binary_string.js";
2
3
 
3
4
  export const TransformAttachmentFlags = {
4
5
  /**
@@ -31,6 +32,18 @@ export class TransformAttachment {
31
32
  this.flags = DEFAULT_FLAGS;
32
33
  }
33
34
 
35
+ toString() {
36
+ return JSON.stringify(this.toJSON());
37
+ }
38
+
39
+ toJSON() {
40
+ return {
41
+ transform: this.transform.toJSON(),
42
+ parent: this.parent,
43
+ flags: int32_to_binary_string(this.flags)
44
+ };
45
+ }
46
+
34
47
  get immediate() {
35
48
  return this.getFlag(TransformAttachmentFlags.Immediate);
36
49
  }
@@ -1,6 +1,13 @@
1
1
  import { System } from "../System.js";
2
2
  import { TransformAttachment, TransformAttachmentFlags } from "./TransformAttachment.js";
3
3
  import { Transform } from "../transform/Transform.js";
4
+ import { min2 } from "../../../core/math/min2.js";
5
+
6
+ /**
7
+ * @readonly
8
+ * @type {number}
9
+ */
10
+ const QUEUE_ITERATION_COUNT = 32;
4
11
 
5
12
  class UpdateContext {
6
13
  constructor() {
@@ -27,22 +34,63 @@ class UpdateContext {
27
34
  * @type {Transform|null}
28
35
  */
29
36
  this.parent_transform = null;
37
+
38
+ /**
39
+ *
40
+ * @type {EntityComponentDataset|null}
41
+ */
42
+ this.ecd = null;
43
+ }
44
+
45
+ toString() {
46
+ return `UpdateContext{ attachment:${this.attachment}, entity:${this.entity} }`;
30
47
  }
31
48
 
32
49
  update() {
33
50
  this.transform.multiplyTransforms(this.parent_transform, this.attachment.transform);
34
51
  }
35
52
 
53
+ /**
54
+ *
55
+ * @returns {boolean}
56
+ */
57
+ bind_parent() {
58
+
59
+ const parent_transform = this.ecd.getComponent(this.attachment.parent, Transform);
60
+
61
+ if (parent_transform === undefined) {
62
+ return false;
63
+ }
64
+
65
+ this.parent_transform = parent_transform;
66
+
67
+ return true;
68
+ }
69
+
36
70
  link() {
37
- this.parent_transform.position.onChanged.add(this.update, this);
38
- this.parent_transform.rotation.onChanged.add(this.update, this);
39
- this.parent_transform.scale.onChanged.add(this.update, this);
71
+ const t_parent = this.parent_transform;
72
+
73
+ t_parent.position.onChanged.add(this.update, this);
74
+ t_parent.rotation.onChanged.add(this.update, this);
75
+ t_parent.scale.onChanged.add(this.update, this);
76
+
77
+ const t_attachment = this.attachment.transform;
78
+ t_attachment.position.onChanged.add(this.update,this);
79
+ t_attachment.rotation.onChanged.add(this.update,this);
80
+ t_attachment.scale.onChanged.add(this.update,this);
40
81
  }
41
82
 
42
83
  unlink() {
43
- this.parent_transform.position.onChanged.remove(this.update, this);
44
- this.parent_transform.rotation.onChanged.remove(this.update, this);
45
- this.parent_transform.scale.onChanged.remove(this.update, this);
84
+ const transform = this.parent_transform;
85
+
86
+ transform.position.onChanged.remove(this.update, this);
87
+ transform.rotation.onChanged.remove(this.update, this);
88
+ transform.scale.onChanged.remove(this.update, this);
89
+
90
+ const t_attachment = this.attachment.transform;
91
+ t_attachment.position.onChanged.remove(this.update,this);
92
+ t_attachment.rotation.onChanged.remove(this.update,this);
93
+ t_attachment.scale.onChanged.remove(this.update,this);
46
94
  }
47
95
  }
48
96
 
@@ -58,6 +106,54 @@ export class TransformAttachmentSystem extends System {
58
106
  * @private
59
107
  */
60
108
  this.__contexts = [];
109
+
110
+ /**
111
+ *
112
+ * @type {UpdateContext[]}
113
+ * @private
114
+ */
115
+ this.__queue = [];
116
+ this.__queue_size = 0;
117
+ this.__queue_cursor = 0;
118
+ }
119
+
120
+ /**
121
+ *
122
+ * @param {UpdateContext} ctx
123
+ * @private
124
+ */
125
+ __finalize_link(ctx) {
126
+
127
+ ctx.link();
128
+
129
+ this.__contexts[ctx.entity] = ctx;
130
+
131
+ if ((ctx.attachment.flags & TransformAttachmentFlags.Immediate) !== 0) {
132
+ ctx.update();
133
+ }
134
+ }
135
+
136
+ /**
137
+ *
138
+ * @param {UpdateContext} ctx
139
+ * @private
140
+ */
141
+ __enqueue(ctx) {
142
+ this.__queue[this.__queue_size++] = ctx;
143
+ }
144
+
145
+ __dequeue_entity(entity) {
146
+ for (let i = 0; i < this.__queue_size; i++) {
147
+ const ctx = this.__queue[i];
148
+
149
+ if (ctx.entity === entity) {
150
+ this.__queue.splice(i, 1);
151
+ this.__queue_size--;
152
+ return true;
153
+ }
154
+ }
155
+
156
+ return false;
61
157
  }
62
158
 
63
159
  /**
@@ -68,22 +164,23 @@ export class TransformAttachmentSystem extends System {
68
164
  */
69
165
  link(attachment, transform, entity) {
70
166
  const ctx = new UpdateContext();
71
- ctx.system = this;
72
167
 
73
168
  ctx.attachment = attachment;
74
169
  ctx.transform = transform;
75
170
  ctx.entity = entity;
76
171
 
77
172
 
78
- ctx.parent_transform = this.entityManager.dataset.getComponent(attachment.parent, Transform);
173
+ const ecd = this.entityManager.dataset;
79
174
 
80
- ctx.link();
81
-
82
- this.__contexts[entity] = ctx;
175
+ ctx.ecd = ecd;
83
176
 
84
- if ((attachment.flags & TransformAttachmentFlags.Immediate) !== 0) {
85
- ctx.update();
177
+ if (ctx.bind_parent()) {
178
+ this.__finalize_link(ctx);
179
+ } else {
180
+ // failed to bind parent, queue up instead
181
+ this.__enqueue(ctx);
86
182
  }
183
+
87
184
  }
88
185
 
89
186
  /**
@@ -93,12 +190,37 @@ export class TransformAttachmentSystem extends System {
93
190
  * @param {number} entity
94
191
  */
95
192
  unlink(attachment, transform, entity) {
193
+ const ctx = this.__contexts[entity];
96
194
 
195
+ if (ctx !== undefined) {
97
196
 
98
- const ctx = this.__contexts[entity];
197
+ delete this.__contexts[entity];
99
198
 
100
- delete this.__contexts[entity];
199
+ ctx.unlink();
200
+
201
+ } else {
202
+ // no context found, check the queue
203
+ this.__dequeue_entity(entity);
204
+ }
101
205
 
102
- ctx.unlink();
206
+ }
207
+
208
+ update(timeDelta) {
209
+ const step_count = min2(this.__queue_size, QUEUE_ITERATION_COUNT);
210
+ for (let i = 0; i < step_count; i++) {
211
+ const index = this.__queue_cursor % this.__queue_size;
212
+
213
+ const ctx = this.__queue[index];
214
+
215
+ if (ctx.bind_parent()) {
216
+ this.__finalize_link(ctx);
217
+
218
+ this.__queue.splice(index, 1);
219
+ this.__queue_size--;
220
+
221
+ } else {
222
+ this.__queue_cursor++;
223
+ }
224
+ }
103
225
  }
104
226
  }