@woosh/meep-engine 3.16.0 → 3.17.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 (410) hide show
  1. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  3. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  4. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  7. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  10. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  11. package/editor/view/node-graph/NodeGraphView.js +84 -8
  12. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  15. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  16. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  17. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  20. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  22. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  25. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  27. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  28. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  29. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  30. package/editor/view/particles/effect/particle-editor.css +123 -35
  31. package/package.json +1 -1
  32. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  34. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  35. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  37. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  38. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  39. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  40. package/src/core/graph/csr/CSRGraph.js +14 -6
  41. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  44. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  47. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  50. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  53. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  55. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  56. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  57. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  59. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  62. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  65. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  68. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  70. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  71. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  74. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  76. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  77. package/src/engine/animation/AnimationUtils.js +246 -246
  78. package/src/engine/animation/Animations.js +74 -74
  79. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  80. package/src/engine/ecs/EventType.d.ts +0 -1
  81. package/src/engine/ecs/EventType.js +1 -26
  82. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  85. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  86. package/src/engine/ecs/parent/EntityNode.js +4 -9
  87. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  89. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  93. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  95. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  96. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  97. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  98. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  99. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  100. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  101. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  102. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  103. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  104. package/src/engine/graphics3/LightSystem.js +4 -8
  105. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/MeshSystem.js +9 -19
  107. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  109. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  111. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  112. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  113. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  114. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  115. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  116. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  117. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  118. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  119. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  120. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  121. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  122. package/src/shade/playground/particle_editor/README.md +43 -2
  123. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  124. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  125. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  126. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  127. package/src/shade/renderer/Renderer.js +13 -1
  128. package/src/shade/renderer/particles/DESIGN.md +104 -19
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  130. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  131. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  132. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  133. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  134. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  135. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  136. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  137. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  138. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  139. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  140. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  141. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  142. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  143. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  144. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  145. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  146. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  148. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  149. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  151. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  153. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  155. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  157. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  158. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  159. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  160. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  161. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  162. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  163. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  164. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  165. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  166. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  168. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  169. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  170. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  171. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  172. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  174. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  175. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  176. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  177. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  178. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  179. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  180. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  181. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  182. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  183. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  185. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  186. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  187. package/src/shade/renderer/particles/graph_particles.js +105 -262
  188. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  189. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  190. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  191. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  192. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  193. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  194. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  196. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  197. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  198. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  199. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  200. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  202. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  203. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  205. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  206. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  207. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  208. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  209. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  210. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  211. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  212. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  213. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  214. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  215. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  216. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  217. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  218. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  219. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  220. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  221. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  222. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  223. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  224. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  225. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  226. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  227. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  228. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  229. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  230. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  235. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  236. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  237. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  238. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  239. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  240. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  241. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  242. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  244. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  245. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  246. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  247. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  248. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  250. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  251. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  252. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  253. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  254. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  255. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  256. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  257. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  258. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  259. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  261. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  262. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  263. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  264. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  265. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  266. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  267. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  268. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  270. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  271. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  272. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  273. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  274. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  275. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  276. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  277. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  279. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  280. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  281. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  282. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  283. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  285. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  286. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  287. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  288. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  289. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  291. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +26 -0
  292. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +1 -1
  293. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  294. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  295. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  296. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  297. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  298. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  299. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  300. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  302. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  303. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  305. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  306. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  307. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  308. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  309. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  310. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  311. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  312. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  314. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  315. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  317. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  318. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  320. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  321. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  323. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  324. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  325. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  326. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  327. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  328. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  329. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  330. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  331. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  332. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  333. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  334. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  335. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  336. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  337. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  338. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  339. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  340. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  341. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  342. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  343. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  344. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  345. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  346. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  347. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  348. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  349. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  350. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  351. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  352. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  353. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  354. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  355. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  356. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  357. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  374. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  375. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  376. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  377. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  378. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  379. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  380. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  381. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  382. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  383. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  384. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  385. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  386. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  387. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  388. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  389. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  390. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  391. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  392. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  393. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  394. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  395. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  396. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  397. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  398. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  399. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  400. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  401. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  402. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  403. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  404. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  405. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  406. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  407. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  408. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  409. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  410. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -1,286 +1,438 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import { GPUDatabase } from "../../buffer/table/GPUDatabase.js";
3
- import {
4
- PARTICLE_DATABASE_INITIAL_SIZE,
5
- PARTICLE_DATABASE_SPEC,
6
- PARTICLE_EMITTER_STATE_TABLE,
7
- PARTICLE_EMITTER_TABLE
8
- } from "../data/PARTICLE_DATABASE_SPEC.js";
9
- import { PARTICLE_EMITTER_STATE_EMPTY } from "../data/PARTICLE_EMITTER_STATE.js";
10
- import { PARTICLE_GENERATION_FIRST, particle_generation_next } from "../data/particle_header.js";
11
- import { create_emitter_record, ParticleEmitter } from "./ParticleEmitter.js";
12
- import { ProgramArena } from "./ProgramArena.js";
13
-
14
- /**
15
- * Staging for one emitter row. Reused across every write — the table serializes it into its page
16
- * shadow synchronously, so nothing outlives the call. This is what keeps a frame's flush free of
17
- * allocation whatever the number of rows it restages.
18
- */
19
- const RECORD = create_emitter_record();
20
-
21
- /**
22
- * The emitter set: {@link ParticleEmitter} instances on the CPU, one GPU table row each.
23
- *
24
- * Rows live in a {@link GPUDatabase} table ({@link ../data/PARTICLE_DATABASE_SPEC.js}), which is
25
- * what makes the set editable at runtime. Adding an emitter stages one record; removing one frees
26
- * its slot for the next add; changing one restages that record and nothing else. Rows are handed
27
- * out by the table and are stable for an emitter's lifetime — they have to be, because every live
28
- * particle's record header stores the row of the emitter that spawned it.
29
- *
30
- * Reuse of a freed row is made safe by the **generation**: every add takes the next value of one
31
- * running counter, so no two emitters this registry has ever registered share one, and the
32
- * particles of a removed emitter — which carry its generation — cannot be mistaken for the new
33
- * tenant's. The simulate pass kills them on sight; see {@link ../data/particle_header.js}.
34
- *
35
- * The GPU-owned per-emitter state is the database's second table, claimed and released here at the
36
- * emitter's row and never written by the CPU otherwise.
37
- *
38
- * Programs are the one thing NOT in the database: they are variable-length and shared, so they go
39
- * into a packed {@link ProgramArena} buffer and the emitter record carries offsets into it. The arena is
40
- * append-only and content-deduplicated — two emitters compiled from the same graph land on one
41
- * program id, which is what the simulation-order bucketing buckets on. Removing an emitter does not
42
- * reclaim its program: the dense `program_id`s are baked into every other emitter's record, so
43
- * compacting them would mean rewriting the whole table to save a few hundred words.
44
- */
45
- export class EmitterRegistry {
46
-
47
- /**
48
- * @type {GPUDatabase}
49
- */
50
- database;
51
-
52
- /**
53
- * Packed instruction + constant buffers for every distinct program in the set.
54
- * @type {ProgramArena}
55
- */
56
- arena = new ProgramArena();
57
-
58
- /**
59
- * The live emitters, in no particular order — {@link ParticleEmitter#row} is the identity.
60
- * @type {ParticleEmitter[]}
61
- */
62
- emitters = [];
63
-
64
- /**
65
- * The live emitters by table row. Sparse: a freed row reads `undefined` until it is reused.
66
- * Rows are small dense integers handed out lowest-free-first, so an array is the right index.
67
- *
68
- * @type {ParticleEmitter[]}
69
- */
70
- by_row = [];
71
-
72
- /**
73
- * Whether {@link arena} has grown since the instruction/constant buffers were last built. Those
74
- * two are monolithic (a program is variable-length, so there is no table to page them into), so
75
- * a new program means re-uploading them whole — which is why the arena dedupes by content and
76
- * why this is a separate flag from the per-row dirtiness the table tracks.
77
- *
78
- * @type {boolean}
79
- */
80
- programs_dirty = true;
81
-
82
- /**
83
- * The generation the next add will take.
84
- * @type {number}
85
- */
86
- #generation = PARTICLE_GENERATION_FIRST;
87
-
88
- /**
89
- * @param {GPUDevice} device
90
- */
91
- constructor(device) {
92
- assert.defined(device, 'device');
93
-
94
- this.database = new GPUDatabase({
95
- device,
96
- definition: PARTICLE_DATABASE_SPEC,
97
- label: 'Particles / Emitters',
98
- initial_data_size_bytes: PARTICLE_DATABASE_INITIAL_SIZE,
99
- });
100
- }
101
-
102
- /**
103
- * The emitter table.
104
- * @returns {GPUTypedTable}
105
- */
106
- get table() {
107
- return this.database.get(PARTICLE_EMITTER_TABLE);
108
- }
109
-
110
- /**
111
- * The GPU-owned state table: one row per emitter, at the emitter's row, claimed and released
112
- * here and otherwise the GPU's.
113
- * @returns {GPUTypedTable}
114
- */
115
- get state_table() {
116
- return this.database.get(PARTICLE_EMITTER_STATE_TABLE);
117
- }
118
-
119
- /**
120
- * @returns {number} number of live emitters
121
- */
122
- get count() {
123
- return this.emitters.length;
124
- }
125
-
126
- /**
127
- * Number of distinct compiled programs — the number of buckets the simulation-order coherence
128
- * pass histograms into.
129
- *
130
- * @returns {number}
131
- */
132
- get program_count() {
133
- return this.arena.program_count;
134
- }
135
-
136
- /**
137
- * Register an emitter and give it a table row.
138
- *
139
- * @param {ParticleEmitter|object} config a {@link ParticleEmitter}, or a description
140
- * {@link ParticleEmitter.from} accepts
141
- * @param {number} [transform_row] the emitter node's row in the scene's `transforms` table, if
142
- * already known; {@link set_transform_row} sets it later otherwise
143
- * @returns {ParticleEmitter}
144
- */
145
- add(config, transform_row = 0) {
146
- const emitter = config.isParticleEmitter === true ? config : ParticleEmitter.from(config);
147
-
148
- assert.equal(emitter.row, -1, 'emitter.row — this emitter is already registered');
149
-
150
- const program_count_before = this.arena.program_count;
151
-
152
- emitter.placement = this.arena.add(emitter.effect.program);
153
-
154
- if (this.arena.program_count !== program_count_before) {
155
- this.programs_dirty = true;
156
- }
157
-
158
- emitter.generation = this.#generation;
159
- this.#generation = particle_generation_next(this.#generation);
160
-
161
- emitter.transform_row = transform_row;
162
-
163
- // An emitter given no seed gets one that tells it apart from every other emitter this
164
- // registry has seen. Two emitters of one effect are the common case, and an identical seed
165
- // makes them the same particles in the same places.
166
- if (emitter.seed === 0) {
167
- emitter.seed = emitter.generation;
168
- }
169
-
170
- // The table picks the slot — the lowest free one, so a removed emitter's row is reused
171
- // rather than the id space growing without bound.
172
- emitter.row = this.table.add(emitter.write_record(RECORD));
173
- emitter.dirty = false;
174
-
175
- // Claim the state row alongside: zeroed, so the first tick starts it over. The CPU never
176
- // writes it again — see PARTICLE_EMITTER_STATE.
177
- this.state_table.set(emitter.row, PARTICLE_EMITTER_STATE_EMPTY);
178
-
179
- this.emitters.push(emitter);
180
- this.by_row[emitter.row] = emitter;
181
-
182
- return emitter;
183
- }
184
-
185
- /**
186
- * Unregister an emitter and free its table row.
187
- *
188
- * Its particles are not touched here; they carry its generation, the row they will read is
189
- * zero-filled (or a later tenant's, with a different generation), and the simulate pass frees
190
- * them the next time it sees them. Retiring an emitter gently — spawn rate to zero, remove once
191
- * its particles have died — remains available to an author who wants the tail to play out.
192
- *
193
- * @param {ParticleEmitter} emitter
194
- */
195
- remove(emitter) {
196
- assert.defined(emitter, 'emitter');
197
- assert.equal(emitter.isParticleEmitter, true, 'emitter.isParticleEmitter !== true');
198
-
199
- const position = this.emitters.indexOf(emitter);
200
-
201
- assert.notEqual(position, -1, 'emitter is not registered here');
202
-
203
- // Swap-remove: order carries no meaning, and a splice would allocate.
204
- const last = this.emitters.pop();
205
-
206
- if (last !== emitter) {
207
- this.emitters[position] = last;
208
- }
209
-
210
- this.by_row[emitter.row] = undefined;
211
- this.table.remove(emitter.row);
212
- this.state_table.remove(emitter.row);
213
-
214
- emitter.row = -1;
215
- emitter.generation = 0;
216
- emitter.placement = undefined;
217
- }
218
-
219
- /**
220
- * Look up a live emitter by its table row.
221
- *
222
- * @param {number} row
223
- * @returns {ParticleEmitter|undefined}
224
- */
225
- get(row) {
226
- return this.by_row[row];
227
- }
228
-
229
- /**
230
- * Point an emitter's record at its node's row in the scene's `transforms` table. The row is
231
- * restaged on the next flush.
232
- *
233
- * @param {ParticleEmitter} emitter
234
- * @param {number} transform_row
235
- */
236
- set_transform_row(emitter, transform_row) {
237
- assert.isNonNegativeInteger(transform_row, 'transform_row');
238
-
239
- if (emitter.transform_row === transform_row) {
240
- return;
241
- }
242
-
243
- emitter.transform_row = transform_row;
244
- emitter.dirty = true;
245
- }
246
-
247
- /**
248
- * Restage the table rows of every emitter whose fields have moved since the last flush.
249
- *
250
- * Cheap to call every frame: an emitter that has not changed costs one boolean test, and the
251
- * table coalesces repeated writes to one row within a frame anyway. Allocates nothing — every
252
- * row goes through the one module-static staging record.
253
- */
254
- flush() {
255
- const table = this.table;
256
- const emitters = this.emitters;
257
- const count = emitters.length;
258
-
259
- for (let i = 0; i < count; i++) {
260
- const emitter = emitters[i];
261
-
262
- if (!emitter.dirty) {
263
- continue;
264
- }
265
-
266
- table.set(emitter.row, emitter.write_record(RECORD));
267
- emitter.dirty = false;
268
- }
269
- }
270
-
271
- /**
272
- * The packed program buffer — header, constant pools, code — rebuilt whole.
273
- *
274
- * Call when {@link programs_dirty}; it clears the flag. The buffer is monolithic (a program is
275
- * variable-length, so there is no table to page it into), so this is a full rebuild — the
276
- * arena's content deduplication is what keeps it rare (it only grows when an effect the set has
277
- * never seen is registered).
278
- *
279
- * @returns {Uint32Array}
280
- */
281
- build_program() {
282
- this.programs_dirty = false;
283
-
284
- return this.arena.build_program_buffer();
285
- }
286
- }
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { GPUDatabase } from "../../buffer/table/GPUDatabase.js";
3
+ import {
4
+ PARTICLE_DATABASE_INITIAL_SIZE,
5
+ PARTICLE_DATABASE_SPEC,
6
+ PARTICLE_EMITTER_STATE_TABLE,
7
+ PARTICLE_EMITTER_TABLE
8
+ } from "../data/PARTICLE_DATABASE_SPEC.js";
9
+ import { PARTICLE_EMITTER_STATE_EMPTY } from "../data/PARTICLE_EMITTER_STATE.js";
10
+ import { create_emitter_record, write_emitter_record } from "../data/particle_emitter_record.js";
11
+ import { PARTICLE_GENERATION_FIRST, particle_generation_next } from "../data/particle_header.js";
12
+ import { GPUParticleEmitterContext } from "./GPUParticleEmitterContext.js";
13
+ import { ParticleEmitter } from "./ParticleEmitter.js";
14
+ import { ProgramHeap } from "./ProgramHeap.js";
15
+
16
+ /**
17
+ * Staging for one emitter row. Reused across every write — the table serializes it into its page
18
+ * shadow synchronously, so nothing outlives the call. This is what keeps a frame's flush free of
19
+ * allocation whatever the number of rows it restages.
20
+ */
21
+ const RECORD = create_emitter_record();
22
+
23
+ /**
24
+ * The emitter set: {@link ParticleEmitter} instances on the CPU, one GPU table row each.
25
+ *
26
+ * Rows live in a {@link GPUDatabase} table ({@link ../data/PARTICLE_DATABASE_SPEC.js}), which is
27
+ * what makes the set editable at runtime. Adding an emitter stages one record; removing one frees
28
+ * its slot for the next add; changing one restages that record and nothing else. Rows are handed
29
+ * out by the table and are stable for an emitter's lifetime — they have to be, because every live
30
+ * particle's record header stores the row of the emitter that spawned it.
31
+ *
32
+ * Reuse of a freed row is made safe by the **generation**: every add takes the next value of one
33
+ * running counter, so no two emitters this registry has ever registered share one, and the
34
+ * particles of a removed emitter — which carry its generation — cannot be mistaken for the new
35
+ * tenant's. The simulate pass kills them on sight; see {@link ../data/particle_header.js}.
36
+ *
37
+ * **None of that bookkeeping is on the emitter.** An emitter is a scene node and a description of
38
+ * an effect, with nothing in it that depends on a device; the row, the generation, the program
39
+ * placement, the node's `transforms` row and the atlas patch are a
40
+ * {@link ./GPUParticleEmitterContext.js} this registry holds against the emitter and hands out
41
+ * through {@link EmitterRegistry#context}. Registering an emitter creates one, removing it destroys
42
+ * one, and an emitter with no context here is an emitter this registry does not have.
43
+ *
44
+ * The GPU-owned per-emitter state is the database's second table, claimed and released here at the
45
+ * emitter's row and never written by the CPU otherwise.
46
+ *
47
+ * Programs are the one thing NOT in the database: they are variable-length and shared, so they go
48
+ * into a packed {@link ProgramHeap} buffer and the emitter record carries offsets into it. The heap
49
+ * is content-deduplicated and reference-counted — two emitters compiled from the same graph land on
50
+ * one program id, which is what the simulation-order bucketing buckets on, and the last emitter of
51
+ * an effect is what releases it.
52
+ */
53
+ export class EmitterRegistry {
54
+
55
+ /**
56
+ * @type {GPUDatabase}
57
+ */
58
+ database;
59
+
60
+ /**
61
+ * Packed instruction + constant buffers for every distinct program in the set.
62
+ * @type {ProgramHeap}
63
+ */
64
+ programs = new ProgramHeap();
65
+
66
+ /**
67
+ * The live emitters, in no particular order — the row in {@link context} is the identity.
68
+ * @type {ParticleEmitter[]}
69
+ */
70
+ emitters = [];
71
+
72
+ /**
73
+ * The live emitters by table row. Sparse: a freed row reads `undefined` until it is reused.
74
+ * Rows are small dense integers handed out lowest-free-first, so an array is the right index.
75
+ *
76
+ * @type {ParticleEmitter[]}
77
+ */
78
+ by_row = [];
79
+
80
+ /**
81
+ * Whether {@link programs} has changed since {@link build_program} last packed it. The buffer is
82
+ * monolithic (a program is variable-length, so there is no table to page it into), so any change
83
+ * means re-uploading it whole — which is why the heap dedupes by content, and why this is
84
+ * separate from the per-row dirtiness the table tracks.
85
+ *
86
+ * Read off the heap's own version rather than kept as a flag: there is then no path that changes
87
+ * the programs and forgets to say so.
88
+ *
89
+ * @returns {boolean}
90
+ */
91
+ get programs_dirty() {
92
+ return this.programs.version !== this.#built_programs_version;
93
+ }
94
+
95
+ /**
96
+ * {@link ProgramHeap#version} the last {@link build_program} packed. `-1` for "nothing has been
97
+ * built yet", which no version reaches.
98
+ *
99
+ * @type {number}
100
+ */
101
+ #built_programs_version = -1;
102
+
103
+ /**
104
+ * {@link ProgramHeap#placement_version} the rows were last staged against. When the heap repacks,
105
+ * every program that was already placed moves, so every row built from the old offsets has to be
106
+ * written again — see {@link flush}.
107
+ *
108
+ * @type {number}
109
+ */
110
+ #staged_placement_version = 0;
111
+
112
+ /**
113
+ * What this registry knows about each emitter it holds, and the only place any of it exists.
114
+ *
115
+ * @type {Map<ParticleEmitter, GPUParticleEmitterContext>}
116
+ */
117
+ #contexts = new Map();
118
+
119
+ /**
120
+ * The generation the next add will take.
121
+ * @type {number}
122
+ */
123
+ #generation = PARTICLE_GENERATION_FIRST;
124
+
125
+ /**
126
+ * @param {GPUDevice} device
127
+ */
128
+ constructor(device) {
129
+ assert.defined(device, 'device');
130
+
131
+ this.database = new GPUDatabase({
132
+ device,
133
+ definition: PARTICLE_DATABASE_SPEC,
134
+ label: 'Particles / Emitters',
135
+ initial_data_size_bytes: PARTICLE_DATABASE_INITIAL_SIZE,
136
+ });
137
+ }
138
+
139
+ /**
140
+ * The emitter table.
141
+ * @returns {GPUTypedTable}
142
+ */
143
+ get table() {
144
+ return this.database.get(PARTICLE_EMITTER_TABLE);
145
+ }
146
+
147
+ /**
148
+ * The GPU-owned state table: one row per emitter, at the emitter's row, claimed and released
149
+ * here and otherwise the GPU's.
150
+ * @returns {GPUTypedTable}
151
+ */
152
+ get state_table() {
153
+ return this.database.get(PARTICLE_EMITTER_STATE_TABLE);
154
+ }
155
+
156
+ /**
157
+ * @returns {number} number of live emitters
158
+ */
159
+ get count() {
160
+ return this.emitters.length;
161
+ }
162
+
163
+ /**
164
+ * Number of distinct compiled programs registered.
165
+ * @returns {number}
166
+ */
167
+ get program_count() {
168
+ return this.programs.program_count;
169
+ }
170
+
171
+ /**
172
+ * Number of buckets the simulation-order coherence pass histograms into: every live program id
173
+ * is below it. See {@link ProgramHeap#program_id_capacity}.
174
+ *
175
+ * @returns {number}
176
+ */
177
+ get program_id_capacity() {
178
+ return this.programs.program_id_capacity;
179
+ }
180
+
181
+ /**
182
+ * Register slots the hungriest registered effect needs — a property of the whole set, since
183
+ * there is one simulate pipeline and it is chosen once. What a caller does with the number (pick
184
+ * a register backing, refuse the effect, warn an author) is the caller's; see
185
+ * {@link ProgramHeap#max_register_count}.
186
+ *
187
+ * @returns {number}
188
+ */
189
+ get max_register_count() {
190
+ return this.programs.max_register_count;
191
+ }
192
+
193
+ /**
194
+ * What this registry assigned an emitter: its table row, generation, program placement, the
195
+ * `transforms` row of its node and its atlas patch.
196
+ *
197
+ * `undefined` for an emitter this registry does not hold — which is the way to ask whether one
198
+ * is live, there being no `-1` on the emitter to read instead.
199
+ *
200
+ * @param {ParticleEmitter} emitter
201
+ * @returns {GPUParticleEmitterContext|undefined}
202
+ */
203
+ context(emitter) {
204
+ return this.#contexts.get(emitter);
205
+ }
206
+
207
+ /**
208
+ * Register an emitter and give it a table row.
209
+ *
210
+ * @param {ParticleEmitter|object} config a {@link ParticleEmitter}, or a description
211
+ * {@link ParticleEmitter.from} accepts
212
+ * @param {number} [transform_row] the emitter node's row in the scene's `transforms` table, if
213
+ * already known; {@link set_transform_row} sets it later otherwise
214
+ * @returns {ParticleEmitter}
215
+ */
216
+ add(config, transform_row = 0) {
217
+ const emitter = config.isParticleEmitter === true ? config : ParticleEmitter.from(config);
218
+
219
+ assert.equal(this.#contexts.has(emitter), false, 'this emitter is already registered');
220
+
221
+ const context = new GPUParticleEmitterContext();
222
+
223
+ context.placement = this.programs.acquire(emitter.program);
224
+
225
+ context.generation = this.#generation;
226
+ this.#generation = particle_generation_next(this.#generation);
227
+
228
+ context.transform_row = transform_row;
229
+
230
+ // The table picks the slot — the lowest free one, so a removed emitter's row is reused
231
+ // rather than the id space growing without bound.
232
+ context.row = this.table.add(write_emitter_record(RECORD, emitter, context));
233
+ context.staged(emitter);
234
+
235
+ // Claim the state row alongside: zeroed, so the first tick starts it over. The CPU never
236
+ // writes it again — see PARTICLE_EMITTER_STATE.
237
+ this.state_table.set(context.row, PARTICLE_EMITTER_STATE_EMPTY);
238
+
239
+ this.#contexts.set(emitter, context);
240
+ this.emitters.push(emitter);
241
+ this.by_row[context.row] = emitter;
242
+
243
+ return emitter;
244
+ }
245
+
246
+ /**
247
+ * Unregister an emitter and free its table row.
248
+ *
249
+ * Its particles are not touched here; they carry its generation, the row they will read is
250
+ * zero-filled (or a later tenant's, with a different generation), and the simulate pass frees
251
+ * them the next time it sees them. Retiring an emitter gently — spawn rate to zero, remove once
252
+ * its particles have died — remains available to an author who wants the tail to play out.
253
+ *
254
+ * The emitter itself comes out of this unchanged: what it loses is its context, which is
255
+ * everything being registered gave it. Its program goes with the context — the heap counts
256
+ * references, so an effect is only unpacked when its last emitter leaves.
257
+ *
258
+ * @param {ParticleEmitter} emitter
259
+ */
260
+ remove(emitter) {
261
+ assert.defined(emitter, 'emitter');
262
+ assert.equal(emitter.isParticleEmitter, true, 'emitter.isParticleEmitter !== true');
263
+
264
+ const context = this.#contexts.get(emitter);
265
+
266
+ assert.defined(context, 'emitter is not registered here');
267
+
268
+ const position = this.emitters.indexOf(emitter);
269
+
270
+ // Swap-remove: order carries no meaning, and a splice would allocate.
271
+ const last = this.emitters.pop();
272
+
273
+ if (last !== emitter) {
274
+ this.emitters[position] = last;
275
+ }
276
+
277
+ this.by_row[context.row] = undefined;
278
+ this.table.remove(context.row);
279
+ this.state_table.remove(context.row);
280
+
281
+ this.programs.release(context.placement);
282
+
283
+ context.placement = null;
284
+
285
+ this.#contexts.delete(emitter);
286
+ }
287
+
288
+ /**
289
+ * Give a registered emitter different bytecode to run: its old program loses a reference, the new
290
+ * one gains one, and its row is restaged with the new placement.
291
+ *
292
+ * This is the runtime edit the emitter table has always allowed and the program buffer used not
293
+ * to — an author reworking an effect in the editor changes the program of an emitter that is
294
+ * already live, and the effect it used to run goes away with the last emitter that ran it.
295
+ *
296
+ * The attribute {@link ../layout/ParticleLayout.js layout} is NOT part of this and must not
297
+ * change with it: particles already in flight are laid out the old way, and new bytecode reading
298
+ * them at new offsets would read somebody else's field. Re-laying-out an effect means new
299
+ * emitters.
300
+ *
301
+ * @param {ParticleEmitter} emitter
302
+ * @param {import("../isa/ParticleProgram.js").ParticleProgram} program
303
+ */
304
+ set_program(emitter, program) {
305
+ assert.defined(program, 'program');
306
+
307
+ const context = this.#contexts.get(emitter);
308
+
309
+ assert.defined(context, 'emitter is not registered here');
310
+
311
+ const previous = context.placement;
312
+
313
+ // acquired before the old one is let go, so that re-setting the program an emitter already
314
+ // runs — which is what an editor does on every keystroke that changes nothing — neither
315
+ // unpacks it nor gives it a new id
316
+ context.placement = this.programs.acquire(program);
317
+
318
+ emitter.program = program;
319
+
320
+ this.programs.release(previous);
321
+
322
+ context.invalidate();
323
+ }
324
+
325
+ /**
326
+ * Look up a live emitter by its table row.
327
+ *
328
+ * @param {number} row
329
+ * @returns {ParticleEmitter|undefined}
330
+ */
331
+ get(row) {
332
+ return this.by_row[row];
333
+ }
334
+
335
+ /**
336
+ * Point an emitter's record at its node's row in the scene's `transforms` table. The row is
337
+ * restaged on the next flush.
338
+ *
339
+ * @param {ParticleEmitter} emitter
340
+ * @param {number} transform_row
341
+ */
342
+ set_transform_row(emitter, transform_row) {
343
+ assert.isNonNegativeInteger(transform_row, 'transform_row');
344
+
345
+ const context = this.#contexts.get(emitter);
346
+
347
+ assert.defined(context, 'emitter is not registered here');
348
+
349
+ if (context.transform_row === transform_row) {
350
+ return;
351
+ }
352
+
353
+ context.transform_row = transform_row;
354
+ context.invalidate();
355
+ }
356
+
357
+ /**
358
+ * Point an emitter's record at the patch its texture was packed into: uv offset `.xy`, uv scale
359
+ * `.zw`. The row is restaged on the next flush.
360
+ *
361
+ * For whoever owns the atlas to call, on registration and again after every repack — a repack
362
+ * moves every patch, and an emitter left holding the old one draws the wrong image.
363
+ *
364
+ * @param {ParticleEmitter} emitter
365
+ * @param {ArrayLike<number>} region [uvOffsetX, uvOffsetY, uvScaleX, uvScaleY]
366
+ */
367
+ set_atlas_region(emitter, region) {
368
+ assert.equal(region.length, 4, 'region.length');
369
+
370
+ const context = this.#contexts.get(emitter);
371
+
372
+ assert.defined(context, 'emitter is not registered here');
373
+
374
+ const atlas_region = context.atlas_region;
375
+
376
+ if (
377
+ atlas_region[0] === region[0] && atlas_region[1] === region[1]
378
+ && atlas_region[2] === region[2] && atlas_region[3] === region[3]
379
+ ) {
380
+ return;
381
+ }
382
+
383
+ atlas_region.set(region);
384
+ context.invalidate();
385
+ }
386
+
387
+ /**
388
+ * Restage the table rows of every emitter whose fields have moved since the last flush.
389
+ *
390
+ * "Moved" is the node's own change counter: an emitter is restaged when its
391
+ * {@link ../../scene/Node3D.js Node3D#version} is not the one its row was staged from, or when
392
+ * something in its context changed. So an author marks an edited emitter the way they mark any
393
+ * edited node, with `needsUpdate = true`, and there is no second flag to forget.
394
+ *
395
+ * Cheap to call every frame: an emitter that has not changed costs one integer comparison, and
396
+ * the table coalesces repeated writes to one row within a frame anyway. Walks the context map
397
+ * rather than the emitter list so that a pass over the set is not also a lookup per emitter.
398
+ * Stages through the one module-static record, so a frame's restaging allocates nothing however
399
+ * many rows it touches.
400
+ */
401
+ flush() {
402
+ const table = this.table;
403
+
404
+ const placement_version = this.programs.placement_version;
405
+
406
+ // A repack moved every program that was already placed, so every row holds offsets into
407
+ // where its code used to be. That is the one change no emitter announces — it is not an edit
408
+ // to any of them — so it is asked of the heap once here rather than pushed onto each context.
409
+ const relocated = placement_version !== this.#staged_placement_version;
410
+
411
+ this.#staged_placement_version = placement_version;
412
+
413
+ for (const [emitter, context] of this.#contexts) {
414
+ if (!relocated && !context.is_stale(emitter)) {
415
+ continue;
416
+ }
417
+
418
+ table.set(context.row, write_emitter_record(RECORD, emitter, context));
419
+ context.staged(emitter);
420
+ }
421
+ }
422
+
423
+ /**
424
+ * The packed program buffer — header, constant pools, code — rebuilt whole.
425
+ *
426
+ * Call when {@link programs_dirty}; it answers the flag. The buffer is monolithic (a program is
427
+ * variable-length, so there is no table to page it into), so this is a full rebuild — the heap's
428
+ * content deduplication is what keeps it rare (it changes only when an effect the set has never
429
+ * seen is registered, or when the sections are repacked to make room for one).
430
+ *
431
+ * @returns {Uint32Array}
432
+ */
433
+ build_program() {
434
+ this.#built_programs_version = this.programs.version;
435
+
436
+ return this.programs.build_buffer();
437
+ }
438
+ }