@woosh/meep-engine 2.36.30 → 2.37.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.
@@ -1,9 +1,9 @@
1
1
  export class Action<CTX> {
2
2
  computeByteSize(): number
3
3
 
4
- apply(context: CTX): void
4
+ apply(context: CTX): Promise<void>
5
5
 
6
- revert(context: CTX): void
6
+ revert(context: CTX): Promise<void>
7
7
 
8
8
  get isAction(): boolean
9
9
  }
@@ -21,16 +21,16 @@ export class Action {
21
21
  * Apply action
22
22
  * @param {CTX} context
23
23
  */
24
- apply(context) {
25
-
24
+ async apply(context) {
25
+ throw new Error('Not Implemented. Needs to be overridden in subclass');
26
26
  }
27
27
 
28
28
  /**
29
29
  * Revert action, restoring state to just before this action was applied
30
30
  * @param {CTX} context
31
31
  */
32
- revert(context) {
33
-
32
+ async revert(context) {
33
+ throw new Error('Not Implemented. Needs to be overridden in subclass');
34
34
  }
35
35
  }
36
36
 
@@ -41,24 +41,24 @@ export class ActionGroup extends Action {
41
41
  return result;
42
42
  }
43
43
 
44
- apply(context) {
44
+ async apply(context) {
45
45
  const members = this.members;
46
46
  const n = members.length;
47
47
  for (let i = 0; i < n; i++) {
48
48
  const action = members[i];
49
49
 
50
- action.apply(context);
50
+ await action.apply(context);
51
51
  }
52
52
  }
53
53
 
54
- revert(context) {
54
+ async revert(context) {
55
55
  const members = this.members;
56
56
  const n = members.length;
57
57
 
58
58
  for (let i = n - 1; i >= 0; i--) {
59
59
  const action = members[i];
60
60
 
61
- action.revert(context);
61
+ await action.revert(context);
62
62
  }
63
63
  }
64
64
  }
@@ -8,20 +8,20 @@ export class ActionProcessor<CTX, MD> {
8
8
 
9
9
  getMarkCount(): number
10
10
 
11
- getMarkDescription(index: number): MD
11
+ getMarkDescription(index: number): Readonly<MD>
12
12
 
13
13
  readonly onMarkAdded: Signal<MD>
14
14
  readonly onMarkRemoved: Signal<MD>
15
15
 
16
- undo(): void
16
+ undo(): Promise<void>
17
17
 
18
- redo(): void
18
+ redo(): Promise<void>
19
19
 
20
- navigateTo(mark: MD): boolean
20
+ navigateTo(mark: MD): Promise<boolean>
21
21
 
22
- doMany(actions: Action<CTX>[]): void
22
+ doMany(actions: Action<CTX>[]): Promise<void>
23
23
 
24
- do(action: Action<CTX>): this
24
+ do(action: Action<CTX>): Promise<void>
25
25
 
26
26
  clear(): void
27
27
  }
@@ -26,11 +26,13 @@ export class ActionProcessor {
26
26
  * @type {List<Action>}
27
27
  */
28
28
  this.history = new List();
29
+
29
30
  /**
30
31
  *
31
32
  * @type {C}
32
33
  */
33
34
  this.context = context;
35
+
34
36
  /**
35
37
  *
36
38
  * @type {Array<Mark<M>>}
@@ -53,6 +55,7 @@ export class ActionProcessor {
53
55
  * @type {number}
54
56
  */
55
57
  this.historyMarkLimit = 10000;
58
+
56
59
  /**
57
60
  * Maximum amount of memory allowed for the history
58
61
  * @type {number}
@@ -64,11 +67,19 @@ export class ActionProcessor {
64
67
  * @type {Signal<M> | Signal}
65
68
  */
66
69
  this.onMarkAdded = new Signal();
70
+
67
71
  /**
68
72
  *
69
73
  * @type {Signal<M> | Signal}
70
74
  */
71
75
  this.onMarkRemoved = new Signal();
76
+
77
+ /**
78
+ *
79
+ * @type {Promise<never>}
80
+ * @private
81
+ */
82
+ this.__last_promise = Promise.resolve();
72
83
  }
73
84
 
74
85
  /**
@@ -153,7 +164,9 @@ export class ActionProcessor {
153
164
  * Insert a mark into the timeline, all actions between marks are grouped logically for UNDO and REDO mechanism
154
165
  * @param {D} description
155
166
  */
156
- mark(description) {
167
+ async mark(description) {
168
+ await this.__barrier();
169
+
157
170
  const marks = this.marks;
158
171
  const numMarks = marks.length;
159
172
 
@@ -241,7 +254,25 @@ export class ActionProcessor {
241
254
  * @returns {boolean} true on success, false otherwise (e.g. mark not found)
242
255
  * @throws {Error} when mark is non-unique
243
256
  */
244
- navigateTo(mark_descriptor) {
257
+ async navigateTo(mark_descriptor) {
258
+ await this.__barrier();
259
+
260
+ const p = this.__unsafe_navigateTo(mark_descriptor);
261
+
262
+ this.__last_promise = p;
263
+
264
+ return await p;
265
+ }
266
+
267
+ /**
268
+ * Navigate to a specific mark back or forward
269
+ * @param {M} mark_descriptor specific mark description
270
+ * @returns {boolean} true on success, false otherwise (e.g. mark not found)
271
+ * @throws {Error} when mark is non-unique
272
+ * @private
273
+ */
274
+ async __unsafe_navigateTo(mark_descriptor) {
275
+
245
276
  // find all matching marks
246
277
  const matching_marks = this.__find_mark_indices_by_description(mark_descriptor);
247
278
 
@@ -270,7 +301,7 @@ export class ActionProcessor {
270
301
 
271
302
  while (target > this.cursor) {
272
303
  // navigate forward
273
- this.redo();
304
+ await this.__unsafe_redo();
274
305
 
275
306
  if (temp_cursor === this.cursor) {
276
307
  // no cursor change
@@ -284,7 +315,7 @@ export class ActionProcessor {
284
315
 
285
316
  while (target < this.cursor) {
286
317
  // navigate backwards
287
- this.undo();
318
+ await this.__unsafe_undo();
288
319
 
289
320
  if (temp_cursor === this.cursor) {
290
321
  // no cursor change
@@ -299,7 +330,22 @@ export class ActionProcessor {
299
330
  return true;
300
331
  }
301
332
 
302
- undo() {
333
+ async undo() {
334
+ await this.__barrier();
335
+
336
+ const p = this.__unsafe_undo();
337
+
338
+ this.__last_promise = p;
339
+
340
+ await p;
341
+ }
342
+
343
+ /**
344
+ *
345
+ * @returns {Promise<void>}
346
+ * @private
347
+ */
348
+ async __unsafe_undo() {
303
349
  let markAddress = 0;
304
350
  /**
305
351
  *
@@ -327,7 +373,8 @@ export class ActionProcessor {
327
373
  while (this.cursor > markAddress && this.cursor > 0) {
328
374
  this.cursor--;
329
375
  const action = this.history.get(this.cursor);
330
- action.revert(this.context);
376
+
377
+ await action.revert(this.context);
331
378
  }
332
379
 
333
380
  if (mark !== null) {
@@ -338,9 +385,24 @@ export class ActionProcessor {
338
385
  /**
339
386
  * redo previous sequence of actions on the timeline, up to the next mark
340
387
  */
341
- redo() {
388
+ async redo() {
389
+ await this.__barrier();
390
+
391
+ const p = this.__unsafe_redo();
342
392
 
393
+ this.__last_promise = p;
394
+
395
+ await p;
396
+ }
397
+
398
+ /**
399
+ *
400
+ * @returns {Promise<void>}
401
+ * @private
402
+ */
403
+ async __unsafe_redo() {
343
404
  let mark = this.history.length;
405
+
344
406
  //find mark
345
407
  const marks = this.marks;
346
408
 
@@ -358,7 +420,7 @@ export class ActionProcessor {
358
420
  while (this.cursor < mark) {
359
421
  const action = this.history.get(this.cursor);
360
422
 
361
- action.apply(this.context);
423
+ await action.apply(this.context);
362
424
 
363
425
  this.historyMemoryUsage += action.computeByteSize();
364
426
 
@@ -370,37 +432,77 @@ export class ActionProcessor {
370
432
  *
371
433
  * @param {Action[]} actions
372
434
  */
373
- doMany(actions) {
374
- const n = actions.length;
435
+ async doMany(actions) {
436
+ await this.__barrier();
375
437
 
376
- for (let i = 0; i < n; i++) {
438
+ const p = new Promise(async (resolve, reject) => {
377
439
 
378
- const action = actions[i];
440
+ const n = actions.length;
379
441
 
380
- this.do(action);
442
+ for (let i = 0; i < n; i++) {
381
443
 
382
- }
444
+ const action = actions[i];
445
+
446
+ await this.__unsafe_do(action);
447
+
448
+ }
449
+
450
+ resolve();
451
+ });
452
+
453
+ this.__last_promise = p;
454
+
455
+ await p;
383
456
  }
384
457
 
385
458
  /**
386
459
  * Execute a given action and add it to the timeline
387
460
  * @param {Action} action
388
- * @returns {ActionProcessor} this instance, useful for call chaining
389
461
  */
390
- do(action) {
391
- action.apply(this.context);
462
+ async do(action) {
463
+ await this.__barrier();
464
+
465
+ const p = this.__unsafe_do(action);
466
+
467
+ this.__last_promise = p;
468
+
469
+ await p;
470
+ }
471
+
472
+ /**
473
+ *
474
+ * @param {Action} action
475
+ * @returns {Promise<void>}
476
+ * @private
477
+ */
478
+ async __unsafe_do(action) {
479
+ await action.apply(this.context);
392
480
 
393
481
  this.historyMemoryUsage += action.computeByteSize();
394
482
 
483
+ const history = this.history;
484
+
395
485
  //doing action invalidates "future" branch of history, we need to clear all actions beyond cursor
396
- if (this.cursor < this.history.length) {
397
- this.history.crop(0, this.cursor);
486
+ if (this.cursor < history.length) {
487
+ history.crop(0, this.cursor);
398
488
  // TODO clear future marks
399
489
  }
400
490
 
401
- this.history.add(action);
491
+ history.add(action);
492
+
402
493
  this.cursor++;
494
+ }
403
495
 
404
- return this;
496
+ /**
497
+ * Await for synchronization barrier
498
+ * @returns {Promise<void>}
499
+ * @private
500
+ */
501
+ async __barrier() {
502
+ try {
503
+ await this.__last_promise;
504
+ } catch (e) {
505
+ // ignore
506
+ }
405
507
  }
406
508
  }
@@ -8,52 +8,57 @@ class DummyContext {
8
8
  }
9
9
 
10
10
  class DummyAction extends Action {
11
- apply(context) {
11
+ async apply(context) {
12
12
  // do nothing
13
13
  context.value++;
14
14
  }
15
15
 
16
- revert(context) {
16
+ async revert(context) {
17
17
  context.value--;
18
18
  }
19
19
  }
20
20
 
21
- test('navigateTo backwards', () => {
21
+ test('navigateTo backwards', async () => {
22
22
  const ctx = new DummyContext();
23
23
  const ap = new ActionProcessor(ctx);
24
24
 
25
25
  ap.mark('a');
26
- ap.do(new DummyAction());
26
+ await ap.do(new DummyAction());
27
27
  ap.mark('b');
28
- ap.do(new DummyAction());
28
+ await ap.do(new DummyAction());
29
29
 
30
- expect(ap.navigateTo('a')).toBe(true);
30
+ const nav_result = await ap.navigateTo('a');
31
+
32
+ expect(nav_result).toBe(true);
31
33
 
32
34
  expect(ctx.value).toBe(1);
33
35
  });
34
36
 
35
- test('navigateTo forward', () => {
37
+ test('navigateTo forward', async () => {
36
38
  const ctx = new DummyContext();
37
39
  const ap = new ActionProcessor(ctx);
38
40
 
39
41
  ap.mark('a');
40
- ap.do(new DummyAction());
42
+ await ap.do(new DummyAction());
41
43
  ap.mark('b');
42
- ap.do(new DummyAction());
44
+ await ap.do(new DummyAction());
43
45
  ap.mark('c');
44
46
 
45
- ap.undo();
46
- ap.undo();
47
- ap.undo();
47
+ await ap.undo();
48
+ await ap.undo();
49
+ await ap.undo();
50
+
51
+ const nav_result = await ap.navigateTo('c');
48
52
 
49
- expect(ap.navigateTo('c')).toBe(true);
53
+ expect(nav_result).toBe(true);
50
54
 
51
55
  expect(ctx.value).toBe(2);
52
56
  });
53
57
 
54
- test('navigateTo non-existent on empty', () => {
58
+ test('navigateTo non-existent on empty', async () => {
55
59
  const ctx = new DummyContext();
56
60
  const ap = new ActionProcessor(ctx);
57
61
 
58
- expect(ap.navigateTo('a')).toBe(false);
62
+ const nav_result = await ap.navigateTo('a');
63
+ expect(nav_result).toBe(false);
59
64
  });
@@ -15,7 +15,7 @@ class ComponentAddAction extends Action {
15
15
  this.dataset = null;
16
16
  }
17
17
 
18
- apply(editor) {
18
+ async apply(editor) {
19
19
  /**
20
20
  * @type {EntityManager}
21
21
  */
@@ -38,7 +38,7 @@ class ComponentAddAction extends Action {
38
38
  dataset.addComponentToEntity(this.entity, this.component);
39
39
  }
40
40
 
41
- revert(editor) {
41
+ async revert(editor) {
42
42
  const clazz = this.component.constructor;
43
43
  this.dataset.removeComponentFromEntity(this.entity, clazz);
44
44
  }
@@ -13,7 +13,7 @@ class ComponentRemoveAction extends Action {
13
13
  this.component = null;
14
14
  }
15
15
 
16
- apply(editor) {
16
+ async apply(editor) {
17
17
  /**
18
18
  * @type {Engine}
19
19
  */
@@ -29,7 +29,7 @@ class ComponentRemoveAction extends Action {
29
29
  this.component = ecd.removeComponentFromEntity(this.entity, this.componentType);
30
30
  }
31
31
 
32
- revert(editor) {
32
+ async revert(editor) {
33
33
  const em = editor.engine.entityManager;
34
34
  em.addComponentToEntity(this.entity, this.component);
35
35
  }
@@ -19,7 +19,7 @@ class EntityCreateAction extends Action {
19
19
  this.ecd = null;
20
20
  }
21
21
 
22
- apply(editor) {
22
+ async apply(editor) {
23
23
  if (this.ecd === null) {
24
24
  /**
25
25
  * @type {EntityManager}
@@ -39,7 +39,7 @@ class EntityCreateAction extends Action {
39
39
  }
40
40
  }
41
41
 
42
- revert(editor) {
42
+ async revert(editor) {
43
43
  this.ecd.removeEntity(this.entity);
44
44
  }
45
45
  }
@@ -18,7 +18,7 @@ class EntityRemoveAction extends Action {
18
18
  *
19
19
  * @param {Editor} editor
20
20
  */
21
- apply(editor) {
21
+ async apply(editor) {
22
22
  const em = editor.engine.entityManager;
23
23
 
24
24
  const ecd = em.dataset;
@@ -32,7 +32,7 @@ class EntityRemoveAction extends Action {
32
32
  *
33
33
  * @param {Editor} editor
34
34
  */
35
- revert(editor) {
35
+ async revert(editor) {
36
36
  const em = editor.engine.entityManager;
37
37
 
38
38
  const ecd = em.dataset;
@@ -22,7 +22,7 @@ class PaintTerrainOverlayAction extends Action {
22
22
  this.oldColor = new Vector4();
23
23
  }
24
24
 
25
- apply(editor) {
25
+ async apply(editor) {
26
26
  const terrain = obtainTerrain(editor.engine.entityManager.dataset);
27
27
  const overlay = this.overlay = terrain.overlay;
28
28
 
@@ -33,7 +33,7 @@ class PaintTerrainOverlayAction extends Action {
33
33
  overlay.paintPoint(this.x, this.y, this.color);
34
34
  }
35
35
 
36
- revert(editor) {
36
+ async revert(editor) {
37
37
  const overlay = this.overlay;
38
38
 
39
39
  overlay.clearPoint(this.x, this.y);
@@ -109,7 +109,7 @@ export class PatchTerrainHeightAction extends Action {
109
109
  terrain.heightTexture.needsUpdate = true;
110
110
  }
111
111
 
112
- apply(context) {
112
+ async apply(context) {
113
113
 
114
114
  /**
115
115
  *
@@ -135,7 +135,7 @@ export class PatchTerrainHeightAction extends Action {
135
135
  this.updateTerrain();
136
136
  }
137
137
 
138
- revert(context) {
138
+ async revert(context) {
139
139
 
140
140
  /**
141
141
  *
@@ -99,7 +99,7 @@ export class PatchTerrainTextureAction extends Action {
99
99
 
100
100
  }
101
101
 
102
- apply(context) {
102
+ async apply(context) {
103
103
  /**
104
104
  *
105
105
  * @type {Terrain}
@@ -126,7 +126,7 @@ export class PatchTerrainTextureAction extends Action {
126
126
  this.updateTerrain();
127
127
  }
128
128
 
129
- revert(context) {
129
+ async revert(context) {
130
130
  /**
131
131
  *
132
132
  * @type {Terrain}
@@ -20,7 +20,7 @@ class SelectionAddAction extends Action {
20
20
  this.entities = entities;
21
21
  }
22
22
 
23
- apply(editor) {
23
+ async apply(editor) {
24
24
  this.oldState = editor.selection.clone();
25
25
 
26
26
  const selection = editor.selection;
@@ -28,7 +28,7 @@ class SelectionAddAction extends Action {
28
28
  selection.addAllUnique(this.entities);
29
29
  }
30
30
 
31
- revert(editor) {
31
+ async revert(editor) {
32
32
  editor.selection.copy(this.oldState);
33
33
  }
34
34
  }
@@ -9,12 +9,12 @@ class SelectionClearAction extends Action {
9
9
  this.oldState = null;
10
10
  }
11
11
 
12
- apply(editor) {
12
+ async apply(editor) {
13
13
  this.oldState = editor.selection.clone();
14
14
  editor.selection.reset();
15
15
  }
16
16
 
17
- revert(editor) {
17
+ async revert(editor) {
18
18
  editor.selection.copy(this.oldState);
19
19
  }
20
20
  }
@@ -10,7 +10,7 @@ class SelectionRemoveAction extends Action {
10
10
  this.entities = entities;
11
11
  }
12
12
 
13
- apply(editor) {
13
+ async apply(editor) {
14
14
  const selection = editor.selection;
15
15
  this.oldState = selection.clone();
16
16
  for (let i = 0; i < this.entities.length; i++) {
@@ -22,7 +22,7 @@ class SelectionRemoveAction extends Action {
22
22
  }
23
23
  }
24
24
 
25
- revert(editor) {
25
+ async revert(editor) {
26
26
  editor.selection.copy(this.oldState);
27
27
  }
28
28
  }
@@ -24,7 +24,7 @@ class TransformModifyAction extends Action {
24
24
  *
25
25
  * @param {Editor} editor
26
26
  */
27
- apply(editor) {
27
+ async apply(editor) {
28
28
  const entityManager = editor.engine.entityManager;
29
29
 
30
30
  const component = entityManager.getComponent(this.entity, Transform);
@@ -34,7 +34,7 @@ class TransformModifyAction extends Action {
34
34
  component.copy(this.modified);
35
35
  }
36
36
 
37
- revert(editor) {
37
+ async revert(editor) {
38
38
  const entityManager = editor.engine.entityManager;
39
39
 
40
40
  const component = entityManager.getComponent(this.entity, Transform);
@@ -15,7 +15,7 @@ class WriteGridValueAction extends Action {
15
15
  this.oldValue = null;
16
16
  }
17
17
 
18
- apply(editor) {
18
+ async apply(editor) {
19
19
  const obstacle = editor.engine.entityManager.getComponent(this.entity, GridObstacle);
20
20
  this.obstacle = obstacle;
21
21
 
@@ -26,7 +26,7 @@ class WriteGridValueAction extends Action {
26
26
  gridField.pointSet(this.x, this.y, this.value);
27
27
  }
28
28
 
29
- revert(editor) {
29
+ async revert(editor) {
30
30
  const gridField = obstacle2grid(this.obstacle);
31
31
 
32
32
  gridField.pointSet(this.x, this.y, this.oldValue);
@@ -199,9 +199,10 @@ function prepareMeshLibrary(editor) {
199
199
 
200
200
  const transform = new Transform();
201
201
 
202
- editor.actions.mark('New Mesh Placed from Library');
202
+ const actions = editor.actions;
203
+ actions.mark('New Mesh Placed from Library');
203
204
  const entityCreateAction = new EntityCreateAction();
204
- editor.actions.do(entityCreateAction);
205
+ actions.do(entityCreateAction);
205
206
 
206
207
  terrain.raycast(source, direction, function (hit, normal, geometry) {
207
208
  function handleMeshSetEvent() {
@@ -217,7 +218,7 @@ function prepareMeshLibrary(editor) {
217
218
  transform.position.add(offset);
218
219
 
219
220
  //remove listener
220
- entityManager.removeEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
221
+ ecd.removeEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
221
222
  }
222
223
 
223
224
  if (hit !== null) {
@@ -229,7 +230,7 @@ function prepareMeshLibrary(editor) {
229
230
 
230
231
 
231
232
  //wait for mesh to load
232
- entityManager.addEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
233
+ ecd.addEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
233
234
  }
234
235
  }, noop);
235
236
 
@@ -238,12 +239,13 @@ function prepareMeshLibrary(editor) {
238
239
  mesh.url = url;
239
240
 
240
241
 
241
- editor.actions
242
- .do(new ComponentAddAction(entityCreateAction.entity, transform))
243
- .do(new ComponentAddAction(entityCreateAction.entity, mesh))
242
+ actions.doMany([
243
+ new ComponentAddAction(entityCreateAction.entity, transform),
244
+ new ComponentAddAction(entityCreateAction.entity, mesh),
244
245
  //automatically select newly placed object
245
- .do(new SelectionClearAction())
246
- .do(new SelectionAddAction([entityCreateAction.entity]));
246
+ new SelectionClearAction(),
247
+ new SelectionAddAction([entityCreateAction.entity])
248
+ ]);
247
249
 
248
250
  }
249
251
 
@@ -15,7 +15,8 @@ export function three_object_to_entity_composition(root) {
15
15
  // copy object transform
16
16
  node_transform.position.copy(root.position);
17
17
  node_transform.scale.copy(root.scale);
18
- node_transform.rotation.copy(root.quaternion);
18
+ const euler = root.rotation;
19
+ node_transform.rotation.fromEulerAngles(euler.x, euler.y, euler.z);
19
20
 
20
21
  const entity = node.entity;
21
22
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.36.30",
8
+ "version": "2.37.2",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",