@woosh/meep-engine 3.23.0 → 3.24.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.
Files changed (522) hide show
  1. package/editor/entity_world_bounds.d.ts +9 -3
  2. package/editor/entity_world_bounds.d.ts.map +1 -1
  3. package/editor/entity_world_bounds.js +61 -4
  4. package/package.json +1 -1
  5. package/src/core/binary/allocator/OffsetAllocator.d.ts +40 -2
  6. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  7. package/src/core/binary/allocator/OffsetAllocator.js +124 -29
  8. package/src/core/collection/map/u32_index.d.ts +83 -0
  9. package/src/core/collection/map/u32_index.d.ts.map +1 -0
  10. package/src/core/collection/map/u32_index.js +247 -0
  11. package/src/engine/asset/loaders/GLTFSceneBundleAssetLoader.d.ts +2 -2
  12. package/src/engine/asset/loaders/GLTFSceneBundleAssetLoader.js +2 -2
  13. package/src/engine/asset/loaders/USDSceneBundleAssetLoader.d.ts +1 -2
  14. package/src/engine/asset/loaders/USDSceneBundleAssetLoader.d.ts.map +1 -1
  15. package/src/engine/asset/loaders/USDSceneBundleAssetLoader.js +4 -12
  16. package/src/engine/ecs/gpu/GPUStateAuthority.d.ts +55 -0
  17. package/src/engine/ecs/gpu/GPUStateAuthority.d.ts.map +1 -0
  18. package/src/engine/ecs/gpu/GPUStateAuthority.js +72 -0
  19. package/src/engine/ecs/gpu/GPUStateAuthorityFlags.d.ts +40 -0
  20. package/src/engine/ecs/gpu/GPUStateAuthorityFlags.d.ts.map +1 -0
  21. package/src/engine/ecs/gpu/GPUStateAuthorityFlags.js +79 -0
  22. package/src/engine/ecs/gpu/gpu_authority_assert_cpu.d.ts +18 -0
  23. package/src/engine/ecs/gpu/gpu_authority_assert_cpu.d.ts.map +1 -0
  24. package/src/engine/ecs/gpu/gpu_authority_assert_cpu.js +27 -0
  25. package/src/engine/ecs/gpu/gpu_authority_declare.d.ts +23 -0
  26. package/src/engine/ecs/gpu/gpu_authority_declare.d.ts.map +1 -0
  27. package/src/engine/ecs/gpu/gpu_authority_declare.js +39 -0
  28. package/src/engine/ecs/gpu/gpu_authority_grant.d.ts +15 -0
  29. package/src/engine/ecs/gpu/gpu_authority_grant.d.ts.map +1 -0
  30. package/src/engine/ecs/gpu/gpu_authority_grant.js +38 -0
  31. package/src/engine/ecs/gpu/gpu_authority_has.d.ts +18 -0
  32. package/src/engine/ecs/gpu/gpu_authority_has.d.ts.map +1 -0
  33. package/src/engine/ecs/gpu/gpu_authority_has.js +28 -0
  34. package/src/engine/ecs/gpu/gpu_authority_revoke.d.ts +24 -0
  35. package/src/engine/ecs/gpu/gpu_authority_revoke.d.ts.map +1 -0
  36. package/src/engine/ecs/gpu/gpu_authority_revoke.js +49 -0
  37. package/src/engine/ecs/ik/IKProblem.d.ts +14 -0
  38. package/src/engine/ecs/ik/IKProblem.d.ts.map +1 -1
  39. package/src/engine/ecs/ik/IKProblem.js +16 -0
  40. package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts.map +1 -1
  41. package/src/engine/ecs/ik/InverseKinematicsSystem.js +19 -0
  42. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +2 -2
  43. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +2 -2
  44. package/src/engine/ecs/ik/ik_bone_chain.d.ts +11 -2
  45. package/src/engine/ecs/ik/ik_bone_chain.d.ts.map +1 -1
  46. package/src/engine/ecs/ik/ik_bone_chain.js +47 -2
  47. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  48. package/src/engine/ecs/ik/ik_write_local_rotation.js +6 -0
  49. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  50. package/src/engine/ecs/speaker/VoiceSystem.js +6 -0
  51. package/src/engine/ecs/transform/T64WorldCache.d.ts +50 -0
  52. package/src/engine/ecs/transform/T64WorldCache.d.ts.map +1 -0
  53. package/src/engine/ecs/transform/T64WorldCache.js +139 -0
  54. package/src/engine/ecs/transform/t64_evaluate_world.d.ts +31 -0
  55. package/src/engine/ecs/transform/t64_evaluate_world.d.ts.map +1 -0
  56. package/src/engine/ecs/transform/t64_evaluate_world.js +126 -0
  57. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts +8 -0
  58. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  59. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +59 -30
  60. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts.map +1 -1
  61. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.js +4 -11
  62. package/src/engine/graphics/ecs/mesh-v2/SHADED_GEOMETRY_EVENT_CHANGE.d.ts +14 -0
  63. package/src/engine/graphics/ecs/mesh-v2/SHADED_GEOMETRY_EVENT_CHANGE.d.ts.map +1 -0
  64. package/src/engine/graphics/ecs/mesh-v2/SHADED_GEOMETRY_EVENT_CHANGE.js +13 -0
  65. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.d.ts +2 -14
  66. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.d.ts.map +1 -1
  67. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +11 -21
  68. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.d.ts +1 -0
  69. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +7 -0
  70. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.d.ts +5 -6
  71. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.d.ts.map +1 -1
  72. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.js +13 -11
  73. package/src/engine/graphics/ecs/mesh-v2/shaded_geometry_announce_change.d.ts +14 -0
  74. package/src/engine/graphics/ecs/mesh-v2/shaded_geometry_announce_change.d.ts.map +1 -0
  75. package/src/engine/graphics/ecs/mesh-v2/shaded_geometry_announce_change.js +21 -0
  76. package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts +1 -1
  77. package/src/engine/graphics/ecs/particles/ParticleEffect.js +1 -1
  78. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.d.ts +1 -1
  79. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +1 -1
  80. package/src/engine/graphics3/AnimationGraphSystem.js +3 -3
  81. package/src/engine/graphics3/AnimationSystem.js +2 -2
  82. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
  83. package/src/engine/graphics3/DebugDrawSystem.js +2 -4
  84. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +34 -27
  85. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  86. package/src/engine/graphics3/GPUParticleEmitterSystem.js +185 -85
  87. package/src/engine/graphics3/HighlightOutlineSystem.d.ts +4 -3
  88. package/src/engine/graphics3/HighlightOutlineSystem.d.ts.map +1 -1
  89. package/src/engine/graphics3/HighlightOutlineSystem.js +7 -9
  90. package/src/engine/graphics3/LightSystem.d.ts +7 -5
  91. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  92. package/src/engine/graphics3/LightSystem.js +36 -22
  93. package/src/engine/graphics3/MeshSystem.d.ts +43 -39
  94. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  95. package/src/engine/graphics3/MeshSystem.js +324 -331
  96. package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -1
  97. package/src/engine/graphics3/PathDisplaySystem.js +2 -4
  98. package/src/engine/graphics3/PickingQuery.d.ts +15 -21
  99. package/src/engine/graphics3/PickingQuery.d.ts.map +1 -1
  100. package/src/engine/graphics3/PickingQuery.js +21 -25
  101. package/src/engine/graphics3/PickingSystem.d.ts +60 -31
  102. package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
  103. package/src/engine/graphics3/PickingSystem.js +416 -273
  104. package/src/engine/graphics3/ShadedGeometrySystem.d.ts +22 -12
  105. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/ShadedGeometrySystem.js +309 -174
  107. package/src/engine/graphics3/TerrainSystem.d.ts +19 -11
  108. package/src/engine/graphics3/TerrainSystem.d.ts.map +1 -1
  109. package/src/engine/graphics3/TerrainSystem.js +102 -53
  110. package/src/engine/graphics3/TooltipComponentSystem.d.ts.map +1 -1
  111. package/src/engine/graphics3/TooltipComponentSystem.js +2 -14
  112. package/src/engine/graphics3/Trail3DSystem.d.ts.map +1 -1
  113. package/src/engine/graphics3/Trail3DSystem.js +2 -4
  114. package/src/engine/graphics3/animation/ClipListPlayer.d.ts +4 -4
  115. package/src/engine/graphics3/animation/ClipListPlayer.d.ts.map +1 -1
  116. package/src/engine/graphics3/animation/ClipListPlayer.js +2 -2
  117. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts +9 -11
  118. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts.map +1 -1
  119. package/src/engine/graphics3/highlight/pack_highlight_table.js +11 -18
  120. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts +4 -3
  121. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
  122. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +75 -74
  123. package/src/engine/graphics3/prefab/Prefab.d.ts +130 -0
  124. package/src/engine/graphics3/prefab/Prefab.d.ts.map +1 -0
  125. package/src/engine/graphics3/prefab/Prefab.js +162 -0
  126. package/src/engine/graphics3/prefab/PrefabInstance.d.ts +72 -0
  127. package/src/engine/graphics3/prefab/PrefabInstance.d.ts.map +1 -0
  128. package/src/engine/graphics3/prefab/PrefabInstance.js +80 -0
  129. package/src/engine/graphics3/prefab/prefab_compile.d.ts +23 -0
  130. package/src/engine/graphics3/prefab/prefab_compile.d.ts.map +1 -0
  131. package/src/engine/graphics3/prefab/prefab_compile.js +334 -0
  132. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts +46 -0
  133. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts.map +1 -0
  134. package/src/engine/graphics3/prefab/prefab_instantiate.js +230 -0
  135. package/src/engine/graphics3/prefab/prefab_normalize_skin_frames.d.ts +70 -0
  136. package/src/engine/graphics3/prefab/prefab_normalize_skin_frames.d.ts.map +1 -0
  137. package/src/engine/graphics3/prefab/prefab_normalize_skin_frames.js +210 -0
  138. package/src/engine/graphics3/prefab/prefab_place.d.ts +60 -0
  139. package/src/engine/graphics3/prefab/prefab_place.d.ts.map +1 -0
  140. package/src/engine/graphics3/prefab/prefab_place.js +130 -0
  141. package/src/engine/graphics3/prefab/prefab_skin_clip_scale_conflicts.d.ts +60 -0
  142. package/src/engine/graphics3/prefab/prefab_skin_clip_scale_conflicts.d.ts.map +1 -0
  143. package/src/engine/graphics3/prefab/prefab_skin_clip_scale_conflicts.js +128 -0
  144. package/src/engine/graphics3/prefab/prefab_skin_frame.d.ts +40 -0
  145. package/src/engine/graphics3/prefab/prefab_skin_frame.d.ts.map +1 -0
  146. package/src/engine/graphics3/prefab/prefab_skin_frame.js +116 -0
  147. package/src/engine/graphics3/preview/make_model_preview_scene.d.ts +16 -14
  148. package/src/engine/graphics3/preview/make_model_preview_scene.d.ts.map +1 -1
  149. package/src/engine/graphics3/preview/make_model_preview_scene.js +126 -154
  150. package/src/engine/graphics3/preview/make_model_thumbnail.js +1 -1
  151. package/src/engine/graphics3/register_instance_animation.d.ts +6 -6
  152. package/src/engine/graphics3/register_instance_animation.d.ts.map +1 -1
  153. package/src/engine/graphics3/register_instance_animation.js +4 -4
  154. package/src/engine/graphics3/shade_bundle_bounds.d.ts +2 -6
  155. package/src/engine/graphics3/shade_bundle_bounds.d.ts.map +1 -1
  156. package/src/engine/graphics3/shade_bundle_bounds.js +47 -55
  157. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts +11 -18
  158. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts.map +1 -1
  159. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +48 -53
  160. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +17 -2
  161. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -1
  162. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +284 -256
  163. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +13 -15
  164. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  165. package/src/engine/physics/cloth/ecs/ClothInstance.js +12 -14
  166. package/src/engine/physics/cloth/ecs/ClothRig.d.ts +7 -8
  167. package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -1
  168. package/src/engine/physics/cloth/ecs/ClothRig.js +7 -8
  169. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +5 -4
  170. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  171. package/src/engine/physics/cloth/ecs/ClothSystem.js +11 -12
  172. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +19 -23
  173. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  174. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +201 -199
  175. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  176. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +8 -0
  177. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +18 -34
  178. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -1
  179. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +58 -115
  180. package/src/engine/physics/cloth/playground/README.md +11 -11
  181. package/src/engine/physics/cloth/playground/garment_build.d.ts +0 -1
  182. package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -1
  183. package/src/engine/physics/cloth/playground/garment_build.js +64 -58
  184. package/src/engine/physics/cloth/playground/garment_main.js +59 -33
  185. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  186. package/src/shade/RENDERER_CONTRACT.md +85 -53
  187. package/src/shade/device/ShadeGPUCommandContext.d.ts +23 -0
  188. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  189. package/src/shade/device/ShadeGPUCommandContext.js +36 -0
  190. package/src/shade/playground/add_random_meshes.d.ts +4 -4
  191. package/src/shade/playground/add_random_meshes.d.ts.map +1 -1
  192. package/src/shade/playground/add_random_meshes.js +20 -16
  193. package/src/shade/playground/avboit/main.d.ts +1 -0
  194. package/src/shade/playground/avboit/main.d.ts.map +1 -1
  195. package/src/shade/playground/avboit/main.js +916 -875
  196. package/src/shade/playground/basis_textures/main.d.ts.map +1 -1
  197. package/src/shade/playground/basis_textures/main.js +22 -8
  198. package/src/shade/playground/basis_textures/texture_inventory.d.ts +2 -2
  199. package/src/shade/playground/basis_textures/texture_inventory.d.ts.map +1 -1
  200. package/src/shade/playground/basis_textures/texture_inventory.js +6 -7
  201. package/src/shade/playground/ground_seam/main.d.ts.map +1 -1
  202. package/src/shade/playground/ground_seam/main.js +13 -4
  203. package/src/shade/playground/ground_seam/make_ground.d.ts +13 -4
  204. package/src/shade/playground/ground_seam/make_ground.d.ts.map +1 -1
  205. package/src/shade/playground/ground_seam/make_ground.js +21 -16
  206. package/src/shade/playground/particle_ecs/bonfire_editor.js +3 -3
  207. package/src/shade/playground/particle_system/main.js +11 -10
  208. package/src/shade/playground/particle_system/particle_prototype.d.ts +15 -10
  209. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  210. package/src/shade/playground/particle_system/particle_prototype.js +56 -15
  211. package/src/shade/playground/particle_system/particle_scene.d.ts +68 -28
  212. package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -1
  213. package/src/shade/playground/particle_system/particle_scene.js +149 -53
  214. package/src/shade/playground/readback_crossover/index.html +41 -0
  215. package/src/shade/playground/readback_crossover/main.d.ts +2 -0
  216. package/src/shade/playground/readback_crossover/main.d.ts.map +1 -0
  217. package/src/shade/playground/readback_crossover/main.js +372 -0
  218. package/src/shade/playground/skinned_blas_refit/README.md +11 -7
  219. package/src/shade/playground/skinned_blas_refit/add_floor.d.ts +13 -44
  220. package/src/shade/playground/skinned_blas_refit/add_floor.d.ts.map +1 -1
  221. package/src/shade/playground/skinned_blas_refit/add_floor.js +19 -59
  222. package/src/shade/playground/skinned_blas_refit/main.d.ts.map +1 -1
  223. package/src/shade/playground/skinned_blas_refit/main.js +67 -58
  224. package/src/shade/playground/skinned_blas_refit/place_character.d.ts +40 -54
  225. package/src/shade/playground/skinned_blas_refit/place_character.d.ts.map +1 -1
  226. package/src/shade/playground/skinned_blas_refit/place_character.js +47 -74
  227. package/src/shade/playground/skinned_blas_refit/sweep_views.d.ts +3 -3
  228. package/src/shade/playground/skinned_blas_refit/sweep_views.d.ts.map +1 -1
  229. package/src/shade/playground/skinned_blas_refit/sweep_views.js +4 -3
  230. package/src/shade/playground/skinned_blas_refit/verify_clone_leaves.d.ts +3 -3
  231. package/src/shade/playground/skinned_blas_refit/verify_clone_leaves.d.ts.map +1 -1
  232. package/src/shade/playground/skinned_blas_refit/verify_clone_leaves.js +11 -4
  233. package/src/shade/playground/skinned_blas_refit/verify_instance_bounds.d.ts.map +1 -1
  234. package/src/shade/playground/skinned_blas_refit/verify_instance_bounds.js +38 -9
  235. package/src/shade/playground/skinned_blas_refit/verify_skinned_blas_refit.d.ts +3 -3
  236. package/src/shade/playground/skinned_blas_refit/verify_skinned_blas_refit.d.ts.map +1 -1
  237. package/src/shade/playground/skinned_blas_refit/verify_skinned_blas_refit.js +9 -6
  238. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.d.ts +3 -3
  239. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.d.ts.map +1 -1
  240. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.js +3 -3
  241. package/src/shade/playground/skinned_mesh_soup/README.md +6 -6
  242. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -1
  243. package/src/shade/playground/skinned_mesh_soup/main.js +44 -102
  244. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +5 -5
  245. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -1
  246. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +42 -33
  247. package/src/shade/playground/sponza_path_trace/main.d.ts +1 -0
  248. package/src/shade/playground/sponza_path_trace/main.d.ts.map +1 -1
  249. package/src/shade/playground/sponza_path_trace/main.js +393 -363
  250. package/src/shade/playground/ssr_reprojection/main.js +11 -9
  251. package/src/shade/playground/ssr_reprojection/scene.d.ts +15 -4
  252. package/src/shade/playground/ssr_reprojection/scene.d.ts.map +1 -1
  253. package/src/shade/playground/ssr_reprojection/scene.js +32 -11
  254. package/src/shade/playground/ssr_variance/main.js +1 -1
  255. package/src/shade/playground/ssr_variance/scene.d.ts +10 -4
  256. package/src/shade/playground/ssr_variance/scene.d.ts.map +1 -1
  257. package/src/shade/playground/ssr_variance/scene.js +39 -15
  258. package/src/shade/playground/vgeo_viewer/main.js +6 -2
  259. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
  260. package/src/shade/playground/volumetrics_froxel/main.js +936 -922
  261. package/src/shade/renderer/GraphicsContext.d.ts +7 -0
  262. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  263. package/src/shade/renderer/GraphicsContext.js +11 -0
  264. package/src/shade/renderer/Renderer.d.ts +3 -2
  265. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  266. package/src/shade/renderer/Renderer.js +3 -2
  267. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +39 -47
  268. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  269. package/src/shade/renderer/animation/GPUAnimationManager.js +319 -256
  270. package/src/shade/renderer/animation/ShadeAnimationChannel.d.ts +26 -22
  271. package/src/shade/renderer/animation/ShadeAnimationChannel.d.ts.map +1 -1
  272. package/src/shade/renderer/animation/ShadeAnimationChannel.js +28 -23
  273. package/src/shade/renderer/animation/ShadeAnimationClip.d.ts +14 -6
  274. package/src/shade/renderer/animation/ShadeAnimationClip.d.ts.map +1 -1
  275. package/src/shade/renderer/animation/ShadeAnimationClip.js +20 -8
  276. package/src/shade/renderer/animation/Skin.d.ts +19 -19
  277. package/src/shade/renderer/animation/Skin.d.ts.map +1 -1
  278. package/src/shade/renderer/animation/Skin.js +15 -13
  279. package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts +6 -5
  280. package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts.map +1 -1
  281. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +9 -9
  282. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +5 -59
  283. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -1
  284. package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +8 -132
  285. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts +65 -62
  286. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  287. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +148 -194
  288. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.d.ts.map +1 -1
  289. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +4 -7
  290. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +2 -2
  291. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +2 -2
  292. package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.d.ts +74 -0
  293. package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.d.ts.map +1 -0
  294. package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.js +240 -0
  295. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts +14 -0
  296. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  297. package/src/shade/renderer/buffer/table/GPUDatabase.js +64 -6
  298. package/src/shade/renderer/buffer/table/GPUDatabaseReadback.d.ts +190 -0
  299. package/src/shade/renderer/buffer/table/GPUDatabaseReadback.d.ts.map +1 -0
  300. package/src/shade/renderer/buffer/table/GPUDatabaseReadback.js +1323 -0
  301. package/src/shade/renderer/buffer/table/GPUPageMetadata.d.ts +10 -0
  302. package/src/shade/renderer/buffer/table/GPUPageMetadata.d.ts.map +1 -1
  303. package/src/shade/renderer/buffer/table/GPUPageMetadata.js +12 -0
  304. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +96 -0
  305. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +1 -0
  306. package/src/shade/renderer/buffer/table/GPUReadbackStatus.d.ts +15 -0
  307. package/src/shade/renderer/buffer/table/GPUReadbackStatus.d.ts.map +1 -0
  308. package/src/shade/renderer/buffer/table/GPUReadbackStatus.js +34 -0
  309. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +75 -7
  310. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  311. package/src/shade/renderer/buffer/table/GPUTypedTable.js +518 -135
  312. package/src/shade/renderer/buffer/table/GPU_TABLE_GATHER_DESCRIPTOR_WORDS.d.ts +19 -0
  313. package/src/shade/renderer/buffer/table/GPU_TABLE_GATHER_DESCRIPTOR_WORDS.d.ts.map +1 -0
  314. package/src/shade/renderer/buffer/table/GPU_TABLE_GATHER_DESCRIPTOR_WORDS.js +18 -0
  315. package/src/shade/renderer/buffer/table/WordViews.d.ts +39 -0
  316. package/src/shade/renderer/buffer/table/WordViews.d.ts.map +1 -0
  317. package/src/shade/renderer/buffer/table/WordViews.js +62 -0
  318. package/src/shade/renderer/buffer/table/gpu_table_gather_shader.d.ts +27 -0
  319. package/src/shade/renderer/buffer/table/gpu_table_gather_shader.d.ts.map +1 -0
  320. package/src/shade/renderer/buffer/table/gpu_table_gather_shader.js +117 -0
  321. package/src/shade/renderer/buffer/table/serde/generate_record_writer.d.ts +121 -0
  322. package/src/shade/renderer/buffer/table/serde/generate_record_writer.d.ts.map +1 -0
  323. package/src/shade/renderer/buffer/table/serde/generate_record_writer.js +395 -0
  324. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts.map +1 -1
  325. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +70 -20
  326. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +1 -1
  327. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  328. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +6 -5
  329. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +3 -3
  330. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  331. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +15 -11
  332. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_bake_for_scene.d.ts +1 -1
  333. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_bake_for_scene.js +4 -4
  334. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_generate_tree_from_scene.d.ts +1 -3
  335. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_generate_tree_from_scene.d.ts.map +1 -1
  336. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_generate_tree_from_scene.js +5 -4
  337. package/src/shade/renderer/global_illumination/brick4/gpu/bake/brick4_bake_basic.js +1 -1
  338. package/src/shade/renderer/global_illumination/lpv/bake/GPULightProbeVolumeRenderer.d.ts.map +1 -1
  339. package/src/shade/renderer/global_illumination/lpv/bake/GPULightProbeVolumeRenderer.js +1 -4
  340. package/src/shade/renderer/global_illumination/lpv/lpv_visualise_probes.d.ts +8 -3
  341. package/src/shade/renderer/global_illumination/lpv/lpv_visualise_probes.d.ts.map +1 -1
  342. package/src/shade/renderer/global_illumination/lpv/lpv_visualise_probes.js +19 -14
  343. package/src/shade/renderer/global_illumination/lpv/placement/graph_generate_probe_locations.d.ts.map +1 -1
  344. package/src/shade/renderer/global_illumination/lpv/placement/graph_generate_probe_locations.js +9 -1
  345. package/src/shade/renderer/global_illumination/lpv/placement/software/lpv_generate_probe_locations2.d.ts +3 -3
  346. package/src/shade/renderer/global_illumination/lpv/placement/software/lpv_generate_probe_locations2.d.ts.map +1 -1
  347. package/src/shade/renderer/global_illumination/lpv/placement/software/lpv_generate_probe_locations2.js +12 -5
  348. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +1 -1
  349. package/src/shade/renderer/light/GPULightCollection.d.ts +29 -0
  350. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  351. package/src/shade/renderer/light/GPULightCollection.js +444 -329
  352. package/src/shade/renderer/light/LIGHT_DATABASE_SPEC.d.ts.map +1 -1
  353. package/src/shade/renderer/light/LIGHT_DATABASE_SPEC.js +7 -1
  354. package/src/shade/renderer/light/LightCollection.d.ts +29 -1
  355. package/src/shade/renderer/light/LightCollection.d.ts.map +1 -1
  356. package/src/shade/renderer/light/LightCollection.js +50 -1
  357. package/src/shade/renderer/light/make_sunlight.js +2 -2
  358. package/src/shade/renderer/light/model/Light.d.ts +29 -2
  359. package/src/shade/renderer/light/model/Light.d.ts.map +1 -1
  360. package/src/shade/renderer/light/model/Light.js +30 -2
  361. package/src/shade/renderer/light/model/io/light_record_directional.d.ts +3 -1
  362. package/src/shade/renderer/light/model/io/light_record_directional.d.ts.map +1 -1
  363. package/src/shade/renderer/light/model/io/light_record_directional.js +8 -5
  364. package/src/shade/renderer/light/model/io/light_record_point.d.ts +3 -1
  365. package/src/shade/renderer/light/model/io/light_record_point.d.ts.map +1 -1
  366. package/src/shade/renderer/light/model/io/light_record_point.js +5 -2
  367. package/src/shade/renderer/light/model/io/light_record_spot.d.ts +4 -1
  368. package/src/shade/renderer/light/model/io/light_record_spot.d.ts.map +1 -1
  369. package/src/shade/renderer/light/model/io/light_record_spot.js +10 -6
  370. package/src/shade/renderer/light/place_light.d.ts +16 -0
  371. package/src/shade/renderer/light/place_light.d.ts.map +1 -0
  372. package/src/shade/renderer/light/place_light.js +36 -0
  373. package/src/shade/renderer/light/shader_refresh_light_poses.d.ts +44 -0
  374. package/src/shade/renderer/light/shader_refresh_light_poses.d.ts.map +1 -0
  375. package/src/shade/renderer/light/shader_refresh_light_poses.js +154 -0
  376. package/src/shade/renderer/lightmap/GPULightMap.js +1 -1
  377. package/src/shade/renderer/lightmap/bake/raster/graph_draw_lightmap_viz_buffer.d.ts.map +1 -1
  378. package/src/shade/renderer/lightmap/bake/raster/graph_draw_lightmap_viz_buffer.js +12 -14
  379. package/src/shade/renderer/loader/SceneBundle.d.ts +53 -28
  380. package/src/shade/renderer/loader/SceneBundle.d.ts.map +1 -1
  381. package/src/shade/renderer/loader/SceneBundle.js +111 -56
  382. package/src/shade/renderer/loader/SceneNode.d.ts +81 -0
  383. package/src/shade/renderer/loader/SceneNode.d.ts.map +1 -0
  384. package/src/shade/renderer/loader/SceneNode.js +114 -0
  385. package/src/shade/renderer/loader/gltf/load_gltf.d.ts +3 -2
  386. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  387. package/src/shade/renderer/loader/gltf/load_gltf.js +110 -142
  388. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  389. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.d.ts +4 -4
  390. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.d.ts.map +1 -1
  391. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +2 -2
  392. package/src/shade/renderer/loader/usd/load_usd.d.ts +3 -2
  393. package/src/shade/renderer/loader/usd/load_usd.d.ts.map +1 -1
  394. package/src/shade/renderer/loader/usd/load_usd.js +4 -5
  395. package/src/shade/renderer/loader/usd/usd_build_scene.d.ts +3 -3
  396. package/src/shade/renderer/loader/usd/usd_build_scene.d.ts.map +1 -1
  397. package/src/shade/renderer/loader/usd/usd_build_scene.js +47 -55
  398. package/src/shade/renderer/material/GPUMaterialManager.js +1 -1
  399. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +5 -4
  400. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  401. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +8 -15
  402. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.d.ts +15 -13
  403. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.d.ts.map +1 -1
  404. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.js +40 -21
  405. package/src/shade/renderer/particles/DESIGN.md +53 -50
  406. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +31 -17
  407. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  408. package/src/shade/renderer/particles/GPUParticleSystem.js +54 -91
  409. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +12 -11
  410. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  411. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +12 -11
  412. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +13 -19
  413. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -1
  414. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +19 -25
  415. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +34 -24
  416. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  417. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +53 -40
  418. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +1 -1
  419. package/src/shade/renderer/path_tracer/graph_path_trace_scene.js +1 -1
  420. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  421. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_meshes_classify_buckets.d.ts.map +1 -1
  422. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_meshes_classify_buckets.js +2 -1
  423. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_scene_meshes_cull_and_classify_buckets.d.ts.map +1 -1
  424. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_scene_meshes_cull_and_classify_buckets.js +5 -2
  425. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +1 -1
  426. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +1 -1
  427. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +1 -1
  428. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +1 -1
  429. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +1 -1
  430. package/src/shade/renderer/scene/DynamicMesh.d.ts +13 -4
  431. package/src/shade/renderer/scene/DynamicMesh.d.ts.map +1 -1
  432. package/src/shade/renderer/scene/DynamicMesh.js +14 -4
  433. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.d.ts +38 -18
  434. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.d.ts.map +1 -1
  435. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.js +112 -60
  436. package/src/shade/renderer/scene/GPUSceneContext.d.ts +55 -48
  437. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  438. package/src/shade/renderer/scene/GPUSceneContext.js +594 -928
  439. package/src/shade/renderer/scene/NODE3D_STRUCT.d.ts +5 -5
  440. package/src/shade/renderer/scene/NODE3D_STRUCT.js +5 -5
  441. package/src/shade/renderer/scene/Node3D.d.ts +27 -8
  442. package/src/shade/renderer/scene/Node3D.d.ts.map +1 -1
  443. package/src/shade/renderer/scene/Node3DDirtyList.d.ts +59 -0
  444. package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +1 -0
  445. package/src/shade/renderer/scene/Scene.d.ts +18 -60
  446. package/src/shade/renderer/scene/Scene.d.ts.map +1 -1
  447. package/src/shade/renderer/scene/Scene.js +53 -146
  448. package/src/shade/renderer/scene/SkinnedMesh.d.ts +10 -93
  449. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
  450. package/src/shade/renderer/scene/bvh/StaticSceneBVH.d.ts +24 -3
  451. package/src/shade/renderer/scene/bvh/StaticSceneBVH.d.ts.map +1 -1
  452. package/src/shade/renderer/scene/bvh/StaticSceneBVH.js +53 -13
  453. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy.d.ts +5 -6
  454. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy.d.ts.map +1 -1
  455. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy.js +5 -6
  456. package/src/shade/renderer/scene/optimization/scene_optimize.d.ts +10 -2
  457. package/src/shade/renderer/scene/optimization/scene_optimize.d.ts.map +1 -1
  458. package/src/shade/renderer/scene/optimization/scene_optimize.js +266 -230
  459. package/src/shade/renderer/scene/rows/GPUEntityBoundsReadback.d.ts +53 -0
  460. package/src/shade/renderer/scene/rows/GPUEntityBoundsReadback.d.ts.map +1 -0
  461. package/src/shade/renderer/scene/rows/GPUEntityBoundsReadback.js +144 -0
  462. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts +227 -0
  463. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -0
  464. package/src/shade/renderer/scene/rows/GPUSceneRows.js +1136 -0
  465. package/src/shade/renderer/scene/rows/place_primitive.d.ts +48 -0
  466. package/src/shade/renderer/scene/rows/place_primitive.d.ts.map +1 -0
  467. package/src/shade/renderer/scene/rows/place_primitive.js +98 -0
  468. package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts +10 -8
  469. package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts.map +1 -1
  470. package/src/shade/renderer/scene/serialization/deserialize_scene.js +24 -27
  471. package/src/shade/renderer/scene/serialization/serialize_scene.d.ts +9 -2
  472. package/src/shade/renderer/scene/serialization/serialize_scene.d.ts.map +1 -1
  473. package/src/shade/renderer/scene/serialization/serialize_scene.js +25 -4
  474. package/src/shade/renderer/scene/verify_gpu_tlas_build.js +2 -2
  475. package/src/shade/renderer/shader/chunk/light/DIRECTIONAL_LIGHT_STRUCT.d.ts.map +1 -1
  476. package/src/shade/renderer/shader/chunk/light/DIRECTIONAL_LIGHT_STRUCT.js +5 -0
  477. package/src/shade/renderer/shader/chunk/light/POINT_LIGHT_STRUCT.d.ts.map +1 -1
  478. package/src/shade/renderer/shader/chunk/light/POINT_LIGHT_STRUCT.js +5 -0
  479. package/src/shade/renderer/shader/chunk/light/SPOT_LIGHT_STRUCT.d.ts.map +1 -1
  480. package/src/shade/renderer/shader/chunk/light/SPOT_LIGHT_STRUCT.js +5 -0
  481. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.d.ts +39 -0
  482. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.d.ts.map +1 -1
  483. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +112 -64
  484. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +7 -0
  485. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  486. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +1541 -1352
  487. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +1 -1
  488. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +1 -1
  489. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +1 -1
  490. package/src/shade/renderer/shadow/map/csm/CSM_CASTER_RANGE_STRUCT.d.ts +29 -0
  491. package/src/shade/renderer/shadow/map/csm/CSM_CASTER_RANGE_STRUCT.d.ts.map +1 -0
  492. package/src/shade/renderer/shadow/map/csm/CSM_CASTER_RANGE_STRUCT.js +43 -0
  493. package/src/shade/renderer/shadow/map/csm/SHADOWMAP_CSM_CASTER_RANGE_MARGIN_FRACTION.d.ts +13 -0
  494. package/src/shade/renderer/shadow/map/csm/SHADOWMAP_CSM_CASTER_RANGE_MARGIN_FRACTION.d.ts.map +1 -0
  495. package/src/shade/renderer/shadow/map/csm/SHADOWMAP_CSM_CASTER_RANGE_MARGIN_FRACTION.js +12 -0
  496. package/src/shade/renderer/shadow/map/csm/shader/shader_csm_caster_range.d.ts +49 -0
  497. package/src/shade/renderer/shadow/map/csm/shader/shader_csm_caster_range.d.ts.map +1 -0
  498. package/src/shade/renderer/shadow/map/csm/shader/shader_csm_caster_range.js +194 -0
  499. package/src/shade/renderer/shadow/map/csm_debug_draw_preview.js +1 -1
  500. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +1 -1
  501. package/src/shade/renderer/shadow/ray/RTXShadows.js +1 -1
  502. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.js +1 -1
  503. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.d.ts.map +1 -1
  504. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.js +3 -1
  505. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +8 -8
  506. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  507. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +4 -3
  508. package/src/engine/graphics3/instantiate_scene_bundle.js +0 -164
  509. package/src/engine/graphics3/shade_node_to_entity_composition.js +0 -88
  510. package/src/shade/renderer/animation/compute_skin_world_bounds.js +0 -146
  511. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +0 -531
  512. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +0 -76
  513. package/src/shade/renderer/animation/skin_bind_transform.js +0 -33
  514. package/src/shade/renderer/animation/skin_frame_defect.js +0 -132
  515. package/src/shade/renderer/animation/skin_joint_matrix.js +0 -46
  516. package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +0 -216
  517. package/src/shade/renderer/scene/InstanceBatch.js +0 -235
  518. package/src/shade/renderer/scene/Mesh.js +0 -168
  519. package/src/shade/renderer/scene/Node3D.js +0 -263
  520. package/src/shade/renderer/scene/SceneNodeBinding.js +0 -56
  521. package/src/shade/renderer/scene/SkinnedMesh.js +0 -170
  522. package/src/shade/renderer/scene/TransformAuthority.js +0 -33
@@ -2,6 +2,13 @@ import { assert } from "../../../../core/assert.js";
2
2
  import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
3
3
  import { BitSet } from "../../../../core/binary/BitSet.js";
4
4
  import { platform_compute_endianness } from "../../../../core/binary/platform_compute_endianness.js";
5
+ import {
6
+ U32_INDEX_MISS,
7
+ u32_index_clear,
8
+ u32_index_count,
9
+ u32_index_get,
10
+ u32_index_set,
11
+ } from "../../../../core/collection/map/u32_index.js";
5
12
  import { read_wgsl_type_value } from "../../shader/type/serde/read_gpu_typed_buffer.js";
6
13
  import { write_wgsl_type_value } from "../../shader/type/serde/write_gpu_typed_buffer.js";
7
14
  import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
@@ -9,11 +16,13 @@ import { GPU_DATABASE_BUFFER_ELEMENT_TYPE } from "./GPU_DATABASE_BUFFER_ELEMENT_
9
16
  import { GPU_DATABASE_UPLOAD_BUFFER_LIMIT } from "./GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js";
10
17
  import { GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS } from "./GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js";
11
18
  import { GPUPageMetadata } from "./GPUPageMetadata.js";
19
+ import { WordViews } from "./WordViews.js";
12
20
 
13
21
  /**
14
22
  * @typedef {import("./GPUTypedTableDescriptor.js").GPUTypedTableDescriptor} GPUTypedTableDescriptor
15
23
  * @typedef {import("./PageBufferPool.js").PageBufferPool} PageBufferPool
16
24
  * @typedef {import("../../shader/type/WebGPUType.js").WebGPUType} WebGPUType
25
+ * @typedef {import("./serde/generate_record_writer.js").GPURecordWriter} GPURecordWriter
17
26
  */
18
27
 
19
28
  const SCRATCH_BUFFER = new BinaryBuffer();
@@ -27,11 +36,18 @@ const READ_STAGE_BUFFER = BinaryBuffer.fromEndianness(platform_compute_endiannes
27
36
  // top-level alignment round-up is a no-op.
28
37
  const WRITE_STAGE_BUFFER = BinaryBuffer.fromEndianness(platform_compute_endianness());
29
38
 
39
+ /**
40
+ * Byte size of one {@link GPU_DATABASE_BUFFER_ELEMENT_TYPE} — the width every offset into the
41
+ * database, and into either upload stream, is counted in.
42
+ * @type {number}
43
+ */
44
+ const WORD_SIZE = GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
45
+
30
46
  /**
31
47
  * Byte stride of one entry in {@link GPUTypedTable.partial_upload_descriptors}.
32
48
  * @type {number}
33
49
  */
34
- const PARTIAL_DESCRIPTOR_BYTES = GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS * GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
50
+ const PARTIAL_DESCRIPTOR_BYTES = GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS * WORD_SIZE;
35
51
 
36
52
  /**
37
53
  * Index of the `word_count` word within one partial-upload descriptor — the one word retiring a
@@ -54,34 +70,6 @@ function empty_upload_buffer() {
54
70
  return buffer;
55
71
  }
56
72
 
57
- /**
58
- * How far {@link GPUTypedTable.set} walks back through
59
- * {@link GPUTypedTable.element_upload_buffer} looking for an existing
60
- * record targeting the same element id.
61
- *
62
- * Two records aimed at the same row race in the upload compute shader —
63
- * shader threads run concurrently within a workgroup and the GPU is free
64
- * to retire them in any order, so "last writer wins" depends on schedule,
65
- * not buffer order. Scanning back 256 records covers a full 256-thread
66
- * workgroup of accumulated writes, which is enough to keep duplicates
67
- * within one dispatch unit out of the buffer.
68
- *
69
- * Cross-workgroup duplicates are still possible (a hot row touched once
70
- * per frame for many frames could in theory have stale entries spaced
71
- * further apart than this window). For our current usage that doesn't
72
- * happen in practice; if it ever does, swap this linear scan for a tiny
73
- * bloom filter or a sparse `id → record_offset` map.
74
- *
75
- * The partial-write stream took the map route from the start — see
76
- * {@link GPUTypedTable#set_bytes} — not because its hazard is worse but
77
- * because a row can own several ranges at once and every write has to
78
- * find all of them, so a scan deep enough to be safe would have cost
79
- * more than the feature saves.
80
- *
81
- * @type {number}
82
- */
83
- const UPLOAD_BUFFER_DEDUP_LOOKBACK = 256;
84
-
85
73
  /**
86
74
  * see "Rendering 'Assassin's Creed Shadows'" GDC 2025 by Nicolas Lopez
87
75
  * @template T
@@ -133,26 +121,80 @@ export class GPUTypedTable {
133
121
  * Index of the most recent pending descriptor for a row — the head of that row's chain
134
122
  * through {@link #partial_descriptor_previous}.
135
123
  *
136
- * {@link set}'s dedupe can afford a backwards scan because it only ever looks for one record;
137
- * a row here can own several descriptors at once, and every write has to find all of them. A
138
- * scan deep enough to be safe would cost more than the whole feature saves, so the stream
139
- * carries an index instead — which also makes reconciliation exact rather than windowed.
124
+ * A row can own several descriptors at once — that is the point, a caller updating three
125
+ * scattered fields should not pay for the span between them — and every write has to find all
126
+ * of them, which is why this was an index from the start rather than the backwards scan
127
+ * {@link set} used to run. {@link #pending_records} has since gone the same way.
140
128
  *
141
- * Empty for a table that never takes a partial write, which is the only thing {@link set} and
129
+ * A row whose descriptors have all been retired holds `-1` rather than losing its slot, which
130
+ * {@link u32_index_get} reports as {@link U32_INDEX_MISS} — the same thing a row that never
131
+ * took a partial write reads as, and every site below wants them to mean the same thing.
132
+ *
133
+ * `null` for a table that never takes a partial write, which is the only thing {@link set} and
142
134
  * {@link remove} have to check.
143
135
  *
144
- * @type {Map<number,number>}
136
+ * @type {Uint32Array|null}
145
137
  */
146
- #partial_descriptor_head = new Map();
138
+ #partial_descriptor_head = null;
147
139
 
148
140
  /**
149
141
  * For descriptor `i`, the index of the previous pending descriptor targeting the same row, or
150
142
  * -1 at the end of the chain. Parallel to {@link partial_upload_descriptors}, and kept on this
151
143
  * side rather than in a fifth descriptor word so the GPU-side stride stays at four.
152
144
  *
153
- * @type {number[]}
145
+ * Never cleared, and does not need to be: descriptor indices restart at zero each time the
146
+ * stream is dispatched, every descriptor writes its own entry as it is appended, and a chain
147
+ * is only ever walked from a head recorded during the frame doing the walking. A stale entry
148
+ * is therefore overwritten before anything can read it.
149
+ *
150
+ * @type {Int32Array}
154
151
  */
155
- #partial_descriptor_previous = [];
152
+ #partial_descriptor_previous = new Int32Array(0);
153
+
154
+ /**
155
+ * Byte offset in {@link element_upload_buffer} of the whole record pending for a row, for
156
+ * every row that has one.
157
+ *
158
+ * Two records aimed at the same row race in the upload shader — each is consumed by its own
159
+ * thread within a single dispatch and the GPU retires threads in whatever order it likes — so
160
+ * the buffer must hold at most one record per row, and every write has to find the one already
161
+ * there. This was a backwards scan over the last 256 records, which is a window: deep enough
162
+ * to be safe and it costs more than the write it protects, shallow enough to be cheap and a
163
+ * hot row can slip a stale record past it. At a few hundred rows a frame the scan was most of
164
+ * what {@link set} did.
165
+ *
166
+ * An index is both cheaper and exact, and the exactness is worth more. {@link set_bytes} had
167
+ * one interleaving it could only resolve through that window — a range landing on a whole
168
+ * record queued further back than the scan reached — and it is gone: a row is now never live
169
+ * in both streams at once.
170
+ *
171
+ * A `u32_index` rather than a `Map`, because this one is cleared and refilled every frame and
172
+ * `Map.clear` drops its backing store each time. Over a frame of 300 rows that is worth about
173
+ * 9% of {@link set_record} and 19% at 3000, and rather more of the garbage: 105 collections
174
+ * against 43 across the same run. It is invisible in {@link set}, whose cost is the reflective
175
+ * type walk and not the bookkeeping around it.
176
+ *
177
+ * `null` until the table takes its first write.
178
+ *
179
+ * @type {Uint32Array|null}
180
+ */
181
+ #pending_records = null;
182
+
183
+ /**
184
+ * Word views over {@link element_upload_buffer}'s storage, for the generated record writers.
185
+ * Rebuilt by {@link WordViews} itself whenever the buffer grows out from under them.
186
+ *
187
+ * @type {WordViews}
188
+ */
189
+ #upload_words = new WordViews();
190
+
191
+ /**
192
+ * Word views over {@link partial_upload_data}'s storage, on the same terms as
193
+ * {@link #upload_words}.
194
+ *
195
+ * @type {WordViews}
196
+ */
197
+ #partial_words = new WordViews();
156
198
 
157
199
  /**
158
200
  * When a page is updated - it is marked as dirty.
@@ -169,6 +211,23 @@ export class GPUTypedTable {
169
211
  */
170
212
  occupancy = new BitSet();
171
213
 
214
+ /**
215
+ * Per row, how many times it has been freed — `null` until {@link track_generations}, so a
216
+ * table nothing reads back pays one null check per {@link remove} and no memory.
217
+ *
218
+ * Rows are reused the moment they are freed: {@link add} takes the lowest clear bit, exactly as
219
+ * `EntityComponentDataset.createEntity` does, so a free and an add in one frame leave occupancy
220
+ * as it was and put a stranger where an in-flight readback was aimed. The generation is what
221
+ * tells the two apart, and it lives here rather than with the reader because the table is the
222
+ * thing that reuses rows: every path that frees one moves it, and no caller has to remember to.
223
+ *
224
+ * The plan puts a real generation in the row's flags word once entities are rows; until then
225
+ * this is it.
226
+ *
227
+ * @type {Uint32Array|null}
228
+ */
229
+ #generations = null;
230
+
172
231
  /**
173
232
  * Pages whose GPU-side header (live_count + occupancy bitmap) is stale and
174
233
  * needs to be re-uploaded. Set when {@link occupancy} or
@@ -211,6 +270,22 @@ export class GPUTypedTable {
211
270
  return id;
212
271
  }
213
272
 
273
+ /**
274
+ * {@link add} through a generated writer — {@link set_record} is to {@link set} what this is to
275
+ * {@link add}.
276
+ *
277
+ * @param {T} value
278
+ * @param {GPURecordWriter} writer generated against this table's type
279
+ * @returns {number} element index
280
+ */
281
+ add_record(value, writer) {
282
+ const id = this.occupancy.nextClearBit(0);
283
+
284
+ this.set_record(id, value, writer);
285
+
286
+ return id;
287
+ }
288
+
214
289
  /**
215
290
  * Remove an element from the table, freeing its slot.
216
291
  * The slot will be zero-filled in the CPU shadow and an upload will be queued.
@@ -227,6 +302,10 @@ export class GPUTypedTable {
227
302
 
228
303
  this.occupancy.set(element_index, false);
229
304
 
305
+ if (this.#generations !== null) {
306
+ this.#bump_generation(element_index);
307
+ }
308
+
230
309
  const page_index = Math.floor(element_index / this.descriptor.elements_per_page);
231
310
  const page = this.pages.get(page_index);
232
311
 
@@ -251,6 +330,54 @@ export class GPUTypedTable {
251
330
  }
252
331
  }
253
332
 
333
+ /**
334
+ * Start counting how many times each row is freed. Idempotent. Called by whoever reads rows
335
+ * back and needs to know whether the row it asked about is still the row that answered — see
336
+ * {@link generation_of}.
337
+ */
338
+ track_generations() {
339
+ if (this.#generations === null) {
340
+ this.#generations = new Uint32Array(Math.max(64, this.element_capacity));
341
+ }
342
+ }
343
+
344
+ /**
345
+ * How many times a row has been freed since {@link track_generations}. Zero for a table that
346
+ * is not tracking, and for a row never freed since.
347
+ *
348
+ * @param {number} row
349
+ * @returns {number}
350
+ */
351
+ generation_of(row) {
352
+ const generations = this.#generations;
353
+
354
+ if (generations === null || row >= generations.length) {
355
+ return 0;
356
+ }
357
+
358
+ return generations[row];
359
+ }
360
+
361
+ /**
362
+ * @param {number} row
363
+ */
364
+ #bump_generation(row) {
365
+ let generations = this.#generations;
366
+
367
+ if (row >= generations.length) {
368
+ const grown = new Uint32Array(Math.max(generations.length * 2, row + 1));
369
+
370
+ grown.set(generations);
371
+
372
+ generations = grown;
373
+ this.#generations = generations;
374
+ }
375
+
376
+ // wraps at 2^32, which needs a row to be freed four billion times before a reader of
377
+ // exactly that age could be fooled
378
+ generations[row] = (generations[row] + 1) >>> 0;
379
+ }
380
+
254
381
  /**
255
382
  * Reset every slot to free in one shot. Cheaper than calling
256
383
  * {@link remove} per element when the caller knows it's about to
@@ -268,6 +395,15 @@ export class GPUTypedTable {
268
395
  return;
269
396
  }
270
397
 
398
+ if (this.#generations !== null) {
399
+ // every occupied row is being freed, and each one will be reused
400
+ const occupancy = this.occupancy;
401
+
402
+ for (let row = occupancy.nextSetBit(0); row !== -1; row = occupancy.nextSetBit(row + 1)) {
403
+ this.#bump_generation(row);
404
+ }
405
+ }
406
+
271
407
  this.occupancy.reset();
272
408
 
273
409
  for (const [page_index, page] of this.pages) {
@@ -279,39 +415,61 @@ export class GPUTypedTable {
279
415
  }
280
416
 
281
417
  /**
282
- * Walk back up to {@link UPLOAD_BUFFER_DEDUP_LOOKBACK} records in the upload buffer
283
- * looking for one targeting the given element.
418
+ * Byte offset of the whole record already pending for a row, or -1. One record per id is the
419
+ * invariant that keeps the upload shader race-free — see {@link #pending_records}.
284
420
  *
285
- * On a hit the buffer position is left just past the matched record's u32 id (ready
286
- * for the value bytes to be overwritten); on a miss the position is restored to the
287
- * tail. One record per id is the invariant that keeps the upload shader race-free:
288
- * each record is consumed by its own thread within a single dispatch, so two records
289
- * for the same row would be last-writer-wins, non-deterministically.
421
+ * Also where a stream dispatched since the last write gets noticed. `GPUDatabase` rewinds
422
+ * {@link element_upload_buffer} by moving its position back to zero and tells nobody, so the
423
+ * index can outlive the records it points at. Every mutator consults this before appending —
424
+ * that is the invariant the staleness check rests on, and why it lives here rather than being
425
+ * repeated at each of the appends.
290
426
  *
291
427
  * @param {number} element_index
292
- * @returns {number} byte offset of the matched record, or -1
428
+ * @returns {number} byte offset of the pending record, or -1
293
429
  */
294
- #find_upload_record(element_index) {
295
- const upload_buffer = this.element_upload_buffer;
430
+ #pending_record_offset(element_index) {
431
+ const pending = this.#pending_records;
296
432
 
297
- const step_size = u32.size + this.descriptor.packed_element_size_bytes;
298
- const tail = upload_buffer.position;
433
+ if (pending === null || u32_index_count(pending) === 0) {
434
+ return -1;
435
+ }
299
436
 
300
- const max_lookback = Math.min(
301
- UPLOAD_BUFFER_DEDUP_LOOKBACK,
302
- Math.floor(tail / step_size),
303
- );
437
+ if (this.element_upload_buffer.position === 0) {
438
+ // the stream was dispatched and rewound since these were recorded
439
+ u32_index_clear(pending);
304
440
 
305
- for (let i = 1; i <= max_lookback; i++) {
306
- const candidate_start = tail - i * step_size;
307
- upload_buffer.position = candidate_start;
308
- if (upload_buffer.readUint32() === element_index) {
309
- return candidate_start;
310
- }
441
+ return -1;
311
442
  }
312
443
 
313
- upload_buffer.position = tail;
314
- return -1;
444
+ return u32_index_get(pending, element_index);
445
+ }
446
+
447
+ /**
448
+ * The page holding a row, ready to be written through: allocated if the table has never
449
+ * reached that far, and given a CPU shadow if it was handed one without.
450
+ *
451
+ * @param {number} element_index
452
+ * @returns {GPUPageMetadata}
453
+ */
454
+ #page_for_write(element_index) {
455
+ const page_index = Math.floor(element_index / this.descriptor.elements_per_page);
456
+
457
+ let page = this.pages.get(page_index);
458
+
459
+ if (page === undefined) {
460
+ page = this.#allocate_page(page_index);
461
+ }
462
+
463
+ if (page.cpu_data_buffer === undefined) {
464
+ // Materialize CPU buffer on demand (e.g. pre-allocated stub from GPUDatabase)
465
+ const pool = this.page_buffer_pool;
466
+
467
+ page.cpu_data_buffer = pool !== undefined
468
+ ? pool.acquire().buffer
469
+ : new ArrayBuffer(this.descriptor.page_byte_size);
470
+ }
471
+
472
+ return page;
315
473
  }
316
474
 
317
475
  /**
@@ -331,13 +489,16 @@ export class GPUTypedTable {
331
489
  // must not leave both the old value record and this zero record alive in one
332
490
  // dispatch, or the GPU row becomes schedule-dependent.
333
491
  const tail = upload_buffer.position;
334
- const existing_record = this.#find_upload_record(element_index);
492
+ const existing_record = this.#pending_record_offset(element_index);
335
493
 
336
494
  if (existing_record >= 0) {
337
- // position is just past the matched record's id; zero its value bytes
495
+ // zero the matched record's value bytes, leaving its id alone
496
+ upload_buffer.position = existing_record + u32.size;
497
+
338
498
  for (let i = 0; i < word_count; i++) {
339
499
  upload_buffer.writeUint32(0);
340
500
  }
501
+
341
502
  upload_buffer.position = tail;
342
503
  return;
343
504
  }
@@ -346,6 +507,8 @@ export class GPUTypedTable {
346
507
  for (let i = 0; i < word_count; i++) {
347
508
  upload_buffer.writeUint32(0);
348
509
  }
510
+
511
+ this.#pending_records = u32_index_set(this.#pending_records, element_index, tail);
349
512
  }
350
513
 
351
514
  /**
@@ -494,29 +657,12 @@ export class GPUTypedTable {
494
657
  const type = this.descriptor.type;
495
658
 
496
659
  // write to the local page if resident
497
- const page_index = Math.floor(element_index / this.descriptor.elements_per_page);
498
- let page = this.pages.get(page_index);
499
-
500
-
501
- if (page === undefined) {
502
- // allocate page
503
- page = this.#allocate_page(page_index);
504
- }
660
+ const page = this.#page_for_write(element_index);
505
661
 
506
662
  // Track occupancy: only increment if slot was previously free
507
663
  const was_occupied = this.occupancy.get(element_index);
508
664
  this.occupancy.set(element_index, true);
509
665
 
510
- if (page.cpu_data_buffer === undefined) {
511
- // Materialize CPU buffer on demand (e.g. pre-allocated stub from GPUDatabase)
512
- const pool = this.page_buffer_pool;
513
- if (pool !== undefined) {
514
- page.cpu_data_buffer = pool.acquire().buffer;
515
- } else {
516
- page.cpu_data_buffer = new ArrayBuffer(this.descriptor.page_byte_size);
517
- }
518
- }
519
-
520
666
  // page is resident, write to the CPU data
521
667
  const record_size = this.descriptor.packed_element_size_bytes;
522
668
  SCRATCH_BUFFER.fromArrayBuffer(page.cpu_data_buffer);
@@ -528,7 +674,7 @@ export class GPUTypedTable {
528
674
 
529
675
  if (!was_occupied) {
530
676
  page.occupancy_count++;
531
- this.header_dirty_pages.set(page_index, true);
677
+ this.header_dirty_pages.set(page.index, true);
532
678
  }
533
679
 
534
680
  // write to the upload buffer.
@@ -537,14 +683,14 @@ export class GPUTypedTable {
537
683
  // the full stride after writing, since write_wgsl_type_value does
538
684
  // not pad to type.size when the last field ends earlier.
539
685
  //
540
- // Before appending, walk back UPLOAD_BUFFER_DEDUP_LOOKBACK records
541
- // looking for an entry with the same id. If we find one, overwrite
542
- // its value bytes in place and leave the tail where it was — that
543
- // way the upload shader sees only the latest value per row, which
544
- // matters because two records targeting the same slot race within
545
- // a workgroup (last-writer-wins is non-deterministic). The CPU
546
- // shadow above is already idempotent w.r.t. repeated set() calls,
547
- // so the GPU side is the only thing that needed the dedupe.
686
+ // Before appending, ask the index whether this row already has a
687
+ // record queued. If it does, overwrite its value bytes in place and
688
+ // leave the tail where it was — that way the upload shader sees only
689
+ // the latest value per row, which matters because two records
690
+ // targeting the same slot race within a workgroup (last-writer-wins
691
+ // is non-deterministic). The CPU shadow above is already idempotent
692
+ // w.r.t. repeated set() calls, so the GPU side is the only thing that
693
+ // needed the dedupe.
548
694
  //
549
695
  // A whole record also supersedes any sub-record range queued for this row earlier in the
550
696
  // frame — see set_bytes for the ordering rules across the two streams.
@@ -555,14 +701,15 @@ export class GPUTypedTable {
555
701
  const step_size = value_offset + record_size;
556
702
  const tail = upload_buffer.position;
557
703
 
558
- const dedup_record_start = this.#find_upload_record(element_index);
704
+ const dedup_record_start = this.#pending_record_offset(element_index);
559
705
 
560
706
  if (dedup_record_start >= 0) {
561
- // Overwrite the existing record's value section. Position is
562
- // already past its u32 id from the lookup, so we can write the
563
- // value directly. Restore the tail so subsequent writes still
564
- // append at the end.
707
+ // Overwrite the existing record's value section, then restore the tail so subsequent
708
+ // writes still append at the end.
709
+ upload_buffer.position = dedup_record_start + value_offset;
710
+
565
711
  write_wgsl_type_value(value, upload_buffer, type);
712
+
566
713
  upload_buffer.position = tail;
567
714
  } else {
568
715
  // Reserve the whole stride up front. The writes below only reach the last
@@ -574,9 +721,91 @@ export class GPUTypedTable {
574
721
  upload_buffer.writeUint32(element_index);
575
722
  write_wgsl_type_value(value, upload_buffer, type);
576
723
  upload_buffer.position = tail + step_size;
724
+
725
+ this.#pending_records = u32_index_set(this.#pending_records, element_index, tail);
577
726
  }
578
727
  }
579
728
 
729
+ /**
730
+ * Write a whole record through a writer generated for its type, instead of walking the type
731
+ * on every call.
732
+ *
733
+ * Same contract as {@link set} in every other respect — the row is created if the table has
734
+ * never held it, the CPU shadow is patched, and the record joins the whole-record upload
735
+ * stream — and the bytes it leaves behind are the same ones. What changes is the cost: the
736
+ * field offsets are constants inside {@link GPURecordWriter.write_record} rather than
737
+ * something rediscovered per field per row, and the shadow and the queued record are written
738
+ * through views rather than a `BinaryBuffer` cursor.
739
+ *
740
+ * A writer naming only some of the type's fields is legal here and means what it says: the
741
+ * rest of the record keeps whatever the shadow held, and so does the queued record. That is
742
+ * rarely what a caller wants from *this* method, though — a partial writer belongs in
743
+ * {@link set_fields}, which uploads only the bytes it wrote.
744
+ *
745
+ * @param {number} element_index
746
+ * @param {T} value
747
+ * @param {GPURecordWriter} writer generated against this table's type
748
+ */
749
+ set_record(element_index, value, writer) {
750
+ assert.isNonNegativeInteger(element_index, 'element_index');
751
+ assert.defined(writer, 'writer');
752
+ // The message names no type: `WebGPUType.toString` rebuilds the struct's whole WGSL
753
+ // declaration, and an interpolation is evaluated whether the assertion holds or not — which
754
+ // put a code generator on the per-row write path, where a writer exists to take one off.
755
+ assert.equal(writer.type, this.descriptor.type, `writer was generated against a type other than '${this.descriptor.name}''s — its field offsets would land anywhere in a record of this one`);
756
+
757
+ const descriptor = this.descriptor;
758
+ const record_size = descriptor.packed_element_size_bytes;
759
+
760
+ const page = this.#page_for_write(element_index);
761
+
762
+ const was_occupied = this.occupancy.get(element_index);
763
+ this.occupancy.set(element_index, true);
764
+
765
+ const in_page_index = element_index % descriptor.elements_per_page;
766
+ const shadow = page.words.over(page.cpu_data_buffer);
767
+
768
+ writer.write_record(
769
+ shadow.f32,
770
+ shadow.u32,
771
+ (page.cpu_data_address + in_page_index * record_size) / WORD_SIZE,
772
+ value
773
+ );
774
+
775
+ page.cpu_version++;
776
+
777
+ if (!was_occupied) {
778
+ page.occupancy_count++;
779
+ this.header_dirty_pages.set(page.index, true);
780
+ }
781
+
782
+ // A whole record supersedes any range queued for the row earlier in the frame.
783
+ this.#retire_partial_records(element_index);
784
+
785
+ const upload_buffer = this.element_upload_buffer;
786
+ const step_size = u32.size + record_size;
787
+ const tail = upload_buffer.position;
788
+
789
+ let record_start = this.#pending_record_offset(element_index);
790
+
791
+ if (record_start < 0) {
792
+ // Reserve the whole stride before taking views over the buffer — this is the call
793
+ // that can move the storage they would be over.
794
+ upload_buffer.ensureCapacity(tail + step_size);
795
+
796
+ upload_buffer.writeUint32(element_index);
797
+ upload_buffer.position = tail + step_size;
798
+
799
+ this.#pending_records = u32_index_set(this.#pending_records, element_index, tail);
800
+
801
+ record_start = tail;
802
+ }
803
+
804
+ const upload = this.#upload_words.over(upload_buffer.data);
805
+
806
+ writer.write_record(upload.f32, upload.u32, (record_start + u32.size) / WORD_SIZE, value);
807
+ }
808
+
580
809
  /**
581
810
  * Overwrite one field (or sub-struct) of an existing record, leaving the rest of it untouched
582
811
  * — on the CPU shadow and on the GPU alike.
@@ -638,11 +867,8 @@ export class GPUTypedTable {
638
867
  * the GPU is free to retire threads in any order. In append order:
639
868
  *
640
869
  * - **partial after whole-record** — the pending whole record is patched in place and nothing
641
- * is appended, so the row is not in both streams at once. This is the one case bounded by
642
- * {@link UPLOAD_BUFFER_DEDUP_LOOKBACK}, since it borrows {@link set}'s backwards scan. A
643
- * whole record further back than the window is missed and the range is queued beside it —
644
- * still correct, because {@link GPUDatabase.update} dispatches a table's whole records
645
- * before its ranges, so the range lands on top of the record exactly as append order said.
870
+ * is appended, so the row is not in both streams at once. The record is found through
871
+ * {@link #pending_records}, so it is found however long ago it was queued.
646
872
  * - **whole-record after partial** — {@link set} retires the row's pending descriptors (blanks
647
873
  * their word count, making those threads no-ops); the whole record supersedes them.
648
874
  * - **partial after partial, the same range or a sub-range of a pending one** — the pending
@@ -653,8 +879,8 @@ export class GPUTypedTable {
653
879
  * are retired and one covering their union is appended, its words read back out of the CPU
654
880
  * shadow, which is already correct by then.
655
881
  *
656
- * Every case but the first is exact rather than windowed: a row's pending descriptors are
657
- * indexed by id, so they are found however long the stream has grown.
882
+ * Every one of those is exact rather than windowed: both streams index a row's pending
883
+ * entries by id, so they are found however long either stream has grown.
658
884
  *
659
885
  * @param {number} element_index row to patch; must already be occupied
660
886
  * @param {number} byte_offset offset of the range within the record, 4-byte aligned
@@ -670,10 +896,8 @@ export class GPUTypedTable {
670
896
 
671
897
  const descriptor = this.descriptor;
672
898
  const record_size = descriptor.packed_element_size_bytes;
673
- const word_size = GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
674
-
675
- assert.equal(byte_offset % word_size, 0, `byte_offset (${byte_offset}) must be a multiple of ${word_size} — the database buffer is an array<u32> and a partial write is not masked`);
676
- assert.equal(byte_length % word_size, 0, `byte_length (${byte_length}) must be a multiple of ${word_size} — the database buffer is an array<u32> and a partial write is not masked`);
899
+ assert.equal(byte_offset % WORD_SIZE, 0, `byte_offset (${byte_offset}) must be a multiple of ${WORD_SIZE} — the database buffer is an array<u32> and a partial write is not masked`);
900
+ assert.equal(byte_length % WORD_SIZE, 0, `byte_length (${byte_length}) must be a multiple of ${WORD_SIZE} — the database buffer is an array<u32> and a partial write is not masked`);
677
901
  assert.lessThanOrEqual(byte_offset + byte_length, record_size, `range [${byte_offset}, ${byte_offset + byte_length}) reaches past the end of a ${record_size} byte record of '${descriptor.name}'`);
678
902
 
679
903
  assert.ok(this.occupancy.get(element_index), `element ${element_index} of '${descriptor.name}' is not occupied — a partial write patches a record, it does not create one`);
@@ -715,10 +939,7 @@ export class GPUTypedTable {
715
939
  // A pending whole record for this row wins the frame outright: patch its bytes and append
716
940
  // nothing, so the row stays in exactly one stream.
717
941
  const upload_buffer = this.element_upload_buffer;
718
- const upload_tail = upload_buffer.position;
719
- const pending_record = this.#find_upload_record(element_index);
720
-
721
- upload_buffer.position = upload_tail;
942
+ const pending_record = this.#pending_record_offset(element_index);
722
943
 
723
944
  if (pending_record >= 0) {
724
945
  new Uint8Array(upload_buffer.data, pending_record + u32.size + byte_offset, byte_length)
@@ -727,7 +948,163 @@ export class GPUTypedTable {
727
948
  return;
728
949
  }
729
950
 
730
- this.#stage_partial_range(element_index, byte_offset / word_size, byte_length / word_size, record_bytes);
951
+ this.#stage_partial_range(element_index, byte_offset / WORD_SIZE, byte_length / WORD_SIZE, record_bytes);
952
+ }
953
+
954
+ /**
955
+ * Patch the fields a generated writer owns, on the CPU shadow and on the GPU, and leave the
956
+ * rest of the record alone — {@link set_bytes} for a struct whose owned fields are known at
957
+ * generation time rather than named per call.
958
+ *
959
+ * The point is the bookkeeping, not the stores. `set_bytes` has to treat every range as an
960
+ * event of its own: a page lookup, a record view, a pending-record check, a chain walk, a
961
+ * descriptor append. A caller patching a struct's two CPU-owned blocks through it pays all of
962
+ * that twice, which measured at roughly double one range's cost rather than the few extra
963
+ * words the second range actually is. A writer knows both ranges belong to one row's update
964
+ * before the call starts, so this pays for the row once: the fields reach the shadow and the
965
+ * stream's data blob in a single pass over the value, and the descriptors go out together.
966
+ *
967
+ * The ordering rules across the two streams are {@link set_bytes}'s and are followed exactly
968
+ * — a row's update lands on a pending whole record if there is one, and joins ranges already
969
+ * queued for the row through the same merge, one range at a time, because what to do with an
970
+ * overlap is a property of what is pending and not of the writer.
971
+ *
972
+ * The row must already be occupied, for the reason a partial write always must: it patches a
973
+ * record, it does not create one.
974
+ *
975
+ * @param {number} element_index
976
+ * @param {T} value
977
+ * @param {GPURecordWriter} writer generated against this table's type
978
+ */
979
+ set_fields(element_index, value, writer) {
980
+ assert.isNonNegativeInteger(element_index, 'element_index');
981
+ assert.defined(writer, 'writer');
982
+ // Naming no type in the message is deliberate — see `set_record`.
983
+ assert.equal(writer.type, this.descriptor.type, `writer was generated against a type other than '${this.descriptor.name}''s — its field offsets would land anywhere in a record of this one`);
984
+
985
+ const descriptor = this.descriptor;
986
+ const record_size = descriptor.packed_element_size_bytes;
987
+ const elements_per_page = descriptor.elements_per_page;
988
+
989
+ const page = this.pages.get(Math.floor(element_index / elements_per_page));
990
+
991
+ assert.ok(this.occupancy.get(element_index), `element ${element_index} of '${descriptor.name}' is not occupied — a partial write patches a record, it does not create one`);
992
+ assert.defined(page, `page of element ${element_index} of '${descriptor.name}'`);
993
+ assert.defined(page.cpu_data_buffer, `page ${page.index} of '${descriptor.name}' has no CPU shadow — a partial write cannot run against an unmapped page`);
994
+
995
+ const record_byte_base = page.cpu_data_address + (element_index % elements_per_page) * record_size;
996
+ const record_word_base = record_byte_base / WORD_SIZE;
997
+
998
+ const shadow = page.words.over(page.cpu_data_buffer);
999
+
1000
+ const pending_record = this.#pending_record_offset(element_index);
1001
+
1002
+ if (pending_record >= 0) {
1003
+ // A whole record for this row is already queued: patch it where it lies, append
1004
+ // nothing, and the row stays in exactly one stream.
1005
+ writer.write_record(shadow.f32, shadow.u32, record_word_base, value);
1006
+
1007
+ const upload = this.#upload_words.over(this.element_upload_buffer.data);
1008
+
1009
+ writer.write_record(upload.f32, upload.u32, (pending_record + u32.size) / WORD_SIZE, value);
1010
+
1011
+ page.cpu_version++;
1012
+
1013
+ return;
1014
+ }
1015
+
1016
+ const ranges = writer.ranges;
1017
+
1018
+ if (this.#partial_descriptor_head !== null && u32_index_get(this.#partial_descriptor_head, element_index) !== U32_INDEX_MISS) {
1019
+ // The row already owns ranges this frame. Whether each of ours patches one in place or
1020
+ // has to absorb it into a union depends on what is pending, not on the writer, so the
1021
+ // ranges go through the general path one at a time.
1022
+ writer.write_record(shadow.f32, shadow.u32, record_word_base, value);
1023
+
1024
+ page.cpu_version++;
1025
+
1026
+ const record_bytes = new Uint8Array(page.cpu_data_buffer, record_byte_base, record_size);
1027
+
1028
+ for (let i = 0; i < ranges.length; i += 2) {
1029
+ this.#stage_partial_range(element_index, ranges[i] / WORD_SIZE, ranges[i + 1] / WORD_SIZE, record_bytes);
1030
+ }
1031
+
1032
+ return;
1033
+ }
1034
+
1035
+ const descriptors = this.partial_upload_descriptors;
1036
+ const data = this.partial_upload_data;
1037
+
1038
+ const descriptor_tail = descriptors.position;
1039
+ const data_tail = data.position;
1040
+ const data_byte_count = writer.range_word_count * WORD_SIZE;
1041
+
1042
+ // Both reservations happen before the views are taken: growing is what moves the storage
1043
+ // they would otherwise be over.
1044
+ descriptors.ensureCapacity(descriptor_tail + (ranges.length / 2) * PARTIAL_DESCRIPTOR_BYTES);
1045
+ data.ensureCapacity(data_tail + data_byte_count);
1046
+
1047
+ const blob = this.#partial_words.over(data.data);
1048
+ const data_word_base = data_tail / WORD_SIZE;
1049
+
1050
+ writer.write_record_and_ranges(
1051
+ shadow.f32, shadow.u32, record_word_base,
1052
+ blob.f32, blob.u32, data_word_base,
1053
+ value
1054
+ );
1055
+
1056
+ page.cpu_version++;
1057
+
1058
+ data.position = data_tail + data_byte_count;
1059
+
1060
+ const previous = this.#previous_capacity(
1061
+ descriptor_tail / PARTIAL_DESCRIPTOR_BYTES + ranges.length / 2
1062
+ );
1063
+
1064
+ let head = -1;
1065
+ let range_word = data_word_base;
1066
+
1067
+ descriptors.position = descriptor_tail;
1068
+
1069
+ for (let i = 0; i < ranges.length; i += 2) {
1070
+ const word_count = ranges[i + 1] / WORD_SIZE;
1071
+
1072
+ descriptors.writeUint32(element_index);
1073
+ descriptors.writeUint32(ranges[i] / WORD_SIZE);
1074
+ descriptors.writeUint32(word_count);
1075
+ descriptors.writeUint32(range_word);
1076
+
1077
+ const index = descriptor_tail / PARTIAL_DESCRIPTOR_BYTES + i / 2;
1078
+
1079
+ previous[index] = head;
1080
+ head = index;
1081
+
1082
+ range_word += word_count;
1083
+ }
1084
+
1085
+ this.#partial_descriptor_head = u32_index_set(this.#partial_descriptor_head, element_index, head);
1086
+ }
1087
+
1088
+ /**
1089
+ * {@link #partial_descriptor_previous}, guaranteed to hold at least `count` entries.
1090
+ *
1091
+ * @param {number} count
1092
+ * @returns {Int32Array}
1093
+ */
1094
+ #previous_capacity(count) {
1095
+ const previous = this.#partial_descriptor_previous;
1096
+
1097
+ if (count <= previous.length) {
1098
+ return previous;
1099
+ }
1100
+
1101
+ const grown = new Int32Array(Math.max(64, previous.length * 2, count));
1102
+
1103
+ grown.set(previous);
1104
+
1105
+ this.#partial_descriptor_previous = grown;
1106
+
1107
+ return grown;
731
1108
  }
732
1109
 
733
1110
  /**
@@ -743,16 +1120,17 @@ export class GPUTypedTable {
743
1120
  const descriptors = this.partial_upload_descriptors;
744
1121
  const data = this.partial_upload_data;
745
1122
  const previous = this.#partial_descriptor_previous;
746
- const word_size = GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
747
-
748
1123
  const tail = descriptors.position;
749
- const head = this.#partial_descriptor_head.get(element_index);
1124
+ // -1 whether the row never had a chain or had one retired — see #partial_descriptor_head
1125
+ const head = this.#partial_descriptor_head === null
1126
+ ? U32_INDEX_MISS
1127
+ : u32_index_get(this.#partial_descriptor_head, element_index);
750
1128
 
751
1129
  let lo = word_offset;
752
1130
  let hi = word_offset + word_count;
753
1131
  let overlaps = false;
754
1132
 
755
- for (let i = head === undefined ? -1 : head; i >= 0; i = previous[i]) {
1133
+ for (let i = head; i >= 0; i = previous[i]) {
756
1134
  descriptors.position = i * PARTIAL_DESCRIPTOR_BYTES + u32.size;
757
1135
 
758
1136
  const candidate_lo = descriptors.readUint32();
@@ -773,8 +1151,8 @@ export class GPUTypedTable {
773
1151
 
774
1152
  descriptors.position = tail;
775
1153
 
776
- new Uint8Array(data.data, (candidate_data + (lo - candidate_lo)) * word_size, word_count * word_size)
777
- .set(record_bytes.subarray(lo * word_size, hi * word_size));
1154
+ new Uint8Array(data.data, (candidate_data + (lo - candidate_lo)) * WORD_SIZE, word_count * WORD_SIZE)
1155
+ .set(record_bytes.subarray(lo * WORD_SIZE, hi * WORD_SIZE));
778
1156
 
779
1157
  return;
780
1158
  }
@@ -791,7 +1169,7 @@ export class GPUTypedTable {
791
1169
  for (let absorbing = overlaps; absorbing;) {
792
1170
  absorbing = false;
793
1171
 
794
- for (let i = head === undefined ? -1 : head; i >= 0; i = previous[i]) {
1172
+ for (let i = head; i >= 0; i = previous[i]) {
795
1173
  descriptors.position = i * PARTIAL_DESCRIPTOR_BYTES + u32.size;
796
1174
 
797
1175
  const candidate_lo = descriptors.readUint32();
@@ -820,11 +1198,11 @@ export class GPUTypedTable {
820
1198
 
821
1199
  descriptors.position = tail;
822
1200
 
823
- const data_offset = data.position / word_size;
1201
+ const data_offset = data.position / WORD_SIZE;
824
1202
 
825
1203
  // The words come out of the shadow rather than out of the caller's `source`, so a union
826
1204
  // wider than what this call supplied still carries the right values.
827
- data.writeBytes(record_bytes, lo * word_size, (hi - lo) * word_size);
1205
+ data.writeBytes(record_bytes, lo * WORD_SIZE, (hi - lo) * WORD_SIZE);
828
1206
 
829
1207
  descriptors.writeUint32(element_index);
830
1208
  descriptors.writeUint32(lo);
@@ -833,8 +1211,8 @@ export class GPUTypedTable {
833
1211
 
834
1212
  const index = tail / PARTIAL_DESCRIPTOR_BYTES;
835
1213
 
836
- previous[index] = head === undefined ? -1 : head;
837
- this.#partial_descriptor_head.set(element_index, index);
1214
+ this.#previous_capacity(index + 1)[index] = head;
1215
+ this.#partial_descriptor_head = u32_index_set(this.#partial_descriptor_head, element_index, index);
838
1216
  }
839
1217
 
840
1218
  /**
@@ -849,13 +1227,13 @@ export class GPUTypedTable {
849
1227
  #retire_partial_records(element_index) {
850
1228
  const heads = this.#partial_descriptor_head;
851
1229
 
852
- if (heads.size === 0) {
1230
+ if (heads === null) {
853
1231
  return;
854
1232
  }
855
1233
 
856
- const head = heads.get(element_index);
1234
+ const head = u32_index_get(heads, element_index);
857
1235
 
858
- if (head === undefined) {
1236
+ if (head === U32_INDEX_MISS) {
859
1237
  return;
860
1238
  }
861
1239
 
@@ -870,7 +1248,9 @@ export class GPUTypedTable {
870
1248
 
871
1249
  descriptors.position = tail;
872
1250
 
873
- heads.delete(element_index);
1251
+ // retired rather than removed: blanking the slot would cut the probe chains running
1252
+ // through it, and -1 already reads back as absent
1253
+ this.#partial_descriptor_head = u32_index_set(heads, element_index, -1);
874
1254
  }
875
1255
 
876
1256
  /**
@@ -881,8 +1261,11 @@ export class GPUTypedTable {
881
1261
  this.partial_upload_descriptors.position = 0;
882
1262
  this.partial_upload_data.position = 0;
883
1263
 
884
- this.#partial_descriptor_head.clear();
885
- this.#partial_descriptor_previous.length = 0;
1264
+ if (this.#partial_descriptor_head !== null) {
1265
+ u32_index_clear(this.#partial_descriptor_head);
1266
+ }
1267
+
1268
+ // #partial_descriptor_previous needs no clearing — see its declaration
886
1269
  }
887
1270
 
888
1271
  /**
@@ -940,15 +1323,15 @@ export class GPUTypedTable {
940
1323
  assert.isNonNegativeInteger(offset, 'offset');
941
1324
 
942
1325
  const occupancy = this.occupancy;
943
- const capacity = this.element_capacity;
944
1326
 
945
1327
  let written = 0;
946
1328
 
947
- for (let i = 0; i < capacity; i++) {
948
- if (occupancy.get(i)) {
949
- target[offset + written] = i;
950
- written++;
951
- }
1329
+ // Word by word rather than bit by bit: rows can sit a long way above the live ones — an
1330
+ // entity's row is its id, and a band of node rows sits above every entity — and the
1331
+ // stretch between costs a word read per thirty-two rows rather than a read per row.
1332
+ for (let i = occupancy.nextSetBit(0); i !== -1; i = occupancy.nextSetBit(i + 1)) {
1333
+ target[offset + written] = i;
1334
+ written++;
952
1335
  }
953
1336
 
954
1337
  assert.equal(written, this.count, 'occupied slot scan disagreed with the table count');