@woosh/meep-engine 3.14.3 → 3.14.5

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 (137) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts +30 -0
  3. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts.map +1 -0
  4. package/src/core/geom/3d/mat4/m4_rigidity_defect.js +71 -0
  5. package/src/engine/Engine.d.ts.map +1 -1
  6. package/src/engine/Engine.js +733 -726
  7. package/src/engine/network/NetworkSession.d.ts +24 -4
  8. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  9. package/src/engine/network/NetworkSession.js +28 -4
  10. package/src/engine/network/orchestrator/NetworkPeer.d.ts +96 -7
  11. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  12. package/src/engine/network/orchestrator/NetworkPeer.js +392 -68
  13. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +6 -1
  14. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  15. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +6 -0
  16. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +105 -13
  17. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  18. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +306 -59
  19. package/src/engine/network/replication/Replicator.d.ts +43 -7
  20. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  21. package/src/engine/network/replication/Replicator.js +309 -111
  22. package/src/engine/network/sim/ActionLog.d.ts +23 -3
  23. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  24. package/src/engine/network/sim/ActionLog.js +26 -3
  25. package/src/engine/network/sim/ActionRecordCursor.d.ts +82 -0
  26. package/src/engine/network/sim/ActionRecordCursor.d.ts.map +1 -0
  27. package/src/engine/network/sim/ActionRecordCursor.js +120 -0
  28. package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
  29. package/src/engine/network/sim/RewindEngine.js +13 -29
  30. package/src/engine/network/sim/SimAction.d.ts +15 -0
  31. package/src/engine/network/sim/SimAction.d.ts.map +1 -1
  32. package/src/engine/network/sim/SimAction.js +15 -0
  33. package/src/engine/network/sim/SimActionExecutor.d.ts +51 -2
  34. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  35. package/src/engine/network/sim/SimActionExecutor.js +74 -7
  36. package/src/engine/network/transport/Channel.d.ts +1 -1
  37. package/src/engine/network/transport/Channel.js +1 -1
  38. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  39. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  40. package/src/engine/simulation/Ticker.js +4 -1
  41. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  42. package/src/engine/ui/GUIEngine.js +6 -0
  43. package/src/shade/playground/particle_system/README.md +98 -0
  44. package/src/shade/playground/particle_system/index.html +124 -0
  45. package/src/shade/playground/particle_system/main.d.ts +2 -0
  46. package/src/shade/playground/particle_system/main.d.ts.map +1 -0
  47. package/src/shade/playground/particle_system/main.js +180 -0
  48. package/src/shade/playground/particle_system/particle_scene.d.ts +52 -0
  49. package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -0
  50. package/src/shade/playground/particle_system/particle_scene.js +553 -0
  51. package/src/shade/playground/skinned_mesh_soup/README.md +143 -0
  52. package/src/shade/playground/skinned_mesh_soup/index.html +202 -0
  53. package/src/shade/playground/skinned_mesh_soup/main.d.ts +7 -0
  54. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -0
  55. package/src/shade/playground/skinned_mesh_soup/main.js +567 -0
  56. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +29 -0
  57. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -0
  58. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +428 -0
  59. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  60. package/src/shade/renderer/animation/GPUAnimationManager.js +1958 -1796
  61. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +357 -0
  62. package/src/shade/renderer/animation/skin_bind_transform.d.ts +25 -0
  63. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +1 -0
  64. package/src/shade/renderer/animation/skin_bind_transform.js +33 -0
  65. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +67 -0
  66. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -0
  67. package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +199 -0
  68. package/src/shade/renderer/animation/skin_frame_defect.d.ts +34 -0
  69. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +1 -0
  70. package/src/shade/renderer/animation/skin_frame_defect.js +132 -0
  71. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +24 -0
  72. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +1 -0
  73. package/src/shade/renderer/animation/skin_joint_matrix.js +46 -0
  74. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +70 -0
  75. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +1 -0
  76. package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +216 -0
  77. package/src/shade/renderer/animation/skin_test_fixtures.d.ts +86 -0
  78. package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +1 -0
  79. package/src/shade/renderer/animation/skin_test_fixtures.js +250 -0
  80. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +18 -4
  81. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts.map +1 -1
  82. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +18 -4
  83. package/src/shade/renderer/particles/DESIGN.md +5 -5
  84. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +1 -1
  85. package/src/shade/renderer/particles/cull/chunk_particle_cull.js +3 -1
  86. package/src/shade/renderer/particles/data/chunk_particle_emitter.js +1 -1
  87. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +43 -0
  88. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -0
  89. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +44 -0
  90. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +33 -59
  91. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  92. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +185 -118
  93. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +15 -10
  94. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  95. package/src/shade/renderer/particles/graph/compile_particle_graph.js +205 -170
  96. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +44 -0
  97. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -0
  98. package/src/shade/renderer/particles/graph/particle_graph_authoring.js +108 -0
  99. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +89 -0
  100. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -0
  101. package/src/shade/renderer/particles/isa/InstructionStream.js +162 -0
  102. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +1 -48
  103. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
  104. package/src/shade/renderer/particles/isa/ParticleAssembler.js +25 -114
  105. package/src/shade/renderer/particles/particle_prototype.d.ts.map +1 -1
  106. package/src/shade/renderer/particles/particle_prototype.js +181 -180
  107. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +4 -4
  108. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  109. package/src/shade/renderer/particles/runtime/create_particle_effect.js +19 -19
  110. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +1 -1
  111. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +4 -2
  112. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +4 -4
  113. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  114. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +7 -5
  115. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
  116. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +3 -1
  117. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  118. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +3 -1
  119. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  120. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +5 -2
  121. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  122. package/src/shade/renderer/particles/shaders/shader_particle_render.js +9 -2
  123. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  124. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -1
  125. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +1 -1
  126. package/src/shade/renderer/scene/SkinnedMesh.d.ts +11 -0
  127. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
  128. package/src/shade/renderer/scene/SkinnedMesh.js +11 -0
  129. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts +33 -3
  130. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts.map +1 -1
  131. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.js +58 -37
  132. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts +0 -54
  133. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts.map +0 -1
  134. package/src/shade/renderer/particles/graph/ParticleGraph.js +0 -99
  135. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts +0 -2
  136. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts.map +0 -1
  137. package/src/shade/renderer/particles/prototypeParticleSystem.js +0 -448
@@ -1,726 +1,733 @@
1
- /**
2
- *
3
- */
4
-
5
- import { assert } from "../core/assert.js";
6
- import Vector1 from "../core/geom/Vector1.js";
7
- import { Localization } from "../core/localization/Localization.js";
8
- import { ModuleRegistry } from "../core/model/ModuleRegistry.js";
9
- import ObservedBoolean from "../core/model/ObservedBoolean.js";
10
- import ConcurrentExecutor from '../core/process/executor/ConcurrentExecutor.js';
11
- import EmptyView from "../view/elements/EmptyView.js";
12
- import { ViewStack } from "../view/elements/navigation/ViewStack.js";
13
- import { GraphicsContextFailureView } from "../view/graphics/GraphicsContextFailureView.js";
14
-
15
- import { AssetManager } from './asset/AssetManager.js';
16
- import { AssetPreloader } from "./asset/preloader/AssetPreloader.js";
17
- import { MetricCollection } from "./development/performance/MetricCollection.js";
18
- import { MetricStatistics } from "./development/performance/MetricStatistics.js";
19
- import { PeriodicConsolePrinter } from "./development/performance/monitor/PeriodicConsolePrinter.js";
20
- import { EntityManager } from "./ecs/EntityManager.js";
21
- import { BinarySerializationRegistry } from "./ecs/storage/binary/BinarySerializationRegistry.js";
22
- import { GraphicsEngine } from './graphics3/GraphicsEngine.js';
23
- import { GamepadDevice } from "./input/devices/GamepadDevice.js";
24
- import KeyboardDevice from "./input/devices/KeyboardDevice.js";
25
- import { PointerDevice } from "./input/devices/PointerDevice.js";
26
- import { StaticKnowledgeDatabase, StaticKnowledgeState } from "./knowledge/database/StaticKnowledgeDatabase.js";
27
- import { logger } from "./logging/GlobalLogger.js";
28
- import { OptionGroup } from "./options/OptionGroup.js";
29
- import { EnginePluginManager } from "./plugin/EnginePluginManager.js";
30
- import SceneManager from "./scene/SceneManager.js";
31
- import Ticker from "./simulation/Ticker.js";
32
- import SoundEngine from './sound/SoundEngine.js';
33
-
34
- import GUIEngine from './ui/GUIEngine.js';
35
-
36
-
37
- class EngineSettings {
38
- graphics_control_viewport_size = new ObservedBoolean(true);
39
- simulation_speed = new Vector1(1);
40
- input_mouse_sensitivity = new Vector1(5);
41
- }
42
-
43
- const METRIC_ID_FRAME = 'frame_delay';
44
- const METRIC_ID_RENDER = 'render_time';
45
- const METRIC_ID_SIMULATION = 'simulation_time';
46
-
47
- /**
48
- * How long a context loss is tolerated before the simulation is paused as well, in seconds. Most losses
49
- * are resolved in well under a second, and stopping the world for those would be more disruptive than
50
- * letting it run; a longer outage is worth pausing for, as the player cannot see what is happening.
51
- * @type {number}
52
- */
53
- const CONTEXT_LOSS_SIMULATION_PAUSE_DELAY = 2;
54
-
55
- class Engine {
56
- #last_frame_timestamp = 0;
57
-
58
- /**
59
- * Handle of the pending render-loop frame, `-1` when the loop is not running
60
- * @type {number}
61
- */
62
- #animation_frame_handle = -1;
63
-
64
- /**
65
- * Handle of the pending "pause the simulation, this outage is taking a while" timer, `-1` when none
66
- * @type {number}
67
- */
68
- #context_loss_pause_timer = -1;
69
-
70
- /**
71
- * Whether the simulation was paused because of a context loss, and therefore should be resumed once
72
- * the context comes back
73
- * @type {boolean}
74
- */
75
- #paused_by_context_loss = false;
76
-
77
- /**
78
- * Absent when the engine is constructed with `enableGraphics: false`.
79
- * @type {GraphicsEngine|null}
80
- */
81
- graphics = null;
82
-
83
- /**
84
- * Absent when the engine is constructed with `enableAudio: false`.
85
- * @type {SoundEngine|null}
86
- */
87
- sound = null;
88
-
89
- viewStack = new ViewStack();
90
-
91
- /**
92
- *
93
- * Static database
94
- * Can contain tables of structured data that the application often works with.
95
- * Engine startup will load all tables ensuring that data is ready once startup finishes
96
- * Use sparingly, this is not suitable for very large amounts of data as all the data will be in memory and deserialization will stall engine startup
97
- * Example: inventory item table, monster table
98
- * @readonly
99
- * @type {StaticKnowledgeDatabase}
100
- */
101
- staticKnowledge = new StaticKnowledgeDatabase();
102
-
103
- /**
104
- * @readonly
105
- * @type {EnginePluginManager}
106
- */
107
- plugins = new EnginePluginManager();
108
-
109
- /**
110
- * Main simulation ticker
111
- * @readonly
112
- * @type {Ticker}
113
- */
114
- ticker = new Ticker();
115
-
116
- /**
117
- * Default executor, enables time-sharing concurrency
118
- * @readonly
119
- * @type {ConcurrentExecutor}
120
- */
121
- executor = new ConcurrentExecutor(1, 10);
122
-
123
- /**
124
- *
125
- * @param {EnginePlatform} platform
126
- * @param {EntityManager} [entityManager]
127
- * @param {boolean} [enableGraphics]
128
- * @param {boolean} [enableAudio]
129
- * @param {boolean} [debug]
130
- * @constructor
131
- */
132
- constructor(platform, {
133
- entityManager,
134
- enableGraphics = true,
135
- enableAudio = true,
136
- debug = true
137
- } = {}) {
138
- assert.defined(platform, 'platform');
139
-
140
- /**
141
- *
142
- * @type {EnginePlatform}
143
- */
144
- this.platform = platform;
145
-
146
- this.staticKnowledge.validation_enabled = debug;
147
-
148
-
149
- /**
150
- *
151
- * @type {MetricCollection}
152
- */
153
- this.performance = new MetricCollection();
154
-
155
-
156
- this.__using_external_entity_manager = entityManager !== undefined;
157
-
158
- //setup entity component system
159
- if (this.__using_external_entity_manager) {
160
- // external entity manager provided
161
- this.entityManager = entityManager;
162
- } else {
163
- this.entityManager = new EntityManager();
164
- }
165
-
166
-
167
- /**
168
- *
169
- * @type {AssetManager<Engine>}
170
- */
171
- this.assetManager = new AssetManager({
172
- context: this,
173
- executor: this.executor
174
- });
175
-
176
- this.__performacne_monitor = new PeriodicConsolePrinter(15, () => {
177
- const metrics = this.performance;
178
- const stats = new MetricStatistics();
179
-
180
- // RENDER
181
- const m_render = metrics.get(METRIC_ID_RENDER);
182
-
183
- m_render.computeStats(stats);
184
-
185
- const v_render = (stats.mean * 1000).toFixed(2);
186
-
187
- // SIMULATION
188
- const m_sim = metrics.get(METRIC_ID_SIMULATION);
189
-
190
- m_sim.computeStats(stats);
191
-
192
- const v_sim = (stats.mean * 1000).toFixed(2);
193
-
194
- // FPS
195
-
196
- const m_frame = metrics.get(METRIC_ID_FRAME);
197
-
198
- m_frame.computeStats(stats);
199
-
200
- const v_fps = (1 / stats.mean).toFixed(2);
201
-
202
-
203
- // clear stats
204
- m_render.clear();
205
- m_sim.clear();
206
- m_frame.clear();
207
-
208
- return `FPS: ${v_fps}, RENDER: ${v_render}ms, SIMULATION: ${v_sim}ms`;
209
- });
210
-
211
-
212
- if (!this.__using_external_entity_manager) {
213
- // subscribe simulator to common ticker
214
- this.ticker.onTick.add(timeDelta => {
215
- const t0 = performance.now();
216
- this.entityManager.update(timeDelta);
217
-
218
- const t1 = performance.now();
219
- const duration = (t1 - t0) / 1000;
220
-
221
- // record frame time
222
- this.performance.get(METRIC_ID_SIMULATION).record(duration);
223
- });
224
- }
225
-
226
- // initialize performance metrics
227
- this.performance.create({
228
- name: METRIC_ID_RENDER
229
- });
230
- this.performance.create({
231
- name: METRIC_ID_SIMULATION
232
- });
233
- this.performance.create({
234
- name: METRIC_ID_FRAME
235
- });
236
-
237
- /**
238
- *
239
- * @type {OptionGroup}
240
- */
241
- this.options = new OptionGroup("options");
242
-
243
- /**
244
- *
245
- * @type {ModuleRegistry}
246
- */
247
- this.moduleRegistry = new ModuleRegistry();
248
-
249
- /**
250
- * @readonly
251
- * @type {BinarySerializationRegistry}
252
- */
253
- this.binarySerializationRegistry = new BinarySerializationRegistry();
254
-
255
- this.settings = new EngineSettings();
256
-
257
-
258
- /**
259
- * @deprecated use plugins instead
260
- */
261
- this.services = {};
262
-
263
- /**
264
- *
265
- * @type {Storage}
266
- */
267
- this.storage = this.platform.getStorage();
268
-
269
-
270
- this.localization = new Localization();
271
- this.localization.setAssetManager(this.assetManager);
272
-
273
-
274
- if (enableGraphics !== false) {
275
-
276
- /**
277
- * @type {GraphicsEngine}
278
- */
279
- const graphicsEngine = new GraphicsEngine();
280
-
281
- this.graphics = graphicsEngine;
282
-
283
- // The renderer is not started here. Acquiring a WebGPU device is asynchronous and a
284
- // constructor is the wrong place to fail from, so `start()` does it — which is also
285
- // what makes the device restartable (E1).
286
-
287
- graphicsEngine.on.contextLost.add(this.#handle_context_lost, this);
288
- graphicsEngine.on.contextRestored.add(this.#handle_context_restored, this);
289
- graphicsEngine.on.contextFailed.add(this.#handle_context_failed, this);
290
-
291
- } else {
292
- logger.info('enableGraphics option is not set, no graphics engine will be created');
293
- }
294
-
295
- if (enableAudio !== false) {
296
- //sound engine
297
- const soundEngine = new SoundEngine();
298
- soundEngine.volume = 1;
299
-
300
- /**
301
- *
302
- * @type {SoundEngine}
303
- */
304
- this.sound = soundEngine;
305
- } else {
306
- logger.info('enableAudio option is not set, no audio engine will be created');
307
- }
308
-
309
- /**
310
- * Graphical User Interface engine
311
- * @type {GUIEngine}
312
- */
313
- this.gui = new GUIEngine();
314
-
315
- /**
316
- * @readonly
317
- * @type {SceneManager}
318
- */
319
- this.sceneManager = new SceneManager(this.entityManager, this.ticker.clock);
320
-
321
- this.initializeViews();
322
-
323
- this.devices = {
324
- pointer: new PointerDevice(this.viewStack.el),
325
- keyboard: new KeyboardDevice(this.viewStack.el),
326
- gamepad: new GamepadDevice()
327
- };
328
-
329
- this.devices.pointer.start();
330
- this.devices.keyboard.start();
331
- this.devices.gamepad.start();
332
-
333
- //process settings
334
- this.initializeSettings();
335
-
336
- logger.info("engine initialized");
337
-
338
- /**
339
- * Toggles GraphicsEngine rendering on and off
340
- * @type {boolean}
341
- */
342
- this.renderingEnabled = true;
343
-
344
-
345
- this.plugins.initialize(this);
346
- }
347
-
348
- #clear_context_loss_pause_timer() {
349
- if (this.#context_loss_pause_timer !== -1) {
350
- clearTimeout(this.#context_loss_pause_timer);
351
- this.#context_loss_pause_timer = -1;
352
- }
353
- }
354
-
355
- /**
356
- * Pauses the simulation, if it is running. {@link Ticker#pause} freezes the clock, so no simulation
357
- * time accrues while we wait and the first step after resuming is a normal-sized one.
358
- *
359
- * A ticker that is already paused is left alone - somebody else owns that pause and it is not ours to
360
- * undo when the context comes back.
361
- */
362
- #pause_simulation_for_context_loss() {
363
- this.#context_loss_pause_timer = -1;
364
-
365
- if (this.#paused_by_context_loss) {
366
- return;
367
- }
368
-
369
- const ticker = this.ticker;
370
-
371
- if (!ticker.is_active || ticker.is_paused) {
372
- return;
373
- }
374
-
375
- this.#paused_by_context_loss = true;
376
-
377
- logger.warn('Graphics context outage is taking a while, pausing simulation');
378
-
379
- ticker.pause();
380
- }
381
-
382
- #handle_context_lost() {
383
- logger.warn('Graphics context lost, waiting for recovery');
384
-
385
- this.#clear_context_loss_pause_timer();
386
-
387
- this.#context_loss_pause_timer = setTimeout(
388
- () => this.#pause_simulation_for_context_loss(),
389
- CONTEXT_LOSS_SIMULATION_PAUSE_DELAY * 1000
390
- );
391
- }
392
-
393
- #handle_context_restored() {
394
- this.#clear_context_loss_pause_timer();
395
-
396
- if (this.#paused_by_context_loss) {
397
- this.#paused_by_context_loss = false;
398
-
399
- if (this.ticker.is_active) {
400
- this.ticker.resume();
401
- }
402
- }
403
-
404
- logger.info('Graphics context restored, rendering resumed');
405
- }
406
-
407
- /**
408
- * @param {ShadeDeviceFailure} reason carries a message written to be shown to a person
409
- */
410
- #handle_context_failed(reason) {
411
- logger.error(`Graphics context recovery abandoned (${reason}), no further rendering is possible`);
412
-
413
- this.#clear_context_loss_pause_timer();
414
-
415
- // nothing can be drawn any more, so let the simulation stop rather than burn cycles invisibly.
416
- // Application state is left intact, which keeps a save-on-failure handler viable.
417
- this.#pause_simulation_for_context_loss();
418
-
419
- this.viewStack.addChild(new GraphicsContextFailureView());
420
- }
421
-
422
- #initialize_audio() {
423
- const sound = this.sound;
424
- if (sound === null) {
425
- // no sound engine
426
- return;
427
- }
428
-
429
- // hook up context resumption to input
430
- if (this.devices.pointer !== undefined) {
431
-
432
- this.devices.pointer.on.down.addOne(sound.resumeContext, sound);
433
- this.devices.keyboard.on.down.addOne(sound.resumeContext, sound);
434
-
435
- }
436
- }
437
-
438
- initializeViews() {
439
-
440
- const gameView = new EmptyView();
441
-
442
- gameView.addClass('game-view');
443
-
444
- gameView.css({
445
- left: 0,
446
- top: 0,
447
- position: "absolute",
448
- pointerEvents: "none"
449
- });
450
- this.gameView = gameView;
451
-
452
- this.viewStack.push(gameView, 'game-view');
453
-
454
- if (this.graphics !== null) {
455
-
456
- const viewport = this.graphics.viewport;
457
-
458
- viewport.css({
459
- pointerEvents: "auto"
460
- });
461
-
462
- gameView.addChild(viewport);
463
- //bind size of renderer viewport to game view
464
- viewport.bindSignal(gameView.size.onChanged, viewport.size.set.bind(viewport.size));
465
- gameView.on.linked.add(function () {
466
- viewport.size.copy(gameView.size);
467
- });
468
-
469
- }
470
- }
471
-
472
- initializeSettings() {
473
- logger.info('Initializing engine settings...');
474
-
475
- const engine = this;
476
-
477
- function setViewportToWindowSize() {
478
- // get parent element
479
- let parentElement = engine.viewStack.el.parentElement;
480
-
481
- while (parentElement !== null && parentElement.innerWidth === undefined) {
482
- // traverse up until we find an element with defined dimensions
483
- parentElement = parentElement.parentElement;
484
- }
485
-
486
- if (parentElement === null || parentElement === undefined) {
487
- // no parent element, default to window
488
- parentElement = window;
489
- }
490
-
491
- engine.viewStack.size.set(parentElement.innerWidth, parentElement.innerHeight);
492
- }
493
-
494
- this.settings.graphics_control_viewport_size.process(function (value) {
495
- if (value) {
496
- setViewportToWindowSize();
497
- window.addEventListener("resize", setViewportToWindowSize, false);
498
- } else {
499
- window.removeEventListener("resize", setViewportToWindowSize);
500
- }
501
- });
502
-
503
- logger.info('Engine settings initilized.');
504
- }
505
-
506
- benchmark() {
507
- const duration = 2000;
508
- let count = 0;
509
- const t0 = Date.now();
510
- let t1;
511
- while (true) {
512
- this.entityManager.update(0.0000000001);
513
- t1 = Date.now();
514
- if (t1 - t0 > duration) {
515
- break;
516
- }
517
- count++;
518
- }
519
- //normalize
520
- const elapsed = (t1 - t0) / 1000;
521
- const rate = (count / elapsed);
522
- return rate;
523
- }
524
-
525
- /**
526
- * Returns preloader object
527
- * @param {String} listURL
528
- */
529
- loadAssetList(listURL) {
530
- const preloader = new AssetPreloader();
531
- const assetManager = this.assetManager;
532
- assetManager.get({
533
- path: listURL, type: "json", callback: function (asset) {
534
- preloader.addAll(asset.create());
535
- preloader.load(assetManager);
536
- }
537
- });
538
- return preloader;
539
- }
540
-
541
- render() {
542
- assert.isInstanceOf(this, Engine, 'this', 'Engine');
543
-
544
- const graphics = this.graphics;
545
-
546
- if (graphics === null) {
547
- // headless
548
- return;
549
- }
550
-
551
- if (graphics.autoDraw) {
552
- // request redraw
553
- graphics.needDraw = true;
554
- }
555
-
556
- if (this.renderingEnabled && graphics.needDraw) {
557
-
558
- const metrics = this.performance;
559
-
560
- const t0 = performance.now();
561
-
562
- graphics.render();
563
-
564
- const t1 = performance.now();
565
- const frame_time = (t1 - t0) / 1000;
566
-
567
- // record frame time
568
- metrics.get(METRIC_ID_RENDER).record(frame_time);
569
- }
570
- }
571
-
572
- /**
573
- * Startup
574
- * @returns {Promise}
575
- */
576
- async start() {
577
- // First, and awaited: systems read `graphics.viewport` and size themselves off it during
578
- // their own startup, so the device has to exist before the entity manager runs.
579
- if (this.graphics !== null) {
580
- await this.graphics.start();
581
- }
582
-
583
- this.assetManager.startup();
584
-
585
- this.#initialize_audio();
586
-
587
- const promiseEntityManager = () => {
588
- return new Promise((resolve, reject) => {
589
- //initialize entity manager
590
- this.entityManager.startup(resolve, reject);
591
- });
592
- }
593
-
594
- this.__performacne_monitor.start();
595
-
596
- const sound = this.sound;
597
-
598
- const soundStarted = sound === null
599
- ? Promise.resolve()
600
- : sound.start();
601
-
602
- const knowledge = this.staticKnowledge;
603
-
604
- // static knowledge is engine-lifetime content and a stop does not drop it, so a restart keeps
605
- // what the first start loaded. `load` accepts the Initial state only; callers that want the
606
- // tables re-read call `reset` first.
607
- const knowledgeLoaded = knowledge.state === StaticKnowledgeState.Initial
608
- ? knowledge.load(this.assetManager, this.executor)
609
- : Promise.resolve();
610
-
611
- return Promise.all([
612
- soundStarted.then(promiseEntityManager),
613
- knowledgeLoaded,
614
- this.gui.startup(this),
615
- this.plugins.startup()
616
- ]).then(async () => {
617
-
618
- this.#last_frame_timestamp = performance.now();
619
-
620
- /**
621
- * Starting the engine
622
- */
623
- this.#animation_frame_handle = requestAnimationFrame(this.#animation_frame);
624
-
625
- //start simulation
626
- this.ticker.start({ maxTimeout: 200 });
627
- //self.uiController.init(self);
628
-
629
- //load options
630
- await this.options.attachToStorage('lazykitty.komrade.options', this.storage);
631
-
632
- logger.info('engine started');
633
- }, function (e) {
634
- logger.error(`Engine Failed to start: ${e}`);
635
-
636
- // the caller awaits start() to know whether the engine came up;
637
- // swallowing this reported success after a failed startup
638
- throw e;
639
- });
640
-
641
-
642
- }
643
-
644
- /**
645
- * Fired every animation frame via {@link requestAnimationFrame}
646
- */
647
- #animation_frame = () => {
648
-
649
- const t0 = this.#last_frame_timestamp;
650
- const t1 = performance.now();
651
- this.#animation_frame_handle = requestAnimationFrame(this.#animation_frame);
652
- this.render();
653
-
654
- const frame_delay = (t1 - t0) / 1000;
655
-
656
- // swap variables
657
- this.#last_frame_timestamp = t1;
658
-
659
- // record metric
660
- this.performance.get(METRIC_ID_FRAME).record(frame_delay);
661
- }
662
-
663
- /**
664
- * Shutdown the engine, disposing all relevant resources
665
- */
666
- async stop() {
667
- this.__performacne_monitor.stop();
668
-
669
- // stop the ticker. pause() throws when the loop is not currently
670
- // running, which would abort the whole teardown below
671
- this.ticker.stop();
672
-
673
- // and the render loop, which re-requests itself every frame and would
674
- // otherwise keep calling render() on a torn-down engine forever
675
- cancelAnimationFrame(this.#animation_frame_handle);
676
- this.#animation_frame_handle = -1;
677
-
678
- // shutdown entity manager
679
- if (!this.__using_external_entity_manager) {
680
- await new Promise((resolve, reject) => this.entityManager.shutdown(resolve, reject));
681
- }
682
-
683
- // shutdown plugins
684
- await this.plugins.shutdown();
685
-
686
- // stop gui
687
- await this.gui.shutdown();
688
-
689
-
690
- // TODO shutdown executor
691
-
692
- await this.assetManager.shutdown();
693
-
694
- // last, so that everything above still has a live context to release its GPU resources through
695
- const graphics = this.graphics;
696
-
697
- if (graphics !== null) {
698
- graphics.stop();
699
- }
700
- }
701
-
702
- exit() {
703
- window.close();
704
- }
705
-
706
- /**
707
- * @returns {Promise}
708
- */
709
- requestExit() {
710
- return this.gui.confirmTextDialog({
711
- title: this.localization.getString('system_confirm_exit_to_system.title'),
712
- text: this.localization.getString('system_confirm_exit_to_system.text')
713
- }).then(() => {
714
- this.exit();
715
- });
716
- }
717
- }
718
-
719
-
720
- /**
721
- * @readonly
722
- * @type {boolean}
723
- */
724
- Engine.prototype.isEngine = true;
725
-
726
- export default Engine;
1
+ /**
2
+ *
3
+ */
4
+
5
+ import { assert } from "../core/assert.js";
6
+ import Vector1 from "../core/geom/Vector1.js";
7
+ import { Localization } from "../core/localization/Localization.js";
8
+ import { ModuleRegistry } from "../core/model/ModuleRegistry.js";
9
+ import ObservedBoolean from "../core/model/ObservedBoolean.js";
10
+ import ConcurrentExecutor from '../core/process/executor/ConcurrentExecutor.js';
11
+ import EmptyView from "../view/elements/EmptyView.js";
12
+ import { ViewStack } from "../view/elements/navigation/ViewStack.js";
13
+ import { GraphicsContextFailureView } from "../view/graphics/GraphicsContextFailureView.js";
14
+
15
+ import { AssetManager } from './asset/AssetManager.js';
16
+ import { AssetPreloader } from "./asset/preloader/AssetPreloader.js";
17
+ import { MetricCollection } from "./development/performance/MetricCollection.js";
18
+ import { MetricStatistics } from "./development/performance/MetricStatistics.js";
19
+ import { PeriodicConsolePrinter } from "./development/performance/monitor/PeriodicConsolePrinter.js";
20
+ import { EntityManager } from "./ecs/EntityManager.js";
21
+ import { BinarySerializationRegistry } from "./ecs/storage/binary/BinarySerializationRegistry.js";
22
+ import { GraphicsEngine } from './graphics3/GraphicsEngine.js';
23
+ import { GamepadDevice } from "./input/devices/GamepadDevice.js";
24
+ import KeyboardDevice from "./input/devices/KeyboardDevice.js";
25
+ import { PointerDevice } from "./input/devices/PointerDevice.js";
26
+ import { StaticKnowledgeDatabase, StaticKnowledgeState } from "./knowledge/database/StaticKnowledgeDatabase.js";
27
+ import { logger } from "./logging/GlobalLogger.js";
28
+ import { OptionGroup } from "./options/OptionGroup.js";
29
+ import { EnginePluginManager } from "./plugin/EnginePluginManager.js";
30
+ import SceneManager from "./scene/SceneManager.js";
31
+ import Ticker from "./simulation/Ticker.js";
32
+ import SoundEngine from './sound/SoundEngine.js';
33
+
34
+ import GUIEngine from './ui/GUIEngine.js';
35
+
36
+
37
+ class EngineSettings {
38
+ graphics_control_viewport_size = new ObservedBoolean(true);
39
+ simulation_speed = new Vector1(1);
40
+ input_mouse_sensitivity = new Vector1(5);
41
+ }
42
+
43
+ const METRIC_ID_FRAME = 'frame_delay';
44
+ const METRIC_ID_RENDER = 'render_time';
45
+ const METRIC_ID_SIMULATION = 'simulation_time';
46
+
47
+ /**
48
+ * How long a context loss is tolerated before the simulation is paused as well, in seconds. Most losses
49
+ * are resolved in well under a second, and stopping the world for those would be more disruptive than
50
+ * letting it run; a longer outage is worth pausing for, as the player cannot see what is happening.
51
+ * @type {number}
52
+ */
53
+ const CONTEXT_LOSS_SIMULATION_PAUSE_DELAY = 2;
54
+
55
+ class Engine {
56
+ #last_frame_timestamp = 0;
57
+
58
+ /**
59
+ * Handle of the pending render-loop frame, `-1` when the loop is not running
60
+ * @type {number}
61
+ */
62
+ #animation_frame_handle = -1;
63
+
64
+ /**
65
+ * Handle of the pending "pause the simulation, this outage is taking a while" timer, `-1` when none
66
+ * @type {number}
67
+ */
68
+ #context_loss_pause_timer = -1;
69
+
70
+ /**
71
+ * Whether the simulation was paused because of a context loss, and therefore should be resumed once
72
+ * the context comes back
73
+ * @type {boolean}
74
+ */
75
+ #paused_by_context_loss = false;
76
+
77
+ /**
78
+ * Absent when the engine is constructed with `enableGraphics: false`.
79
+ * @type {GraphicsEngine|null}
80
+ */
81
+ graphics = null;
82
+
83
+ /**
84
+ * Absent when the engine is constructed with `enableAudio: false`.
85
+ * @type {SoundEngine|null}
86
+ */
87
+ sound = null;
88
+
89
+ viewStack = new ViewStack();
90
+
91
+ /**
92
+ *
93
+ * Static database
94
+ * Can contain tables of structured data that the application often works with.
95
+ * Engine startup will load all tables ensuring that data is ready once startup finishes
96
+ * Use sparingly, this is not suitable for very large amounts of data as all the data will be in memory and deserialization will stall engine startup
97
+ * Example: inventory item table, monster table
98
+ * @readonly
99
+ * @type {StaticKnowledgeDatabase}
100
+ */
101
+ staticKnowledge = new StaticKnowledgeDatabase();
102
+
103
+ /**
104
+ * @readonly
105
+ * @type {EnginePluginManager}
106
+ */
107
+ plugins = new EnginePluginManager();
108
+
109
+ /**
110
+ * Main simulation ticker
111
+ * @readonly
112
+ * @type {Ticker}
113
+ */
114
+ ticker = new Ticker();
115
+
116
+ /**
117
+ * Default executor, enables time-sharing concurrency
118
+ * @readonly
119
+ * @type {ConcurrentExecutor}
120
+ */
121
+ executor = new ConcurrentExecutor(1, 10);
122
+
123
+ /**
124
+ *
125
+ * @param {EnginePlatform} platform
126
+ * @param {EntityManager} [entityManager]
127
+ * @param {boolean} [enableGraphics]
128
+ * @param {boolean} [enableAudio]
129
+ * @param {boolean} [debug]
130
+ * @constructor
131
+ */
132
+ constructor(platform, {
133
+ entityManager,
134
+ enableGraphics = true,
135
+ enableAudio = true,
136
+ debug = true
137
+ } = {}) {
138
+ assert.defined(platform, 'platform');
139
+
140
+ /**
141
+ *
142
+ * @type {EnginePlatform}
143
+ */
144
+ this.platform = platform;
145
+
146
+ this.staticKnowledge.validation_enabled = debug;
147
+
148
+
149
+ /**
150
+ *
151
+ * @type {MetricCollection}
152
+ */
153
+ this.performance = new MetricCollection();
154
+
155
+
156
+ this.__using_external_entity_manager = entityManager !== undefined;
157
+
158
+ //setup entity component system
159
+ if (this.__using_external_entity_manager) {
160
+ // external entity manager provided
161
+ this.entityManager = entityManager;
162
+ } else {
163
+ this.entityManager = new EntityManager();
164
+ }
165
+
166
+
167
+ /**
168
+ *
169
+ * @type {AssetManager<Engine>}
170
+ */
171
+ this.assetManager = new AssetManager({
172
+ context: this,
173
+ executor: this.executor
174
+ });
175
+
176
+ this.__performacne_monitor = new PeriodicConsolePrinter(15, () => {
177
+ const metrics = this.performance;
178
+ const stats = new MetricStatistics();
179
+
180
+ // RENDER
181
+ const m_render = metrics.get(METRIC_ID_RENDER);
182
+
183
+ m_render.computeStats(stats);
184
+
185
+ const v_render = (stats.mean * 1000).toFixed(2);
186
+
187
+ // SIMULATION
188
+ const m_sim = metrics.get(METRIC_ID_SIMULATION);
189
+
190
+ m_sim.computeStats(stats);
191
+
192
+ const v_sim = (stats.mean * 1000).toFixed(2);
193
+
194
+ // FPS
195
+
196
+ const m_frame = metrics.get(METRIC_ID_FRAME);
197
+
198
+ m_frame.computeStats(stats);
199
+
200
+ const v_fps = (1 / stats.mean).toFixed(2);
201
+
202
+
203
+ // clear stats
204
+ m_render.clear();
205
+ m_sim.clear();
206
+ m_frame.clear();
207
+
208
+ return `FPS: ${v_fps}, RENDER: ${v_render}ms, SIMULATION: ${v_sim}ms`;
209
+ });
210
+
211
+
212
+ if (!this.__using_external_entity_manager) {
213
+ // subscribe simulator to common ticker
214
+ this.ticker.onTick.add(timeDelta => {
215
+ const t0 = performance.now();
216
+ this.entityManager.update(timeDelta);
217
+
218
+ const t1 = performance.now();
219
+ const duration = (t1 - t0) / 1000;
220
+
221
+ // record frame time
222
+ this.performance.get(METRIC_ID_SIMULATION).record(duration);
223
+ });
224
+ }
225
+
226
+ // initialize performance metrics
227
+ this.performance.create({
228
+ name: METRIC_ID_RENDER
229
+ });
230
+ this.performance.create({
231
+ name: METRIC_ID_SIMULATION
232
+ });
233
+ this.performance.create({
234
+ name: METRIC_ID_FRAME
235
+ });
236
+
237
+ /**
238
+ *
239
+ * @type {OptionGroup}
240
+ */
241
+ this.options = new OptionGroup("options");
242
+
243
+ /**
244
+ *
245
+ * @type {ModuleRegistry}
246
+ */
247
+ this.moduleRegistry = new ModuleRegistry();
248
+
249
+ /**
250
+ * @readonly
251
+ * @type {BinarySerializationRegistry}
252
+ */
253
+ this.binarySerializationRegistry = new BinarySerializationRegistry();
254
+
255
+ this.settings = new EngineSettings();
256
+
257
+
258
+ /**
259
+ * @deprecated use plugins instead
260
+ */
261
+ this.services = {};
262
+
263
+ /**
264
+ *
265
+ * @type {Storage}
266
+ */
267
+ this.storage = this.platform.getStorage();
268
+
269
+
270
+ this.localization = new Localization();
271
+ this.localization.setAssetManager(this.assetManager);
272
+
273
+
274
+ if (enableGraphics !== false) {
275
+
276
+ /**
277
+ * @type {GraphicsEngine}
278
+ */
279
+ const graphicsEngine = new GraphicsEngine();
280
+
281
+ this.graphics = graphicsEngine;
282
+
283
+ // The renderer is not started here. Acquiring a WebGPU device is asynchronous and a
284
+ // constructor is the wrong place to fail from, so `start()` does it — which is also
285
+ // what makes the device restartable (E1).
286
+
287
+ graphicsEngine.on.contextLost.add(this.#handle_context_lost, this);
288
+ graphicsEngine.on.contextRestored.add(this.#handle_context_restored, this);
289
+ graphicsEngine.on.contextFailed.add(this.#handle_context_failed, this);
290
+
291
+ } else {
292
+ logger.info('enableGraphics option is not set, no graphics engine will be created');
293
+ }
294
+
295
+ if (enableAudio !== false) {
296
+ //sound engine
297
+ const soundEngine = new SoundEngine();
298
+ soundEngine.volume = 1;
299
+
300
+ /**
301
+ *
302
+ * @type {SoundEngine}
303
+ */
304
+ this.sound = soundEngine;
305
+ } else {
306
+ logger.info('enableAudio option is not set, no audio engine will be created');
307
+ }
308
+
309
+ /**
310
+ * Graphical User Interface engine
311
+ * @type {GUIEngine}
312
+ */
313
+ this.gui = new GUIEngine();
314
+
315
+ /**
316
+ * @readonly
317
+ * @type {SceneManager}
318
+ */
319
+ this.sceneManager = new SceneManager(this.entityManager, this.ticker.clock);
320
+
321
+ this.initializeViews();
322
+
323
+ this.devices = {
324
+ pointer: new PointerDevice(this.viewStack.el),
325
+ keyboard: new KeyboardDevice(this.viewStack.el),
326
+ gamepad: new GamepadDevice()
327
+ };
328
+
329
+ this.devices.pointer.start();
330
+ this.devices.keyboard.start();
331
+ this.devices.gamepad.start();
332
+
333
+ //process settings
334
+ this.initializeSettings();
335
+
336
+ logger.info("engine initialized");
337
+
338
+ /**
339
+ * Toggles GraphicsEngine rendering on and off
340
+ * @type {boolean}
341
+ */
342
+ this.renderingEnabled = true;
343
+
344
+
345
+ this.plugins.initialize(this);
346
+ }
347
+
348
+ #clear_context_loss_pause_timer() {
349
+ if (this.#context_loss_pause_timer !== -1) {
350
+ clearTimeout(this.#context_loss_pause_timer);
351
+ this.#context_loss_pause_timer = -1;
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Pauses the simulation, if it is running. {@link Ticker#pause} freezes the clock, so no simulation
357
+ * time accrues while we wait and the first step after resuming is a normal-sized one.
358
+ *
359
+ * A ticker that is already paused is left alone - somebody else owns that pause and it is not ours to
360
+ * undo when the context comes back.
361
+ */
362
+ #pause_simulation_for_context_loss() {
363
+ this.#context_loss_pause_timer = -1;
364
+
365
+ if (this.#paused_by_context_loss) {
366
+ return;
367
+ }
368
+
369
+ const ticker = this.ticker;
370
+
371
+ if (!ticker.is_active || ticker.is_paused) {
372
+ return;
373
+ }
374
+
375
+ this.#paused_by_context_loss = true;
376
+
377
+ logger.warn('Graphics context outage is taking a while, pausing simulation');
378
+
379
+ ticker.pause();
380
+ }
381
+
382
+ #handle_context_lost() {
383
+ logger.warn('Graphics context lost, waiting for recovery');
384
+
385
+ this.#clear_context_loss_pause_timer();
386
+
387
+ this.#context_loss_pause_timer = setTimeout(
388
+ () => this.#pause_simulation_for_context_loss(),
389
+ CONTEXT_LOSS_SIMULATION_PAUSE_DELAY * 1000
390
+ );
391
+ }
392
+
393
+ #handle_context_restored() {
394
+ this.#clear_context_loss_pause_timer();
395
+
396
+ if (this.#paused_by_context_loss) {
397
+ this.#paused_by_context_loss = false;
398
+
399
+ if (this.ticker.is_active) {
400
+ this.ticker.resume();
401
+ }
402
+ }
403
+
404
+ logger.info('Graphics context restored, rendering resumed');
405
+ }
406
+
407
+ /**
408
+ * @param {ShadeDeviceFailure} reason carries a message written to be shown to a person
409
+ */
410
+ #handle_context_failed(reason) {
411
+ logger.error(`Graphics context recovery abandoned (${reason}), no further rendering is possible`);
412
+
413
+ this.#clear_context_loss_pause_timer();
414
+
415
+ // nothing can be drawn any more, so let the simulation stop rather than burn cycles invisibly.
416
+ // Application state is left intact, which keeps a save-on-failure handler viable.
417
+ this.#pause_simulation_for_context_loss();
418
+
419
+ this.viewStack.addChild(new GraphicsContextFailureView());
420
+ }
421
+
422
+ #initialize_audio() {
423
+ const sound = this.sound;
424
+ if (sound === null) {
425
+ // no sound engine
426
+ return;
427
+ }
428
+
429
+ // hook up context resumption to input
430
+ if (this.devices.pointer !== undefined) {
431
+
432
+ this.devices.pointer.on.down.addOne(sound.resumeContext, sound);
433
+ this.devices.keyboard.on.down.addOne(sound.resumeContext, sound);
434
+
435
+ }
436
+ }
437
+
438
+ initializeViews() {
439
+
440
+ const gameView = new EmptyView();
441
+
442
+ gameView.addClass('game-view');
443
+
444
+ gameView.css({
445
+ left: 0,
446
+ top: 0,
447
+ position: "absolute",
448
+ pointerEvents: "none"
449
+ });
450
+ this.gameView = gameView;
451
+
452
+ this.viewStack.push(gameView, 'game-view');
453
+
454
+ if (this.graphics !== null) {
455
+
456
+ const viewport = this.graphics.viewport;
457
+
458
+ viewport.css({
459
+ pointerEvents: "auto"
460
+ });
461
+
462
+ gameView.addChild(viewport);
463
+ //bind size of renderer viewport to game view
464
+ viewport.bindSignal(gameView.size.onChanged, viewport.size.set.bind(viewport.size));
465
+ gameView.on.linked.add(function () {
466
+ viewport.size.copy(gameView.size);
467
+ });
468
+
469
+ }
470
+ }
471
+
472
+ initializeSettings() {
473
+ logger.info('Initializing engine settings...');
474
+
475
+ const engine = this;
476
+
477
+ function setViewportToWindowSize() {
478
+ // get parent element
479
+ let parentElement = engine.viewStack.el.parentElement;
480
+
481
+ while (parentElement !== null && parentElement.innerWidth === undefined) {
482
+ // traverse up until we find an element with defined dimensions
483
+ parentElement = parentElement.parentElement;
484
+ }
485
+
486
+ if (parentElement === null || parentElement === undefined) {
487
+ // no parent element, default to window
488
+ parentElement = window;
489
+ }
490
+
491
+ engine.viewStack.size.set(parentElement.innerWidth, parentElement.innerHeight);
492
+ }
493
+
494
+ this.settings.graphics_control_viewport_size.process(function (value) {
495
+ if (value) {
496
+ setViewportToWindowSize();
497
+ window.addEventListener("resize", setViewportToWindowSize, false);
498
+ } else {
499
+ window.removeEventListener("resize", setViewportToWindowSize);
500
+ }
501
+ });
502
+
503
+ logger.info('Engine settings initilized.');
504
+ }
505
+
506
+ benchmark() {
507
+ const duration = 2000;
508
+ let count = 0;
509
+ const t0 = Date.now();
510
+ let t1;
511
+ while (true) {
512
+ this.entityManager.update(0.0000000001);
513
+ t1 = Date.now();
514
+ if (t1 - t0 > duration) {
515
+ break;
516
+ }
517
+ count++;
518
+ }
519
+ //normalize
520
+ const elapsed = (t1 - t0) / 1000;
521
+ const rate = (count / elapsed);
522
+ return rate;
523
+ }
524
+
525
+ /**
526
+ * Returns preloader object
527
+ * @param {String} listURL
528
+ */
529
+ loadAssetList(listURL) {
530
+ const preloader = new AssetPreloader();
531
+ const assetManager = this.assetManager;
532
+ assetManager.get({
533
+ path: listURL, type: "json", callback: function (asset) {
534
+ preloader.addAll(asset.create());
535
+ preloader.load(assetManager);
536
+ }
537
+ });
538
+ return preloader;
539
+ }
540
+
541
+ render() {
542
+ assert.isInstanceOf(this, Engine, 'this', 'Engine');
543
+
544
+ const graphics = this.graphics;
545
+
546
+ if (graphics === null) {
547
+ // headless
548
+ return;
549
+ }
550
+
551
+ if (graphics.autoDraw) {
552
+ // request redraw
553
+ graphics.needDraw = true;
554
+ }
555
+
556
+ if (this.renderingEnabled && graphics.needDraw) {
557
+
558
+ const metrics = this.performance;
559
+
560
+ const t0 = performance.now();
561
+
562
+ graphics.render();
563
+
564
+ const t1 = performance.now();
565
+ const frame_time = (t1 - t0) / 1000;
566
+
567
+ // record frame time
568
+ metrics.get(METRIC_ID_RENDER).record(frame_time);
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Startup
574
+ * @returns {Promise}
575
+ */
576
+ async start() {
577
+ // First, and awaited: systems read `graphics.viewport` and size themselves off it during
578
+ // their own startup, so the device has to exist before the entity manager runs.
579
+ if (this.graphics !== null) {
580
+ await this.graphics.start();
581
+ }
582
+
583
+ this.assetManager.startup();
584
+
585
+ this.#initialize_audio();
586
+
587
+ const promiseEntityManager = () => {
588
+ return new Promise((resolve, reject) => {
589
+ //initialize entity manager
590
+ this.entityManager.startup(resolve, reject);
591
+ });
592
+ }
593
+
594
+ this.__performacne_monitor.start();
595
+
596
+ const sound = this.sound;
597
+
598
+ const soundStarted = sound === null
599
+ ? Promise.resolve()
600
+ : sound.start();
601
+
602
+ const knowledge = this.staticKnowledge;
603
+
604
+ // static knowledge is engine-lifetime content and a stop does not drop it, so a restart keeps
605
+ // what the first start loaded. `load` accepts the Initial state only; callers that want the
606
+ // tables re-read call `reset` first.
607
+ const knowledgeLoaded = knowledge.state === StaticKnowledgeState.Initial
608
+ ? knowledge.load(this.assetManager, this.executor)
609
+ : Promise.resolve();
610
+
611
+ return Promise.all([
612
+ soundStarted.then(promiseEntityManager),
613
+ knowledgeLoaded,
614
+ this.gui.startup(this),
615
+ this.plugins.startup()
616
+ ]).then(async () => {
617
+
618
+ this.#last_frame_timestamp = performance.now();
619
+
620
+ /**
621
+ * Starting the engine
622
+ */
623
+ this.#animation_frame_handle = requestAnimationFrame(this.#animation_frame);
624
+
625
+ //start simulation
626
+ this.ticker.start({ maxTimeout: 200 });
627
+ //self.uiController.init(self);
628
+
629
+ //load options
630
+ await this.options.attachToStorage('lazykitty.komrade.options', this.storage);
631
+
632
+ logger.info('engine started');
633
+ }, function (e) {
634
+ logger.error(`Engine Failed to start: ${e}`);
635
+
636
+ // the caller awaits start() to know whether the engine came up;
637
+ // swallowing this reported success after a failed startup
638
+ throw e;
639
+ });
640
+
641
+
642
+ }
643
+
644
+ /**
645
+ * Fired every animation frame via {@link requestAnimationFrame}
646
+ */
647
+ #animation_frame = () => {
648
+
649
+ const t0 = this.#last_frame_timestamp;
650
+ const t1 = performance.now();
651
+ this.#animation_frame_handle = requestAnimationFrame(this.#animation_frame);
652
+ this.render();
653
+
654
+ const frame_delay = (t1 - t0) / 1000;
655
+
656
+ // swap variables
657
+ this.#last_frame_timestamp = t1;
658
+
659
+ // record metric
660
+ this.performance.get(METRIC_ID_FRAME).record(frame_delay);
661
+ }
662
+
663
+ /**
664
+ * Shutdown the engine, disposing all relevant resources
665
+ */
666
+ async stop() {
667
+ this.__performacne_monitor.stop();
668
+
669
+ // stop the ticker. pause() throws when the loop is not currently
670
+ // running, which would abort the whole teardown below
671
+ this.ticker.stop();
672
+
673
+ // and the render loop, which re-requests itself every frame and would
674
+ // otherwise keep calling render() on a torn-down engine forever
675
+ cancelAnimationFrame(this.#animation_frame_handle);
676
+ this.#animation_frame_handle = -1;
677
+
678
+ // the input devices the constructor started. The gamepad one polls on its own animation
679
+ // frame loop, so left alone it outlives the engine the same way the render loop would;
680
+ // the other two hold listeners on a view stack that is about to go away
681
+ this.devices.pointer.stop();
682
+ this.devices.keyboard.stop();
683
+ this.devices.gamepad.stop();
684
+
685
+ // shutdown entity manager
686
+ if (!this.__using_external_entity_manager) {
687
+ await new Promise((resolve, reject) => this.entityManager.shutdown(resolve, reject));
688
+ }
689
+
690
+ // shutdown plugins
691
+ await this.plugins.shutdown();
692
+
693
+ // stop gui
694
+ await this.gui.shutdown();
695
+
696
+
697
+ // TODO shutdown executor
698
+
699
+ await this.assetManager.shutdown();
700
+
701
+ // last, so that everything above still has a live context to release its GPU resources through
702
+ const graphics = this.graphics;
703
+
704
+ if (graphics !== null) {
705
+ graphics.stop();
706
+ }
707
+ }
708
+
709
+ exit() {
710
+ window.close();
711
+ }
712
+
713
+ /**
714
+ * @returns {Promise}
715
+ */
716
+ requestExit() {
717
+ return this.gui.confirmTextDialog({
718
+ title: this.localization.getString('system_confirm_exit_to_system.title'),
719
+ text: this.localization.getString('system_confirm_exit_to_system.text')
720
+ }).then(() => {
721
+ this.exit();
722
+ });
723
+ }
724
+ }
725
+
726
+
727
+ /**
728
+ * @readonly
729
+ * @type {boolean}
730
+ */
731
+ Engine.prototype.isEngine = true;
732
+
733
+ export default Engine;