@woosh/meep-engine 2.165.0 → 2.165.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -108
- package/build/bundle-worker-image-decoder.js +1 -1
- package/package.json +1 -1
- package/samples/generation/SampleGenerator0.js +3 -3
- package/samples/generation/filters/SampleGroundMoistureFilter.js +63 -56
- package/samples/generation/filters/SampleNoise20_0.js +6 -1
- package/samples/generation/themes/SampleTheme0.js +47 -47
- package/src/core/color/YCoCg/rgb_to_ycocg.d.ts +17 -0
- package/src/core/color/YCoCg/rgb_to_ycocg.d.ts.map +1 -0
- package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -0
- package/src/core/color/YCoCg/ycocg_to_rgb.d.ts +14 -0
- package/src/core/color/YCoCg/ycocg_to_rgb.d.ts.map +1 -0
- package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -0
- package/src/engine/REVIEW_2026_07_03.md +211 -0
- package/src/engine/animation/curve/actionProcessorOperations/curveActions.d.ts.map +1 -1
- package/src/engine/animation/curve/actionProcessorOperations/curveActions.js +3 -2
- package/src/engine/animation/curve/compression/sample_animation_curve_to_float_array.js +1 -1
- package/src/engine/animation/keyed2/AnimationTrackPlayback.d.ts.map +1 -1
- package/src/engine/animation/keyed2/AnimationTrackPlayback.js +3 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +2 -2
- package/src/engine/asset/preloader/AssetPreloader.d.ts.map +1 -1
- package/src/engine/asset/preloader/AssetPreloader.js +6 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +5 -2
- package/src/engine/ecs/Entity.js +1 -1
- package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
- package/src/engine/ecs/EntityComponentDataset.js +1 -0
- package/src/engine/ecs/EntityManager.js +1 -1
- package/src/engine/ecs/EntityObserver.js +1 -1
- package/src/engine/ecs/animation/AnimationClip.js +1 -1
- package/src/engine/ecs/attachment/AttachmentSystem.js +2 -2
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +1 -1
- package/src/engine/ecs/attachment/TransformAttachmentBinding.js +11 -0
- package/src/engine/ecs/fow/FogOfWar.js +2 -2
- package/src/engine/ecs/fow/shader/screenSpaceFogOfWarShader.d.ts.map +1 -1
- package/src/engine/ecs/fow/shader/screenSpaceFogOfWarShader.js +8 -0
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +1 -1
- package/src/engine/ecs/gui/position/ViewportPosition.d.ts +3 -1
- package/src/engine/ecs/gui/position/ViewportPosition.d.ts.map +1 -1
- package/src/engine/ecs/gui/position/ViewportPosition.js +4 -1
- package/src/engine/ecs/storage/binary/BinarySerializationRegistry.d.ts +10 -0
- package/src/engine/ecs/storage/binary/BinarySerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/binary/BinarySerializationRegistry.js +43 -0
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.d.ts +93 -0
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.d.ts.map +1 -0
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -0
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.d.ts +48 -0
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.d.ts.map +1 -0
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -0
- package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -0
- package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.d.ts +53 -0
- package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.d.ts.map +1 -0
- package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -0
- package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.d.ts +53 -0
- package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -0
- package/src/engine/ecs/storage/binary/object-v2/README.md +110 -0
- package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.d.ts +13 -0
- package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.d.ts.map +1 -0
- package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -0
- package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.d.ts +29 -0
- package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.d.ts.map +1 -0
- package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -0
- package/src/engine/graphics/camera/makeScreenScissorFrustum.d.ts.map +1 -1
- package/src/engine/graphics/camera/makeScreenScissorFrustum.js +11 -10
- package/src/engine/graphics/canvas/canvas2d_draw_linear_scale.d.ts.map +1 -1
- package/src/engine/graphics/canvas/canvas2d_draw_linear_scale.js +3 -1
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/readAnimationGraphDefinitionFromJSON.d.ts +1 -1
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/readAnimationGraphDefinitionFromJSON.d.ts.map +1 -1
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/readAnimationGraphDefinitionFromJSON.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/writeAnimationGraphDefinitionToJSON.d.ts.map +1 -1
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/writeAnimationGraphDefinitionToJSON.js +2 -0
- package/src/engine/graphics/ecs/camera/Camera.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/Camera.js +4 -0
- package/src/engine/graphics/ecs/camera/auto_set_camera_clipping_planes.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/auto_set_camera_clipping_planes.js +7 -2
- package/src/engine/graphics/ecs/camera/set_camera_aspect_ratio.js +1 -1
- package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.js +4 -1
- package/src/engine/graphics/ecs/light/binding/LightBinding.js +2 -2
- package/src/engine/graphics/ecs/mesh/MeshSystem.js +1 -1
- package/src/engine/graphics/ecs/mesh/SkeletonUtils.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +1 -1
- package/src/engine/graphics/ecs/water/WaterSystem.d.ts +6 -0
- package/src/engine/graphics/ecs/water/WaterSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/WaterSystem.js +12 -1
- package/src/engine/graphics/geometry/buffered/ensureGeometryBoundingSphere.js +1 -1
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_nearest.d.ts.map +1 -1
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_nearest.js +0 -15
- package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.js +10 -10
- package/src/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.js +1 -1
- package/src/engine/graphics/material/SplatMaterial.js +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +3 -2
- package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +1 -1
- package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/group/ParticleGroup.js +3 -2
- package/src/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.js +1 -1
- package/src/engine/graphics/sh3/gi/material/MaterialTransformer.d.ts +1 -8
- package/src/engine/graphics/sh3/gi/material/MaterialTransformer.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/MaterialTransformer.js +2 -14
- package/src/engine/graphics/sh3/gi/material/ProbeResolutionStage.d.ts +9 -0
- package/src/engine/graphics/sh3/gi/material/ProbeResolutionStage.d.ts.map +1 -0
- package/src/engine/graphics/sh3/gi/material/ProbeResolutionStage.js +14 -0
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.js +4 -1
- package/src/engine/graphics/sh3/lpg/LightProbeGrid.d.ts +69 -0
- package/src/engine/graphics/sh3/lpg/LightProbeGrid.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/LightProbeGrid.js +173 -0
- package/src/engine/graphics/sh3/lpg/LightProbeGridBaker.d.ts +24 -0
- package/src/engine/graphics/sh3/lpg/LightProbeGridBaker.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/LightProbeGridBaker.js +88 -0
- package/src/engine/graphics/sh3/lpg/build_light_probe_grid_for_scene.d.ts +20 -0
- package/src/engine/graphics/sh3/lpg/build_light_probe_grid_for_scene.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/build_light_probe_grid_for_scene.js +79 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_obtain_storage_cached.d.ts +11 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_obtain_storage_cached.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_obtain_storage_cached.js +84 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_textures.d.ts +29 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_textures.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_textures.js +160 -0
- package/src/engine/graphics/sh3/lpg/material/LightProbeGridMaterialTransformer.d.ts +23 -0
- package/src/engine/graphics/sh3/lpg/material/LightProbeGridMaterialTransformer.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/LightProbeGridMaterialTransformer.js +228 -0
- package/src/engine/graphics/sh3/lpg/material/chunk_lpg_get_irradiance_at.d.ts +2 -0
- package/src/engine/graphics/sh3/lpg/material/chunk_lpg_get_irradiance_at.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/chunk_lpg_get_irradiance_at.js +24 -0
- package/src/engine/graphics/sh3/lpg/material/common.d.ts +3 -0
- package/src/engine/graphics/sh3/lpg/material/common.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/common.js +73 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_fragment_shader.d.ts +6 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_fragment_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_fragment_shader.js +42 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_vertex_shader.d.ts +6 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_vertex_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_vertex_shader.js +49 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/space_fragment_transform_shader.d.ts +9 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/space_fragment_transform_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/space_fragment_transform_shader.js +12 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_fragment_shader.d.ts +6 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_fragment_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_fragment_shader.js +30 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_vertex_shader.d.ts +6 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_vertex_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_vertex_shader.js +41 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/preamble.frag.d.ts +3 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/preamble.frag.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/preamble.frag.js +15 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/preamble.vert.d.ts +3 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/preamble.vert.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/preamble.vert.js +9 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/space_vertex_transform_shader.d.ts +9 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/space_vertex_transform_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/material/space_vertex/space_vertex_transform_shader.js +12 -0
- package/src/engine/graphics/sh3/lpg/serialization/LightProbeGridSerializationAdapter.d.ts +20 -0
- package/src/engine/graphics/sh3/lpg/serialization/LightProbeGridSerializationAdapter.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/serialization/LightProbeGridSerializationAdapter.js +59 -0
- package/src/engine/graphics/sh3/path_tracer/material/MaterialConverter.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/material/MaterialConverter.js +8 -0
- package/src/engine/graphics/sh3/prototypeLightProbeGrid.d.ts +2 -0
- package/src/engine/graphics/sh3/prototypeLightProbeGrid.d.ts.map +1 -0
- package/src/engine/graphics/sh3/prototypeLightProbeGrid.js +170 -0
- package/src/engine/graphics/texture/atlas/AtlasLookupTexture.js +1 -1
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.js +1 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +1 -1
- package/src/engine/graphics/texture/sampler/sampler2d_copy_with_margins.js +3 -3
- package/src/engine/graphics/util/build_max_height_pyramid.d.ts.map +1 -1
- package/src/engine/graphics/util/build_max_height_pyramid.js +5 -2
- package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +1 -1
- package/src/engine/input/ecs/components/InputBinding.d.ts +7 -1
- package/src/engine/input/ecs/components/InputBinding.d.ts.map +1 -1
- package/src/engine/input/ecs/components/InputBinding.js +8 -1
- package/src/engine/input/ecs/systems/InputControllerSystem.js +1 -1
- package/src/engine/intelligence/behavior/decorator/OverrideContextBehavior.d.ts +2 -1
- package/src/engine/intelligence/behavior/decorator/OverrideContextBehavior.d.ts.map +1 -1
- package/src/engine/intelligence/behavior/decorator/OverrideContextBehavior.js +6 -2
- package/src/engine/intelligence/behavior/selector/WeightedElement.js +1 -1
- package/src/engine/knowledge/database/StaticKnowledgeDatabase.d.ts.map +1 -1
- package/src/engine/knowledge/database/StaticKnowledgeDatabase.js +2 -4
- package/src/engine/logging/elastic/ElasticSearchLogger.d.ts +1 -2
- package/src/engine/logging/elastic/ElasticSearchLogger.d.ts.map +1 -1
- package/src/engine/logging/elastic/ElasticSearchLogger.js +14 -10
- package/src/engine/navigation/ecs/path_following/PathFollower.js +1 -1
- package/src/engine/navigation/grid/find_path_on_grid_astar.d.ts.map +1 -1
- package/src/engine/navigation/grid/find_path_on_grid_astar.js +5 -2
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +17 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
- package/src/engine/scene/SceneManager.d.ts.map +1 -1
- package/src/engine/scene/SceneManager.js +4 -2
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +9 -0
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +6 -1
- package/src/engine/sound/material/concrete/json/serializeSoundMaterialToJSON.js +1 -1
- package/src/generation/theme/ThemeEngine.d.ts +9 -0
- package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
- package/src/generation/theme/ThemeEngine.js +37 -2
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Object-graph binary serialization — design
|
|
2
|
+
|
|
3
|
+
## 1. Purpose
|
|
4
|
+
|
|
5
|
+
A **general-purpose** binary serializer for arbitrary JavaScript object graphs, layered on the existing
|
|
6
|
+
[`BinaryClassSerializationAdapter`](../BinaryClassSerializationAdapter.js) / upgrader / registry machinery.
|
|
7
|
+
|
|
8
|
+
The defining goal — and the thing an earlier draft got wrong — is that you can serialize an object of class `X`
|
|
9
|
+
**without hand-writing an adapter for it**. Classes are serialized by reflection over their own fields by default; a
|
|
10
|
+
hand-written adapter is an *optional override* for classes that want a compact or custom encoding.
|
|
11
|
+
|
|
12
|
+
What it does:
|
|
13
|
+
|
|
14
|
+
- **Reflection by default.** Plain objects and class instances are serialized field-by-field with no per-class setup.
|
|
15
|
+
- **Adapters as an override.** If a class has an adapter registered in the [`BinarySerializationRegistry`](../BinarySerializationRegistry.js),
|
|
16
|
+
that adapter owns the encoding (compact, custom, versioned). Reflection is the fallback, not the only path.
|
|
17
|
+
- **Reference semantics.** Shared references are preserved as one instance, and cycles are handled — objects are
|
|
18
|
+
tracked by identity and re-encoded as back-references.
|
|
19
|
+
- **Compact framing.** Class names and reflective field-name sets ("shapes") are interned into two tables written
|
|
20
|
+
ahead of the body and referenced by a small integer, so a thousand same-class objects pay for the name once.
|
|
21
|
+
- **Versioning + bidirectional migration.** Adapter-backed classes carry a version; on read, a mismatch is migrated
|
|
22
|
+
through the registered [`BinaryClassUpgrader`](../BinaryClassUpgrader.js) chain in *either* direction.
|
|
23
|
+
- **Minification-safe identity.** A class is identified by its adapter registration or its static `typeName`, never by
|
|
24
|
+
the minifier-mangled `constructor.name`.
|
|
25
|
+
|
|
26
|
+
## 2. Where it sits
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
BinaryBuffer byte stream
|
|
30
|
+
└─ BinaryClassSerializationAdapter per-class hand-written serialize/deserialize (optional override)
|
|
31
|
+
└─ BinarySerializationRegistry typeName → adapter, migrator graph, class → typeName
|
|
32
|
+
└─ ModuleRegistry typeName → class (for reflective reconstruction)
|
|
33
|
+
└─ BinaryObjectSerializer / BinaryObjectDeSerializer ← THIS: reflection + adapters + references + tables
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This complements the existing storage layer rather than duplicating it. [`BinaryBufferSerializer`](../../BinaryBufferSerializer.js)
|
|
37
|
+
serializes an ECS dataset as *homogeneous collections of components* (one class name per collection, delegating nested
|
|
38
|
+
objects to the older adapter-only `object/BinaryObjectSerializationAdapter`). This system is the general graph tool it
|
|
39
|
+
was missing: heterogeneous graphs, adapter-less classes, and shared/cyclic references. It could serve as a drop-in for
|
|
40
|
+
that nested-object role; that integration is deliberately out of scope here.
|
|
41
|
+
|
|
42
|
+
## 3. Wire format
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
DOCUMENT := HEADER CLASS_TABLE SHAPE_TABLE BODY
|
|
46
|
+
|
|
47
|
+
HEADER := u16(OBJECT_MAGIC=0x4F33) u8(OBJECT_FORMAT_VERSION=1)
|
|
48
|
+
|
|
49
|
+
CLASS_TABLE := uvar(count) record* record := utf8(typeName) uvar(version)
|
|
50
|
+
SHAPE_TABLE := uvar(count) shape* shape := uvar(fieldCount) utf8(name)*
|
|
51
|
+
|
|
52
|
+
BODY := VALUE
|
|
53
|
+
|
|
54
|
+
VALUE := u8(TAG_NULL) | u8(TAG_UNDEFINED) | u8(TAG_FALSE) | u8(TAG_TRUE)
|
|
55
|
+
| u8(TAG_POS_INT) uvar(n) // n >= 0, fits a varint
|
|
56
|
+
| u8(TAG_NEG_INT) uvar(-n) // n < 0, fits a varint
|
|
57
|
+
| u8(TAG_FLOAT) float64 // any other number (float, NaN, ±Infinity, huge int)
|
|
58
|
+
| u8(TAG_STRING) utf8
|
|
59
|
+
| u8(TAG_BIGINT) utf8(decimal)
|
|
60
|
+
| u8(TAG_REF) uvar(objectIndex) // back-reference to an already-seen object/array
|
|
61
|
+
| u8(TAG_ARRAY) uvar(length) VALUE*
|
|
62
|
+
| u8(TAG_PLAIN_OBJECT) uvar(shapeIdx) VALUE* // reconstructed as {}
|
|
63
|
+
| u8(TAG_REFLECT_OBJECT) uvar(classIdx) uvar(shapeIdx) VALUE* // new Klass() + field assignment
|
|
64
|
+
| u8(TAG_ADAPTER_OBJECT) uvar(classIdx) <adapter payload>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Object indices.** Every `ARRAY`, `PLAIN_OBJECT`, `REFLECT_OBJECT` and `ADAPTER_OBJECT` is assigned an index in
|
|
68
|
+
first-seen order — the writer keeps an identity `Map`, the reader an array. Indices are *not written* for new objects
|
|
69
|
+
(both sides count in lock-step); only `TAG_REF` carries an explicit index. Both sides register an object **before**
|
|
70
|
+
reading/writing its contents, so a cyclic child resolves to the (partially built) parent.
|
|
71
|
+
|
|
72
|
+
**Two version numbers.** `OBJECT_FORMAT_VERSION` versions the framing; the per-class `version` in the class table
|
|
73
|
+
versions one class's payload and drives migration. They never interfere.
|
|
74
|
+
|
|
75
|
+
**Prepended tables.** The body is built into a scratch buffer while the class/shape tables fill; the document then
|
|
76
|
+
writes header + tables + a byte-copy of the body. Reading is a single forward pass (the tables come first). The only
|
|
77
|
+
cost over single-pass writing is that final copy — as intended.
|
|
78
|
+
|
|
79
|
+
## 4. Reflection
|
|
80
|
+
|
|
81
|
+
A class instance with no adapter, or a plain object, is written by enumerating `Object.keys(value)` (own enumerable
|
|
82
|
+
string keys, in insertion order). The ordered name list is interned into the shape table; the body carries the shape
|
|
83
|
+
index and one `VALUE` per field. This *reads* the instance's fields but never mutates it, and assumes no special field
|
|
84
|
+
names — it serializes whatever is there.
|
|
85
|
+
|
|
86
|
+
On read:
|
|
87
|
+
- `PLAIN_OBJECT` → `{}`, fields assigned.
|
|
88
|
+
- `REFLECT_OBJECT` → `new Klass()` (from the module registry, or the adapter's `klass` if one happens to be
|
|
89
|
+
registered), fields assigned over it.
|
|
90
|
+
|
|
91
|
+
Because the shape is self-describing, reflective encoding is naturally tolerant of additive field changes: a reader
|
|
92
|
+
simply materialises whatever fields the shape lists. Semantic changes that need real migration are a reason to graduate
|
|
93
|
+
the class to an adapter.
|
|
94
|
+
|
|
95
|
+
**Strings and keys are NUL-safe.** All strings this system writes itself — type names, field names, string values and
|
|
96
|
+
bigints — use a byte-length-prefixed UTF-8 codec ([`objectStringCodec.js`](./objectStringCodec.js)), not
|
|
97
|
+
`BinaryBuffer.writeUTF8String`. The latter is NUL-*terminated* on read, so it cannot round-trip a string (or object
|
|
98
|
+
key) containing `U+0000`; the byte-length prefix has no terminator and round-trips every code point. (Strings an
|
|
99
|
+
*adapter* writes with `buffer.writeUTF8String` still follow that primitive's own behaviour — another reason adapters
|
|
100
|
+
own their encoding.)
|
|
101
|
+
|
|
102
|
+
## 5. Adapters (the override)
|
|
103
|
+
|
|
104
|
+
When the writer's registry has an adapter for a value's class, the value is written as `ADAPTER_OBJECT`: a class-table
|
|
105
|
+
index plus the adapter's own payload. The adapter fully controls the bytes (e.g. three `float64`s for a `Vector3`,
|
|
106
|
+
skipping field names and per-value tags), and can recurse into arbitrary children through the driving context via
|
|
107
|
+
`this.context.writeValue` / `readValue` (see [`ObjectSerializationAdapter`](./ObjectSerializationAdapter.js)). Adapter
|
|
108
|
+
objects participate in the same reference table, so shared/cyclic adapter instances work too.
|
|
109
|
+
|
|
110
|
+
Adapters are the extension point for anything reflection shouldn't own: `Vector3`, typed arrays, `Map`/`Set`/`Date`,
|
|
111
|
+
quantised or bit-packed encodings, or classes needing explicit migration.
|
|
112
|
+
|
|
113
|
+
## 6. Reference semantics
|
|
114
|
+
|
|
115
|
+
Objects and arrays are tracked by **identity**. The first time an instance is seen it is encoded in full and assigned
|
|
116
|
+
the next index; every later occurrence — whether a shared child or a cycle back to an ancestor — is a `TAG_REF` to that
|
|
117
|
+
index. This preserves object identity across the graph (`restored.a === restored.b`) and makes cyclic graphs
|
|
118
|
+
serialisable instead of stack-overflowing. Primitives are never reference-tracked.
|
|
119
|
+
|
|
120
|
+
## 7. Versioning & migration
|
|
121
|
+
|
|
122
|
+
Adapter classes store their `version` once per class in the table. On read, the deserializer resolves migration
|
|
123
|
+
**lazily, on the first `ADAPTER_OBJECT` of the class** — never at class-table time — because whether a class was written
|
|
124
|
+
via an adapter (and so whether the version means anything) is a property of the body tag, not of the reader's registry.
|
|
125
|
+
If the stored version differs from the reader's adapter version (in *either* direction), the payload is run through the
|
|
126
|
+
shortest [`BinaryClassUpgrader`](../BinaryClassUpgrader.js) chain the registry can find. The chain is really a
|
|
127
|
+
*migrator*: it can move down a version as readily as up, so there is no artificial "data must not be newer" rule — only
|
|
128
|
+
"a path must exist".
|
|
129
|
+
|
|
130
|
+
Reflective classes are stored at version 0 and are never migrated (their shapes already make them additively tolerant).
|
|
131
|
+
|
|
132
|
+
**Nesting caveat.** A migrator reads one object's payload and rewrites it; the fresh scratch buffers make this
|
|
133
|
+
re-entrant. As with the existing collection path, migrating an *adapter class whose payload itself nests other objects*
|
|
134
|
+
requires the migrator to reproduce that nested framing — rare, and a reason to migrate leaf classes or use reflection.
|
|
135
|
+
|
|
136
|
+
## 8. Class identity & minification
|
|
137
|
+
|
|
138
|
+
The name written for a class is resolved, in order:
|
|
139
|
+
1. the name its adapter is registered under (`registry.getClassName(constructor)` — a reverse index keyed on the
|
|
140
|
+
constructor object, so it survives minification), else
|
|
141
|
+
2. the class's static `typeName` (the deliberate escape hatch: a minifier mangles `constructor.name` but leaves a
|
|
142
|
+
`static typeName = "Foo"` string intact).
|
|
143
|
+
|
|
144
|
+
A class instance with neither an adapter nor a `typeName` cannot be safely round-tripped and is rejected at write time
|
|
145
|
+
with a message pointing at the fix. Plain objects (prototype `Object.prototype` or null) need no name — they round-trip
|
|
146
|
+
as `{}`.
|
|
147
|
+
|
|
148
|
+
## 9. Registries
|
|
149
|
+
|
|
150
|
+
- **Serializer** needs only a `BinarySerializationRegistry` (adapters + `getClassName` + versions).
|
|
151
|
+
- **Deserializer** needs the same registry plus a `ModuleRegistry` (`typeName → class`) to reconstruct reflective
|
|
152
|
+
class instances. The module registry is optional when the data has no reflective class instances (plain objects,
|
|
153
|
+
arrays and adapter-backed classes don't need it). A reflective class missing from it is a clear read-time error.
|
|
154
|
+
|
|
155
|
+
## 10. Non-goals & limitations
|
|
156
|
+
|
|
157
|
+
- **Not self-contained for exotic built-ins.** `Date`, `Map`, `Set`, `RegExp`, typed arrays and `ArrayBuffer` are not
|
|
158
|
+
handled natively — register an adapter for them. This keeps the core small; the adapter mechanism is the seam.
|
|
159
|
+
- **Functions and symbols** cannot be serialized (thrown at write). Symbol-keyed and non-enumerable properties are
|
|
160
|
+
ignored (`Object.keys` semantics), matching JSON expectations.
|
|
161
|
+
- **Reconstruction uses `new Klass()`** — classes need a usable no-argument constructor. Null-prototype objects come
|
|
162
|
+
back as ordinary `{}`.
|
|
163
|
+
- **Depth, not breadth, is the limit.** References make cyclic/shared graphs safe, but a pathologically deep graph can
|
|
164
|
+
still exhaust the call stack.
|
|
165
|
+
|
|
166
|
+
## 11. Security posture
|
|
167
|
+
|
|
168
|
+
Deserialization only ever constructs (a) classes with a registered adapter, or (b) classes present in the module
|
|
169
|
+
registry, or (c) plain objects/arrays. It never instantiates an attacker-named arbitrary type and never invokes
|
|
170
|
+
stream-driven methods beyond a class's own `deserialize`. This registry/module allow-list is the standard mitigation
|
|
171
|
+
for the reflective-deserializer RCE lineage (Java `Serializable`, `pickle`, .NET `BinaryFormatter`). Reflection does
|
|
172
|
+
read *all* own-enumerable fields of the objects you hand it — use an adapter when a class must control or withhold
|
|
173
|
+
fields.
|
|
174
|
+
|
|
175
|
+
## 12. API
|
|
176
|
+
|
|
177
|
+
`BinaryObjectSerializer(registry)` — `serialize(buffer, value)`, `writeValue(buffer, value)` (for nesting adapters).
|
|
178
|
+
|
|
179
|
+
`BinaryObjectDeSerializer(registry, modules?)` — `deserialize(buffer)`, `readValue(buffer)` (for nesting adapters),
|
|
180
|
+
`setModuleRegistry(modules)`.
|
|
181
|
+
|
|
182
|
+
`ObjectSerializationAdapter extends BinaryClassSerializationAdapter` — base for nesting adapters; `initialize(context)`
|
|
183
|
+
captures the serializer/deserializer into `this.context`.
|
|
184
|
+
|
|
185
|
+
`BinarySerializationRegistry.getClassName(klass)` — reverse of `registerAdapter`; constructor → registered name.
|
|
186
|
+
|
|
187
|
+
## 13. Tests
|
|
188
|
+
|
|
189
|
+
- `*.reflection.spec.js` — primitives (incl. bigint, NaN/±Infinity, large-int→float), plain objects, arrays, reflective
|
|
190
|
+
class instances, nesting, field-order preservation, no-mutation.
|
|
191
|
+
- `*.references.spec.js` — shared references, self-cycles, mutual cycles, self-containing arrays, cycles through class
|
|
192
|
+
instances and container adapters.
|
|
193
|
+
- `*.adapters.spec.js` — adapter used & respected, adapter framing vs reflective, adapter objects nested/shared,
|
|
194
|
+
container adapter recursing through the context.
|
|
195
|
+
- `*.versioning.spec.js` — forward migration, **backward** migration, version-match no-op, missing-path error, shared
|
|
196
|
+
class-table migration.
|
|
197
|
+
- `*.errors.spec.js` — function/symbol rejection, missing typeName, missing module registry, bad magic / format
|
|
198
|
+
version, `typeName`-as-identity, class+shape interning, large homogeneous collection.
|
|
199
|
+
- `BinarySerializationRegistry.spec.js` — `getClassName` reverse-lookup.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire-format constants for the general object-graph binary serializer.
|
|
3
|
+
*
|
|
4
|
+
* A document is: `HEADER CLASS_TABLE SHAPE_TABLE BODY`, where BODY is a single self-describing VALUE. Every
|
|
5
|
+
* object/array in the body is assigned an implicit index in first-seen order so later occurrences of the same
|
|
6
|
+
* instance (shared references, cycles) encode as a compact back-reference. Class names and reflective field-name
|
|
7
|
+
* sets ("shapes") are interned into the two prepended tables and referenced by index, so a graph of a thousand
|
|
8
|
+
* same-class objects pays for the class name and field names exactly once.
|
|
9
|
+
*
|
|
10
|
+
* @author Alex Goldring
|
|
11
|
+
* @copyright Company Named Limited (c) 2026
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Magic marker at the head of a document (`'O','3'`). Lets a reader reject a foreign/corrupt stream.
|
|
15
|
+
* @readonly
|
|
16
|
+
* @type {number}
|
|
17
|
+
*/
|
|
18
|
+
export const OBJECT_MAGIC: number;
|
|
19
|
+
/**
|
|
20
|
+
* Version of the framing (header + tables + value tags). Independent of any individual class's payload version.
|
|
21
|
+
* @readonly
|
|
22
|
+
* @type {number}
|
|
23
|
+
*/
|
|
24
|
+
export const OBJECT_FORMAT_VERSION: number;
|
|
25
|
+
/** `null`. @readonly @type {number} */
|
|
26
|
+
export const TAG_NULL: number;
|
|
27
|
+
/** `undefined`. @readonly @type {number} */
|
|
28
|
+
export const TAG_UNDEFINED: number;
|
|
29
|
+
/** boolean `false`. @readonly @type {number} */
|
|
30
|
+
export const TAG_FALSE: number;
|
|
31
|
+
/** boolean `true`. @readonly @type {number} */
|
|
32
|
+
export const TAG_TRUE: number;
|
|
33
|
+
/** non-negative integer that fits a varint; followed by `uvar(value)`. @readonly @type {number} */
|
|
34
|
+
export const TAG_POS_INT: number;
|
|
35
|
+
/** negative integer that fits a varint; followed by `uvar(-value)`. @readonly @type {number} */
|
|
36
|
+
export const TAG_NEG_INT: number;
|
|
37
|
+
/** any other number (float, NaN, Infinity, out-of-range integer); followed by `float64`. @readonly @type {number} */
|
|
38
|
+
export const TAG_FLOAT: number;
|
|
39
|
+
/** string; followed by a length-prefixed UTF-8 string. @readonly @type {number} */
|
|
40
|
+
export const TAG_STRING: number;
|
|
41
|
+
/** bigint; followed by its base-10 string form. @readonly @type {number} */
|
|
42
|
+
export const TAG_BIGINT: number;
|
|
43
|
+
/** back-reference to an already-seen object/array; followed by `uvar(objectIndex)`. @readonly @type {number} */
|
|
44
|
+
export const TAG_REF: number;
|
|
45
|
+
/** array; followed by `uvar(length)` then `length` VALUEs. @readonly @type {number} */
|
|
46
|
+
export const TAG_ARRAY: number;
|
|
47
|
+
/** plain object (reconstructed as `{}`); followed by `uvar(shapeIndex)` then one VALUE per field. @readonly @type {number} */
|
|
48
|
+
export const TAG_PLAIN_OBJECT: number;
|
|
49
|
+
/** reflective class instance; followed by `uvar(classIndex) uvar(shapeIndex)` then one VALUE per field. @readonly @type {number} */
|
|
50
|
+
export const TAG_REFLECT_OBJECT: number;
|
|
51
|
+
/** class instance serialized by a registered adapter; followed by `uvar(classIndex)` then the adapter payload. @readonly @type {number} */
|
|
52
|
+
export const TAG_ADAPTER_OBJECT: number;
|
|
53
|
+
//# sourceMappingURL=ObjectBinaryFormat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObjectBinaryFormat.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,2BAFU,MAAM,CAEmB;AAEnC;;;;GAIG;AACH,oCAFU,MAAM,CAEuB;AAIvC,uCAAuC;AACvC,uBAD6B,MAAM,CACT;AAC1B,4CAA4C;AAC5C,4BADkC,MAAM,CACT;AAC/B,gDAAgD;AAChD,wBADsC,MAAM,CACjB;AAC3B,+CAA+C;AAC/C,uBADqC,MAAM,CACjB;AAC1B,mGAAmG;AACnG,0BADyF,MAAM,CAClE;AAC7B,gGAAgG;AAChG,0BADsF,MAAM,CAC/D;AAC7B,qHAAqH;AACrH,wBAD2G,MAAM,CACtF;AAC3B,mFAAmF;AACnF,yBADyE,MAAM,CACnD;AAC5B,4EAA4E;AAC5E,yBADkE,MAAM,CAC5C;AAC5B,gHAAgH;AAChH,sBADsG,MAAM,CACnF;AACzB,uFAAuF;AACvF,wBAD6E,MAAM,CACvD;AAC5B,8HAA8H;AAC9H,+BADoH,MAAM,CACvF;AACnC,oIAAoI;AACpI,iCAD0H,MAAM,CAC3F;AACrC,2IAA2I;AAC3I,iCADiI,MAAM,CAClG"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire-format constants for the general object-graph binary serializer.
|
|
3
|
+
*
|
|
4
|
+
* A document is: `HEADER CLASS_TABLE SHAPE_TABLE BODY`, where BODY is a single self-describing VALUE. Every
|
|
5
|
+
* object/array in the body is assigned an implicit index in first-seen order so later occurrences of the same
|
|
6
|
+
* instance (shared references, cycles) encode as a compact back-reference. Class names and reflective field-name
|
|
7
|
+
* sets ("shapes") are interned into the two prepended tables and referenced by index, so a graph of a thousand
|
|
8
|
+
* same-class objects pays for the class name and field names exactly once.
|
|
9
|
+
*
|
|
10
|
+
* @author Alex Goldring
|
|
11
|
+
* @copyright Company Named Limited (c) 2026
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Magic marker at the head of a document (`'O','3'`). Lets a reader reject a foreign/corrupt stream.
|
|
16
|
+
* @readonly
|
|
17
|
+
* @type {number}
|
|
18
|
+
*/
|
|
19
|
+
export const OBJECT_MAGIC = 0x4F33;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Version of the framing (header + tables + value tags). Independent of any individual class's payload version.
|
|
23
|
+
* @readonly
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
export const OBJECT_FORMAT_VERSION = 1;
|
|
27
|
+
|
|
28
|
+
// --- value tags (one byte, first byte of every VALUE) ---
|
|
29
|
+
|
|
30
|
+
/** `null`. @readonly @type {number} */
|
|
31
|
+
export const TAG_NULL = 0;
|
|
32
|
+
/** `undefined`. @readonly @type {number} */
|
|
33
|
+
export const TAG_UNDEFINED = 1;
|
|
34
|
+
/** boolean `false`. @readonly @type {number} */
|
|
35
|
+
export const TAG_FALSE = 2;
|
|
36
|
+
/** boolean `true`. @readonly @type {number} */
|
|
37
|
+
export const TAG_TRUE = 3;
|
|
38
|
+
/** non-negative integer that fits a varint; followed by `uvar(value)`. @readonly @type {number} */
|
|
39
|
+
export const TAG_POS_INT = 4;
|
|
40
|
+
/** negative integer that fits a varint; followed by `uvar(-value)`. @readonly @type {number} */
|
|
41
|
+
export const TAG_NEG_INT = 5;
|
|
42
|
+
/** any other number (float, NaN, Infinity, out-of-range integer); followed by `float64`. @readonly @type {number} */
|
|
43
|
+
export const TAG_FLOAT = 6;
|
|
44
|
+
/** string; followed by a length-prefixed UTF-8 string. @readonly @type {number} */
|
|
45
|
+
export const TAG_STRING = 7;
|
|
46
|
+
/** bigint; followed by its base-10 string form. @readonly @type {number} */
|
|
47
|
+
export const TAG_BIGINT = 8;
|
|
48
|
+
/** back-reference to an already-seen object/array; followed by `uvar(objectIndex)`. @readonly @type {number} */
|
|
49
|
+
export const TAG_REF = 9;
|
|
50
|
+
/** array; followed by `uvar(length)` then `length` VALUEs. @readonly @type {number} */
|
|
51
|
+
export const TAG_ARRAY = 10;
|
|
52
|
+
/** plain object (reconstructed as `{}`); followed by `uvar(shapeIndex)` then one VALUE per field. @readonly @type {number} */
|
|
53
|
+
export const TAG_PLAIN_OBJECT = 11;
|
|
54
|
+
/** reflective class instance; followed by `uvar(classIndex) uvar(shapeIndex)` then one VALUE per field. @readonly @type {number} */
|
|
55
|
+
export const TAG_REFLECT_OBJECT = 12;
|
|
56
|
+
/** class instance serialized by a registered adapter; followed by `uvar(classIndex)` then the adapter payload. @readonly @type {number} */
|
|
57
|
+
export const TAG_ADAPTER_OBJECT = 13;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience base class for a hand-written {@link BinaryClassSerializationAdapter} whose class nests other objects
|
|
3
|
+
* that should be serialized through the object-graph system (so they share its reference de-duplication, class table
|
|
4
|
+
* and shape table).
|
|
5
|
+
*
|
|
6
|
+
* Before every use, the driving context passes itself to {@link initialize}: a {@link BinaryObjectSerializer} while
|
|
7
|
+
* writing, a {@link BinaryObjectDeSerializer} while reading. Both expose `writeValue`/`readValue` respectively, so a
|
|
8
|
+
* subclass recurses with `this.context.writeValue(buffer, child)` / `this.context.readValue(buffer)`.
|
|
9
|
+
*
|
|
10
|
+
* Leaf adapters that only read/write primitives do not need this base class — extend
|
|
11
|
+
* {@link BinaryClassSerializationAdapter} directly and ignore the context.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle note: the object-graph driver calls {@link initialize} before *every* object it (de)serializes (to
|
|
14
|
+
* re-bind the current context) and never calls `finalize()`. Adapters used on this path must therefore not rely on
|
|
15
|
+
* the once-per-run acquire/`finalize`-release lifecycle that the ECS collection path provides.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* class InventoryAdapter extends ObjectSerializationAdapter {
|
|
19
|
+
* klass = Inventory;
|
|
20
|
+
* version = 0;
|
|
21
|
+
*
|
|
22
|
+
* serialize(buffer, inventory) {
|
|
23
|
+
* buffer.writeUintVar(inventory.items.length);
|
|
24
|
+
* for (const item of inventory.items) {
|
|
25
|
+
* this.context.writeValue(buffer, item); // any registered type, plain object, or null
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* deserialize(buffer, inventory) {
|
|
30
|
+
* const n = buffer.readUintVar();
|
|
31
|
+
* for (let i = 0; i < n; i++) {
|
|
32
|
+
* inventory.items.push(this.context.readValue(buffer));
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* @author Alex Goldring
|
|
38
|
+
* @copyright Company Named Limited (c) 2026
|
|
39
|
+
*/
|
|
40
|
+
export class ObjectSerializationAdapter extends BinaryClassSerializationAdapter<any> {
|
|
41
|
+
constructor();
|
|
42
|
+
/**
|
|
43
|
+
* The context driving the current (de)serialization, used to recurse into nested values.
|
|
44
|
+
* @type {BinaryObjectSerializer|BinaryObjectDeSerializer}
|
|
45
|
+
*/
|
|
46
|
+
context: BinaryObjectSerializer | BinaryObjectDeSerializer;
|
|
47
|
+
/**
|
|
48
|
+
* @param {BinaryObjectSerializer|BinaryObjectDeSerializer} context
|
|
49
|
+
*/
|
|
50
|
+
initialize(context: BinaryObjectSerializer | BinaryObjectDeSerializer): void;
|
|
51
|
+
}
|
|
52
|
+
import { BinaryClassSerializationAdapter } from "../BinaryClassSerializationAdapter.js";
|
|
53
|
+
//# sourceMappingURL=ObjectSerializationAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObjectSerializationAdapter.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;IAEI;;;OAGG;IACH,SAFU,iDAA+C,CAE1C;IAEf;;OAEG;IACH,oBAFW,iDAA+C,QAIzD;CACJ;gDAvD+C,uCAAuC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BinaryClassSerializationAdapter } from "../BinaryClassSerializationAdapter.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convenience base class for a hand-written {@link BinaryClassSerializationAdapter} whose class nests other objects
|
|
5
|
+
* that should be serialized through the object-graph system (so they share its reference de-duplication, class table
|
|
6
|
+
* and shape table).
|
|
7
|
+
*
|
|
8
|
+
* Before every use, the driving context passes itself to {@link initialize}: a {@link BinaryObjectSerializer} while
|
|
9
|
+
* writing, a {@link BinaryObjectDeSerializer} while reading. Both expose `writeValue`/`readValue` respectively, so a
|
|
10
|
+
* subclass recurses with `this.context.writeValue(buffer, child)` / `this.context.readValue(buffer)`.
|
|
11
|
+
*
|
|
12
|
+
* Leaf adapters that only read/write primitives do not need this base class — extend
|
|
13
|
+
* {@link BinaryClassSerializationAdapter} directly and ignore the context.
|
|
14
|
+
*
|
|
15
|
+
* Lifecycle note: the object-graph driver calls {@link initialize} before *every* object it (de)serializes (to
|
|
16
|
+
* re-bind the current context) and never calls `finalize()`. Adapters used on this path must therefore not rely on
|
|
17
|
+
* the once-per-run acquire/`finalize`-release lifecycle that the ECS collection path provides.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* class InventoryAdapter extends ObjectSerializationAdapter {
|
|
21
|
+
* klass = Inventory;
|
|
22
|
+
* version = 0;
|
|
23
|
+
*
|
|
24
|
+
* serialize(buffer, inventory) {
|
|
25
|
+
* buffer.writeUintVar(inventory.items.length);
|
|
26
|
+
* for (const item of inventory.items) {
|
|
27
|
+
* this.context.writeValue(buffer, item); // any registered type, plain object, or null
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* deserialize(buffer, inventory) {
|
|
32
|
+
* const n = buffer.readUintVar();
|
|
33
|
+
* for (let i = 0; i < n; i++) {
|
|
34
|
+
* inventory.items.push(this.context.readValue(buffer));
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* @author Alex Goldring
|
|
40
|
+
* @copyright Company Named Limited (c) 2026
|
|
41
|
+
*/
|
|
42
|
+
export class ObjectSerializationAdapter extends BinaryClassSerializationAdapter {
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The context driving the current (de)serialization, used to recurse into nested values.
|
|
46
|
+
* @type {BinaryObjectSerializer|BinaryObjectDeSerializer}
|
|
47
|
+
*/
|
|
48
|
+
context = null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {BinaryObjectSerializer|BinaryObjectDeSerializer} context
|
|
52
|
+
*/
|
|
53
|
+
initialize(context) {
|
|
54
|
+
this.context = context;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# object-v2 — general object-graph binary serialization
|
|
2
|
+
|
|
3
|
+
Serialize an arbitrary JavaScript object graph to a `BinaryBuffer` and back. Classes are serialized by **reflection by
|
|
4
|
+
default** — no per-class code required — and a hand-written adapter is an optional override for classes that want a
|
|
5
|
+
compact or custom encoding. Shared references and cycles are preserved; class names and field-name sets are interned so
|
|
6
|
+
repeated types are cheap.
|
|
7
|
+
|
|
8
|
+
See [DESIGN.md](./DESIGN.md) for the wire format and rationale.
|
|
9
|
+
|
|
10
|
+
## Quick start (reflection — no adapter)
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
|
|
14
|
+
import { ModuleRegistry } from "../../../../../core/model/ModuleRegistry.js";
|
|
15
|
+
import { BinarySerializationRegistry } from "../BinarySerializationRegistry.js";
|
|
16
|
+
import { BinaryObjectSerializer } from "./BinaryObjectSerializer.js";
|
|
17
|
+
import { BinaryObjectDeSerializer } from "./BinaryObjectDeSerializer.js";
|
|
18
|
+
|
|
19
|
+
class Monster {
|
|
20
|
+
name = "";
|
|
21
|
+
hp = 0;
|
|
22
|
+
loot = [];
|
|
23
|
+
}
|
|
24
|
+
Monster.typeName = "Monster"; // stable identity (survives minification)
|
|
25
|
+
|
|
26
|
+
const registry = new BinarySerializationRegistry(); // no adapter needed
|
|
27
|
+
const modules = new ModuleRegistry();
|
|
28
|
+
modules.add("Monster", Monster); // so the reader can reconstruct it
|
|
29
|
+
|
|
30
|
+
const buffer = new BinaryBuffer();
|
|
31
|
+
new BinaryObjectSerializer(registry).serialize(buffer, someMonster);
|
|
32
|
+
|
|
33
|
+
buffer.position = 0;
|
|
34
|
+
const restored = new BinaryObjectDeSerializer(registry, modules).deserialize(buffer);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Plain objects and arrays need no registration at all:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
new BinaryObjectSerializer(registry).serialize(buffer, { a: 1, tags: ["x", "y"], nested: { z: true } });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Custom adapter (the override)
|
|
44
|
+
|
|
45
|
+
Register a `BinaryClassSerializationAdapter` and it takes over that class's encoding — used automatically wherever the
|
|
46
|
+
class appears in the graph:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
class Vector3Adapter extends BinaryClassSerializationAdapter {
|
|
50
|
+
klass = Vector3;
|
|
51
|
+
version = 0;
|
|
52
|
+
serialize(buffer, v) { buffer.writeFloat64(v.x); buffer.writeFloat64(v.y); buffer.writeFloat64(v.z); }
|
|
53
|
+
deserialize(buffer, v) { v.x = buffer.readFloat64(); v.y = buffer.readFloat64(); v.z = buffer.readFloat64(); }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
registry.registerAdapter(new Vector3Adapter()); // Vector3 now uses this everywhere
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A class that nests other objects extends `ObjectSerializationAdapter` and recurses through `this.context`:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { ObjectSerializationAdapter } from "./ObjectSerializationAdapter.js";
|
|
63
|
+
|
|
64
|
+
class BagAdapter extends ObjectSerializationAdapter {
|
|
65
|
+
klass = Bag;
|
|
66
|
+
version = 0;
|
|
67
|
+
serialize(buffer, bag) {
|
|
68
|
+
buffer.writeUintVar(bag.items.length);
|
|
69
|
+
for (const item of bag.items) this.context.writeValue(buffer, item); // any type, or null
|
|
70
|
+
}
|
|
71
|
+
deserialize(buffer, bag) {
|
|
72
|
+
const n = buffer.readUintVar();
|
|
73
|
+
for (let i = 0; i < n; i++) bag.items.push(this.context.readValue(buffer));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## References & cycles
|
|
79
|
+
|
|
80
|
+
Shared and cyclic references are preserved automatically:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
const shared = { id: 1 };
|
|
84
|
+
const restored = /* round trip */ { a: shared, b: shared };
|
|
85
|
+
restored.a === restored.b; // true
|
|
86
|
+
|
|
87
|
+
const node = {}; node.self = node;
|
|
88
|
+
/* round trip */ node.self === node; // true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Versioning
|
|
92
|
+
|
|
93
|
+
Bump an adapter's `version` when its layout changes and register a migrator. Migration runs in **either** direction —
|
|
94
|
+
reading older *or* newer data works as long as a path exists:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
class WidgetMigrator_0_1 extends BinaryClassUpgrader {
|
|
98
|
+
__startVersion = 0;
|
|
99
|
+
__targetVersion = 1;
|
|
100
|
+
upgrade(source, target) { /* read v0 bytes from source, write v1 bytes to target */ }
|
|
101
|
+
}
|
|
102
|
+
registry.registerUpgrader("Widget", new WidgetMigrator_0_1());
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## What it does not do
|
|
106
|
+
|
|
107
|
+
- **No native `Date`/`Map`/`Set`/typed arrays** — register an adapter for them.
|
|
108
|
+
- **No functions or symbols** (serialization throws; symbol/non-enumerable keys are ignored).
|
|
109
|
+
- **Reconstruction uses `new Klass()`** — classes need a no-argument constructor and must be registered (adapter or
|
|
110
|
+
module registry) so the reader can build them; only registered/plain types are ever instantiated.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write a string as `uvar(byteLength)` + raw UTF-8 bytes.
|
|
3
|
+
* @param {BinaryBuffer} buffer
|
|
4
|
+
* @param {string} value
|
|
5
|
+
*/
|
|
6
|
+
export function writeObjectString(buffer: BinaryBuffer, value: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Read a string written by {@link writeObjectString}.
|
|
9
|
+
* @param {BinaryBuffer} buffer
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function readObjectString(buffer: BinaryBuffer): string;
|
|
13
|
+
//# sourceMappingURL=objectStringCodec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectStringCodec.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/ecs/storage/binary/object-v2/objectStringCodec.js"],"names":[],"mappings":"AAuBA;;;;GAIG;AACH,+DAFW,MAAM,QAOhB;AAED;;;;GAIG;AACH,wDAFa,MAAM,CASlB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NUL-safe string codec for the object-graph serializer.
|
|
3
|
+
*
|
|
4
|
+
* The engine's {@link BinaryBuffer.writeUTF8String}/{@link BinaryBuffer.readUTF8String} pair is intentionally
|
|
5
|
+
* NUL-terminated on read (an in-band `0x00` byte ends the string), so it cannot round-trip strings — or object keys —
|
|
6
|
+
* that contain `U+0000`. Because this serializer must handle *arbitrary* strings and field names by reflection, it
|
|
7
|
+
* uses this byte-length-prefixed encoding instead: `uvar(byteLength)` followed by the raw UTF-8 bytes. There is no
|
|
8
|
+
* terminator, so every code point (including `U+0000`) round-trips, and a following value never desynchronizes.
|
|
9
|
+
*
|
|
10
|
+
* @author Alex Goldring
|
|
11
|
+
* @copyright Company Named Limited (c) 2026
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @type {TextEncoder}
|
|
16
|
+
*/
|
|
17
|
+
const ENCODER = new TextEncoder();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @type {TextDecoder}
|
|
21
|
+
*/
|
|
22
|
+
const DECODER = new TextDecoder();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Write a string as `uvar(byteLength)` + raw UTF-8 bytes.
|
|
26
|
+
* @param {BinaryBuffer} buffer
|
|
27
|
+
* @param {string} value
|
|
28
|
+
*/
|
|
29
|
+
export function writeObjectString(buffer, value) {
|
|
30
|
+
const bytes = ENCODER.encode(value);
|
|
31
|
+
|
|
32
|
+
buffer.writeUintVar(bytes.length);
|
|
33
|
+
buffer.writeBytes(bytes, 0, bytes.length);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read a string written by {@link writeObjectString}.
|
|
38
|
+
* @param {BinaryBuffer} buffer
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function readObjectString(buffer) {
|
|
42
|
+
const length = buffer.readUintVar();
|
|
43
|
+
|
|
44
|
+
const bytes = new Uint8Array(length);
|
|
45
|
+
buffer.readBytes(bytes, 0, length);
|
|
46
|
+
|
|
47
|
+
return DECODER.decode(bytes);
|
|
48
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize `value` with a serializer built on `registry`, then deserialize it with a deserializer built on
|
|
3
|
+
* `registry` + `modules`. Shared by the object-graph specs.
|
|
4
|
+
*
|
|
5
|
+
* @template T
|
|
6
|
+
* @param {T} value
|
|
7
|
+
* @param {{registry?: BinarySerializationRegistry, modules?: ModuleRegistry}} [options]
|
|
8
|
+
* @returns {T}
|
|
9
|
+
*/
|
|
10
|
+
export function roundTrip<T>(value: T, { registry, modules }?: {
|
|
11
|
+
registry?: BinarySerializationRegistry;
|
|
12
|
+
modules?: ModuleRegistry;
|
|
13
|
+
}): T;
|
|
14
|
+
/**
|
|
15
|
+
* @param {*} value
|
|
16
|
+
* @param {BinarySerializationRegistry} [registry]
|
|
17
|
+
* @returns {BinaryBuffer} rewound to position 0
|
|
18
|
+
*/
|
|
19
|
+
export function serialize(value: any, registry?: BinarySerializationRegistry): BinaryBuffer;
|
|
20
|
+
/**
|
|
21
|
+
* @param {*} value
|
|
22
|
+
* @param {BinarySerializationRegistry} [registry]
|
|
23
|
+
* @returns {number} byte length of the serialized form
|
|
24
|
+
*/
|
|
25
|
+
export function serializedSize(value: any, registry?: BinarySerializationRegistry): number;
|
|
26
|
+
import { BinarySerializationRegistry } from "../BinarySerializationRegistry.js";
|
|
27
|
+
import { ModuleRegistry } from "../../../../../core/model/ModuleRegistry.js";
|
|
28
|
+
import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
|
|
29
|
+
//# sourceMappingURL=serializerTestHarness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializerTestHarness.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,+DAHW;IAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC;IAAC,OAAO,CAAC,EAAE,cAAc,CAAA;CAAC,KAS5E;AAED;;;;GAIG;AACH,iDAHW,2BAA2B,GACzB,YAAY,CAQxB;AAED;;;;GAIG;AACH,sDAHW,2BAA2B,GACzB,MAAM,CAIlB;4CAzC2C,mCAAmC;+BADhD,6CAA6C;6BAD/C,4CAA4C"}
|