@woosh/meep-engine 2.44.8 → 2.45.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.
@@ -29,7 +29,7 @@ export class HashMap<K, V> implements Iterable<[V, K]> {
29
29
 
30
30
  forEach(callback: (value: V, key: K, map: this) => any, thisArg?: any): void
31
31
 
32
- [Symbol.iterator](): IterableIterator<[V, K]>
32
+ [Symbol.iterator](): IterableIterator<[K, V]>
33
33
 
34
34
  values(): IterableIterator<V>
35
35
 
@@ -40,6 +40,7 @@ const DEFAULT_LOAD_FACTOR = 0.75;
40
40
 
41
41
  /**
42
42
  * Implements part of {@link Map} interface
43
+ * @copyright Alex Goldring (c) 2023
43
44
  * @template K,V
44
45
  * @extends Map<K,V>
45
46
  */
@@ -519,7 +520,7 @@ export class HashMap {
519
520
  */
520
521
  const entry = bucket[i];
521
522
 
522
- yield [entry.value, entry.key];
523
+ yield [entry.key, entry.value];
523
524
  }
524
525
  }
525
526
 
@@ -1,7 +1,7 @@
1
1
  import { assert } from "../assert.js";
2
2
 
3
3
  /**
4
- *
4
+ * Represents information about a language
5
5
  */
6
6
  export class LanguageMetadata {
7
7
  /**
@@ -10,12 +10,32 @@ export class LanguageMetadata {
10
10
  */
11
11
  reading_speed = 10
12
12
 
13
- fromJSON({ reading_speed = 10 }) {
13
+ /**
14
+ * Ordered list of fallback languages, if no value is found for a key in this language, other languages will be attempted in order
15
+ * Identified by localization key
16
+ * @see {@link #locale}
17
+ * @type {string[]}
18
+ */
19
+ fallback_languages = []
20
+
21
+ /**
22
+ * Localization key,
23
+ * @see ISO 639
24
+ * @type {string}
25
+ */
26
+ locale = "";
27
+
28
+ fromJSON({
29
+ reading_speed = 10,
30
+ fallback_languages = []
31
+ }) {
14
32
 
15
33
  assert.isNumber(reading_speed, 'reading_speed');
16
34
  assert.greaterThan(reading_speed, 0, 'reading_speed');
17
35
 
18
36
  this.reading_speed = reading_speed;
37
+
38
+ this.fallback_languages = fallback_languages;
19
39
  }
20
40
 
21
41
  static fromJSON(j) {
@@ -0,0 +1,37 @@
1
+ //
2
+
3
+ import { assert } from "../assert.js";
4
+
5
+ /**
6
+ * Represents data for a single localization
7
+ */
8
+ export class LocaleDataset {
9
+ /**
10
+ * Localized strings, identified by localization key
11
+ * @type {Object<string>}
12
+ * @private
13
+ */
14
+ __strings = {}
15
+
16
+ /**
17
+ *
18
+ * Does a given key exist?
19
+ * @param {string} key
20
+ * @return {boolean}
21
+ */
22
+ hasString(key) {
23
+ assert.isString(key, 'key');
24
+
25
+ return this.__strings[key] !== undefined;
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @param {string} key
31
+ * @returns {string|undefined}
32
+ */
33
+ getString(key) {
34
+ return this.__strings[key];
35
+ }
36
+
37
+ }
@@ -25,7 +25,7 @@ function validationMockSeed(template) {
25
25
 
26
26
  const EMPTY_SEED = Object.freeze({});
27
27
 
28
- const DEFAULT_LANGUAGE_METADATA = new LanguageMetadata();
28
+ const DEFAULT_LANGUAGE_METADATA = Object.freeze(new LanguageMetadata());
29
29
 
30
30
  export class Localization {
31
31
  constructor() {
@@ -117,7 +117,8 @@ export class Localization {
117
117
  }
118
118
 
119
119
  /**
120
- *
120
+ * Request locale switch
121
+ * Assumes a specific folder structure, each different locale is stored with its locale code as name, and there's also "languages.json" file expected to be in the folder
121
122
  * @param {string} locale
122
123
  * @param {string} path where to look for localization data
123
124
  * @returns {Promise}
@@ -132,7 +133,14 @@ export class Localization {
132
133
  throw new Error('AssetManager is not set');
133
134
  }
134
135
 
135
- const pLoadData = am.promise(`${path}/${locale}.json`, "json")
136
+ let _path = path.trim();
137
+
138
+ while (_path.endsWith('/') || _path.endsWith('\\')) {
139
+ // remove trailing slash if present
140
+ _path.slice(0, _path.length - 1);
141
+ }
142
+
143
+ const pLoadData = am.promise(`${_path}/${locale}.json`, "json")
136
144
  .then(asset => {
137
145
  const json = asset.create();
138
146
 
@@ -148,7 +156,7 @@ export class Localization {
148
156
 
149
157
  });
150
158
 
151
- const pLoadMetadata = am.promise(`${path}/languages.json`, "json")
159
+ const pLoadMetadata = am.promise(`${_path}/languages.json`, "json")
152
160
  .then(asset => {
153
161
  const languages_metadata = asset.create();
154
162
 
@@ -162,14 +170,6 @@ export class Localization {
162
170
  return Promise.all([pLoadData, pLoadMetadata]);
163
171
  }
164
172
 
165
- /**
166
- *
167
- * @param {String} id
168
- * @return {boolean}
169
- */
170
- hasString(id) {
171
- return this.json[id] !== undefined;
172
- }
173
173
 
174
174
  /**
175
175
  *
@@ -216,29 +216,38 @@ export class Localization {
216
216
  }
217
217
 
218
218
  /**
219
- *
220
- * @param {string} id
219
+ * Get a localized string by a key
220
+ * @param {string} key
221
221
  * @param {object} [seed]
222
222
  *
223
223
  * @returns {string}
224
224
  */
225
- getString(id, seed = EMPTY_SEED) {
226
- assert.isString(id, 'id');
225
+ getString(key, seed = EMPTY_SEED) {
226
+ assert.isString(key, 'id');
227
227
 
228
- const value = this.json[id];
228
+ const value = this.json[key];
229
229
 
230
230
  if (value === undefined) {
231
231
 
232
232
  if (this.debug) {
233
- this.__debugMissingKey(id, seed)
233
+ this.__debugMissingKey(key, seed)
234
234
  }
235
235
 
236
236
  //no value, provide substitute
237
- return `@${id}`;
237
+ return `@${key}`;
238
238
 
239
239
  }
240
240
 
241
241
  //value needs to be seeded
242
242
  return seedVariablesIntoTemplateString(value, seed);
243
243
  }
244
+
245
+ /**
246
+ * Does a given key exist?
247
+ * @param {string} key
248
+ * @return {boolean}
249
+ */
250
+ hasString(key) {
251
+ return this.json[key] !== undefined;
252
+ }
244
253
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { assert } from "../../assert.js";
7
7
  import Signal from "../../events/signal/Signal.js";
8
- import { noop, returnZero } from "../../function/Functions.js";
8
+ import { noop } from "../../function/Functions.js";
9
9
  import ObservedInteger from "../../model/ObservedInteger.js";
10
10
  import { TaskSignal } from "./TaskSignal.js";
11
11
  import TaskState from "./TaskState.js";
@@ -27,14 +27,14 @@ class Task {
27
27
  name,
28
28
  initializer = noop,
29
29
  cycleFunction,
30
- computeProgress = returnZero,
30
+ computeProgress,
31
31
  dependencies = [],
32
32
  estimatedDuration = 1
33
33
  }
34
34
  ) {
35
35
 
36
- assert.typeOf(cycleFunction, "function", 'cycleFunction');
37
- assert.typeOf(estimatedDuration, 'number', 'estimatedDuration');
36
+ assert.isFunction(cycleFunction, 'cycleFunction');
37
+ assert.isNumber(estimatedDuration, 'estimatedDuration');
38
38
 
39
39
 
40
40
  this.dependencies = dependencies;
@@ -59,7 +59,12 @@ class Task {
59
59
  */
60
60
  this.initialize = initializer;
61
61
 
62
- this.computeProgress = computeProgress;
62
+ if (computeProgress !== undefined) {
63
+
64
+ // override progress function
65
+ this.computeProgress = computeProgress;
66
+
67
+ }
63
68
 
64
69
  this.on = {
65
70
  started: new Signal(),
@@ -87,6 +92,18 @@ class Task {
87
92
  this.__executedCycleCount = 0;
88
93
  }
89
94
 
95
+ computeProgress() {
96
+
97
+ const cycles = this.__executedCycleCount;
98
+
99
+ if (cycles === 0) {
100
+ return 0;
101
+ }
102
+
103
+ // inverse logarithmic progression, never reaches 1
104
+ return 1 - (1 / cycles);
105
+ }
106
+
90
107
  /**
91
108
  * Time in milliseconds that the task has been executing for, suspended time does not count
92
109
  * @returns {number}
@@ -0,0 +1,29 @@
1
+ import Task from "../Task.js";
2
+ import { TaskSignal } from "../TaskSignal.js";
3
+ import { assert } from "../../../assert.js";
4
+
5
+ /**
6
+ *
7
+ * @param {string} name
8
+ * @param {Iterator} iterator
9
+ * @param {TaskSignal.Continue|TaskSignal.Yield} [cycle_signal] what to signal at the end of each iteration, Continue will provide better throughput, whereas Yield will produce very low load on the CPU
10
+ * @returns {Task}
11
+ */
12
+ export function iteratorTask(name, iterator, cycle_signal = TaskSignal.Continue) {
13
+ assert.defined(iterator, 'iterator');
14
+ assert.notNull(iterator, 'iterator');
15
+ assert.isFunction(iterator.next, 'iterator.next');
16
+
17
+ return new Task({
18
+ name,
19
+ cycleFunction() {
20
+ const next = iterator.next();
21
+
22
+ if (next.done) {
23
+ return TaskSignal.EndSuccess
24
+ } else {
25
+ return cycle_signal;
26
+ }
27
+ }
28
+ })
29
+ }
@@ -1,6 +1,8 @@
1
1
  import { BitSet } from "../../../core/binary/BitSet.js";
2
2
  import { InstancedMeshGroup } from "../../graphics/geometry/instancing/InstancedMeshGroup.js";
3
- import { queryBinaryNode_FrustumIntersections_Data } from "../../../core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js";
3
+ import {
4
+ queryBinaryNode_FrustumIntersections_Data
5
+ } from "../../../core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js";
4
6
  import Quaternion from "../../../core/geom/Quaternion.js";
5
7
  import Vector3 from "../../../core/geom/Vector3.js";
6
8
  import { compose_matrix4_array } from "../../../core/geom/3d/compose_matrix4_array.js";
@@ -30,6 +32,10 @@ export class ViewState {
30
32
  * @type {InstancedMeshGroup}
31
33
  */
32
34
  this.instances = new InstancedMeshGroup();
35
+
36
+ // make shrinking unlikely to prevent thrashing
37
+ this.instances.shrinkFactor = 0.4;
38
+ this.instances.shrinkConstant = 1024;
33
39
 
34
40
  /**
35
41
  *
@@ -20,6 +20,7 @@ import TaskState from "../../../../core/process/task/TaskState.js";
20
20
  import {
21
21
  bvh_query_user_data_overlaps_frustum
22
22
  } from "../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_frustum.js";
23
+ import { iteratorTask } from "../../../../core/process/task/util/iteratorTask.js";
23
24
 
24
25
 
25
26
  /**
@@ -34,6 +35,31 @@ const scratch_point = new SurfacePoint3();
34
35
  */
35
36
  const scratch_ray_0 = new Float32Array(6);
36
37
 
38
+ /**
39
+ *
40
+ * @param {ShadedGeometrySystem} system
41
+ * @return {Generator}
42
+ */
43
+ function* maintenance_generator(system) {
44
+ while (true) { //infinite loop
45
+
46
+ const contexts = system.__view_contexts;
47
+
48
+ for (const [key, value] of contexts) {
49
+
50
+ if (value === undefined) {
51
+ continue;
52
+ }
53
+
54
+ yield* value.maintain();
55
+
56
+ }
57
+
58
+ // a small break between re-runs
59
+ yield;
60
+ }
61
+ }
62
+
37
63
  export class ShadedGeometrySystem extends System {
38
64
  /**
39
65
  *
@@ -100,8 +126,15 @@ export class ShadedGeometrySystem extends System {
100
126
  }
101
127
  }
102
128
  });
129
+
130
+ this.__maintenance_task = iteratorTask(
131
+ 'Maintenance',
132
+ maintenance_generator(this),
133
+ TaskSignal.Yield
134
+ );
103
135
  }
104
136
 
137
+
105
138
  /**
106
139
  *
107
140
  * @returns {ExplicitBinaryBoundingVolumeHierarchy}
@@ -222,6 +255,9 @@ export class ShadedGeometrySystem extends System {
222
255
  this.__optimization_task.state.set(TaskState.INITIAL);
223
256
  engine.executor.run(this.__optimization_task);
224
257
 
258
+ this.__maintenance_task.state.set(TaskState.INITIAL);
259
+ engine.executor.run(this.__maintenance_task);
260
+
225
261
  readyCallback();
226
262
  }
227
263
 
@@ -233,6 +269,7 @@ export class ShadedGeometrySystem extends System {
233
269
  const view_list = graphics.views.elements;
234
270
 
235
271
  engine.executor.removeTask(this.__optimization_task);
272
+ engine.executor.removeTask(this.__maintenance_task);
236
273
 
237
274
  view_list.forEach(this.__handle_view_removed, this);
238
275
  view_list.on.added.remove(this.__handle_view_added, this);
@@ -30,6 +30,25 @@ export class ShadedGeometryRendererContext {
30
30
  ];
31
31
  }
32
32
 
33
+ /**
34
+ * Performs maintenance on the context, mainly to check the hash tables for changes
35
+ * @return {Generator}
36
+ */
37
+ * maintain() {
38
+ const adapters = this.adapters;
39
+ for (let i = 0; i < adapters.length; i++) {
40
+ const adapter = adapters[i];
41
+
42
+ if (adapter === undefined) {
43
+ // adapter may disappear between invocations, since this is a continuation-type function
44
+ continue;
45
+ }
46
+
47
+ // delegate to adapter's maintenance
48
+ yield* adapter.maintain();
49
+ }
50
+ }
51
+
33
52
  /**
34
53
  *
35
54
  * @param {number} index
@@ -2,8 +2,8 @@ import { array_quick_sort_by_comparator } from "../../../../../../core/collectio
2
2
 
3
3
  /**
4
4
  *
5
- * @param {THREE.Object3D} a
6
- * @param {THREE.Object3D} b
5
+ * @param {THREE.Object3D|{material?:Material}} a
6
+ * @param {THREE.Object3D|{material?:Material}} b
7
7
  * @returns {number}
8
8
  */
9
9
  function compare_by_material(a, b) {
@@ -96,6 +96,15 @@ export class AbstractRenderAdapter {
96
96
  this.__object_count = 0;
97
97
  }
98
98
 
99
+ /**
100
+ * Used to perform infrequent housekeeping on the adapter's internal data structures
101
+ * Returns a maintenance sequence
102
+ * @return {Generator}
103
+ */
104
+ * maintain() {
105
+ // override as necessary
106
+ }
107
+
99
108
  /**
100
109
  * @returns {THREE.Object3D[]}
101
110
  */
@@ -1076,12 +1076,24 @@ export class LightManager {
1076
1076
  }
1077
1077
 
1078
1078
  /**
1079
- *
1079
+ * Set resolution of the cluster texture, higher resolution will result in less load on the GPU, but will take up more RAM to represent and more time to build the clusters each frame
1080
1080
  * @param {number} x
1081
1081
  * @param {number} y
1082
1082
  * @param {number} z
1083
1083
  */
1084
1084
  setTileMapResolution(x, y, z) {
1085
+ assert.isNumber(x,'x');
1086
+ assert.isNonNegativeInteger(x,'x');
1087
+ assert.isFiniteNumber(x,'x');
1088
+
1089
+ assert.isNumber(y,'y');
1090
+ assert.isNonNegativeInteger(y,'y');
1091
+ assert.isFiniteNumber(y,'y');
1092
+
1093
+ assert.isNumber(z,'z');
1094
+ assert.isNonNegativeInteger(z,'z');
1095
+ assert.isFiniteNumber(z,'z');
1096
+
1085
1097
  const r = this.__tiles_resolution;
1086
1098
 
1087
1099
  if (x === r.x && y === r.y && z === r.z) {
@@ -0,0 +1,5 @@
1
+ import {EnginePlugin} from "../../../../plugin/EnginePlugin";
2
+
3
+ export class ForwardPlusRenderingPlugin extends EnginePlugin {
4
+ setClusterResolution(x: number, y: number, z: number): void
5
+ }
@@ -8,7 +8,10 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
8
8
  constructor() {
9
9
  super();
10
10
 
11
- this.__light_manager = new LightManager();
11
+ const lightManager = new LightManager();
12
+ lightManager.setTileMapResolution(16, 8, 8);
13
+
14
+ this.__light_manager = lightManager;
12
15
 
13
16
  this.__resolution = new ThreeVector2();
14
17
 
@@ -19,6 +22,16 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
19
22
  });
20
23
  }
21
24
 
25
+ /**
26
+ * Set resolution of the cluster texture, higher resolution will result in less load on the GPU, but will take up more RAM to represent and more time to build the clusters each frame
27
+ * @param {number} x
28
+ * @param {number} y
29
+ * @param {number} z
30
+ */
31
+ setClusterResolution(x, y, z) {
32
+ this.__light_manager.setTileMapResolution(x, y, z);
33
+ }
34
+
22
35
  /**
23
36
  *
24
37
  * @param {CameraView} view
@@ -37,10 +50,6 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
37
50
  }
38
51
 
39
52
  async startup() {
40
- const lm = this.__light_manager;
41
-
42
- lm.setTileMapResolution(16, 8, 8);
43
-
44
53
  const graphics = this.engine.graphics;
45
54
 
46
55
  graphics.getMaterialManager().addCompileStep(this.__material_transformer);
@@ -0,0 +1,7 @@
1
+ import Signal from "../../../core/events/signal/Signal";
2
+
3
+ export class InputDeviceButton{
4
+ readonly up: Signal
5
+ readonly down: Signal
6
+ readonly is_down: boolean
7
+ }
@@ -0,0 +1,22 @@
1
+ import Signal from "../../../core/events/signal/Signal.js";
2
+
3
+ /**
4
+ * Representation of an input device key
5
+ */
6
+ export class InputDeviceButton {
7
+ /**
8
+ * Button press
9
+ * @type {Signal}
10
+ */
11
+ down = new Signal()
12
+ /**
13
+ * Button release
14
+ * @type {Signal}
15
+ */
16
+ up = new Signal()
17
+ /**
18
+ * is button currently being pressed?
19
+ * @type {boolean}
20
+ */
21
+ is_down = false
22
+ }
@@ -1,3 +1,14 @@
1
+ import {InputDeviceButton} from "./InputDeviceButton";
2
+ import Signal from "../../../core/events/signal/Signal";
3
+
1
4
  export default class KeyboardDevice {
5
+ constructor(domElement:EventTarget|Element)
6
+
7
+ readonly on: {up:Signal,down:Signal}
8
+
9
+ readonly keys:{[key:string]:InputDeviceButton}
10
+
11
+ start():void
2
12
 
13
+ stop():void
3
14
  }
@@ -5,6 +5,7 @@
5
5
  import Signal from "../../../core/events/signal/Signal.js";
6
6
  import { KeyCodes } from './KeyCodes.js';
7
7
  import { KeyboardEvents } from "./events/KeyboardEvents.js";
8
+ import { InputDeviceButton } from "./InputDeviceButton.js";
8
9
 
9
10
  /**
10
11
  *
@@ -66,21 +67,23 @@ class KeyboardDevice {
66
67
  up: new Signal()
67
68
  };
68
69
 
69
- //initialize separate events for each key
70
+
71
+ /**
72
+ *
73
+ * @type {Object<InputDeviceButton>}
74
+ */
70
75
  const keys = this.keys = {};
71
76
 
72
77
  const codeToKeyNameMap = [];
73
78
 
79
+ //initialize separate events for each key
74
80
  for (let keyName in KeyCodes) {
75
81
 
76
82
  const keyCode = KeyCodes[keyName];
77
83
 
78
84
  codeToKeyNameMap[keyCode] = keyName;
79
85
 
80
- keys[keyName] = {
81
- down: new Signal(),
82
- up: new Signal()
83
- };
86
+ keys[keyName] = new InputDeviceButton();
84
87
  }
85
88
 
86
89
  this.__handlerKeyDown = (event) => {
@@ -91,7 +94,11 @@ class KeyboardDevice {
91
94
  const keyName = codeToKeyNameMap[keyCode];
92
95
 
93
96
  if (keyName !== undefined) {
94
- keys[keyName].down.send1(event);
97
+ const button = keys[keyName];
98
+
99
+ button.is_down = true;
100
+ button.down.send1(event);
101
+
95
102
  }
96
103
  }
97
104
 
@@ -104,7 +111,10 @@ class KeyboardDevice {
104
111
  const keyName = codeToKeyNameMap[keyCode];
105
112
 
106
113
  if (keyName !== undefined) {
107
- keys[keyName].up.send1(event);
114
+ const button = keys[keyName];
115
+
116
+ button.is_down = false;
117
+ button.up.send1(event);
108
118
  }
109
119
  }
110
120
  }
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.44.8",
8
+ "version": "2.45.2",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",