@makefully/platypus 4.1.10 → 4.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/platypus",
3
- "version": "4.1.10",
3
+ "version": "4.1.13",
4
4
  "description": "2D tile-based HTML5 game engine with a component-based entity model",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -73,7 +73,7 @@
73
73
  "webpack-dev-server": "^5.2.2"
74
74
  },
75
75
  "peerDependencies": {
76
- "@esotericsoftware/spine-pixi-v8": "^4.3.7",
76
+ "@esotericsoftware/spine-pixi-v8": "^4.3.13",
77
77
  "@pixi/sound": "github:Makefully-Studios/sound#feature/media-file",
78
78
  "@tweenjs/tween.js": "^23.1.1",
79
79
  "pako": "^2.1.0",
@@ -7,6 +7,7 @@
7
7
  /* global platypus, setTimeout */
8
8
  import Data from './Data.js';
9
9
  import DataMap from './DataMap.js';
10
+ import PIXIAnimation from './PIXIAnimation.js';
10
11
  import {Assets} from 'pixi.js';
11
12
  import {arrayCache} from './utils/array.js';
12
13
 
@@ -58,6 +59,7 @@ export default class AssetManager {
58
59
  asset.src = '';
59
60
  }
60
61
  assets.delete(alias);
62
+ PIXIAnimation.invalidateTextureSources(alias);
61
63
  }
62
64
 
63
65
  return !counts[alias];
package/src/Messenger.js CHANGED
@@ -1,9 +1,8 @@
1
- /* global platypus, window */
1
+ /* global performance, platypus */
2
2
  import EventHandlerList from './EventHandlerList.js';
3
3
  import {arrayCache, greenSlice} from './utils/array.js';
4
4
 
5
5
  const
6
- getPerfTools = () => typeof performance !== 'undefined' && performance.mark && performance.measure ? performance : null,
7
6
  runBoth = function (f1, f2) {
8
7
  return function () {
9
8
  f1.apply(this, arguments);
@@ -57,8 +56,6 @@ class Messenger {
57
56
 
58
57
  // Debug logging.
59
58
  if (debugLogging || this.debug) {
60
- const perfTools = getPerfTools();
61
-
62
59
  for (let i = 0; i < this.loopCheck.length; i++) {
63
60
  if (this.loopCheck[i] === event) {
64
61
  debugCount += 1;
@@ -72,17 +69,13 @@ class Messenger {
72
69
 
73
70
  this.loopCheck.push(event);
74
71
 
75
- if (perfTools) {
76
- perfTools.mark("a");
77
- }
72
+ performance.mark("a");
78
73
 
79
74
  const
80
75
  count = triggerEvent.apply(this, arguments);
81
76
 
82
- if (perfTools) {
83
- perfTools.mark("b");
84
- perfTools.measure(this.type + ":" + event, 'a', 'b');
85
- }
77
+ performance.mark("b");
78
+ performance.measure(this.type + ":" + event, 'a', 'b');
86
79
  this.loopCheck.length = this.loopCheck.length - 1;
87
80
  if (debugLogging) {
88
81
  if (count) {
@@ -341,7 +334,14 @@ class Messenger {
341
334
  key = methods[i];
342
335
 
343
336
  if (key !== 'constructor') {
344
- if (toProto[key]) {
337
+ const
338
+ descriptor = Object.getOwnPropertyDescriptor(fromProto, key);
339
+
340
+ // Preserve accessors (e.g. `destroyed`); assigning fromProto[key] would
341
+ // evaluate getters and copy a stale value instead.
342
+ if (descriptor.get || descriptor.set) {
343
+ Object.defineProperty(toProto, key, descriptor);
344
+ } else if (Object.prototype.hasOwnProperty.call(toProto, key) || toProto[key]) {
345
345
  toProto[key] = runBoth(toProto[key], fromProto[key]);
346
346
  } else {
347
347
  toProto[key] = fromProto[key];
@@ -4,15 +4,74 @@ import {arrayCache, greenSlice} from './utils/array.js';
4
4
  import Data from './Data.js';
5
5
 
6
6
  const
7
- MAX_KEY_LENGTH_PER_IMAGE = 128,
8
7
  animationCache = {},
9
8
  textureSourceCache = {},
10
9
  doNothing = function () {},
11
10
  emptyFrame = Texture.EMPTY,
12
- regex = /[\[\]{},-]/g,
11
+ imageBasename = /([\w-\.]+)\.(\w+)$/,
12
+ hashString = function (value) {
13
+ const
14
+ str = typeof value === 'string' ? value : JSON.stringify(value);
15
+ let hash = 5381;
16
+
17
+ for (let i = 0; i < str.length; i++) {
18
+ hash = ((hash << 5) + hash) ^ str.charCodeAt(i);
19
+ }
20
+
21
+ return (hash >>> 0).toString(36);
22
+ },
23
+ getTextureCacheKey = function (path) {
24
+ return platypus.assetCache?.getFileId ? platypus.assetCache.getFileId(path) : path;
25
+ },
26
+ resolveTextureAsset = function (path) {
27
+ const
28
+ assetCache = platypus.assetCache;
29
+
30
+ if (!assetCache) {
31
+ return null;
32
+ }
33
+
34
+ let asset = assetCache.get(path);
35
+
36
+ if (asset) {
37
+ return asset;
38
+ }
39
+
40
+ const
41
+ basename = typeof path === 'string' ? path.match(imageBasename) : null;
42
+
43
+ if (basename) {
44
+ asset = assetCache.get(basename[1]);
45
+
46
+ if (asset) {
47
+ return asset;
48
+ }
49
+ }
50
+
51
+ return null;
52
+ },
53
+ getTextureSourceAliases = function (alias) {
54
+ const
55
+ aliases = arrayCache.setUp(alias),
56
+ basename = typeof alias === 'string' ? alias.match(imageBasename) : null;
57
+
58
+ if (platypus.assetCache?.getFileId) {
59
+ const
60
+ fileId = platypus.assetCache.getFileId(alias);
61
+
62
+ if (fileId !== alias) {
63
+ aliases.push(fileId);
64
+ }
65
+ }
66
+
67
+ if (basename) {
68
+ aliases.push(basename[1]);
69
+ }
70
+
71
+ return aliases;
72
+ },
13
73
  getTextureSources = function (images) {
14
74
  const
15
- assetCache = platypus.assetCache,
16
75
  bts = arrayCache.setUp();
17
76
 
18
77
  for (let i = 0; i < images.length; i++) {
@@ -20,17 +79,21 @@ const
20
79
  path = images[i];
21
80
 
22
81
  if (typeof path === 'string') {
23
- if (!textureSourceCache[path]) {
24
- const
25
- asset = assetCache.get(path);
82
+ const
83
+ cacheKey = getTextureCacheKey(path),
84
+ asset = resolveTextureAsset(path);
26
85
 
27
- if (!asset) {
28
- platypus.debug.warn(`PIXIAnimation: "${path}" is not a loaded asset.`);
29
- break;
30
- }
31
- textureSourceCache[path] = asset.source;
86
+ if (!asset) {
87
+ delete textureSourceCache[cacheKey];
88
+ platypus.debug.warn(`PIXIAnimation: "${path}" is not a loaded asset.`);
89
+ break;
90
+ }
91
+
92
+ if (textureSourceCache[cacheKey] !== asset.source) {
93
+ textureSourceCache[cacheKey] = asset.source;
32
94
  }
33
- bts.push(textureSourceCache[path]);
95
+
96
+ bts.push(textureSourceCache[cacheKey]);
34
97
  } else {
35
98
  bts.push(new TextureSource(path));
36
99
  }
@@ -49,9 +112,7 @@ const
49
112
  }
50
113
  }
51
114
 
52
- spriteSheet.id = JSON.stringify(spriteSheet).replace(regex, '');
53
-
54
- return spriteSheet.id;
115
+ return `ss-${hashString(spriteSheet)}`;
55
116
  },
56
117
  getDefaultAnimation = function (length, textures) {
57
118
  const
@@ -397,6 +458,29 @@ const
397
458
  };
398
459
  }
399
460
 
461
+ /**
462
+ * Removes cached texture sources for an unloaded asset alias.
463
+ *
464
+ * @method platypus.PIXIAnimation.invalidateTextureSources
465
+ * @param {*} alias Asset alias or path used when the texture was cached.
466
+ */
467
+ static invalidateTextureSources (alias) {
468
+ const
469
+ aliases = getTextureSourceAliases(alias),
470
+ cacheKey = getTextureCacheKey(alias);
471
+ let i = aliases.length;
472
+
473
+ if (aliases.indexOf(cacheKey) === -1) {
474
+ aliases.push(cacheKey);
475
+ }
476
+
477
+ while (i--) {
478
+ delete textureSourceCache[aliases[i]];
479
+ }
480
+
481
+ arrayCache.recycle(aliases);
482
+ }
483
+
400
484
  /**
401
485
  * This method formats a provided value into a valid PIXIAnimation Sprite Sheet. This includes accepting the EaselJS spec, strings mapping to Platypus sprite sheets, or arrays of either.
402
486
  *
@@ -406,7 +490,6 @@ const
406
490
  */
407
491
  static formatSpriteSheet (spriteSheet) {
408
492
  const
409
- imageParts = /([\w-\.]+)\.(\w+)$/,
410
493
  addAnimations = function (source = {}, destination, speedRatio, firstFrameIndex, id) {
411
494
  const
412
495
  keys = Object.keys(source),
@@ -458,7 +541,6 @@ const
458
541
  ));
459
542
  }
460
543
  },
461
- createId = (images) => images.map((image) => (image.src ?? image).substring(0, MAX_KEY_LENGTH_PER_IMAGE)).join(','),
462
544
  format = function (source, destination) {
463
545
  const
464
546
  {
@@ -479,11 +561,8 @@ const
479
561
  firstImageIndex = dImages.length,
480
562
  firstFrameIndex = dFrames.length;
481
563
 
482
- // Set up id
483
- if (dID) {
484
- destination.id = `${dID};${sID ?? createId(sImages)}`;
485
- } else {
486
- destination.id = sID ?? createId(sImages);
564
+ if (sID) {
565
+ destination.id = dID ? `${dID};${sID}` : sID;
487
566
  }
488
567
 
489
568
  // Set up images array
@@ -546,7 +625,7 @@ const
546
625
  formatImages = function (name) {
547
626
  if (typeof name === 'string') {
548
627
  const
549
- match = name.match(imageParts);
628
+ match = name.match(imageBasename);
550
629
 
551
630
  if (match) {
552
631
  return match[1];
@@ -139,6 +139,10 @@ const
139
139
  },
140
140
 
141
141
  addNewPrivateEvent (event) {
142
+ if (this.destroyed || !this.entities || !this._childEvents) {
143
+ return false;
144
+ }
145
+
142
146
  if (this._childEvents[event]) {
143
147
  return false;
144
148
  }
@@ -148,7 +152,17 @@ const
148
152
  // Listen for message on children
149
153
  for (let x = 0; x < this.entities.length; x++) {
150
154
  const
151
- listenerList = this.entities[x]._listeners[event];
155
+ entity = this.entities[x],
156
+ listeners = entity && entity._listeners;
157
+
158
+ // Child may already be destroyed while still listed (e.g. re-entrant
159
+ // input during container teardown).
160
+ if (!listeners) {
161
+ continue;
162
+ }
163
+
164
+ const
165
+ listenerList = listeners[event];
152
166
 
153
167
  if (listenerList) {
154
168
  const
@@ -156,7 +170,7 @@ const
156
170
 
157
171
  for (let y = 0; y < handlers.length; y++) {
158
172
  this.addChildEventListener(
159
- this.entities[x],
173
+ entity,
160
174
  event,
161
175
  handlers[y]
162
176
  );
@@ -174,7 +188,13 @@ const
174
188
 
175
189
  addChildEventListeners (entity) {
176
190
  const
177
- {_listeners} = entity,
191
+ {_listeners} = entity;
192
+
193
+ if (!_listeners) {
194
+ return;
195
+ }
196
+
197
+ const
178
198
  keys = Object.keys(_listeners),
179
199
  {length} = keys;
180
200
 
@@ -268,10 +288,14 @@ const
268
288
  },
269
289
 
270
290
  destroy: function () {
291
+ // Mark destroyed first so re-entrant input (e.g. Enter:down) cannot
292
+ // walk children mid-teardown after their `_listeners` are nulled.
293
+ this._destroyed = true;
294
+
271
295
  const
272
- entities = greenSlice(this.entities); // Make a copy to handle entities being destroyed while processing list.
296
+ entities = this.entities ? greenSlice(this.entities) : arrayCache.setUp(); // Make a copy to handle entities being destroyed while processing list.
273
297
  let i = entities.length;
274
-
298
+
275
299
  while (i--) {
276
300
  const
277
301
  entity = entities[i];
@@ -279,14 +303,22 @@ const
279
303
  this.removeChildEventListeners(entity);
280
304
  entity.destroy();
281
305
  }
282
-
306
+
283
307
  arrayCache.recycle(entities);
284
- arrayCache.recycle(this.entities);
308
+ if (this.entities) {
309
+ arrayCache.recycle(this.entities);
310
+ this.entities = null;
311
+ }
285
312
  this.owner.entities = null;
286
- arrayCache.recycle(this.childEvents);
287
- this.childEvents = null;
288
- arrayCache.recycle(this.newAdds);
289
- this.newAdds = null;
313
+ this._childEvents = null;
314
+ if (this.childEvents) {
315
+ arrayCache.recycle(this.childEvents);
316
+ this.childEvents = null;
317
+ }
318
+ if (this.newAdds) {
319
+ arrayCache.recycle(this.newAdds);
320
+ this.newAdds = null;
321
+ }
290
322
  }
291
323
  },
292
324
 
@@ -485,7 +517,7 @@ const
485
517
  * @param {*} debug
486
518
  */
487
519
  triggerEventOnChildren: function (event, message, debug) {
488
- if (this.destroyed) {
520
+ if (this.destroyed || !this.entities || !this._childEvents) {
489
521
  return 0;
490
522
  }
491
523
 
@@ -504,7 +536,7 @@ const
504
536
  * @param {*} debug
505
537
  */
506
538
  triggerOnChildren: function (event) {
507
- if (this.destroyed) {
539
+ if (this.destroyed || !this.entities || !this._childEvents) {
508
540
  return 0;
509
541
  }
510
542