@woosh/meep-engine 2.84.9 → 2.84.10

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 (31) hide show
  1. package/README.md +27 -13
  2. package/build/meep.cjs +185 -87
  3. package/build/meep.min.js +1 -1
  4. package/build/meep.module.js +185 -87
  5. package/editor/process/symbolic/makePositionedIconDisplaySymbol.js +2 -4
  6. package/editor/view/EditorView.js +48 -204
  7. package/editor/view/ecs/HierarchicalEntityListView.js +191 -0
  8. package/editor/view/ecs/HierarchicalEntityListView.module.scss +13 -0
  9. package/editor/view/prepareMeshLibrary.js +178 -0
  10. package/editor/view/v2/SplitView.js +104 -0
  11. package/editor/view/v2/ViewManagementSystem.js +0 -0
  12. package/editor/view/v2/prototypeEditor.js +127 -0
  13. package/package.json +1 -1
  14. package/src/core/cache/Cache.d.ts +2 -0
  15. package/src/core/cache/Cache.js +58 -8
  16. package/src/core/cache/Cache.spec.js +38 -0
  17. package/src/core/cache/CacheElement.js +6 -0
  18. package/src/core/cache/LoadingCache.js +25 -2
  19. package/src/core/cache/LoadingCache.spec.js +9 -6
  20. package/src/core/collection/array/arraySetSortingDiff.js +6 -6
  21. package/src/core/collection/table/RowFirstTable.js +364 -368
  22. package/src/core/geom/3d/plane/plane3_compute_ray_intersection.js +3 -1
  23. package/src/core/geom/3d/topology/simplify/prototypeMeshSimplification.js +7 -7
  24. package/src/engine/animation/curve/ecd_bind_animation_curve.js +9 -0
  25. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +8 -1
  26. package/src/engine/graphics/ecs/mesh-v2/aggregate/prototypeSGMesh.js +23 -19
  27. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +2 -2
  28. package/src/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +3 -1
  29. package/src/view/View.js +64 -95
  30. package/src/view/setElementTransform.js +20 -0
  31. package/src/view/setElementVisibility.js +15 -0
@@ -0,0 +1,178 @@
1
+ import { SurfacePoint3 } from "../../src/core/geom/3d/SurfacePoint3.js";
2
+ import Vector2 from "../../src/core/geom/Vector2.js";
3
+ import Vector3 from "../../src/core/geom/Vector3.js";
4
+ import { obtainTerrain } from "../../src/engine/ecs/terrain/util/obtainTerrain.js";
5
+ import { Transform } from "../../src/engine/ecs/transform/Transform.js";
6
+ import Mesh from "../../src/engine/graphics/ecs/mesh/Mesh.js";
7
+ import { MeshEvents } from "../../src/engine/graphics/ecs/mesh/MeshEvents.js";
8
+ import { make_ray_from_viewport_position } from "../../src/engine/graphics/make_ray_from_viewport_position.js";
9
+ import BottomLeftResizeHandleView from "../../src/view/elements/BottomLeftResizeHandleView.js";
10
+ import ComponentAddAction from "../actions/concrete/ComponentAddAction.js";
11
+ import EntityCreateAction from "../actions/concrete/EntityCreateAction.js";
12
+ import SelectionAddAction from "../actions/concrete/SelectionAddAction.js";
13
+ import SelectionClearAction from "../actions/concrete/SelectionClearAction.js";
14
+ import MeshLibraryView from "./library/MeshLibraryView.js";
15
+
16
+ /**
17
+ *
18
+ * @param {Editor} editor
19
+ */
20
+ export function prepareMeshLibrary(editor) {
21
+ let resolveEngine;
22
+
23
+ const pEngine = new Promise(function (resolve, reject) {
24
+ resolveEngine = resolve;
25
+
26
+ });
27
+
28
+ /**
29
+ *
30
+ * @type {Promise<GraphicsEngine>}
31
+ */
32
+ const pGraphicsEngine = pEngine.then(function (e) {
33
+ return e.graphics;
34
+ });
35
+
36
+ const pRenderer = pGraphicsEngine.then(function (graphicsEngine) {
37
+ return graphicsEngine.renderer;
38
+ });
39
+
40
+ const pAssetManager = pEngine.then(e => e.assetManager);
41
+
42
+ const meshLibraryView = new MeshLibraryView(
43
+ editor.meshLibrary,
44
+ pAssetManager,
45
+ pRenderer
46
+ );
47
+
48
+ function handleDropEvent(event) {
49
+ event.stopPropagation();
50
+ event.preventDefault();
51
+
52
+ const dataText = event.dataTransfer.getData('text/json');
53
+ if (dataText === "") {
54
+ //no data
55
+ return;
56
+ }
57
+
58
+ const data = JSON.parse(dataText);
59
+
60
+ const type = data.type;
61
+
62
+ if (type !== "Mesh") {
63
+ //wrong type
64
+ return;
65
+ }
66
+
67
+ const url = data.url;
68
+
69
+ const engine = editor.engine;
70
+ const graphics = engine.graphics;
71
+
72
+ const position = new Vector2(event.clientX, event.clientY);
73
+
74
+ graphics.viewport.positionGlobalToLocal(position, position);
75
+
76
+ const normalizedPosition = new Vector2();
77
+
78
+ //compute world position for drop
79
+ const ray = make_ray_from_viewport_position(engine, position);
80
+
81
+ const source = ray.origin;
82
+ const direction = ray.direction;
83
+
84
+
85
+ const entityManager = engine.entityManager;
86
+ const ecd = entityManager.dataset;
87
+
88
+ const terrain = obtainTerrain(ecd);
89
+
90
+ const worldPosition = new Vector3();
91
+
92
+ const mesh = new Mesh();
93
+
94
+ mesh.castShadow = true;
95
+ mesh.receiveShadow = true;
96
+
97
+ const transform = new Transform();
98
+
99
+ const actions = editor.actions;
100
+ actions.mark('New Mesh Placed from Library');
101
+ const entityCreateAction = new EntityCreateAction();
102
+ actions.do(entityCreateAction);
103
+
104
+ const sp3 = new SurfacePoint3();
105
+
106
+ const hit_found = terrain.raycastFirstSync(sp3, source.x, source.y, source.z, direction.x, direction.y, direction.z);
107
+
108
+ function handleMeshSetEvent() {
109
+ const bb = mesh.boundingBox;
110
+
111
+ const c0 = new Vector3(bb.x0, bb.y0, bb.z0);
112
+ const c1 = new Vector3(bb.x1, bb.y1, bb.z1);
113
+
114
+ const diagonal = c0.distanceTo(c1);
115
+
116
+ const offset = direction.clone().multiplyScalar(diagonal);
117
+
118
+ transform.position.add(offset);
119
+
120
+ //remove listener
121
+ ecd.removeEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
122
+ }
123
+
124
+ if (hit_found) {
125
+ //got a terrain ray hit, set world placement position to that point
126
+ worldPosition.copy(sp3.position);
127
+ } else {
128
+ //set position to the source of the ray pick if there's nothing else available
129
+ worldPosition.copy(source);
130
+
131
+
132
+ //wait for mesh to load
133
+ ecd.addEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
134
+ }
135
+
136
+ transform.position.copy(worldPosition);
137
+
138
+ mesh.url = url;
139
+
140
+
141
+ actions.doMany([
142
+ new ComponentAddAction(entityCreateAction.entity, transform),
143
+ new ComponentAddAction(entityCreateAction.entity, mesh),
144
+ //automatically select newly placed object
145
+ new SelectionClearAction(),
146
+ new SelectionAddAction([entityCreateAction.entity])
147
+ ]);
148
+
149
+ }
150
+
151
+ function handleDragOverEvent(event) {
152
+ event.preventDefault();
153
+ }
154
+
155
+
156
+ meshLibraryView.on.linked.add(function () {
157
+ resolveEngine(editor.engine);
158
+
159
+ const viewport = editor.engine.graphics.viewport;
160
+
161
+ viewport.el.addEventListener('drop', handleDropEvent);
162
+ viewport.el.addEventListener('dragover', handleDragOverEvent);
163
+ });
164
+
165
+ meshLibraryView.on.unlinked.add(function () {
166
+ const viewport = editor.engine.graphics.viewport;
167
+
168
+ viewport.el.removeEventListener('drop', handleDropEvent);
169
+ viewport.el.removeEventListener('dragover', handleDragOverEvent);
170
+ });
171
+
172
+ meshLibraryView.size.set(400, 400);
173
+
174
+ const resizeHandleView = new BottomLeftResizeHandleView(meshLibraryView);
175
+ meshLibraryView.addChild(resizeHandleView);
176
+
177
+ return meshLibraryView;
178
+ }
@@ -0,0 +1,104 @@
1
+ import { clamp01 } from "../../../src/core/math/clamp01.js";
2
+ import { CSS_ABSOLUTE_POSITIONING } from "../../../src/view/CSS_ABSOLUTE_POSITIONING.js";
3
+ import EmptyView from "../../../src/view/elements/EmptyView.js";
4
+
5
+ export class SplitView extends EmptyView {
6
+ #direction = 'x';
7
+ #child_a = new EmptyView();
8
+ #child_b = new EmptyView();
9
+ #fraction = 0.5;
10
+ #round_split_to_nearest_integer = true
11
+
12
+ static from({ a, b, type = 'x', fraction = 0.5 }) {
13
+ const r = new SplitView();
14
+
15
+ r.direction = type;
16
+ r.fraction = fraction;
17
+ r.childA = a;
18
+ r.childB = b;
19
+
20
+ return r;
21
+ }
22
+
23
+ constructor() {
24
+ super();
25
+
26
+ this.addChild(this.#child_a);
27
+ this.addChild(this.#child_b);
28
+
29
+ this.layout();
30
+
31
+ this.bindSignal(this.size.onChanged, this.layout, this);
32
+ this.on.linked.add(this.layout, this);
33
+ }
34
+
35
+ set fraction(v) {
36
+ this.#fraction = v;
37
+ this.layout();
38
+ }
39
+
40
+ set direction(v) {
41
+ if (!['x', 'y'].includes(v)) {
42
+ throw new Error(`Invalid direction '${v}', valid values are 'x' and 'y'`);
43
+ }
44
+
45
+ this.#direction = v;
46
+
47
+ this.layout();
48
+ }
49
+
50
+ set childA(v) {
51
+ this.removeChild(this.#child_a);
52
+
53
+ this.#child_a = v;
54
+ this.addChild(v);
55
+
56
+ this.layout();
57
+ }
58
+
59
+ set childB(v) {
60
+ this.removeChild(this.#child_b);
61
+
62
+ this.#child_b = v;
63
+ this.addChild(v);
64
+
65
+ this.layout();
66
+ }
67
+
68
+ layout() {
69
+ const size = this.size;
70
+ const f = clamp01(this.#fraction);
71
+
72
+ this.#child_a.css(CSS_ABSOLUTE_POSITIONING);
73
+ this.#child_b.css(CSS_ABSOLUTE_POSITIONING);
74
+
75
+ const width = size.x;
76
+ const height = size.y;
77
+ if (this.#direction === 'x') {
78
+ let first = width*f;
79
+ if(this.#round_split_to_nearest_integer){
80
+ first = Math.round(first);
81
+ }
82
+ const second = width - first;
83
+
84
+ this.#child_a.size.set(first, height);
85
+ this.#child_b.size.set(second, height);
86
+
87
+ this.#child_a.position.set(0, 0);
88
+ this.#child_b.position.set(first, 0);
89
+ } else {
90
+
91
+ let first = height*f;
92
+ if(this.#round_split_to_nearest_integer){
93
+ first = Math.round(first);
94
+ }
95
+ const second = width - first;
96
+
97
+ this.#child_a.size.set(width, first);
98
+ this.#child_b.size.set(width, second);
99
+
100
+ this.#child_a.position.set(0, 0);
101
+ this.#child_b.position.set(0, first);
102
+ }
103
+ }
104
+ }
File without changes
@@ -0,0 +1,127 @@
1
+ import Name from "../../../../model/game/ecs/component/Name.js";
2
+ import { initializeEditor } from "../../../../model/game/initializeEditor.js";
3
+ import { randomFromArray } from "../../../src/core/math/random/randomFromArray.js";
4
+ import { randomIntegerBetween } from "../../../src/core/math/random/randomIntegerBetween.js";
5
+ import { seededRandom } from "../../../src/core/math/random/seededRandom.js";
6
+ import { TextureAssetLoader } from "../../../src/engine/asset/loaders/texture/TextureAssetLoader.js";
7
+ import { EntityNode } from "../../../src/engine/ecs/parent/EntityNode.js";
8
+ import { Transform } from "../../../src/engine/ecs/transform/Transform.js";
9
+ import { EngineHarness } from "../../../src/engine/EngineHarness.js";
10
+ import { enableEditor } from "../../enableEditor.js";
11
+
12
+
13
+ /**
14
+ *
15
+ * @param {function} rng
16
+ */
17
+ function randomNode(rng = Math.random) {
18
+ const node = new EntityNode();
19
+
20
+
21
+ node.entity.add(new Transform());
22
+ const ENGLISH_FIRST_NAMES = [
23
+ "Alex", "Anthony","Adam",
24
+ "Barbara", "Bartholomew","Benjamin",
25
+ "Charles", "Christian", "Cindy", "Catherine",
26
+ "Duncan", "David", "Donna", "Dylan",
27
+ "Ethan", "Edward", "Edgar", "Elizabeth",
28
+ "Fletcher", "Francis", "Fae",
29
+ "George", "Gavin", "Gabriel", "Gideon", "Griffin", "Grace", "Gordon",
30
+ "Hugh", "Harry", "Hilla",
31
+ "Ian", "Inge", "Ivy", "Isabella",
32
+ "John", "Jeff", "Joanna","Jack",
33
+ "Kevin", "Kelsey", "Kara", "Kade", "Kenneth", "Kylie", "Kaleb", "Khloe", "Keith", "Katie",
34
+ "Lewis", "Liam",
35
+ "Mathew", "Mark", "Mona",
36
+ "Nickolas", "Nigel", "Natasha",
37
+ "Otto", "Owen", "Olivia", "Oliver", "Odette", "Ophelia", "Orion",
38
+ "Peter", "Paola",
39
+ "Raphael", "Ralf", "Robert",
40
+ "Steward", "Simon", "Samantha",
41
+ "Trevor", "Tylor", "Terry",
42
+ "Uwe", "Uriel", "Uma", "Ulysses",
43
+ "Vladimir", "Vera",
44
+ "Wolfgang", "William", "Wyatt", "Willow", "Wesley", "Wren",
45
+ "Xylo", "Xavier", "Xander",
46
+ ];
47
+
48
+ const ENGLISH_SURNAMES = [
49
+ "Angel", "Aaron", "Abbott", "Abrams", "Ainsley","Anderson","Allen",
50
+ "Baker", "Bank", "Black", "Bale","Brown","Bennet","Bailey",
51
+ "Carter", "Chandler","Cooper","Clarke","Cook",
52
+ "Drake", "Dalton", "Davison", "Dawson", "Day","Davis",
53
+ "Ellis", "Eastwood", "Erickson", "Eaton", "Ellison","Evans","Edwards",
54
+ "Farley", "Fisc", "Fry", "Falcon","Farmer","Fletcher",
55
+ "Gabin", "Green", "Gray", "Gibson", "Grant", "Gordon", "Gill","Gold","Goldring","Golding",
56
+ "Hall", "Harris", "Hill", "Hughes", "Harrison", "Hunt", "Holmes",
57
+ "Ingram", "Irwin", "Irving", "Ives",
58
+ "Johnson","Jones","Jackson",
59
+ "Kelvin","King",
60
+ "Lewis", "Law","Lee",
61
+ "Morris","Mills","Martin","Moore","Morgan","Mitchel",
62
+ "Phillips","Parker","Price",
63
+ "Robinson","Roberts","Richardson",
64
+ "Smith","Scott",
65
+ "Tailor","Taylor","Thomas","Thompson","Turner",
66
+ "White","Wilson","Williams","Wright","Wood","Ward","Watson",
67
+ "Young",
68
+ ];
69
+
70
+ const first_name = randomFromArray(rng, ENGLISH_FIRST_NAMES);
71
+ const last_name = randomFromArray(rng, ENGLISH_SURNAMES);
72
+
73
+ node.entity.add(new Name(`${first_name} ${last_name}`))
74
+
75
+ return node;
76
+ }
77
+
78
+ /**
79
+ *
80
+ * @param {Engine} engine
81
+ * @returns {Promise<void>}
82
+ */
83
+ async function main(engine) {
84
+
85
+ await EngineHarness.buildBasics({
86
+ engine,
87
+ showFps: false
88
+ });
89
+
90
+
91
+ const random = seededRandom();
92
+
93
+ const density = 0.2;
94
+
95
+ const nodes = [];
96
+
97
+ for (let i = 0; i < 50; i++) {
98
+ nodes.push(randomNode(random));
99
+ }
100
+
101
+ while (nodes.length > 4) {
102
+ const child_index = randomIntegerBetween(random, 0, nodes.length - 1);
103
+
104
+ const [child] = nodes.splice(child_index, 1);
105
+
106
+ const parent = randomFromArray(random, nodes);
107
+
108
+ parent.addChild(child);
109
+ }
110
+
111
+ for (let i = 0; i < nodes.length; i++) {
112
+
113
+ const node = nodes[i];
114
+
115
+ node.build(engine.entityManager.dataset);
116
+
117
+ }
118
+
119
+
120
+ enableEditor(engine, initializeEditor).enable();
121
+ }
122
+
123
+ new EngineHarness().initialize({
124
+ configuration(config, engine) {
125
+ config.addLoader('texture', new TextureAssetLoader())
126
+ }
127
+ }).then(main)
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "description": "Fully featured ECS game engine written in JavaScript",
6
6
  "type": "module",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.84.9",
8
+ "version": "2.84.10",
9
9
  "main": "build/meep.module.js",
10
10
  "module": "build/meep.module.js",
11
11
  "exports": {
@@ -44,4 +44,6 @@ export class Cache<K, V> {
44
44
  getOrCompute(key: K, compute: (key: K) => V, compute_context?: any): V
45
45
 
46
46
  recomputeWeight(): void
47
+
48
+ updateElementWeight(key: K): boolean
47
49
  }
@@ -174,14 +174,56 @@ export class Cache {
174
174
  recomputeWeight() {
175
175
  let result = 0;
176
176
 
177
- for (let [key, value] of this.data) {
178
- result += this.keyWeigher(key);
179
- result += this.valueWeigher(value);
177
+ for (let [key, record] of this.data) {
178
+
179
+ const weight = this.computeElementWeight(key, record.value);
180
+ record.weight = weight;
181
+
182
+ result += weight;
180
183
  }
181
184
 
182
185
  this.weight = result;
183
186
  }
184
187
 
188
+ /**
189
+ * Useful when working with wrapped value types, where contents can change and affect overall weight
190
+ * Instead of re-inserting element, we can just update weights
191
+ * NOTE: this method may trigger eviction
192
+ * @param {Key} key
193
+ * @returns {boolean} true when weight successfully updated, false if element was not found in cache
194
+ */
195
+ updateElementWeight(key) {
196
+ const record = this.data.get(key);
197
+
198
+ if (record === undefined) {
199
+ return false;
200
+ }
201
+
202
+ const old_weight = record.weight;
203
+
204
+ const new_weight = this.computeElementWeight(key, record.value);
205
+
206
+ if (new_weight === old_weight) {
207
+ // we're done, no change
208
+ return true;
209
+ }
210
+
211
+ record.weight = new_weight;
212
+
213
+ const delta_weight = new_weight - old_weight;
214
+
215
+ this.weight += delta_weight;
216
+
217
+ if (
218
+ this.weight > this.maxWeight
219
+ && new_weight >= this.maxWeight //make it less likely to drop entire cache
220
+ ) {
221
+ this.evictUntilWeight(this.maxWeight);
222
+ }
223
+
224
+ return true;
225
+ }
226
+
185
227
  /**
186
228
  * @private
187
229
  * @param {Key} key
@@ -189,7 +231,15 @@ export class Cache {
189
231
  * @returns {number}
190
232
  */
191
233
  computeElementWeight(key, value) {
192
- return this.keyWeigher(key) + this.valueWeigher(value);
234
+ const key_weight = this.keyWeigher(key);
235
+
236
+ assert.notNaN(key_weight, 'key_weight');
237
+
238
+ const value_weight = this.valueWeigher(value);
239
+
240
+ assert.notNaN(value_weight, 'value_weight');
241
+
242
+ return key_weight + value_weight;
193
243
  }
194
244
 
195
245
  /**
@@ -264,6 +314,9 @@ export class Cache {
264
314
  //compute weight
265
315
  const elementWeight = this.computeElementWeight(key, value);
266
316
 
317
+ element.weight = elementWeight;
318
+
319
+
267
320
  /**
268
321
  * It's possible that element being added is larger than cache's capacity,
269
322
  * in which case entire cache will be evicted, but there still won't be enough space
@@ -374,14 +427,11 @@ export class Cache {
374
427
 
375
428
  const key = element.key;
376
429
 
377
- //compute weight
378
- const elementWeight = this.computeElementWeight(key, value);
379
-
380
430
  //remove from cache
381
431
  this.data.delete(key);
382
432
 
383
433
  //update weight
384
- this.weight -= elementWeight;
434
+ this.weight -= element.weight;
385
435
  }
386
436
 
387
437
  /**
@@ -179,6 +179,44 @@ test("recomputeWeight", () => {
179
179
  expect(cache.weight).toBe(4);
180
180
  });
181
181
 
182
+ test("updateElementWeight", () => {
183
+ class Record {
184
+ weight = 0;
185
+
186
+ constructor(v) {
187
+ this.weight = v;
188
+ }
189
+ }
190
+
191
+ /**
192
+ *
193
+ * @type {Cache<string, Record>}
194
+ */
195
+ const cache = new Cache({
196
+ valueWeigher: (v) => v.weight,
197
+ });
198
+
199
+ const record_a = new Record(7);
200
+ const record_b = new Record(11);
201
+
202
+ cache.set("a", record_a);
203
+ cache.set("b", record_b);
204
+
205
+ expect(cache.weight).toBe(18);
206
+
207
+ record_a.weight = 3;
208
+
209
+ cache.updateElementWeight("a");
210
+
211
+ expect(cache.weight).toBe(14);
212
+
213
+ record_b.weight = 13;
214
+
215
+ cache.updateElementWeight("b");
216
+
217
+ expect(cache.weight).toBe(16);
218
+ });
219
+
182
220
  test("silentRemove should not notify", () => {
183
221
 
184
222
  const cache = new Cache();
@@ -21,6 +21,12 @@ export class CacheElement {
21
21
  */
22
22
  this.value = null;
23
23
 
24
+ /**
25
+ *
26
+ * @type {number}
27
+ */
28
+ this.weight = 0;
29
+
24
30
  /**
25
31
  * Link to next element (implements linked list)
26
32
  * @type {CacheElement<Key,Value>|null}
@@ -1,6 +1,7 @@
1
1
  //
2
2
 
3
3
  import { assert } from "../assert.js";
4
+ import { returnZero } from "../function/returnZero.js";
4
5
  import { current_time_in_seconds } from "../time/current_time_in_seconds.js";
5
6
  import { Cache } from "./Cache.js";
6
7
 
@@ -17,6 +18,7 @@ class Record {
17
18
  this.value = value;
18
19
  this.time = time;
19
20
  this.failed = false;
21
+ this.weight = 0;
20
22
  }
21
23
  }
22
24
 
@@ -27,6 +29,15 @@ class Record {
27
29
  */
28
30
  const DEFAULT_TIME_TO_LIVE = Infinity;
29
31
 
32
+ /**
33
+ * @template T
34
+ * @param {Record<T>} record
35
+ * @returns {number}
36
+ */
37
+ function record_get_value_weight(record) {
38
+ return record.weight;
39
+ }
40
+
30
41
  /**
31
42
  * Asynchronous cache capable of resolving its own values by keys
32
43
  * Modelled on Guava's LoadingCache concept
@@ -54,6 +65,11 @@ export class LoadingCache {
54
65
  * @type {boolean}
55
66
  */
56
67
  #policyRetryFailed = true;
68
+ /**
69
+ *
70
+ * @type {function(V): number}
71
+ */
72
+ #value_weigher;
57
73
 
58
74
  /**
59
75
  * @see {@link Cache} for more details on what each parameter means
@@ -71,7 +87,7 @@ export class LoadingCache {
71
87
  constructor({
72
88
  maxWeight,
73
89
  keyWeigher,
74
- valueWeigher,
90
+ valueWeigher = returnZero,
75
91
  keyHashFunction,
76
92
  keyEqualityFunction,
77
93
  capacity,
@@ -85,7 +101,7 @@ export class LoadingCache {
85
101
  this.#internal = new Cache({
86
102
  maxWeight,
87
103
  keyWeigher,
88
- valueWeigher,
104
+ valueWeigher: record_get_value_weight,
89
105
  keyHashFunction,
90
106
  keyEqualityFunction,
91
107
  capacity,
@@ -94,6 +110,7 @@ export class LoadingCache {
94
110
  this.#timeToLive = timeToLive;
95
111
  this.#load = load;
96
112
  this.#policyRetryFailed = retryFailed;
113
+ this.#value_weigher = valueWeigher;
97
114
  }
98
115
 
99
116
  /**
@@ -132,6 +149,12 @@ export class LoadingCache {
132
149
 
133
150
  this.#internal.put(key, record);
134
151
 
152
+ promise.then((value) => {
153
+ // re-score value based on actual data
154
+ record.weight = this.#value_weigher(value);
155
+ this.#internal.updateElementWeight(key);
156
+ });
157
+
135
158
  promise.catch(() => {
136
159
  // mark as failure
137
160
  record.failed = true;