topazcube 0.1.31 → 0.1.35

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 (103) hide show
  1. package/LICENSE.txt +0 -0
  2. package/README.md +0 -0
  3. package/dist/Renderer.cjs +20844 -0
  4. package/dist/Renderer.cjs.map +1 -0
  5. package/dist/Renderer.js +20827 -0
  6. package/dist/Renderer.js.map +1 -0
  7. package/dist/client.cjs +91 -260
  8. package/dist/client.cjs.map +1 -1
  9. package/dist/client.js +68 -215
  10. package/dist/client.js.map +1 -1
  11. package/dist/server.cjs +165 -432
  12. package/dist/server.cjs.map +1 -1
  13. package/dist/server.js +117 -370
  14. package/dist/server.js.map +1 -1
  15. package/dist/terminal.cjs +113 -200
  16. package/dist/terminal.cjs.map +1 -1
  17. package/dist/terminal.js +50 -51
  18. package/dist/terminal.js.map +1 -1
  19. package/dist/utils-CRhi1BDa.cjs +259 -0
  20. package/dist/utils-CRhi1BDa.cjs.map +1 -0
  21. package/dist/utils-D7tXt6-2.js +260 -0
  22. package/dist/utils-D7tXt6-2.js.map +1 -0
  23. package/package.json +19 -15
  24. package/src/{client.ts → network/client.js} +170 -403
  25. package/src/{compress-browser.ts → network/compress-browser.js} +2 -4
  26. package/src/{compress-node.ts → network/compress-node.js} +8 -14
  27. package/src/{server.ts → network/server.js} +229 -317
  28. package/src/{terminal.js → network/terminal.js} +0 -0
  29. package/src/{topazcube.ts → network/topazcube.js} +2 -2
  30. package/src/network/utils.js +375 -0
  31. package/src/renderer/Camera.js +191 -0
  32. package/src/renderer/DebugUI.js +703 -0
  33. package/src/renderer/Geometry.js +1049 -0
  34. package/src/renderer/Material.js +64 -0
  35. package/src/renderer/Mesh.js +211 -0
  36. package/src/renderer/Node.js +112 -0
  37. package/src/renderer/Pipeline.js +645 -0
  38. package/src/renderer/Renderer.js +1496 -0
  39. package/src/renderer/Skin.js +792 -0
  40. package/src/renderer/Texture.js +584 -0
  41. package/src/renderer/core/AssetManager.js +394 -0
  42. package/src/renderer/core/CullingSystem.js +308 -0
  43. package/src/renderer/core/EntityManager.js +541 -0
  44. package/src/renderer/core/InstanceManager.js +343 -0
  45. package/src/renderer/core/ParticleEmitter.js +358 -0
  46. package/src/renderer/core/ParticleSystem.js +564 -0
  47. package/src/renderer/core/SpriteSystem.js +349 -0
  48. package/src/renderer/gltf.js +563 -0
  49. package/src/renderer/math.js +161 -0
  50. package/src/renderer/rendering/HistoryBufferManager.js +333 -0
  51. package/src/renderer/rendering/ProbeCapture.js +1495 -0
  52. package/src/renderer/rendering/ReflectionProbeManager.js +352 -0
  53. package/src/renderer/rendering/RenderGraph.js +2258 -0
  54. package/src/renderer/rendering/passes/AOPass.js +308 -0
  55. package/src/renderer/rendering/passes/AmbientCapturePass.js +593 -0
  56. package/src/renderer/rendering/passes/BasePass.js +101 -0
  57. package/src/renderer/rendering/passes/BloomPass.js +420 -0
  58. package/src/renderer/rendering/passes/CRTPass.js +724 -0
  59. package/src/renderer/rendering/passes/FogPass.js +445 -0
  60. package/src/renderer/rendering/passes/GBufferPass.js +730 -0
  61. package/src/renderer/rendering/passes/HiZPass.js +744 -0
  62. package/src/renderer/rendering/passes/LightingPass.js +753 -0
  63. package/src/renderer/rendering/passes/ParticlePass.js +841 -0
  64. package/src/renderer/rendering/passes/PlanarReflectionPass.js +456 -0
  65. package/src/renderer/rendering/passes/PostProcessPass.js +405 -0
  66. package/src/renderer/rendering/passes/ReflectionPass.js +157 -0
  67. package/src/renderer/rendering/passes/RenderPostPass.js +364 -0
  68. package/src/renderer/rendering/passes/SSGIPass.js +266 -0
  69. package/src/renderer/rendering/passes/SSGITilePass.js +305 -0
  70. package/src/renderer/rendering/passes/ShadowPass.js +2072 -0
  71. package/src/renderer/rendering/passes/TransparentPass.js +831 -0
  72. package/src/renderer/rendering/passes/VolumetricFogPass.js +715 -0
  73. package/src/renderer/rendering/shaders/ao.wgsl +182 -0
  74. package/src/renderer/rendering/shaders/bloom.wgsl +97 -0
  75. package/src/renderer/rendering/shaders/bloom_blur.wgsl +80 -0
  76. package/src/renderer/rendering/shaders/crt.wgsl +455 -0
  77. package/src/renderer/rendering/shaders/depth_copy.wgsl +17 -0
  78. package/src/renderer/rendering/shaders/geometry.wgsl +580 -0
  79. package/src/renderer/rendering/shaders/hiz_reduce.wgsl +114 -0
  80. package/src/renderer/rendering/shaders/light_culling.wgsl +204 -0
  81. package/src/renderer/rendering/shaders/lighting.wgsl +932 -0
  82. package/src/renderer/rendering/shaders/lighting_common.wgsl +143 -0
  83. package/src/renderer/rendering/shaders/particle_render.wgsl +672 -0
  84. package/src/renderer/rendering/shaders/particle_simulate.wgsl +440 -0
  85. package/src/renderer/rendering/shaders/postproc.wgsl +293 -0
  86. package/src/renderer/rendering/shaders/render_post.wgsl +289 -0
  87. package/src/renderer/rendering/shaders/shadow.wgsl +117 -0
  88. package/src/renderer/rendering/shaders/ssgi.wgsl +266 -0
  89. package/src/renderer/rendering/shaders/ssgi_accumulate.wgsl +114 -0
  90. package/src/renderer/rendering/shaders/ssgi_propagate.wgsl +132 -0
  91. package/src/renderer/rendering/shaders/volumetric_blur.wgsl +80 -0
  92. package/src/renderer/rendering/shaders/volumetric_composite.wgsl +80 -0
  93. package/src/renderer/rendering/shaders/volumetric_raymarch.wgsl +634 -0
  94. package/src/renderer/utils/BoundingSphere.js +439 -0
  95. package/src/renderer/utils/Frustum.js +281 -0
  96. package/src/renderer/utils/Raycaster.js +761 -0
  97. package/dist/client.d.cts +0 -211
  98. package/dist/client.d.ts +0 -211
  99. package/dist/server.d.cts +0 -120
  100. package/dist/server.d.ts +0 -120
  101. package/dist/terminal.d.cts +0 -64
  102. package/dist/terminal.d.ts +0 -64
  103. package/src/utils.ts +0 -403
@@ -0,0 +1,541 @@
1
+ import { mat4, vec3, quat } from "../math.js"
2
+
3
+ /**
4
+ * EntityManager - Data-oriented entity storage and management
5
+ *
6
+ * Entities are plain objects with:
7
+ * - position: [x, y, z] (Float64)
8
+ * - rotation: [x, y, z, w] (Quaternion)
9
+ * - scale: [x, y, z]
10
+ * - model: "path/to/model.glb|meshName" (ModelID)
11
+ * - animation: "animationName" (optional)
12
+ * - phase: 0.0-1.0 (animation phase, optional)
13
+ * - light: { enabled, position, direction, color, geom, animation } (optional)
14
+ *
15
+ * Sprite properties (for billboard rendering):
16
+ * - sprite: "texture.png|8" (texture path | framesPerRow, optional)
17
+ * - pivot: 'center' | 'bottom' | 'horizontal' (billboard mode)
18
+ * - frame: number or "start|end|fps" (animation frame)
19
+ * - roughness: 0-1 (material roughness, default 0.7)
20
+ * - color: [r, g, b, a] (tint color, default [1,1,1,1])
21
+ *
22
+ * Particle properties (for GPU particle emitters):
23
+ * - particles: ParticleEmitter config object or emitter UID
24
+ * - _emitterUID: internal - UID of registered particle emitter
25
+ *
26
+ * Runtime fields (calculated):
27
+ * - _bsphere: { center: [x,y,z], radius: r }
28
+ * - _matrix: mat4
29
+ * - _uvTransform: [offsetX, offsetY, scaleX, scaleY] (for sprite sheets)
30
+ * - _animState: { currentAnim, time, blendFrom, blendFromTime, blendWeight, blendDuration, isBlending }
31
+ */
32
+ class EntityManager {
33
+ constructor() {
34
+ // Main entity storage: id -> entity
35
+ this.entities = {}
36
+
37
+ // Indices for fast lookup
38
+ this._byModel = {} // modelId -> Set<entityId>
39
+ this._byAnimation = {} // modelId|animation -> Set<entityId>
40
+ this._lights = new Set() // entityIds with lights
41
+ this._particles = new Set() // entityIds with particle emitters
42
+
43
+ // ID generation
44
+ this._nextId = 1
45
+
46
+ // Dirty tracking for batch updates
47
+ this._dirtyEntities = new Set()
48
+ this._dirtyLights = new Set()
49
+ }
50
+
51
+ /**
52
+ * Generate a unique entity ID
53
+ */
54
+ _generateId() {
55
+ return `e${this._nextId++}`
56
+ }
57
+
58
+ /**
59
+ * Create a new entity
60
+ * @param {Object} data - Entity data
61
+ * @returns {string} Entity ID
62
+ */
63
+ create(data = {}) {
64
+ const id = data.id || this._generateId()
65
+
66
+ const entity = {
67
+ position: data.position || [0, 0, 0],
68
+ rotation: data.rotation || [0, 0, 0, 1], // identity quaternion
69
+ scale: data.scale || [1, 1, 1],
70
+ model: data.model || null,
71
+ animation: data.animation || null,
72
+ phase: data.phase || 0,
73
+ light: data.light || null,
74
+ noRounding: data.noRounding || false, // Exempt from pixel/position rounding
75
+
76
+ // Sprite properties (for billboard rendering)
77
+ sprite: data.sprite || null, // "texture.png|8" (texture|framesPerRow)
78
+ pivot: data.pivot || 'center', // 'center', 'bottom', 'horizontal'
79
+ frame: data.frame ?? 0, // integer or "start|end|fps"
80
+ roughness: data.roughness ?? 0.7, // Material roughness (0-1)
81
+ color: data.color || null, // [r, g, b, a] tint color (null = white)
82
+
83
+ // Particle properties (for GPU particle emitters)
84
+ particles: data.particles || null, // ParticleEmitter config or emitter UID
85
+ _emitterUID: null, // Internal: UID of registered emitter
86
+
87
+ // Runtime fields
88
+ _bsphere: { center: [0, 0, 0], radius: 0 }, // radius 0 = not initialized
89
+ _matrix: mat4.create(),
90
+ _uvTransform: null, // [offsetX, offsetY, scaleX, scaleY] for sprites
91
+ _visible: true,
92
+ _dirty: true,
93
+
94
+ // Animation state for blending (used by individual skins)
95
+ _animState: {
96
+ currentAnim: data.animation || null,
97
+ time: (data.phase || 0) * 1.0, // Will be scaled by animation duration
98
+ blendFrom: null,
99
+ blendFromTime: 0,
100
+ blendWeight: 1.0,
101
+ blendDuration: 0.3,
102
+ isBlending: false
103
+ }
104
+ }
105
+
106
+ this.entities[id] = entity
107
+
108
+ // Update indices
109
+ if (entity.model) {
110
+ this._addToModelIndex(id, entity.model)
111
+ if (entity.animation) {
112
+ this._addToAnimationIndex(id, entity.model, entity.animation)
113
+ }
114
+ }
115
+
116
+ if (entity.light && entity.light.enabled) {
117
+ this._lights.add(id)
118
+ }
119
+
120
+ if (entity.particles) {
121
+ this._particles.add(id)
122
+ }
123
+
124
+ this._dirtyEntities.add(id)
125
+ this._updateEntityMatrix(id)
126
+
127
+ return id
128
+ }
129
+
130
+ /**
131
+ * Get entity by ID
132
+ */
133
+ get(id) {
134
+ return this.entities[id]
135
+ }
136
+
137
+ /**
138
+ * Update entity properties
139
+ */
140
+ update(id, data) {
141
+ const entity = this.entities[id]
142
+ if (!entity) return false
143
+
144
+ const oldModel = entity.model
145
+ const oldAnimation = entity.animation
146
+
147
+ // Update transform
148
+ if (data.position) entity.position = data.position
149
+ if (data.rotation) entity.rotation = data.rotation
150
+ if (data.scale) entity.scale = data.scale
151
+
152
+ // Update model reference
153
+ if (data.model !== undefined && data.model !== oldModel) {
154
+ if (oldModel) {
155
+ this._removeFromModelIndex(id, oldModel)
156
+ if (oldAnimation) {
157
+ this._removeFromAnimationIndex(id, oldModel, oldAnimation)
158
+ }
159
+ }
160
+ entity.model = data.model
161
+ if (data.model) {
162
+ this._addToModelIndex(id, data.model)
163
+ }
164
+ }
165
+
166
+ // Update animation
167
+ if (data.animation !== undefined && data.animation !== oldAnimation) {
168
+ const model = data.model !== undefined ? data.model : entity.model
169
+ if (oldAnimation && oldModel) {
170
+ this._removeFromAnimationIndex(id, oldModel, oldAnimation)
171
+ }
172
+ entity.animation = data.animation
173
+ if (data.animation && model) {
174
+ this._addToAnimationIndex(id, model, data.animation)
175
+ }
176
+ }
177
+
178
+ if (data.phase !== undefined) entity.phase = data.phase
179
+
180
+ // Update light
181
+ if (data.light !== undefined) {
182
+ entity.light = data.light
183
+ if (data.light && data.light.enabled) {
184
+ this._lights.add(id)
185
+ this._dirtyLights.add(id)
186
+ } else {
187
+ this._lights.delete(id)
188
+ }
189
+ }
190
+
191
+ entity._dirty = true
192
+ this._dirtyEntities.add(id)
193
+ this._updateEntityMatrix(id)
194
+
195
+ return true
196
+ }
197
+
198
+ /**
199
+ * Delete entity
200
+ */
201
+ delete(id) {
202
+ const entity = this.entities[id]
203
+ if (!entity) return false
204
+
205
+ // Remove from indices
206
+ if (entity.model) {
207
+ this._removeFromModelIndex(id, entity.model)
208
+ if (entity.animation) {
209
+ this._removeFromAnimationIndex(id, entity.model, entity.animation)
210
+ }
211
+ }
212
+
213
+ this._lights.delete(id)
214
+ this._particles.delete(id)
215
+ this._dirtyEntities.delete(id)
216
+ this._dirtyLights.delete(id)
217
+
218
+ delete this.entities[id]
219
+ return true
220
+ }
221
+
222
+ /**
223
+ * Update entity's model matrix from transform
224
+ */
225
+ _updateEntityMatrix(id) {
226
+ const entity = this.entities[id]
227
+ if (!entity) return
228
+
229
+ mat4.identity(entity._matrix)
230
+ mat4.translate(entity._matrix, entity._matrix, entity.position)
231
+
232
+ // Apply rotation from quaternion
233
+ const rotMat = mat4.create()
234
+ mat4.fromQuat(rotMat, entity.rotation)
235
+ mat4.multiply(entity._matrix, entity._matrix, rotMat)
236
+
237
+ mat4.scale(entity._matrix, entity._matrix, entity.scale)
238
+ }
239
+
240
+ /**
241
+ * Update entity's bounding sphere (called when asset is loaded)
242
+ */
243
+ updateBoundingSphere(id, baseBsphere) {
244
+ const entity = this.entities[id]
245
+ if (!entity || !baseBsphere) return
246
+
247
+ // Transform bsphere center by entity matrix
248
+ const center = vec3.create()
249
+ vec3.transformMat4(center, baseBsphere.center, entity._matrix)
250
+
251
+ // Scale radius by max scale component
252
+ const maxScale = Math.max(
253
+ Math.abs(entity.scale[0]),
254
+ Math.abs(entity.scale[1]),
255
+ Math.abs(entity.scale[2])
256
+ )
257
+
258
+ entity._bsphere = {
259
+ center: [center[0], center[1], center[2]],
260
+ radius: baseBsphere.radius * maxScale
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Get all entities using a specific model
266
+ */
267
+ getByModel(modelId) {
268
+ const ids = this._byModel[modelId]
269
+ if (!ids) return []
270
+ return Array.from(ids).map(id => ({ id, entity: this.entities[id] }))
271
+ }
272
+
273
+ /**
274
+ * Get all entities with a specific model and animation
275
+ */
276
+ getByModelAndAnimation(modelId, animation) {
277
+ const key = `${modelId}|${animation}`
278
+ const ids = this._byAnimation[key]
279
+ if (!ids) return []
280
+ return Array.from(ids).map(id => ({ id, entity: this.entities[id] }))
281
+ }
282
+
283
+ /**
284
+ * Get all unique model IDs currently in use
285
+ */
286
+ getActiveModels() {
287
+ return Object.keys(this._byModel)
288
+ }
289
+
290
+ /**
291
+ * Get all entities with lights
292
+ */
293
+ getLights() {
294
+ return Array.from(this._lights).map(id => ({ id, entity: this.entities[id] }))
295
+ }
296
+
297
+ /**
298
+ * Get all entities with particle emitters
299
+ */
300
+ getParticles() {
301
+ return Array.from(this._particles).map(id => ({ id, entity: this.entities[id] }))
302
+ }
303
+
304
+ /**
305
+ * Get dirty entities and clear dirty flags
306
+ */
307
+ consumeDirtyEntities() {
308
+ const dirty = Array.from(this._dirtyEntities)
309
+ this._dirtyEntities.clear()
310
+ for (const id of dirty) {
311
+ const entity = this.entities[id]
312
+ if (entity) entity._dirty = false
313
+ }
314
+ return dirty
315
+ }
316
+
317
+ /**
318
+ * Get dirty lights and clear dirty flags
319
+ */
320
+ consumeDirtyLights() {
321
+ const dirty = Array.from(this._dirtyLights)
322
+ this._dirtyLights.clear()
323
+ return dirty
324
+ }
325
+
326
+ /**
327
+ * Set visibility for entity
328
+ */
329
+ setVisible(id, visible) {
330
+ const entity = this.entities[id]
331
+ if (entity) {
332
+ entity._visible = visible
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Get all visible entities
338
+ */
339
+ getVisible() {
340
+ const result = []
341
+ for (const id in this.entities) {
342
+ if (this.entities[id]._visible) {
343
+ result.push({ id, entity: this.entities[id] })
344
+ }
345
+ }
346
+ return result
347
+ }
348
+
349
+ /**
350
+ * Iterate over all entities
351
+ */
352
+ forEach(callback) {
353
+ for (const id in this.entities) {
354
+ callback(id, this.entities[id])
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Get entity count
360
+ */
361
+ get count() {
362
+ return Object.keys(this.entities).length
363
+ }
364
+
365
+ // Private index management methods
366
+
367
+ _addToModelIndex(id, modelId) {
368
+ if (!this._byModel[modelId]) {
369
+ this._byModel[modelId] = new Set()
370
+ }
371
+ this._byModel[modelId].add(id)
372
+ }
373
+
374
+ _removeFromModelIndex(id, modelId) {
375
+ if (this._byModel[modelId]) {
376
+ this._byModel[modelId].delete(id)
377
+ if (this._byModel[modelId].size === 0) {
378
+ delete this._byModel[modelId]
379
+ }
380
+ }
381
+ }
382
+
383
+ _addToAnimationIndex(id, modelId, animation) {
384
+ const key = `${modelId}|${animation}`
385
+ if (!this._byAnimation[key]) {
386
+ this._byAnimation[key] = new Set()
387
+ }
388
+ this._byAnimation[key].add(id)
389
+ }
390
+
391
+ _removeFromAnimationIndex(id, modelId, animation) {
392
+ const key = `${modelId}|${animation}`
393
+ if (this._byAnimation[key]) {
394
+ this._byAnimation[key].delete(id)
395
+ if (this._byAnimation[key].size === 0) {
396
+ delete this._byAnimation[key]
397
+ }
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Set animation for entity with optional blending
403
+ * @param {string} id - Entity ID
404
+ * @param {string} animation - Animation name
405
+ * @param {number} blendTime - Blend duration (0 = instant switch)
406
+ */
407
+ setAnimation(id, animation, blendTime = 0.3) {
408
+ const entity = this.entities[id]
409
+ if (!entity) return false
410
+
411
+ const animState = entity._animState
412
+
413
+ // If same animation, do nothing
414
+ if (animState.currentAnim === animation) return true
415
+
416
+ // Start blend if we have a current animation and blend time > 0
417
+ if (blendTime > 0 && animState.currentAnim) {
418
+ animState.blendFrom = animState.currentAnim
419
+ animState.blendFromTime = animState.time
420
+ animState.blendWeight = 0
421
+ animState.blendDuration = blendTime
422
+ animState.isBlending = true
423
+ } else {
424
+ animState.isBlending = false
425
+ animState.blendFrom = null
426
+ }
427
+
428
+ animState.currentAnim = animation
429
+ animState.time = 0
430
+
431
+ // Also update the legacy animation field for compatibility
432
+ const oldAnimation = entity.animation
433
+ if (oldAnimation !== animation) {
434
+ if (oldAnimation && entity.model) {
435
+ this._removeFromAnimationIndex(id, entity.model, oldAnimation)
436
+ }
437
+ entity.animation = animation
438
+ if (animation && entity.model) {
439
+ this._addToAnimationIndex(id, entity.model, animation)
440
+ }
441
+ }
442
+
443
+ return true
444
+ }
445
+
446
+ /**
447
+ * Update animation time for entity
448
+ * @param {string} id - Entity ID
449
+ * @param {number} dt - Delta time
450
+ */
451
+ updateAnimationTime(id, dt) {
452
+ const entity = this.entities[id]
453
+ if (!entity) return
454
+
455
+ const animState = entity._animState
456
+ animState.time += dt
457
+
458
+ if (animState.isBlending) {
459
+ animState.blendFromTime += dt
460
+ animState.blendWeight = Math.min(
461
+ animState.blendWeight + dt / animState.blendDuration,
462
+ 1.0
463
+ )
464
+
465
+ if (animState.blendWeight >= 1.0) {
466
+ animState.isBlending = false
467
+ animState.blendFrom = null
468
+ animState.blendWeight = 1.0
469
+ }
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Check if entity is currently blending animations
475
+ */
476
+ isBlending(id) {
477
+ const entity = this.entities[id]
478
+ return entity?._animState?.isBlending || false
479
+ }
480
+
481
+ /**
482
+ * Get animation state for entity
483
+ */
484
+ getAnimationState(id) {
485
+ const entity = this.entities[id]
486
+ return entity?._animState || null
487
+ }
488
+
489
+ /**
490
+ * Clear all entities
491
+ */
492
+ clear() {
493
+ this.entities = {}
494
+ this._byModel = {}
495
+ this._byAnimation = {}
496
+ this._lights.clear()
497
+ this._particles.clear()
498
+ this._dirtyEntities.clear()
499
+ this._dirtyLights.clear()
500
+ }
501
+
502
+ /**
503
+ * Serialize entities for saving
504
+ */
505
+ serialize() {
506
+ const data = {}
507
+ for (const id in this.entities) {
508
+ const e = this.entities[id]
509
+ data[id] = {
510
+ position: [...e.position],
511
+ rotation: [...e.rotation],
512
+ scale: [...e.scale],
513
+ model: e.model,
514
+ animation: e.animation,
515
+ phase: e.phase,
516
+ light: e.light ? { ...e.light } : null,
517
+ // Sprite properties
518
+ sprite: e.sprite,
519
+ pivot: e.pivot,
520
+ frame: e.frame,
521
+ roughness: e.roughness,
522
+ color: e.color ? [...e.color] : null,
523
+ // Particle properties (serialize config, not runtime UID)
524
+ particles: e.particles
525
+ }
526
+ }
527
+ return data
528
+ }
529
+
530
+ /**
531
+ * Deserialize entities from saved data
532
+ */
533
+ deserialize(data) {
534
+ this.clear()
535
+ for (const id in data) {
536
+ this.create({ ...data[id], id })
537
+ }
538
+ }
539
+ }
540
+
541
+ export { EntityManager }