@woosh/meep-engine 2.42.0 → 2.42.1

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 (69) hide show
  1. package/core/assert.js +2 -2
  2. package/core/collection/array/array_swap.js +3 -3
  3. package/core/collection/map/AsyncLoadingCache.js +47 -0
  4. package/core/geom/3d/aabb/aabb3_compute_distance_above_plane_max.js +1 -1
  5. package/core/math/statistics/computeSampleSize_Cochran.js +3 -3
  6. package/editor/ecs/component/editors/geom/QuaternionEditor.js +12 -5
  7. package/engine/Engine.js +6 -1
  8. package/engine/EngineBootstrapper.js +2 -1
  9. package/engine/EngineHarness.js +5 -1
  10. package/engine/asset/AssetManager.js +97 -7
  11. package/engine/development/performance/AbstractMetric.js +1 -0
  12. package/engine/development/performance/RingBufferMetric.js +25 -4
  13. package/engine/ecs/EntityBuilder.js +29 -4
  14. package/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +17 -1
  15. package/engine/graphics/ecs/decal/v2/FPDecalSystem.js +18 -30
  16. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +57 -16
  17. package/engine/graphics/ecs/mesh-v2/allocate_v3.js +37 -0
  18. package/engine/graphics/geometry/MikkT/AddTriToGroup.js +10 -0
  19. package/engine/graphics/geometry/MikkT/AssignRecur.js +84 -0
  20. package/engine/graphics/geometry/MikkT/AvgTSpace.js +38 -0
  21. package/engine/graphics/geometry/MikkT/Build4RuleGroups.js +96 -0
  22. package/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +137 -0
  23. package/engine/graphics/geometry/MikkT/CalcTexArea.js +31 -0
  24. package/engine/graphics/geometry/MikkT/CompareSubGroups.js +26 -0
  25. package/engine/graphics/geometry/MikkT/DegenEpilogue.js +220 -0
  26. package/engine/graphics/geometry/MikkT/DegenPrologue.js +115 -0
  27. package/engine/graphics/geometry/MikkT/EvalTspace.js +128 -0
  28. package/engine/graphics/geometry/MikkT/GenerateInitialVerticesIndexList.js +48 -0
  29. package/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +184 -0
  30. package/engine/graphics/geometry/MikkT/GenerateTSpaces.js +226 -0
  31. package/engine/graphics/geometry/MikkT/GetEdge.js +45 -0
  32. package/engine/graphics/geometry/MikkT/GetNormal.js +16 -0
  33. package/engine/graphics/geometry/MikkT/GetPosition.js +25 -0
  34. package/engine/graphics/geometry/MikkT/GetTexCoord.js +18 -0
  35. package/engine/graphics/geometry/MikkT/InitTriInfo.js +180 -0
  36. package/engine/graphics/geometry/MikkT/Length.js +10 -0
  37. package/engine/graphics/geometry/MikkT/MakeIndex.js +18 -0
  38. package/engine/graphics/geometry/MikkT/MikkTSpace.js +165 -2197
  39. package/engine/graphics/geometry/MikkT/NormalizeSafe.js +21 -0
  40. package/engine/graphics/geometry/MikkT/NotZero.js +10 -0
  41. package/engine/graphics/geometry/MikkT/QuickSort.js +54 -0
  42. package/engine/graphics/geometry/MikkT/QuickSortEdges.js +71 -0
  43. package/engine/graphics/geometry/MikkT/SSubGroup.js +15 -0
  44. package/engine/graphics/geometry/MikkT/STSpace.js +36 -0
  45. package/engine/graphics/geometry/MikkT/constants/GROUP_WITH_ANY.js +1 -0
  46. package/engine/graphics/geometry/MikkT/constants/INTERNAL_RND_SORT_SEED.js +1 -0
  47. package/engine/graphics/geometry/MikkT/constants/MARK_DEGENERATE.js +1 -0
  48. package/engine/graphics/geometry/MikkT/constants/ORIENT_PRESERVING.js +1 -0
  49. package/engine/graphics/geometry/MikkT/constants/QUAD_ONE_DEGEN_TRI.js +1 -0
  50. package/engine/graphics/geometry/MikkT/m_getNormal.js +16 -0
  51. package/engine/graphics/geometry/MikkT/m_getNumFaces.js +8 -0
  52. package/engine/graphics/geometry/MikkT/m_getNumVerticesOfFace.js +11 -0
  53. package/engine/graphics/geometry/MikkT/m_getPosition.js +20 -0
  54. package/engine/graphics/geometry/MikkT/m_getTexCoord.js +16 -0
  55. package/engine/graphics/geometry/MikkT/m_setTSpace.js +35 -0
  56. package/engine/graphics/geometry/MikkT/m_setTSpaceBasic.js +22 -0
  57. package/engine/graphics/geometry/MikkT/malloc.js +16 -0
  58. package/engine/graphics/geometry/MikkT/v3_scale_dot_sub_normalize.js +52 -0
  59. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +3 -2
  60. package/engine/graphics/impostors/octahedral/prototypeBaker.js +5 -5
  61. package/engine/graphics/render/forward_plus/LightManager.js +16 -6
  62. package/engine/graphics/render/forward_plus/data/TextureBackedMemoryRegion.js +7 -1
  63. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +13 -3
  64. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_PREAMBLE.js +2 -1
  65. package/engine/graphics/render/forward_plus/plugin/MaterialTransformer.js +14 -2
  66. package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +2 -2
  67. package/engine/intelligence/behavior/util/RotationBehavior.js +69 -0
  68. package/generation/example/filters/SampleGroundMoistureFilter.js +5 -5
  69. package/package.json +1 -1
package/core/assert.js CHANGED
@@ -167,8 +167,8 @@ function arrayHasNo(haystack, needle, message = 'Array contains the item') {
167
167
 
168
168
  /**
169
169
  * @template T
170
- * @param {T[]|ArrayLike<T>|Float32Array} a
171
- * @param {T[]|ArrayLike<T>|Float32Array} b
170
+ * @param {T[]|ArrayLike<T>|Float32Array|Int32Array} a
171
+ * @param {T[]|ArrayLike<T>|Float32Array|Int32Array} b
172
172
  * @param {string} [message]
173
173
  */
174
174
  function arrayEqual(a, b, message = 'Arrays are not equal') {
@@ -1,8 +1,8 @@
1
1
  /**
2
- *
3
- * @param {T[]|ArrayLike<T>|TypedArray|Uint8ClampedArray|Uint8Array} a
2
+ * @template T
3
+ * @param {T[]|ArrayLike<T>|TypedArray|Uint8ClampedArray|Uint8Array|Float32Array} a
4
4
  * @param {number} a_offset
5
- * @param {T[]|ArrayLike<T>|TypedArray|Uint8ClampedArray|Uint8Array} b
5
+ * @param {T[]|ArrayLike<T>|TypedArray|Uint8ClampedArray|Uint8Array|Float32Array} b
6
6
  * @param {number} b_offset
7
7
  * @param {number} length How many elements should be moved
8
8
  */
@@ -0,0 +1,47 @@
1
+ import { AbstractAsyncMap } from "./AbstractAsyncMap.js";
2
+
3
+ /**
4
+ * Cache wrapper that loads data when it's not found in the cache
5
+ */
6
+ export class AsyncLoadingCache extends AbstractAsyncMap {
7
+ /**
8
+ *
9
+ * @param {Map} cache
10
+ * @param {function(K):Promise<T>} loader
11
+ */
12
+ constructor(cache, loader) {
13
+ super();
14
+
15
+ this.__cache = cache;
16
+ this.__loader = loader;
17
+ this.__pending = new Map();
18
+ }
19
+
20
+ async get(key) {
21
+
22
+ const cached = this.__cache.get(key);
23
+
24
+ if (cached !== undefined) {
25
+ return cached;
26
+ }
27
+
28
+ let promise = this.__pending.get(key);
29
+ let original_loader = false;
30
+
31
+ if (promise === undefined) {
32
+ promise = this.__loader(key);
33
+
34
+ this.__pending.set(key, promise);
35
+
36
+ original_loader = true;
37
+ }
38
+
39
+ const value = await promise;
40
+
41
+ if (original_loader) {
42
+ this.__cache.get(key, value);
43
+ }
44
+
45
+ return value;
46
+ }
47
+ }
@@ -18,7 +18,7 @@ export function aabb3_computeDistanceAbovePlane_max(
18
18
  _x1, _y1, _z1
19
19
  ) {
20
20
 
21
- // pick a corner point nearest along the plane normal
21
+ // pick a corner point that is the closest along the plane normal
22
22
  const px = plane_normal_x > 0 ? _x1 : _x0;
23
23
  const py = plane_normal_y > 0 ? _y1 : _y0;
24
24
  const pz = plane_normal_z > 0 ? _z1 : _z0;
@@ -12,9 +12,9 @@
12
12
  * 99% | 2.57583
13
13
  * 99.9% | 3.29053
14
14
  *
15
- * @param z Standard distribution value. # of standard deviations. See statistical tables
16
- * @param p Fraction of the population with attribute in question. For example if we want to estimate what time people have breakfast and we know that only 30% of people have have breakfast overall, p would be 0.3
17
- * @param e Error tolerance, 0.05 represents 5% error tolerance
15
+ * @param {number} z Standard distribution value. # of standard deviations. See statistical tables
16
+ * @param {number} p Fraction of the population with attribute in question. For example if we want to estimate what time people have breakfast and we know that only 30% of people have have breakfast overall, p would be 0.3
17
+ * @param {number} e Error tolerance, 0.05 represents 5% error tolerance
18
18
  * @returns {number} Sample size
19
19
  */
20
20
  export function computeSampleSize_Cochran(z, p, e) {
@@ -1,7 +1,8 @@
1
1
  import { TypeEditor } from "../../TypeEditor.js";
2
2
  import Vector3 from "../../../../../core/geom/Vector3.js";
3
- import { DEG2RAD, RAD2DEG } from "three/src/math/MathUtils.js";
4
3
  import { makeV3_editor } from "../common/makeV3_editor.js";
4
+ import { DEG_TO_RAD } from "../../../../../core/math/DEG_TO_RAD.js";
5
+ import { RAD_TO_DEG } from "../../../../../core/math/RAD_TO_DEG.js";
5
6
 
6
7
  export class QuaternionEditor extends TypeEditor {
7
8
  build(parent, field, registry) {
@@ -20,7 +21,13 @@ export class QuaternionEditor extends TypeEditor {
20
21
  }
21
22
 
22
23
  lock = true;
23
- q.__setFromEuler(euler.x * DEG2RAD, euler.y * DEG2RAD, euler.z * DEG2RAD);
24
+
25
+ q.__setFromEuler(
26
+ euler.x * DEG_TO_RAD,
27
+ euler.y * DEG_TO_RAD,
28
+ euler.z * DEG_TO_RAD
29
+ );
30
+
24
31
  lock = false;
25
32
  };
26
33
 
@@ -36,9 +43,9 @@ export class QuaternionEditor extends TypeEditor {
36
43
  q.toEulerAnglesXYZ(t);
37
44
 
38
45
  euler.set(
39
- t.x * RAD2DEG,
40
- t.y * RAD2DEG,
41
- t.z * RAD2DEG,
46
+ t.x * RAD_TO_DEG,
47
+ t.y * RAD_TO_DEG,
48
+ t.z * RAD_TO_DEG,
42
49
  );
43
50
 
44
51
  lock = false;
package/engine/Engine.js CHANGED
@@ -84,7 +84,7 @@ class Engine {
84
84
  *
85
85
  * @type {ConcurrentExecutor}
86
86
  */
87
- this.executor = new ConcurrentExecutor(0, 10);
87
+ this.executor = new ConcurrentExecutor(1, 10);
88
88
 
89
89
  this.__using_external_entity_manager = entityManager !== undefined;
90
90
 
@@ -412,6 +412,9 @@ class Engine {
412
412
  start() {
413
413
  const self = this;
414
414
 
415
+
416
+ this.assetManager.startup();
417
+
415
418
  function promiseEntityManager() {
416
419
  return new Promise(function (resolve, reject) {
417
420
  //initialize entity manager
@@ -488,6 +491,8 @@ class Engine {
488
491
 
489
492
 
490
493
  // TODO shutdown executor
494
+
495
+ await this.assetManager.shutdown();
491
496
  }
492
497
 
493
498
  exit() {
@@ -30,7 +30,8 @@ export class EngineBootstrapper {
30
30
 
31
31
  this.rootNode.appendChild(v.el);
32
32
 
33
- const started = engine.start();
33
+ // NOTE: the engine used to be started here, this was moved out
34
+ const started = Promise.resolve();
34
35
 
35
36
  const vExecutionEngineState = new LabelView('');
36
37
  const vAssetManagerState = new LabelView('');
@@ -201,6 +201,7 @@ export class EngineHarness {
201
201
 
202
202
  camera.active.set(true);
203
203
  camera.autoClip = autoClip;
204
+ camera.clip_far = distanceMax;
204
205
  camera.fov.set(fieldOfView);
205
206
 
206
207
  const entityBuilder = new EntityBuilder();
@@ -233,6 +234,7 @@ export class EngineHarness {
233
234
  * @param {boolean} [enableTerrain]
234
235
  * @param {boolean} [enableLights=true]
235
236
  * @param {number} [cameraFieldOfView]
237
+ * @param {number} [cameraFarDistance]
236
238
  * @param {boolean} [cameraController=true]
237
239
  */
238
240
  static async buildBasics({
@@ -249,6 +251,7 @@ export class EngineHarness {
249
251
  enableTerrain = true,
250
252
  enableLights = true,
251
253
  cameraFieldOfView,
254
+ cameraFarDistance,
252
255
  cameraController = true
253
256
  }) {
254
257
 
@@ -262,7 +265,8 @@ export class EngineHarness {
262
265
  pitch,
263
266
  yaw,
264
267
  distance,
265
- fieldOfView: cameraFieldOfView
268
+ fieldOfView: cameraFieldOfView,
269
+ distanceMax: cameraFarDistance
266
270
  });
267
271
 
268
272
  const cameraEntity = camera.entity;
@@ -13,6 +13,9 @@ import { CrossOriginConfig } from "./CORS/CrossOriginConfig.js";
13
13
  import { array_remove_element } from "../../core/collection/array/array_remove_element.js";
14
14
  import { AssetDescription } from "./AssetDescription.js";
15
15
  import { noop } from "../../core/function/Functions.js";
16
+ import { Deque } from "../../core/collection/queue/Deque.js";
17
+ import Task from "../../core/process/task/Task.js";
18
+ import { TaskSignal } from "../../core/process/task/TaskSignal.js";
16
19
 
17
20
  class AssetRequest {
18
21
  /**
@@ -68,6 +71,18 @@ class PendingAsset {
68
71
  }
69
72
  }
70
73
 
74
+ class Response {
75
+ /**
76
+ *
77
+ * @param {Asset} asset
78
+ * @param {AssetRequest} request
79
+ */
80
+ constructor(asset, request) {
81
+ this.asset = asset;
82
+ this.request = request;
83
+ }
84
+ }
85
+
71
86
 
72
87
  /**
73
88
  * @class
@@ -139,6 +154,60 @@ export class AssetManager {
139
154
  * @type {boolean}
140
155
  */
141
156
  this.isAssetManager = true;
157
+
158
+ /**
159
+ *
160
+ * @type {Deque<Response>}
161
+ * @private
162
+ */
163
+ this.__response_queue = new Deque();
164
+
165
+ /**
166
+ *
167
+ * @type {Task}
168
+ * @private
169
+ */
170
+ this.__response_processor = new Task({
171
+ name: "Asset Manager Response processor",
172
+ cycleFunction: () => {
173
+ if (this.__response_queue.isEmpty()) {
174
+ return TaskSignal.Yield;
175
+ }
176
+
177
+ const response = this.__response_queue.removeFirst();
178
+
179
+ this.__process_response(response);
180
+
181
+ return TaskSignal.Continue;
182
+ }
183
+ });
184
+
185
+ this.__is_running = false;
186
+ }
187
+
188
+ startup() {
189
+ if (this.__is_running) {
190
+ return;
191
+ }
192
+
193
+ this.__engine.executor.run(this.__response_processor);
194
+
195
+ this.__is_running = true;
196
+ }
197
+
198
+ async shutdown(immediate = false) {
199
+ if (!this.__is_running) {
200
+ return;
201
+ }
202
+
203
+ if (!immediate) {
204
+ // wait until all responses have been processed
205
+ await Promise.allSettled([Task.promise(this.__response_processor)]);
206
+ }
207
+
208
+ this.__engine.executor.removeTask(this.__response_processor);
209
+
210
+ this.__is_running = false;
142
211
  }
143
212
 
144
213
  /**
@@ -215,6 +284,30 @@ export class AssetManager {
215
284
  this.assets.set(assetDescription, asset);
216
285
  }
217
286
 
287
+ /**
288
+ *
289
+ * @param {Response} response
290
+ * @private
291
+ */
292
+ __process_response(response) {
293
+
294
+ try {
295
+ response.request.successCallback(response.asset);
296
+ } catch (e) {
297
+ console.error("Failed to execute asset success callback", e);
298
+ }
299
+ }
300
+
301
+ /**
302
+ *
303
+ * @param {Asset} asset
304
+ * @param {AssetRequest} request
305
+ * @private
306
+ */
307
+ __schedule_response(asset, request) {
308
+ this.__response_queue.add(new Response(asset, request));
309
+ }
310
+
218
311
  /**
219
312
  *
220
313
  * @param {string} path
@@ -299,18 +392,15 @@ export class AssetManager {
299
392
  //register asset
300
393
  assets.set(assetDescription, asset);
301
394
 
395
+ //clear callbacks
396
+ requestMap.delete(assetDescription);
397
+
302
398
  // process callbacks
303
399
  for (let i = 0; i < requests.length; i++) {
304
400
  const request = requests[i];
305
- try {
306
- request.successCallback(asset);
307
- } catch (e) {
308
- console.error("Failed to execute asset success callback", e);
309
- }
401
+ this.__schedule_response(asset, request);
310
402
  }
311
403
 
312
- //clear callbacks
313
- requestMap.delete(assetDescription);
314
404
  }
315
405
 
316
406
  function failure(error) {
@@ -17,6 +17,7 @@ export class AbstractMetric {
17
17
  /**
18
18
  *
19
19
  * @param {MetricStatistics} result
20
+ * @returns {boolean} whether metric was successfully computed or not
20
21
  */
21
22
  computeStats(result) {
22
23
  throw new Error('Not implemented');
@@ -20,16 +20,37 @@ export class RingBufferMetric extends AbstractMetric {
20
20
  this.__data.push(value);
21
21
  }
22
22
 
23
+ /**
24
+ *
25
+ * @param {MetricStatistics} result
26
+ * @returns {boolean}
27
+ */
23
28
  computeStats(result) {
24
29
 
25
30
  const buffer = this.__data;
26
31
  const array = buffer.data;
27
32
  const data_count = buffer.count;
28
33
 
29
- result.mean = computeStatisticalMean(array, 0, data_count);
30
- result.meadian = computeStatisticalPartialMedian(array, 0, data_count);
31
- result.max = computeArrayMax(array, 0, data_count);
32
- result.min = computeArrayMin(array, 0, data_count);
34
+ if (data_count === 0) {
35
+
36
+ // no data
37
+ result.mean = 0;
38
+ result.meadian = 0;
39
+ result.min = 0;
40
+ result.max = 0;
41
+
42
+ return false;
43
+
44
+ } else {
45
+
46
+ result.mean = computeStatisticalMean(array, 0, data_count);
47
+ result.meadian = computeStatisticalPartialMedian(array, 0, data_count);
48
+ result.max = computeArrayMax(array, 0, data_count);
49
+ result.min = computeArrayMin(array, 0, data_count);
50
+
51
+ return true;
52
+
53
+ }
33
54
  }
34
55
 
35
56
  clear() {
@@ -8,11 +8,30 @@ import { isDefined } from "../../core/process/matcher/Matchers.js";
8
8
  * @enum
9
9
  */
10
10
  export const EntityBuilderFlags = {
11
+ /**
12
+ * Whether the entity is built, set internally
13
+ */
11
14
  Built: 1,
12
- RegisterComponents: 2
15
+ /**
16
+ * If component type is not registered on the {@link EntityComponentDataset} when calling {@link #build} - will register the component first
17
+ */
18
+ RegisterComponents: 2,
19
+ /**
20
+ * Entity builder will watch destruction of the entity (subscribe to event), this will make the EntityBuilder automatically
21
+ * recognize when the corresponding entity is destroyed outside the EntityBuilder API
22
+ * NOTE: useful to turn off to save a bit of memory, as those event listeners take up a bit of space. Feel free to turn this flag off when you don't care to keep the reference to the EntityBuilder
23
+ */
24
+ WatchDestruction: 4
13
25
  };
14
26
 
15
- const DEFAULT_FLAGS = EntityBuilderFlags.RegisterComponents;
27
+ /**
28
+ * Set of default flags
29
+ * @type {number}
30
+ */
31
+ const DEFAULT_FLAGS =
32
+ EntityBuilderFlags.RegisterComponents
33
+ | EntityBuilderFlags.WatchDestruction
34
+ ;
16
35
 
17
36
  /**
18
37
  * Representation of an entity, helps build entities and keep track of them without having to access {@link EntityComponentDataset} directly
@@ -53,6 +72,10 @@ class EntityBuilder {
53
72
  */
54
73
  this.dataset = null;
55
74
 
75
+ /**
76
+ *
77
+ * @type {EntityBuilderFlags|number}
78
+ */
56
79
  this.flags = DEFAULT_FLAGS;
57
80
 
58
81
  /**
@@ -132,7 +155,7 @@ class EntityBuilder {
132
155
  throw new Error("Can not add " + componentInstance + " to EntityBuilder");
133
156
  }
134
157
 
135
- assert.notOk(this.hasComponent(Object.getPrototypeOf(componentInstance).constructor) , 'Component of this type already exists');
158
+ assert.notOk(this.hasComponent(Object.getPrototypeOf(componentInstance).constructor), 'Component of this type already exists');
136
159
 
137
160
  this.element.push(componentInstance);
138
161
 
@@ -378,7 +401,9 @@ class EntityBuilder {
378
401
 
379
402
  this.setFlag(EntityBuilderFlags.Built);
380
403
 
381
- dataset.addEntityEventListener(entity, EventType.EntityRemoved, this.__handleEntityDestroyed, this);
404
+ if (this.getFlag(EntityBuilderFlags.WatchDestruction)) {
405
+ dataset.addEntityEventListener(entity, EventType.EntityRemoved, this.__handleEntityDestroyed, this);
406
+ }
382
407
 
383
408
  this.on.built.send2(entity, dataset);
384
409
  return entity;
@@ -24,6 +24,22 @@ class TopDownCameraControllerSystem extends System {
24
24
 
25
25
  }
26
26
 
27
+ /**
28
+ *
29
+ * @param {TopDownCameraController} ctrl
30
+ * @param {number} entity
31
+ * @private
32
+ */
33
+ __visit_camera_entity(ctrl, entity){
34
+ const transform = this.entityManager.dataset.getComponent(entity,Transform);
35
+
36
+ if(transform === undefined){
37
+ return;
38
+ }
39
+
40
+ computeTopDownTransform(ctrl,transform);
41
+ }
42
+
27
43
  update(timeDelta) {
28
44
  const em = this.entityManager;
29
45
 
@@ -34,7 +50,7 @@ class TopDownCameraControllerSystem extends System {
34
50
  const dataset = em.dataset;
35
51
 
36
52
  if (this.enabled.get() && dataset !== null) {
37
- dataset.traverseEntities([TopDownCameraController, Transform], computeTopDownTransform);
53
+ dataset.traverseComponents(TopDownCameraController, this.__visit_camera_entity, this);
38
54
  }
39
55
 
40
56
  }
@@ -8,6 +8,8 @@ import { Decal as FPDecal } from '../../../render/forward_plus/model/Decal.js';
8
8
  import { AssetManager } from "../../../../asset/AssetManager.js";
9
9
  import { Sampler2D } from "../../../texture/sampler/Sampler2D.js";
10
10
  import { GameAssetType } from "../../../../asset/GameAssetType.js";
11
+ import { assert } from "../../../../../core/assert.js";
12
+ import { AsyncLoadingCache } from "../../../../../core/collection/map/AsyncLoadingCache.js";
11
13
 
12
14
  const placeholder_texture = Sampler2D.uint8(4, 1, 1);
13
15
  placeholder_texture.data.fill(255);
@@ -65,8 +67,20 @@ class Context extends SystemEntityContext {
65
67
  return;
66
68
  }
67
69
 
70
+ /**
71
+ *
72
+ * @type {FPDecalSystem}
73
+ */
74
+ const system = this.system;
75
+
76
+ /**
77
+ * @type {Sampler2D}
78
+ */
79
+ const loaded_texture = await system.__texture_cache.get(_uri);
68
80
 
69
- const loaded_texture = await this.system.internal_loadTexture(_uri);
81
+ assert.defined(loaded_texture, 'loaded_texture');
82
+ assert.notNull(loaded_texture, 'loaded_texture');
83
+ assert.equal(loaded_texture.isSampler2D, true, 'texture.isSampler2D !== true');
70
84
 
71
85
  decal_spec.__cached_sampler = loaded_texture;
72
86
  decal_spec.__cached_uri = _uri;
@@ -147,7 +161,9 @@ export class FPDecalSystem extends AbstractContextSystem {
147
161
  * @type {Map<string, Sampler2D>}
148
162
  * @private
149
163
  */
150
- this.__texture_cache = new Map();
164
+ this.__texture_cache = new AsyncLoadingCache(new Map(), (key) => {
165
+ return this.__assets.promise(key, GameAssetType.Image).then(asset => asset.create());
166
+ });
151
167
 
152
168
  /**
153
169
  *
@@ -171,34 +187,6 @@ export class FPDecalSystem extends AbstractContextSystem {
171
187
  this.__fp_plugin = Reference.NULL;
172
188
  }
173
189
 
174
- /**
175
- *
176
- * @param {string} uri
177
- * @returns {Promise<void>}
178
- */
179
- async internal_loadTexture(uri) {
180
- const cached = this.__texture_cache.get(uri);
181
-
182
- if (cached !== undefined) {
183
- return cached;
184
- }
185
-
186
- const asset = await this.__assets.promise(uri, GameAssetType.Image);
187
-
188
- // check cache again, in case another request has succeeded
189
- const cached_v2 = this.__texture_cache.get(uri);
190
-
191
- if (cached_v2 !== undefined) {
192
- return cached_v2;
193
- }
194
-
195
- const sampler = asset.create();
196
-
197
- this.__texture_cache.set(uri, sampler);
198
-
199
- return sampler;
200
- }
201
-
202
190
  async startup(em, ready_callback, error_callback) {
203
191
  this.__fp_plugin = await this.__engine.plugins.acquire(ForwardPlusRenderingPlugin);
204
192