@woosh/meep-engine 3.22.0 → 3.23.0
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/package.json +1 -1
- package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts +118 -0
- package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts.map +1 -0
- package/src/core/geom/3d/hash-grid/PointHashGrid3.js +283 -0
- package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/BoxShape3D.js +6 -17
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +3 -14
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +3 -21
- package/src/core/geom/3d/shape/PlaneShape3D.d.ts +105 -0
- package/src/core/geom/3d/shape/PlaneShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/PlaneShape3D.js +251 -0
- package/src/core/geom/3d/shape/SphereShape3D.js +3 -3
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +14 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +15 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts +51 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.js +113 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts +46 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.js +82 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts +60 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.js +126 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts +50 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.js +53 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts +52 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.js +61 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts +41 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.js +71 -0
- package/src/core/process/ConcurrencyGate.d.ts +103 -0
- package/src/core/process/ConcurrencyGate.d.ts.map +1 -0
- package/src/core/process/ConcurrencyGate.js +207 -0
- package/src/engine/asset/AssetManager.d.ts +10 -0
- package/src/engine/asset/AssetManager.d.ts.map +1 -1
- package/src/engine/asset/AssetManager.js +10 -0
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +30 -13
- package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +26 -0
- package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -0
- package/src/engine/graphics3/pose/collect_entity_playbacks.js +40 -0
- package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
- package/src/engine/graphics3/pose/query_entity_node_world_pose.js +74 -101
- package/src/engine/physics/cloth/MEASUREMENTS.md +1287 -4
- package/src/engine/physics/cloth/PLAN.md +1967 -1581
- package/src/engine/physics/cloth/build/ClothProxy.d.ts +147 -0
- package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -0
- package/src/engine/physics/cloth/build/ClothProxy.js +207 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +70 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +256 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +176 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.js +435 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts +12 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.js +38 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts +120 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.js +137 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +25 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +190 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +44 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.js +173 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +94 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +275 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts +126 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.js +199 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +9 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +32 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +114 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +243 -0
- package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +66 -57
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +109 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +479 -363
- package/src/engine/physics/cloth/ecs/ClothRig.d.ts +95 -0
- package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothRig.js +140 -0
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +30 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +1021 -773
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +90 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.js +374 -0
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts +32 -3
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.js +283 -254
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +59 -0
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +328 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +13 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +288 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +58 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +199 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +8 -63
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +317 -645
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +61 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_write_back.js +212 -449
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +49 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +115 -0
- package/src/engine/physics/cloth/playground/README.md +155 -1
- package/src/engine/physics/cloth/playground/collide.html +178 -0
- package/src/engine/physics/cloth/playground/collide_build.d.ts +95 -0
- package/src/engine/physics/cloth/playground/collide_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/collide_build.js +315 -0
- package/src/engine/physics/cloth/playground/collide_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/collide_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/collide_main.js +720 -0
- package/src/engine/physics/cloth/playground/garment.html +182 -0
- package/src/engine/physics/cloth/playground/garment_build.d.ts +78 -0
- package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/garment_build.js +359 -0
- package/src/engine/physics/cloth/playground/garment_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/garment_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/garment_main.js +633 -0
- package/src/engine/physics/cloth/solver/ClothState.d.ts +208 -5
- package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/ClothState.js +302 -8
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +87 -0
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_contact_find.js +343 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +112 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.js +448 -0
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts +4 -1
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +16 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +112 -5
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +117 -6
- package/src/engine/physics/cloth/solver/cloth_step.d.ts +40 -9
- package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_step.js +122 -10
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +95 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +285 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +129 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +501 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +75 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +202 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/vgeo_viewer/README.md +18 -0
- package/src/shade/playground/vgeo_viewer/main.js +87 -50
- package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUDatabase.js +191 -62
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +94 -2
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +414 -2
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts +14 -0
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js +13 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts +17 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts.map +1 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js +116 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +9 -4
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +843 -734
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts +12 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.js +13 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +17 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +185 -30
- package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +17 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +141 -24
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +204 -64
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +110 -10
- package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +19 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +19 -6
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +26 -7
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +173 -14
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +458 -51
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +49 -13
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +52 -14
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.js +19 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts +27 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js +48 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js +60 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +32 -2
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +258 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +723 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +49 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +50 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts +3 -0
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts.map +1 -1
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +121 -21
- package/src/shade/renderer/texture/virtual/README.md +6 -4
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +7 -0
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.d.ts +3 -3
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +3 -3
- package/src/engine/ecs/system/SystemExecutionPhase.d.ts +0 -9
- package/src/engine/ecs/system/SystemExecutionPhase.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/NOTES.md +0 -128
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts +0 -6
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.js +0 -5
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +0 -49
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +0 -264
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts +0 -44
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.js +0 -228
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts +0 -3
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.js +0 -73
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts +0 -14
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.js +0 -85
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts +0 -12
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.js +0 -63
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts +0 -12
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.js +0 -243
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts +0 -30
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.js +0 -39
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts +0 -9
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.js +0 -24
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts +0 -9
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +0 -26
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts +0 -11
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.js +0 -12
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts +0 -7
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.js +0 -16
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts +0 -7
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.js +0 -35
|
@@ -5,7 +5,6 @@ import { VGEO_NO_CHILD } from "../cluster/VGEO_NO_CHILD.js";
|
|
|
5
5
|
import { VGEO_SAME_PAGE } from "../cluster/VGEO_SAME_PAGE.js";
|
|
6
6
|
import { VGEO_FRAME_OFFSET } from "../frame/VGEO_FRAME_OFFSET.js";
|
|
7
7
|
import { vgeo_read_frame_extent } from "../frame/vgeo_read_frame_extent.js";
|
|
8
|
-
import { vgeo_validate_container } from "../vgeo_validate_container.js";
|
|
9
8
|
import { VGeoReadOptions } from "./VGeoReadOptions.js";
|
|
10
9
|
import { vgeo_read_directory } from "./vgeo_read_directory.js";
|
|
11
10
|
import { vgeo_read_header, vgeo_resident_head_size, VGEO_MIN_HEAD_BYTES } from "./vgeo_read_header.js";
|
|
@@ -17,6 +16,12 @@ import { vgeo_read_page } from "./vgeo_read_page.js";
|
|
|
17
16
|
*/
|
|
18
17
|
const LE = true;
|
|
19
18
|
|
|
19
|
+
/**
|
|
20
|
+
* What {@link VGeoContainerReader.release_head} leaves behind, shared because it is empty.
|
|
21
|
+
* @type {Uint8Array}
|
|
22
|
+
*/
|
|
23
|
+
const NO_HEAD = new Uint8Array(0);
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* A `.vgeo` container, read a page at a time.
|
|
22
27
|
*
|
|
@@ -42,11 +47,19 @@ const LE = true;
|
|
|
42
47
|
* in for one that did not, and draws both over the same surface. That is what activation is for, and
|
|
43
48
|
* it is why installing a page does bookkeeping rather than only filling a map.
|
|
44
49
|
*
|
|
50
|
+
* **Mechanics, not policy — and the line is deliberate.** Which page to fetch next, which to drop,
|
|
51
|
+
* how many bytes one asset may hold, how many requests may be in flight across a thousand of them:
|
|
52
|
+
* none of that is decided here, because none of it can be decided by one reader looking at one file.
|
|
53
|
+
* `VGeoResidencyManager` is where it is decided and readers register with one; §10's eviction bullet
|
|
54
|
+
* is the reason for the split rather than an apology for it. What *is* here is everything that is
|
|
55
|
+
* about the bytes — the fetch, the retries, the quarantine a page that will not arrive ends up in,
|
|
56
|
+
* and the accounting a budget owner spends against.
|
|
57
|
+
*
|
|
45
58
|
* **Validation is not on this path.** The mandatory reject rules of §4 and §10 are applied
|
|
46
59
|
* unconditionally by {@link vgeo_read_header} and {@link vgeo_read_page} — a file whose magic,
|
|
47
60
|
* versions, encoding or codec this build does not understand is refused rather than misread. The
|
|
48
|
-
* whole-file audit is
|
|
49
|
-
*
|
|
61
|
+
* whole-file audit is `vgeo_audit_container` in `tool/`, it reads every byte of the file, and
|
|
62
|
+
* nothing on the runtime path can reach it.
|
|
50
63
|
*
|
|
51
64
|
* @author Alex Goldring
|
|
52
65
|
* @copyright Company Named Limited (c) 2026
|
|
@@ -89,15 +102,36 @@ export class VGeoContainerReader {
|
|
|
89
102
|
#pending = new Map();
|
|
90
103
|
|
|
91
104
|
/**
|
|
92
|
-
* The
|
|
105
|
+
* The abort for each fetch in flight, by page index — one per fetch, so a want that went stale
|
|
106
|
+
* takes down its own request and no other. See {@link cancel}.
|
|
107
|
+
* @type {Map<number, AbortController>}
|
|
108
|
+
*/
|
|
109
|
+
#aborts = new Map();
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Pages that could not be read, and why, by page index.
|
|
113
|
+
*
|
|
114
|
+
* §10's quarantine. A page that has failed its bounded retries is not asked for again: at a
|
|
115
|
+
* content-addressed URL the failure is deterministic, so asking again asks the same question,
|
|
116
|
+
* and a traversal that keeps wanting the page would keep re-issuing it forever. What makes this
|
|
117
|
+
* survivable rather than a loss is the clamped cut — a quarantined page's parent stays selected
|
|
118
|
+
* and keeps covering that surface correctly, indefinitely.
|
|
119
|
+
*
|
|
120
|
+
* @type {Map<number, string>}
|
|
121
|
+
*/
|
|
122
|
+
#quarantined = new Map();
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The speculative head prefix, while it is still paying for itself.
|
|
93
126
|
*
|
|
94
|
-
* Coarse-first layout is what makes
|
|
95
|
-
*
|
|
96
|
-
*
|
|
127
|
+
* Coarse-first layout is what makes a prefix worth fetching at all: one that overshot the root
|
|
128
|
+
* frame overshot into the pages the traversal is most likely to want next, and serving those
|
|
129
|
+
* from here costs no request. What it is *not* worth is keeping afterwards — see
|
|
130
|
+
* {@link release_head}, which runs by itself the first time a read falls outside it.
|
|
97
131
|
*
|
|
98
132
|
* @type {Uint8Array}
|
|
99
133
|
*/
|
|
100
|
-
#head =
|
|
134
|
+
#head = NO_HEAD;
|
|
101
135
|
|
|
102
136
|
/**
|
|
103
137
|
* @type {number}
|
|
@@ -135,6 +169,16 @@ export class VGeoContainerReader {
|
|
|
135
169
|
* the page starts its counters from the truth. Evicting a page moves its counters back here for
|
|
136
170
|
* the same reason: the parents making those references are still resident and still selectable.
|
|
137
171
|
*
|
|
172
|
+
* **It is bounded by the residency frontier and not by the session's history**, which is worth
|
|
173
|
+
* writing down because it has the shape of an index that leaks. An entry exists only while some
|
|
174
|
+
* resident, selectable cluster refers into a page that is absent, and the two halves are
|
|
175
|
+
* symmetric — eviction and deactivation release exactly what install and activation counted.
|
|
176
|
+
* Measured on a 20,000-page container with the 3,280 pages of its eight coarse bands pinned
|
|
177
|
+
* resident: 6,561 entries after pinning, and still 6,561 after touring all 6,561 pages of the
|
|
178
|
+
* band below them a window at a time and evicting each window. The frontier, unchanged, rather
|
|
179
|
+
* than one entry per page visited. Dropping the pinned pages too leaves 3.
|
|
180
|
+
* {@link pending_reference_page_count} is that number.
|
|
181
|
+
*
|
|
138
182
|
* @type {Map<number, Map<number, number>>}
|
|
139
183
|
*/
|
|
140
184
|
#pending_refs = new Map();
|
|
@@ -173,6 +217,16 @@ export class VGeoContainerReader {
|
|
|
173
217
|
|
|
174
218
|
/**
|
|
175
219
|
* Decoded bytes currently held. The number a residency budget is spent against.
|
|
220
|
+
*
|
|
221
|
+
* It is exactly the decoded page images — the same number the directory's `decoded_size` column
|
|
222
|
+
* and every child-table entry promise *before* a page is fetched, which is what lets a budget
|
|
223
|
+
* owner admit a page rather than discover its cost after paying for it. It is deliberately not
|
|
224
|
+
* what the pages cost in the heap: the reader's own per-page bookkeeping — the `VGeoPage`, its
|
|
225
|
+
* cluster and child tables, the two activation arrays, the map entries — measured 6.4–11.9 KB
|
|
226
|
+
* per page on top of the image. That is 1.2% of a 544 KB page at the sizes design §5 targets and
|
|
227
|
+
* 294% of a 2 KB one, so a budget sized against very small pages has to allow for it and one
|
|
228
|
+
* sized against the design's page size is inside its own rounding.
|
|
229
|
+
*
|
|
176
230
|
* @returns {number}
|
|
177
231
|
*/
|
|
178
232
|
get resident_bytes() {
|
|
@@ -186,9 +240,49 @@ export class VGeoContainerReader {
|
|
|
186
240
|
return this.#pending.size;
|
|
187
241
|
}
|
|
188
242
|
|
|
243
|
+
/**
|
|
244
|
+
* How many not-yet-resident pages the reverse-reference index is holding counters for.
|
|
245
|
+
*
|
|
246
|
+
* Diagnostic, and the number a leak in that index would show up in: an entry exists only while
|
|
247
|
+
* some resident, selectable cluster refers into a page that is not here, so this tracks the
|
|
248
|
+
* residency frontier and must not track how many pages a session has visited. See
|
|
249
|
+
* {@link #pending_refs} for what that was measured at.
|
|
250
|
+
*
|
|
251
|
+
* @returns {number}
|
|
252
|
+
*/
|
|
253
|
+
get pending_reference_page_count() {
|
|
254
|
+
return this.#pending_refs.size;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* How many bytes of speculative head prefix are still held — zero once {@link release_head} has
|
|
259
|
+
* run, which is the normal state after the first read that falls outside the prefix.
|
|
260
|
+
*
|
|
261
|
+
* @returns {number}
|
|
262
|
+
*/
|
|
263
|
+
get head_bytes() {
|
|
264
|
+
return this.#head.byteLength;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* How many pages have failed their retries and will not be asked for again.
|
|
269
|
+
* @returns {number}
|
|
270
|
+
*/
|
|
271
|
+
get quarantined_page_count() {
|
|
272
|
+
return this.#quarantined.size;
|
|
273
|
+
}
|
|
274
|
+
|
|
189
275
|
/**
|
|
190
276
|
* The root page, which is pinned for the life of the reader — §10 makes it the one page that is
|
|
191
277
|
* never evictable, because every traversal starts by walking its parentless groups.
|
|
278
|
+
*
|
|
279
|
+
* What the pin costs is worth writing down, because "pinned for life, times a thousand assets"
|
|
280
|
+
* is the shape of a problem and this one is not: build invariant §11.7 puts exactly one root
|
|
281
|
+
* cluster in the file, and the assembler gives the root page the complete topmost level — so the
|
|
282
|
+
* root page *is* that one cluster. Measured on two torus builds, of 7.7K and 42K source
|
|
283
|
+
* triangles and 8 and 12 levels: 1,732 B and 1,472 B decoded. A thousand of them is under 2 MB,
|
|
284
|
+
* and no builder-side cap on root size is needed to keep it that way.
|
|
285
|
+
*
|
|
192
286
|
* @returns {VGeoPage}
|
|
193
287
|
*/
|
|
194
288
|
get root() {
|
|
@@ -196,13 +290,28 @@ export class VGeoContainerReader {
|
|
|
196
290
|
}
|
|
197
291
|
|
|
198
292
|
/**
|
|
199
|
-
* Every resident page,
|
|
293
|
+
* Every resident page, sorted by page index.
|
|
294
|
+
*
|
|
295
|
+
* A snapshot, and it costs one: an array of every resident page plus a sort of it — 0.29 ms per
|
|
296
|
+
* access measured at 20,000 resident pages. So this is for a tool, a panel or a test, and **not
|
|
297
|
+
* for anything that runs per frame**. {@link resident_pages} is the same set in arrival order
|
|
298
|
+
* and allocates nothing, which is what a per-frame sweep should read.
|
|
299
|
+
*
|
|
200
300
|
* @returns {VGeoPage[]}
|
|
201
301
|
*/
|
|
202
302
|
get pages() {
|
|
203
303
|
return Array.from(this.#pages.values()).sort((a, b) => a.index - b.index);
|
|
204
304
|
}
|
|
205
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Every resident page, in arrival order, allocating nothing.
|
|
308
|
+
*
|
|
309
|
+
* @returns {IterableIterator<VGeoPage>}
|
|
310
|
+
*/
|
|
311
|
+
resident_pages() {
|
|
312
|
+
return this.#pages.values();
|
|
313
|
+
}
|
|
314
|
+
|
|
206
315
|
/**
|
|
207
316
|
* Open a container: header, level table, root page.
|
|
208
317
|
*
|
|
@@ -243,7 +352,8 @@ export class VGeoContainerReader {
|
|
|
243
352
|
§10's always-resident set: the header, the level table, and the root frame — in that order,
|
|
244
353
|
contiguously, and bounded by two fields of the fixed header. Whatever the prefix already
|
|
245
354
|
covered, one range brings the rest, so a cold start is two requests at worst and one
|
|
246
|
-
whenever the prefix was generous. Nothing is fetched twice: the head only grows at its tail
|
|
355
|
+
whenever the prefix was generous. Nothing is fetched twice: the head only grows at its tail,
|
|
356
|
+
and it only grows here, once, before anything else has read a byte of the file.
|
|
247
357
|
*/
|
|
248
358
|
await this.#grow_head(vgeo_resident_head_size(this.#head));
|
|
249
359
|
|
|
@@ -311,6 +421,38 @@ export class VGeoContainerReader {
|
|
|
311
421
|
return this.#pages.has(page_index);
|
|
312
422
|
}
|
|
313
423
|
|
|
424
|
+
/**
|
|
425
|
+
* How many of a page's child pages are resident.
|
|
426
|
+
*
|
|
427
|
+
* §10's eviction rule is written in terms of this: within a residency tier, a page is evictable
|
|
428
|
+
* only while it has no resident child pages in that tier, checked against its own child table.
|
|
429
|
+
* `SAME_PAGE` references never pin and are in no child table by construction (§11.2), so the
|
|
430
|
+
* table is the whole answer. The *rule* is a policy and lives with the budget owner; this
|
|
431
|
+
* lookup is about one page's own bytes and lives here.
|
|
432
|
+
*
|
|
433
|
+
* @param {number} page_index
|
|
434
|
+
* @returns {number} zero for a page that is not resident, and for one no child of which is
|
|
435
|
+
*/
|
|
436
|
+
resident_child_count(page_index) {
|
|
437
|
+
const page = this.#pages.get(page_index);
|
|
438
|
+
|
|
439
|
+
if (page === undefined) {
|
|
440
|
+
return 0;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const children = page.child_page_index;
|
|
444
|
+
|
|
445
|
+
let count = 0;
|
|
446
|
+
|
|
447
|
+
for (let i = 0; i < children.length; i++) {
|
|
448
|
+
if (this.#pages.has(children[i])) {
|
|
449
|
+
count++;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return count;
|
|
454
|
+
}
|
|
455
|
+
|
|
314
456
|
/**
|
|
315
457
|
* Whether a group may be selected from — §10's activation invariant, and **not** the same
|
|
316
458
|
* question as {@link is_resident}.
|
|
@@ -570,16 +712,75 @@ export class VGeoContainerReader {
|
|
|
570
712
|
}
|
|
571
713
|
}
|
|
572
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Whether a page has failed its retries and been quarantined.
|
|
717
|
+
*
|
|
718
|
+
* @param {number} page_index
|
|
719
|
+
* @returns {boolean}
|
|
720
|
+
*/
|
|
721
|
+
is_quarantined(page_index) {
|
|
722
|
+
return this.#quarantined.has(page_index);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Why a page was quarantined, or `undefined` if it was not.
|
|
727
|
+
*
|
|
728
|
+
* §10 asks for the quarantine *and* for telemetry, because a page that stopped arriving is the
|
|
729
|
+
* difference between a coarse asset and a broken deployment, and on screen the two are the same
|
|
730
|
+
* picture.
|
|
731
|
+
*
|
|
732
|
+
* @param {number} page_index
|
|
733
|
+
* @returns {string|undefined}
|
|
734
|
+
*/
|
|
735
|
+
quarantine_reason(page_index) {
|
|
736
|
+
return this.#quarantined.get(page_index);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Every quarantined page and its reason.
|
|
741
|
+
* @returns {IterableIterator<[number, string]>}
|
|
742
|
+
*/
|
|
743
|
+
quarantine() {
|
|
744
|
+
return this.#quarantined.entries();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Let a quarantined page be asked for again.
|
|
749
|
+
*
|
|
750
|
+
* **When** is a policy and is not decided here. A frame checksum that failed at a
|
|
751
|
+
* content-addressed URL will fail again and lifting its quarantine buys nothing; a run of 503s
|
|
752
|
+
* from one edge node is a different thing, and a budget owner that has watched other pages of
|
|
753
|
+
* other assets arrive since has evidence this reader does not have. So the reader quarantines
|
|
754
|
+
* for its own lifetime and offers this, and `VGeoResidencyManager` is where the decision to use
|
|
755
|
+
* it lives.
|
|
756
|
+
*
|
|
757
|
+
* @param {number} [page_index] omit to lift every quarantine
|
|
758
|
+
* @returns {number} how many pages were released
|
|
759
|
+
*/
|
|
760
|
+
clear_quarantine(page_index = undefined) {
|
|
761
|
+
if (page_index === undefined) {
|
|
762
|
+
const released = this.#quarantined.size;
|
|
763
|
+
|
|
764
|
+
this.#quarantined.clear();
|
|
765
|
+
|
|
766
|
+
return released;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return this.#quarantined.delete(page_index) ? 1 : 0;
|
|
770
|
+
}
|
|
771
|
+
|
|
573
772
|
/**
|
|
574
773
|
* Fetch and install one page, from the exact range something already resident named.
|
|
575
774
|
*
|
|
576
775
|
* Concurrent calls for the same page share one fetch; a page already resident is returned
|
|
577
|
-
* without one.
|
|
776
|
+
* without one; a page in quarantine is refused without one.
|
|
578
777
|
*
|
|
579
778
|
* @param {number} page_index
|
|
580
779
|
* @param {number} frame_offset absolute
|
|
581
780
|
* @param {number} fetch_size
|
|
582
781
|
* @returns {Promise<VGeoPage>}
|
|
782
|
+
* @throws {Error} asynchronously — when the page is already quarantined, when it fails every
|
|
783
|
+
* attempt and is quarantined by doing so, or when {@link cancel} abandoned it
|
|
583
784
|
*/
|
|
584
785
|
install(page_index, frame_offset, fetch_size) {
|
|
585
786
|
const resident = this.#pages.get(page_index);
|
|
@@ -594,8 +795,21 @@ export class VGeoContainerReader {
|
|
|
594
795
|
return in_flight;
|
|
595
796
|
}
|
|
596
797
|
|
|
597
|
-
const
|
|
598
|
-
|
|
798
|
+
const quarantined = this.#quarantined.get(page_index);
|
|
799
|
+
|
|
800
|
+
if (quarantined !== undefined) {
|
|
801
|
+
return Promise.reject(new Error(`page ${page_index} is quarantined: ${quarantined}`));
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const abort = new AbortController();
|
|
805
|
+
|
|
806
|
+
this.#aborts.set(page_index, abort);
|
|
807
|
+
|
|
808
|
+
const fetch = this.#fetch_page(page_index, frame_offset, fetch_size, abort.signal)
|
|
809
|
+
.finally(() => {
|
|
810
|
+
this.#pending.delete(page_index);
|
|
811
|
+
this.#aborts.delete(page_index);
|
|
812
|
+
});
|
|
599
813
|
|
|
600
814
|
this.#pending.set(page_index, fetch);
|
|
601
815
|
|
|
@@ -603,13 +817,97 @@ export class VGeoContainerReader {
|
|
|
603
817
|
}
|
|
604
818
|
|
|
605
819
|
/**
|
|
820
|
+
* Abandon a fetch that is still in flight.
|
|
821
|
+
*
|
|
822
|
+
* **What a want that goes stale mid-flight should do, decided:** abort it while it can still be
|
|
823
|
+
* aborted, and install it once it cannot. The camera moved, nothing wants the page now, and the
|
|
824
|
+
* request is worth closing — but once the bytes have landed, refusing to install them is the one
|
|
825
|
+
* choice that pays for the fetch *and* throws the page away. So an abort that arrives too late
|
|
826
|
+
* does nothing, the page installs, and it enters residency untouched — which under LRU on
|
|
827
|
+
* last-touched (design §6) makes it the first candidate for eviction if the camera really is
|
|
828
|
+
* done with it, and free if it is not.
|
|
829
|
+
*
|
|
830
|
+
* The pending promise rejects, so a caller awaiting it sees the abort rather than a page. A
|
|
831
|
+
* cancelled page is **not** quarantined: nothing was wrong with it.
|
|
832
|
+
*
|
|
833
|
+
* @param {number} page_index
|
|
834
|
+
* @returns {boolean} whether there was a fetch to abandon
|
|
835
|
+
*/
|
|
836
|
+
cancel(page_index) {
|
|
837
|
+
const abort = this.#aborts.get(page_index);
|
|
838
|
+
|
|
839
|
+
if (abort === undefined) {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
abort.abort(new Error(`page ${page_index}: the fetch was cancelled`));
|
|
844
|
+
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Abandon every fetch in flight.
|
|
850
|
+
* @returns {number} how many were abandoned
|
|
851
|
+
*/
|
|
852
|
+
cancel_all() {
|
|
853
|
+
let cancelled = 0;
|
|
854
|
+
|
|
855
|
+
for (const page_index of Array.from(this.#aborts.keys())) {
|
|
856
|
+
if (this.cancel(page_index)) {
|
|
857
|
+
cancelled++;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return cancelled;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* One page, retried a bounded number of times and then quarantined — §10.
|
|
866
|
+
*
|
|
606
867
|
* @param {number} page_index
|
|
607
868
|
* @param {number} frame_offset
|
|
608
869
|
* @param {number} fetch_size
|
|
870
|
+
* @param {AbortSignal} signal
|
|
609
871
|
* @returns {Promise<VGeoPage>}
|
|
610
872
|
*/
|
|
611
|
-
async #fetch_page(page_index, frame_offset, fetch_size) {
|
|
612
|
-
const
|
|
873
|
+
async #fetch_page(page_index, frame_offset, fetch_size, signal) {
|
|
874
|
+
const retries = this.#options.fetch_retries;
|
|
875
|
+
|
|
876
|
+
for (let attempt = 0; ; attempt++) {
|
|
877
|
+
try {
|
|
878
|
+
return await this.#read_page(page_index, frame_offset, fetch_size, signal);
|
|
879
|
+
} catch (e) {
|
|
880
|
+
if (signal.aborted) {
|
|
881
|
+
// cancelled rather than failed: nothing is wrong with this page
|
|
882
|
+
throw signal.reason ?? e;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const reason = String(e?.message ?? e);
|
|
886
|
+
|
|
887
|
+
if (attempt >= retries) {
|
|
888
|
+
this.#quarantined.set(page_index, reason);
|
|
889
|
+
|
|
890
|
+
throw new Error(
|
|
891
|
+
`page ${page_index} is quarantined after ${attempt + 1} attempt(s): ${reason}`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
await delay(this.#options.fetch_retry_backoff_ms * (1 << attempt), signal);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* One attempt: the bytes, the checks, the decode, and the residency it becomes.
|
|
902
|
+
*
|
|
903
|
+
* @param {number} page_index
|
|
904
|
+
* @param {number} frame_offset
|
|
905
|
+
* @param {number} fetch_size
|
|
906
|
+
* @param {AbortSignal} signal
|
|
907
|
+
* @returns {Promise<VGeoPage>}
|
|
908
|
+
*/
|
|
909
|
+
async #read_page(page_index, frame_offset, fetch_size, signal) {
|
|
910
|
+
const bytes = await this.#read(frame_offset, fetch_size, signal);
|
|
613
911
|
|
|
614
912
|
if (this.#options.verify_frames) {
|
|
615
913
|
this.#verify_frame(bytes, page_index);
|
|
@@ -652,25 +950,58 @@ export class VGeoContainerReader {
|
|
|
652
950
|
* clamped cluster projected — so a caller that stops early has fetched the pages that would fix
|
|
653
951
|
* the most visible coarseness, not an arbitrary prefix of the list.
|
|
654
952
|
*
|
|
953
|
+
* **A page that will not arrive does not take the batch down.** One flaky edge node, or one
|
|
954
|
+
* corrupt frame out of a thousand assets' worth of pages, must cost exactly that page: it is
|
|
955
|
+
* retried, then quarantined, then skipped, and what comes back is what did install. *Which*
|
|
956
|
+
* pages failed is durable rather than a per-call return — see {@link quarantine} — because a
|
|
957
|
+
* failure that lived only in one call's result is one the next call would repeat.
|
|
958
|
+
*
|
|
959
|
+
* The concurrency bound here is this reader's own. A runtime holding many assets wants a bound
|
|
960
|
+
* across all of them and should install through `VGeoResidencyManager`, which admits every
|
|
961
|
+
* registered reader's fetches through one scheduler: eight readers calling this at the default
|
|
962
|
+
* were measured issuing 48 concurrent range requests.
|
|
963
|
+
*
|
|
655
964
|
* @param {VGeoPageWant[]} wants
|
|
656
965
|
* @param {number} [limit] most pages to install; the rest are left for the next traversal to
|
|
657
966
|
* want again, which it will, from a cut taken with everything this call did install
|
|
967
|
+
* @param {AbortSignal} [signal] stops the batch taking new wants, and abandons what is in flight
|
|
658
968
|
* @returns {Promise<VGeoPage[]>} the pages installed, in completion order
|
|
659
969
|
*/
|
|
660
|
-
async install_wants(wants, limit = wants.length) {
|
|
970
|
+
async install_wants(wants, limit = wants.length, signal = undefined) {
|
|
661
971
|
// Sorted before it is cut, not after: a limit applied to the caller's order would install an
|
|
662
972
|
// arbitrary prefix and the ranking would have bought nothing.
|
|
663
973
|
const queue = wants.slice().sort((a, b) => b.priority - a.priority).slice(0, limit);
|
|
664
974
|
|
|
665
975
|
const installed = [];
|
|
666
976
|
|
|
977
|
+
/**
|
|
978
|
+
* Pages *this* batch has on the wire, so that aborting it abandons those and not whatever
|
|
979
|
+
* another caller started. A signal belongs to one want list; {@link cancel_all} belongs to
|
|
980
|
+
* the reader, and using the second for the first would make two concurrent batches able to
|
|
981
|
+
* cancel each other.
|
|
982
|
+
* @type {Set<number>}
|
|
983
|
+
*/
|
|
984
|
+
const mine = new Set();
|
|
985
|
+
|
|
667
986
|
let next = 0;
|
|
668
987
|
|
|
669
988
|
const worker = async () => {
|
|
670
989
|
while (next < queue.length) {
|
|
990
|
+
if (signal !== undefined && signal.aborted) {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
671
994
|
const want = queue[next++];
|
|
672
995
|
|
|
673
|
-
|
|
996
|
+
mine.add(want.page_index);
|
|
997
|
+
|
|
998
|
+
try {
|
|
999
|
+
installed.push(await this.install_want(want));
|
|
1000
|
+
} catch {
|
|
1001
|
+
// quarantined, or cancelled — either way, not this batch's business
|
|
1002
|
+
} finally {
|
|
1003
|
+
mine.delete(want.page_index);
|
|
1004
|
+
}
|
|
674
1005
|
}
|
|
675
1006
|
};
|
|
676
1007
|
|
|
@@ -682,7 +1013,19 @@ export class VGeoContainerReader {
|
|
|
682
1013
|
workers.push(worker());
|
|
683
1014
|
}
|
|
684
1015
|
|
|
685
|
-
|
|
1016
|
+
const abandon = () => {
|
|
1017
|
+
for (const page_index of mine) {
|
|
1018
|
+
this.cancel(page_index);
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
signal?.addEventListener('abort', abandon, { once: true });
|
|
1023
|
+
|
|
1024
|
+
try {
|
|
1025
|
+
await Promise.all(workers);
|
|
1026
|
+
} finally {
|
|
1027
|
+
signal?.removeEventListener('abort', abandon);
|
|
1028
|
+
}
|
|
686
1029
|
|
|
687
1030
|
return installed;
|
|
688
1031
|
}
|
|
@@ -692,7 +1035,8 @@ export class VGeoContainerReader {
|
|
|
692
1035
|
*
|
|
693
1036
|
* The eviction *rule* — a page is evictable only while it has no resident child pages, and the
|
|
694
1037
|
* root is pinned (§10) — is a residency policy and belongs to whatever is managing the budget,
|
|
695
|
-
* not here. What this does is the dropping
|
|
1038
|
+
* not here. What this does is the dropping; {@link resident_child_count} is the lookup the rule
|
|
1039
|
+
* is written in terms of.
|
|
696
1040
|
*
|
|
697
1041
|
* @param {number} page_index
|
|
698
1042
|
* @returns {boolean} whether a page was there to drop
|
|
@@ -796,47 +1140,38 @@ export class VGeoContainerReader {
|
|
|
796
1140
|
/**
|
|
797
1141
|
* Install every page the directory lists.
|
|
798
1142
|
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
1143
|
+
* @deprecated it reads the whole file, and a renderer holding a reader must not be able to reach
|
|
1144
|
+
* that from the reader. Call `vgeo_install_all_pages` in `tool/` instead, which is a tool
|
|
1145
|
+
* and says so. Kept as a shim because the playground and the specs are what it is for.
|
|
803
1146
|
* @param {function(number, number): void} [on_progress] called with pages installed and total
|
|
804
1147
|
* @returns {Promise<void>}
|
|
805
1148
|
*/
|
|
806
1149
|
async install_all_pages(on_progress = undefined) {
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
let done = 0;
|
|
810
|
-
|
|
811
|
-
for (let index = 0; index < directory.page_count; index++) {
|
|
812
|
-
await this.install(index, directory.frame_offset[index], directory.fetch_size[index]);
|
|
1150
|
+
const { vgeo_install_all_pages } = await import('./tool/vgeo_install_all_pages.js');
|
|
813
1151
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if (on_progress !== undefined) {
|
|
817
|
-
on_progress(done, directory.page_count);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
1152
|
+
await vgeo_install_all_pages(this, on_progress);
|
|
820
1153
|
}
|
|
821
1154
|
|
|
822
1155
|
/**
|
|
823
1156
|
* The whole-file audit — every build invariant of §11, every checksum, and the directory against
|
|
824
1157
|
* what a frame scan reconstructs.
|
|
825
1158
|
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
1159
|
+
* @deprecated it reads the entire file into one `ArrayBuffer`, which is the one thing the rest
|
|
1160
|
+
* of this class exists to avoid and which a multi-GB asset does not survive. Call
|
|
1161
|
+
* `vgeo_audit_container` in `tool/` instead, which refuses a file too large to audit this
|
|
1162
|
+
* way rather than attempting it. Kept as a shim for the playground and the specs.
|
|
830
1163
|
* @returns {Promise<VGeoDefect[]>} empty is the only good answer
|
|
831
1164
|
*/
|
|
832
1165
|
async validate() {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1166
|
+
/*
|
|
1167
|
+
Imported here rather than at the top of the file on purpose: a static import would put
|
|
1168
|
+
`vgeo_validate_container` — and the whole-file scan it carries — in the same module graph as
|
|
1169
|
+
every runtime that opens a container. The dynamic import is what makes "a runtime path
|
|
1170
|
+
cannot reach the audit" true of the bundle and not only of the documentation.
|
|
1171
|
+
*/
|
|
1172
|
+
const { vgeo_audit_container } = await import('./tool/vgeo_audit_container.js');
|
|
838
1173
|
|
|
839
|
-
return
|
|
1174
|
+
return vgeo_audit_container(this);
|
|
840
1175
|
}
|
|
841
1176
|
|
|
842
1177
|
/**
|
|
@@ -844,21 +1179,55 @@ export class VGeoContainerReader {
|
|
|
844
1179
|
*
|
|
845
1180
|
* @param {number} offset
|
|
846
1181
|
* @param {number} length
|
|
1182
|
+
* @param {AbortSignal} [signal]
|
|
847
1183
|
* @returns {Promise<Uint8Array>}
|
|
848
1184
|
*/
|
|
849
|
-
#read(offset, length) {
|
|
850
|
-
|
|
851
|
-
|
|
1185
|
+
#read(offset, length, signal = undefined) {
|
|
1186
|
+
const head = this.#head;
|
|
1187
|
+
|
|
1188
|
+
if (offset + length <= head.byteLength) {
|
|
1189
|
+
return Promise.resolve(head.subarray(offset, offset + length));
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/*
|
|
1193
|
+
The prefix has stopped paying for itself. File order is coarse-first, so a read past the end
|
|
1194
|
+
of the head is a traversal that has walked past the band the head covered, and the pages
|
|
1195
|
+
inside it that were going to be free already were. Holding it from here on holds bytes
|
|
1196
|
+
nothing will read: measured, a 256 KiB prefix over a 22 MB container covered 105 of its
|
|
1197
|
+
4,000 pages, and once those installed the whole prefix served nothing — while staying
|
|
1198
|
+
outside `resident_bytes` and outside anything `evict` could reach. Across a thousand open
|
|
1199
|
+
assets that was a quarter of a gigabyte no budget could see.
|
|
1200
|
+
*/
|
|
1201
|
+
if (head.byteLength > 0) {
|
|
1202
|
+
this.release_head();
|
|
852
1203
|
}
|
|
853
1204
|
|
|
854
|
-
return this.#source.read(offset, length);
|
|
1205
|
+
return this.#source.read(offset, length, signal);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Drop the speculative head prefix.
|
|
1210
|
+
*
|
|
1211
|
+
* Nothing depends on it afterwards: the header and level table are parsed into {@link header}
|
|
1212
|
+
* before anything else runs, the root page owns its own decoded bytes, and every later read
|
|
1213
|
+
* names a range the source can serve. A page whose frame lay inside the prefix costs one request
|
|
1214
|
+
* if it is wanted after this — the same request it would have cost had the prefix been smaller.
|
|
1215
|
+
*
|
|
1216
|
+
* @returns {number} bytes released
|
|
1217
|
+
*/
|
|
1218
|
+
release_head() {
|
|
1219
|
+
const released = this.#head.byteLength;
|
|
1220
|
+
|
|
1221
|
+
this.#head = NO_HEAD;
|
|
1222
|
+
|
|
1223
|
+
return released;
|
|
855
1224
|
}
|
|
856
1225
|
|
|
857
1226
|
/**
|
|
858
1227
|
* Extend the kept head prefix to `to` bytes, fetching only the part that is missing.
|
|
859
1228
|
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
1229
|
+
* Called exactly once, from {@link #open}, before anything else has read a byte — so the copy it
|
|
1230
|
+
* makes happens once per reader, and it never re-copies a prefix that is already in use.
|
|
862
1231
|
*
|
|
863
1232
|
* @param {number} to
|
|
864
1233
|
* @returns {Promise<void>}
|
|
@@ -915,3 +1284,41 @@ export class VGeoContainerReader {
|
|
|
915
1284
|
}
|
|
916
1285
|
}
|
|
917
1286
|
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Wait, unless the caller stops wanting to.
|
|
1290
|
+
*
|
|
1291
|
+
* The timer is cleared on abort rather than left to fire, because a backoff between retries of a
|
|
1292
|
+
* page nobody wants any more is exactly the wait a cancelled fetch should not sit through.
|
|
1293
|
+
*
|
|
1294
|
+
* @param {number} ms
|
|
1295
|
+
* @param {AbortSignal} signal
|
|
1296
|
+
* @returns {Promise<void>}
|
|
1297
|
+
*/
|
|
1298
|
+
function delay(ms, signal) {
|
|
1299
|
+
if (ms <= 0) {
|
|
1300
|
+
return Promise.resolve();
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// Checked rather than relied on: `addEventListener` on a signal that has already aborted never
|
|
1304
|
+
// fires, so a wait started on one would run to term with nobody left who wanted it.
|
|
1305
|
+
if (signal.aborted) {
|
|
1306
|
+
return Promise.reject(signal.reason ?? new Error('the wait was cancelled'));
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
return new Promise((resolve, reject) => {
|
|
1310
|
+
const on_abort = () => {
|
|
1311
|
+
clearTimeout(timer);
|
|
1312
|
+
|
|
1313
|
+
reject(signal.reason ?? new Error('the wait was cancelled'));
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
const timer = setTimeout(() => {
|
|
1317
|
+
signal.removeEventListener('abort', on_abort);
|
|
1318
|
+
|
|
1319
|
+
resolve();
|
|
1320
|
+
}, ms);
|
|
1321
|
+
|
|
1322
|
+
signal.addEventListener('abort', on_abort, { once: true });
|
|
1323
|
+
});
|
|
1324
|
+
}
|