@vworlds/vecs-physics 1.0.53 → 1.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/body/pose_out.js CHANGED
@@ -1,45 +1,59 @@
1
1
  import { Module } from "@vworlds/vecs";
2
- import { getPhysicsEngine, PhysicsSystemTag, } from "../adapter/engine.js";
2
+ import { getPhysicsEngine, PhysicsSystemTag } from "../adapter/engine.js";
3
3
  import { AngularVelocity, Body, BodyType, LinearVelocity, Position, Rotation, } from "./components.js";
4
4
  import { PhysicsCoreModule } from "../core/module.js";
5
+ /** Export moved bodies once per post-step tick and write only changed Dynamic-body components.
6
+ * The pinned fast path refreshes heap views; optional components and modification routing remain
7
+ * unchanged, and no per-body Box2D handle, marshalling object or typed-array view is created. */
5
8
  export class BodyPoseOutModule extends Module {
6
9
  init() {
7
10
  const world = this.world;
8
11
  world.module(PhysicsCoreModule);
9
12
  const engine = getPhysicsEngine(world);
13
+ const fast = engine.fast;
14
+ const move = fast.layout.move;
15
+ const offsets = {
16
+ bodyIndex: move.bodyIndex >> 2,
17
+ x: move.x >> 2,
18
+ y: move.y >> 2,
19
+ angle: move.angle >> 2,
20
+ vx: move.vx >> 2,
21
+ vy: move.vy >> 2,
22
+ w: move.w >> 2,
23
+ };
24
+ const flags = fast.layout.constants.exportVelocity;
25
+ // Numeric ids avoid resolving component classes on every moved body.
26
+ const ids = {
27
+ body: world.entity(Body).eid,
28
+ position: world.entity(Position).eid,
29
+ rotation: world.entity(Rotation).eid,
30
+ linearVelocity: world.entity(LinearVelocity).eid,
31
+ angularVelocity: world.entity(AngularVelocity).eid,
32
+ };
10
33
  world
11
34
  .system("PhysicsPoseSyncOut")
12
35
  .phase(engine.phases.postStep)
13
36
  .run(() => {
14
- const bodyEvents = engine.box2d.b2World_GetBodyEvents(engine.worldId);
15
- try {
16
- for (let i = 0; i < bodyEvents.moveCount; i++) {
17
- const move = bodyEvents.GetMoveEvent(i);
18
- try {
19
- const bodyId = move.bodyId;
20
- const entityId = engine.entityByBodyIndex.get(bodyId.index1);
21
- if (entityId === undefined) {
22
- continue;
23
- }
24
- // A postStep system may destroy an entity after Box2D produced this event.
25
- // Keep the reverse slot until the reaper frees the body; lookup is O(1).
26
- const entity = world.getEntity(entityId);
27
- if (entity === undefined || entity.destroyed) {
28
- continue;
29
- }
30
- const body = entity.get(Body);
31
- if (body?.type !== BodyType.Dynamic) {
32
- continue;
33
- }
34
- writePose(engine, entity, move.transform, bodyId);
35
- }
36
- finally {
37
- move.delete();
38
- }
37
+ // Export after preceding postStep systems; exportMoves also refreshes memory views.
38
+ const exporter = engine.moveExporter;
39
+ const bodyEntities = engine.bodyEntities;
40
+ const count = exporter.exportMoves(engine.world, flags);
41
+ const i32 = fast.i32;
42
+ const f32 = fast.f32;
43
+ const stride = exporter.stride;
44
+ let base = exporter.base;
45
+ for (let i = 0; i < count; i++, base += stride) {
46
+ // A postStep system may destroy an entity after Box2D produced this event.
47
+ // Keep the reverse slot until the reaper frees the body; lookup is O(1).
48
+ const entity = bodyEntities[i32[base + offsets.bodyIndex]];
49
+ if (entity === undefined || entity.destroyed) {
50
+ continue;
39
51
  }
40
- }
41
- finally {
42
- bodyEvents.delete();
52
+ const body = entity.get(ids.body);
53
+ if (body?.type !== BodyType.Dynamic) {
54
+ continue;
55
+ }
56
+ writePose(entity, f32, base, offsets, ids);
43
57
  }
44
58
  })
45
59
  .add(PhysicsSystemTag);
@@ -67,45 +81,35 @@ export class BodyPoseOutModule extends Module {
67
81
  * nothing per body: the new values are read into locals and compared directly, and the `{ x, y }`
68
82
  * literals exist only on the write path.
69
83
  *
70
- * Embind handle rules, which the compare must keep: `move.transform`, `.p` and `.q` are views into
71
- * the move event's own memory (same address on every read, `transform.p` even shares the
72
- * transform's), not copies. Read each once and leave them alone: calling `delete()` on a view runs
73
- * the C++ destructor on an interior pointer of the event, which corrupts the wasm heap. The event's
74
- * single `move.delete()` releases the memory. The linear velocity is a by-value return, a heap copy
75
- * this function owns and deletes.
84
+ * Values come from the move record exported after preceding postStep systems. The angle uses
85
+ * Box2D's b2Rot_GetAngle, preserving its exact bits, and velocities use the same Box2D getters
86
+ * in wasm. Reading these f32 records creates no embind handle or allocation.
76
87
  */
77
- function writePose(engine, entity, transform, bodyId) {
78
- const p = transform.p;
79
- const x = p.x;
80
- const y = p.y;
81
- const position = entity.get(Position);
88
+ function writePose(entity, f32, base, offsets, ids) {
89
+ const x = f32[base + offsets.x];
90
+ const y = f32[base + offsets.y];
91
+ const position = entity.get(ids.position);
82
92
  if (position === undefined || position.x !== x || position.y !== y) {
83
93
  entity.set(Position, { x, y });
84
94
  }
85
- const rotation = entity.get(Rotation);
95
+ const rotation = entity.get(ids.rotation);
86
96
  if (rotation !== undefined) {
87
- const angle = transform.q.GetAngle();
97
+ const angle = f32[base + offsets.angle];
88
98
  if (rotation.angle !== angle) {
89
99
  entity.set(Rotation, { angle });
90
100
  }
91
101
  }
92
- const linearVelocity = entity.get(LinearVelocity);
102
+ const linearVelocity = entity.get(ids.linearVelocity);
93
103
  if (linearVelocity !== undefined) {
94
- const linear = engine.box2d.b2Body_GetLinearVelocity(bodyId);
95
- try {
96
- const vx = linear.x;
97
- const vy = linear.y;
98
- if (linearVelocity.x !== vx || linearVelocity.y !== vy) {
99
- entity.set(LinearVelocity, { x: vx, y: vy });
100
- }
101
- }
102
- finally {
103
- linear.delete();
104
+ const vx = f32[base + offsets.vx];
105
+ const vy = f32[base + offsets.vy];
106
+ if (linearVelocity.x !== vx || linearVelocity.y !== vy) {
107
+ entity.set(LinearVelocity, { x: vx, y: vy });
104
108
  }
105
109
  }
106
- const angularVelocity = entity.get(AngularVelocity);
110
+ const angularVelocity = entity.get(ids.angularVelocity);
107
111
  if (angularVelocity !== undefined) {
108
- const value = engine.box2d.b2Body_GetAngularVelocity(bodyId);
112
+ const value = f32[base + offsets.w];
109
113
  if (angularVelocity.value !== value) {
110
114
  entity.set(AngularVelocity, { value });
111
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pose_out.js","sourceRoot":"","sources":["../../../../src/body/pose_out.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pose_out.js","sourceRoot":"","sources":["../../../../src/body/pose_out.ts"],"names":[],"mappings":"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"}
package/core/module.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { insertPhaseAfter, Module, ON_UPDATE, Phase, Singleton, } from "@vworlds/vecs";
2
2
  import { hasPhysicsEngine, PhysicsEngine, PhysicsSystemTag, } from "../adapter/engine.js";
3
3
  import { withAllocated } from "../adapter/disposal.js";
4
- import { getBox2D } from "../adapter/runtime.js";
4
+ import { getBox2D, getFastPath } from "../adapter/runtime.js";
5
5
  const DEFAULT_PRE_STEP_PHASE = "physics-pre";
6
6
  const DEFAULT_STEP_PHASE = "physics-step";
7
7
  const DEFAULT_POST_STEP_PHASE = "physics-post";
@@ -29,8 +29,10 @@ export class PhysicsCoreModule extends Module {
29
29
  insertPhaseAfter(phases.postStep, phases.step);
30
30
  const box2d = getBox2D();
31
31
  const resolved = resolveOptions(opts);
32
- const engine = new PhysicsEngine(box2d, resolved, phases, opts.debugSink);
32
+ const fast = getFastPath();
33
+ const engine = new PhysicsEngine(box2d, resolved, phases, fast, opts.debugSink);
33
34
  engine.worldId = createWorldId(engine, resolved);
35
+ engine.world = fast.world(engine.worldId);
34
36
  world.component(PhysicsEngine).add(Singleton);
35
37
  world.component(PhysicsEngine).attach(PhysicsEngine, engine);
36
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module.js","sourceRoot":"","sources":["../../../../src/core/module.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,MAAM,EACN,SAAS,EACT,KAAK,EACL,SAAS,GAGV,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GAEjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AA8BjD,MAAM,sBAAsB,GAAG,aAAa,CAAC;AAC7C,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAC1C,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAE/C,MAAM,OAAO,iBAAkB,SAAQ,MAAkC;IAChE,IAAI,CAAC,OAAmC;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,CAAC;QAC3B,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC/B,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAElC,MAAM,MAAM,GAAG;YACb,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,sBAAsB,CAAC;YACvE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;YACnD,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC;YAC7D,QAAQ,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,uBAAuB,CAAC;SAC3E,CAAC;QACF,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5C,gBAAgB,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC9C,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9C,uEAAuE;QACvE,8EAA8E;QAC9E,gBAAgB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACjD,gBAAgB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAE/C,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1E,MAAM,CAAC,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACjD,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9C,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAC/D,CAAC;CACF;AAED,SAAS,aAAa,CACpB,MAAqB,EACrB,OAA+B;IAE/B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,OAAO,aAAa,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,GAAG,EAAE,EAAE;QACpD,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,GAAG,CAAC,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;QACxD,GAAG,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;QAClD,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACxC,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACtD,GAAG,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;QACpD,GAAG,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACtC,GAAG,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;QAChD,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACzC,KAAK,CAAC,wBAAwB,CAC5B,OAAO,EACP,OAAO,CAAC,YAAY,EACpB,OAAO,CAAC,mBAAmB,EAC3B,OAAO,CAAC,YAAY,CACrB,CAAC;QACF,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,SAAS,YAAY,CACnB,KAAY,EACZ,KAAkC,EAClC,WAAmB;IAEnB,MAAM,KAAK,GACT,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8CAA8C;AAC9C,SAAS,cAAc,CAAC,IAAoB;IAC1C,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC;QAC9B,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE;QAChC,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,EAAE;QAC3C,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC;QAC5B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,KAAK;QAC1B,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,IAAI,GAAG;QACtD,oFAAoF;QACpF,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,IAAI,GAAG;QAChD,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;QACrC,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,IAAI,EAAE;QACnD,+EAA+E;QAC/E,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,GAAG;QACtC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,IAAI,GAAG;QAClD,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;QACrC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;KAChD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../../../src/core/module.ts"],"names":[],"mappings":"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"}
package/core/reaper.js CHANGED
@@ -15,29 +15,38 @@ export class PhysicsReaperModule extends Module {
15
15
  // with b2Joint_IsValid because a joint queued from a cascaded body entity
16
16
  // may already be gone.
17
17
  const joints = engine.pendingJointDestroys;
18
- for (const jointId of joints) {
19
- if (engine.box2d.b2Joint_IsValid(jointId)) {
20
- engine.box2d.b2DestroyJoint(jointId, true);
18
+ if (joints.length > 0) {
19
+ for (const jointId of joints) {
20
+ if (engine.box2d.b2Joint_IsValid(jointId)) {
21
+ engine.box2d.b2DestroyJoint(jointId, true);
22
+ }
21
23
  }
24
+ joints.length = 0;
22
25
  }
23
- joints.length = 0;
24
26
  const shapes = engine.pendingShapeDestroys;
25
- for (const shapeId of shapes) {
26
- engine.entityByShapeIndex.delete(shapeId.index1);
27
- engine.box2d.b2DestroyShape(shapeId, true);
27
+ if (shapes.length > 0) {
28
+ for (const shapeId of shapes) {
29
+ engine.entityByShapeIndex.delete(shapeId.index1);
30
+ engine.box2d.b2DestroyShape(shapeId, true);
31
+ }
32
+ shapes.length = 0;
28
33
  }
29
- shapes.length = 0;
30
34
  const chains = engine.pendingChainDestroys;
31
- for (const chainId of chains) {
32
- engine.box2d.b2DestroyChain(chainId);
35
+ if (chains.length > 0) {
36
+ for (const chainId of chains) {
37
+ engine.box2d.b2DestroyChain(chainId);
38
+ }
39
+ chains.length = 0;
33
40
  }
34
- chains.length = 0;
35
41
  const bodies = engine.pendingBodyDestroys;
36
- for (const bodyId of bodies) {
37
- engine.entityByBodyIndex.delete(bodyId.index1);
38
- engine.box2d.b2DestroyBody(bodyId);
42
+ if (bodies.length > 0) {
43
+ for (const bodyId of bodies) {
44
+ engine.entityByBodyIndex.delete(bodyId.index1);
45
+ engine.bodyEntities[bodyId.index1] = undefined;
46
+ engine.box2d.b2DestroyBody(bodyId);
47
+ }
48
+ bodies.length = 0;
39
49
  }
40
- bodies.length = 0;
41
50
  })
42
51
  .add(PhysicsSystemTag);
43
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reaper.js","sourceRoot":"","sources":["../../../../src/core/reaper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,OAAO,mBAAoB,SAAQ,MAAM;IACtC,IAAI;QACT,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAEhC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK;aACF,MAAM,CAAC,eAAe,CAAC;aACvB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;aAC5B,GAAG,CAAC,GAAG,EAAE;YACR,wEAAwE;YACxE,2EAA2E;YAC3E,0EAA0E;YAC1E,uBAAuB;YACvB,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;YAC3C,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;gBAC7B,IAAI,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC;YACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAElB,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;YAC3C,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;gBAC7B,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBACjD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;YACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAElB,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;YAC3C,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;gBAC7B,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YACvC,CAAC;YACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAElB,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC;YAC1C,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;gBAC5B,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC/C,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACrC,CAAC;YACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACpB,CAAC,CAAC;aACD,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC3B,CAAC;CACF"}
1
+ {"version":3,"file":"reaper.js","sourceRoot":"","sources":["../../../../src/core/reaper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,OAAO,mBAAoB,SAAQ,MAAM;IACtC,IAAI;QACT,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAEhC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK;aACF,MAAM,CAAC,eAAe,CAAC;aACvB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;aAC5B,GAAG,CAAC,GAAG,EAAE;YACR,wEAAwE;YACxE,2EAA2E;YAC3E,0EAA0E;YAC1E,uBAAuB;YACvB,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;oBAC7B,IAAI,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC1C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;oBAC7C,CAAC;gBACH,CAAC;gBACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YACpB,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;oBAC7B,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;oBACjD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC7C,CAAC;gBACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YACpB,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;oBAC7B,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;gBACvC,CAAC;gBACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YACpB,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC;YAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;oBAC5B,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBAC/C,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;oBAC/C,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBACrC,CAAC;gBACD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YACpB,CAAC;QACH,CAAC,CAAC;aACD,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC3B,CAAC;CACF"}
package/core/step.js CHANGED
@@ -14,7 +14,7 @@ export class PhysicsStepModule extends Module {
14
14
  engine.warn.warnOnce(0, "delta-mismatch", `frame delta ${delta}ms does not match fixedTimeStep ${engine.fixedTimeStep * 1000}ms; ` +
15
15
  `physics expects a fixed delta.`);
16
16
  }
17
- engine.box2d.b2World_Step(engine.worldId, engine.fixedTimeStep, engine.subSteps);
17
+ engine.box2d._b2x_World_Step(engine.world, engine.fixedTimeStep, engine.subSteps);
18
18
  })
19
19
  .add(PhysicsSystemTag);
20
20
  }
package/core/step.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"step.js","sourceRoot":"","sources":["../../../../src/core/step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,OAAO,iBAAkB,SAAQ,MAAM;IACpC,IAAI;QACT,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAEhC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK;aACF,MAAM,CAAC,aAAa,CAAC;aACrB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;aACzB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACnB,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,EAAE,CAAC;gBACzE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAClB,CAAC,EACD,gBAAgB,EAChB,eAAe,KAAK,mCAAmC,MAAM,CAAC,aAAa,GAAG,IAAI,MAAM;oBACtF,gCAAgC,CACnC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnF,CAAC,CAAC;aACD,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC3B,CAAC;CACF"}
1
+ {"version":3,"file":"step.js","sourceRoot":"","sources":["../../../../src/core/step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,OAAO,iBAAkB,SAAQ,MAAM;IACpC,IAAI;QACT,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAEhC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK;aACF,MAAM,CAAC,aAAa,CAAC;aACrB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;aACzB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACnB,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,EAAE,CAAC;gBACzE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAClB,CAAC,EACD,gBAAgB,EAChB,eAAe,KAAK,mCAAmC,MAAM,CAAC,aAAa,GAAG,IAAI,MAAM;oBACtF,gCAAgC,CACnC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpF,CAAC,CAAC;aACD,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC3B,CAAC;CACF"}
@@ -1,4 +1,7 @@
1
1
  import { Module } from "@vworlds/vecs";
2
+ /** Read contact/hit/sensor spans once per tick and publish fresh public event records in order.
3
+ * Native reads allocate no per-event wrappers; event components retain clear-in-place arrays
4
+ * and are marked modified once per receiving entity with new events. */
2
5
  export declare class EventPublishModule extends Module {
3
6
  init(): void;
4
7
  }
package/events/publish.js CHANGED
@@ -3,21 +3,34 @@ import { ContactEvents, HitEvents, SensorEvents } from "./components.js";
3
3
  import { getPhysicsEngine, PhysicsSystemTag } from "../adapter/engine.js";
4
4
  import { PhysicsCoreModule } from "../core/module.js";
5
5
  import { EventsModule } from "./module.js";
6
+ /** Read contact/hit/sensor spans once per tick and publish fresh public event records in order.
7
+ * Native reads allocate no per-event wrappers; event components retain clear-in-place arrays
8
+ * and are marked modified once per receiving entity with new events. */
6
9
  export class EventPublishModule extends Module {
7
10
  init() {
8
11
  const world = this.world;
9
12
  world.module(PhysicsCoreModule);
10
13
  world.module(EventsModule);
11
14
  const engine = getPhysicsEngine(world);
15
+ const offsets = eventOffsets(engine);
16
+ // Span headers and touched sets are allocated once and reused each step.
17
+ const spans = engine.fast.alloc(engine.fast.layout.contactSpans.size + engine.fast.layout.sensorSpans.size);
18
+ const sensorSpans = spans + engine.fast.layout.contactSpans.size;
19
+ const touchedContact = new Set();
20
+ const touchedSensor = new Set();
21
+ const touchedHit = new Set();
12
22
  world
13
23
  .system("PhysicsEvents")
14
24
  .phase(engine.phases.postStep)
15
25
  .run(() => {
16
- const touchedContact = new Set();
17
- const touchedSensor = new Set();
18
- const touchedHit = new Set();
19
- publishContactAndHitEvents(world, engine, touchedContact, touchedHit);
20
- publishSensorEvents(world, engine, touchedSensor);
26
+ clearSet(touchedContact);
27
+ clearSet(touchedSensor);
28
+ clearSet(touchedHit);
29
+ engine.box2d._b2x_World_GetContactSpans(engine.world, spans);
30
+ engine.box2d._b2x_World_GetSensorSpans(engine.world, sensorSpans);
31
+ engine.fast.sync();
32
+ publishContactAndHitEvents(world, engine, spans, offsets, touchedContact, touchedHit);
33
+ publishSensorEvents(world, engine, sensorSpans, offsets, touchedSensor);
21
34
  markModified(world, touchedContact, ContactEvents);
22
35
  markModified(world, touchedSensor, SensorEvents);
23
36
  markModified(world, touchedHit, HitEvents);
@@ -25,84 +38,104 @@ export class EventPublishModule extends Module {
25
38
  clearUntouchedSensorEvents(world, engine.prevTouchedSensor, touchedSensor);
26
39
  clearUntouchedHitEvents(world, engine.prevTouchedHit, touchedHit);
27
40
  // Store touched sets on engine for the next step's clear pass.
28
- engine.prevTouchedContact.clear();
29
- for (const eid of touchedContact) {
30
- engine.prevTouchedContact.add(eid);
31
- }
32
- engine.prevTouchedSensor.clear();
33
- for (const eid of touchedSensor) {
34
- engine.prevTouchedSensor.add(eid);
35
- }
36
- engine.prevTouchedHit.clear();
37
- for (const eid of touchedHit) {
38
- engine.prevTouchedHit.add(eid);
39
- }
41
+ copySet(engine.prevTouchedContact, touchedContact);
42
+ copySet(engine.prevTouchedSensor, touchedSensor);
43
+ copySet(engine.prevTouchedHit, touchedHit);
40
44
  })
41
45
  .add(PhysicsSystemTag);
42
46
  }
43
47
  }
44
- // Contact begin/end and hit events all come from a single
45
- // `b2World_GetContactEvents` poll, so fetch the event block once per step and
46
- // fan begin/end into `touchedContact` and hits into `touchedHit` (ARCHITECTURE.md ->
47
- // PhysicsEvents: "poll once per step").
48
- // Contact begin and hit events are Embind class handles and are released below; contact end and
49
- // sensor events are plain value objects, so there is nothing to delete for them.
50
- function publishContactAndHitEvents(world, engine, touchedContact, touchedHit) {
51
- const events = engine.box2d.b2World_GetContactEvents(engine.worldId);
52
- try {
53
- for (let i = 0; i < events.beginCount; i++) {
54
- const event = events.GetBeginEvent(i);
55
- try {
56
- emitContactEvent(world, engine, touchedContact, event, "begin");
57
- }
58
- finally {
59
- deleteIfPresent(event);
60
- }
61
- }
62
- for (let i = 0; i < events.endCount; i++) {
63
- const event = events.GetEndEvent(i);
64
- emitContactEvent(world, engine, touchedContact, event, "end");
65
- }
66
- for (let i = 0; i < events.hitCount; i++) {
67
- const event = events.GetHitEvent(i);
68
- try {
69
- emitHitEvent(world, engine, touchedHit, event);
70
- }
71
- finally {
72
- event.delete();
73
- }
74
- }
48
+ function eventOffsets(engine) {
49
+ const L = engine.fast.layout;
50
+ const pair = (s, a, b) => ({
51
+ stride: s.size >> 2,
52
+ a: a >> 2,
53
+ b: b >> 2,
54
+ });
55
+ const words = (o) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, v >> 2]));
56
+ return {
57
+ contact: words({
58
+ beginPtr: L.contactSpans.beginPtr,
59
+ beginCount: L.contactSpans.beginCount,
60
+ endPtr: L.contactSpans.endPtr,
61
+ endCount: L.contactSpans.endCount,
62
+ hitPtr: L.contactSpans.hitPtr,
63
+ hitCount: L.contactSpans.hitCount,
64
+ }),
65
+ sensor: words({
66
+ beginPtr: L.sensorSpans.beginPtr,
67
+ beginCount: L.sensorSpans.beginCount,
68
+ endPtr: L.sensorSpans.endPtr,
69
+ endCount: L.sensorSpans.endCount,
70
+ }),
71
+ begin: pair(L.contactBegin, L.contactBegin.shapeA, L.contactBegin.shapeB),
72
+ end: pair(L.contactEnd, L.contactEnd.shapeA, L.contactEnd.shapeB),
73
+ hit: {
74
+ ...pair(L.contactHit, L.contactHit.shapeA, L.contactHit.shapeB),
75
+ pointX: L.contactHit.pointX >> 2,
76
+ pointY: L.contactHit.pointY >> 2,
77
+ normalX: L.contactHit.normalX >> 2,
78
+ normalY: L.contactHit.normalY >> 2,
79
+ approachSpeed: L.contactHit.approachSpeed >> 2,
80
+ },
81
+ sensorBegin: pair(L.sensorBegin, L.sensorBegin.sensorShape, L.sensorBegin.visitorShape),
82
+ sensorEnd: pair(L.sensorEnd, L.sensorEnd.sensorShape, L.sensorEnd.visitorShape),
83
+ };
84
+ }
85
+ // Poll once per step, reading Box2D's own arrays in begin/end/hit order. Spans are valid
86
+ // until the next step or world mutation; the loop does not call allocating Box2D APIs.
87
+ function publishContactAndHitEvents(world, engine, spans, offsets, touchedContact, touchedHit) {
88
+ const fast = engine.fast;
89
+ const i32 = fast.i32;
90
+ const u32 = fast.u32;
91
+ const f32 = fast.f32;
92
+ const header = spans >> 2;
93
+ const c = offsets.contact;
94
+ const begin = offsets.begin;
95
+ let at = u32[header + c.beginPtr] >> 2;
96
+ for (let i = 0, n = i32[header + c.beginCount]; i < n; i++, at += begin.stride) {
97
+ emitContactEvent(world, engine, touchedContact, i32[at + begin.a], i32[at + begin.b], "begin");
98
+ }
99
+ const end = offsets.end;
100
+ at = u32[header + c.endPtr] >> 2;
101
+ for (let i = 0, n = i32[header + c.endCount]; i < n; i++, at += end.stride) {
102
+ emitContactEvent(world, engine, touchedContact, i32[at + end.a], i32[at + end.b], "end");
75
103
  }
76
- finally {
77
- events.delete();
104
+ const hit = offsets.hit;
105
+ at = u32[header + c.hitPtr] >> 2;
106
+ for (let i = 0, n = i32[header + c.hitCount]; i < n; i++, at += hit.stride) {
107
+ emitHitEvent(world, engine, touchedHit, i32, f32, at, hit);
78
108
  }
79
109
  }
80
- function publishSensorEvents(world, engine, touched) {
81
- const events = engine.box2d.b2World_GetSensorEvents(engine.worldId);
82
- try {
83
- for (let i = 0; i < events.beginCount; i++) {
84
- emitSensorEvent(world, engine, touched, events.GetBeginEvent(i), "begin");
85
- }
86
- for (let i = 0; i < events.endCount; i++) {
87
- emitSensorEvent(world, engine, touched, events.GetEndEvent(i), "end");
88
- }
110
+ function publishSensorEvents(world, engine, spans, offsets, touched) {
111
+ const fast = engine.fast;
112
+ const i32 = fast.i32;
113
+ const u32 = fast.u32;
114
+ const header = spans >> 2;
115
+ const s = offsets.sensor;
116
+ const begin = offsets.sensorBegin;
117
+ let at = u32[header + s.beginPtr] >> 2;
118
+ for (let i = 0, n = i32[header + s.beginCount]; i < n; i++, at += begin.stride) {
119
+ emitSensorEvent(world, engine, touched, i32[at + begin.a], i32[at + begin.b], "begin");
89
120
  }
90
- finally {
91
- events.delete();
121
+ const end = offsets.sensorEnd;
122
+ at = u32[header + s.endPtr] >> 2;
123
+ for (let i = 0, n = i32[header + s.endCount]; i < n; i++, at += end.stride) {
124
+ emitSensorEvent(world, engine, touched, i32[at + end.a], i32[at + end.b], "end");
92
125
  }
93
126
  }
94
- function emitContactEvent(world, engine, touched, event, kind) {
95
- const entityA = getLiveEntity(world, engine.entityByShapeIndex.get(event.shapeIdA.index1));
96
- const entityB = getLiveEntity(world, engine.entityByShapeIndex.get(event.shapeIdB.index1));
127
+ function emitContactEvent(world, engine, touched, shapeA, shapeB, kind) {
128
+ const entityA = getLiveEntity(world, engine.entityByShapeIndex.get(shapeA));
129
+ const entityB = getLiveEntity(world, engine.entityByShapeIndex.get(shapeB));
97
130
  if (entityA === undefined || entityB === undefined) {
98
131
  return;
99
132
  }
100
133
  emitContactToEntity(touched, entityA, entityB, kind);
101
134
  emitContactToEntity(touched, entityB, entityA, kind);
102
135
  }
103
- function emitSensorEvent(world, engine, touched, event, kind) {
104
- const sensor = getLiveEntity(world, engine.entityByShapeIndex.get(event.sensorShapeId.index1));
105
- const visitor = getLiveEntity(world, engine.entityByShapeIndex.get(event.visitorShapeId.index1));
136
+ function emitSensorEvent(world, engine, touched, sensorShape, visitorShape, kind) {
137
+ const sensor = getLiveEntity(world, engine.entityByShapeIndex.get(sensorShape));
138
+ const visitor = getLiveEntity(world, engine.entityByShapeIndex.get(visitorShape));
106
139
  if (sensor === undefined || visitor === undefined) {
107
140
  return;
108
141
  }
@@ -122,14 +155,29 @@ function emitSensorEvent(world, engine, touched, event, kind) {
122
155
  events.end.push({ other: visitor });
123
156
  }
124
157
  }
125
- function emitHitEvent(world, engine, touched, event) {
126
- const entityA = getLiveEntity(world, engine.entityByShapeIndex.get(event.shapeIdA.index1));
127
- const entityB = getLiveEntity(world, engine.entityByShapeIndex.get(event.shapeIdB.index1));
158
+ function emitHitEvent(world, engine, touched, i32, f32, at, hit) {
159
+ const entityA = getLiveEntity(world, engine.entityByShapeIndex.get(i32[at + hit.a]));
160
+ const entityB = getLiveEntity(world, engine.entityByShapeIndex.get(i32[at + hit.b]));
128
161
  if (entityA === undefined || entityB === undefined) {
129
162
  return;
130
163
  }
131
- emitHitToEntity(touched, entityA, entityB, event);
132
- emitHitToEntity(touched, entityB, entityA, event);
164
+ emitHitToEntity(touched, entityA, entityB, f32, at, hit);
165
+ emitHitToEntity(touched, entityB, entityA, f32, at, hit);
166
+ }
167
+ /** Avoid replacing V8's Set backing table when there is nothing to clear. */
168
+ function clearSet(set) {
169
+ if (set.size > 0) {
170
+ set.clear();
171
+ }
172
+ }
173
+ function copySet(target, source) {
174
+ clearSet(target);
175
+ if (source.size === 0) {
176
+ return;
177
+ }
178
+ for (const eid of source) {
179
+ target.add(eid);
180
+ }
133
181
  }
134
182
  function emitContactToEntity(touched, entity, other, kind) {
135
183
  const events = entity.get(ContactEvents);
@@ -148,7 +196,7 @@ function emitContactToEntity(touched, entity, other, kind) {
148
196
  events.end.push({ other });
149
197
  }
150
198
  }
151
- function emitHitToEntity(touched, entity, other, event) {
199
+ function emitHitToEntity(touched, entity, other, f32, at, hit) {
152
200
  const events = entity.get(HitEvents);
153
201
  if (events === undefined) {
154
202
  return;
@@ -159,11 +207,11 @@ function emitHitToEntity(touched, entity, other, event) {
159
207
  }
160
208
  events.hits.push({
161
209
  other,
162
- x: event.point.x,
163
- y: event.point.y,
164
- normalX: event.normal.x,
165
- normalY: event.normal.y,
166
- speed: event.approachSpeed,
210
+ x: f32[at + hit.pointX],
211
+ y: f32[at + hit.pointY],
212
+ normalX: f32[at + hit.normalX],
213
+ normalY: f32[at + hit.normalY],
214
+ speed: f32[at + hit.approachSpeed],
167
215
  });
168
216
  }
169
217
  function getLiveEntity(world, entityId) {
@@ -177,11 +225,17 @@ function getLiveEntity(world, entityId) {
177
225
  return entity;
178
226
  }
179
227
  function markModified(world, touched, component) {
228
+ if (touched.size === 0) {
229
+ return;
230
+ }
180
231
  for (const entityId of touched) {
181
232
  world.getEntity(entityId)?.modified(component);
182
233
  }
183
234
  }
184
235
  function clearUntouchedContactEvents(world, previouslyTouched, touched) {
236
+ if (previouslyTouched.size === 0) {
237
+ return;
238
+ }
185
239
  for (const entityId of previouslyTouched) {
186
240
  if (touched.has(entityId)) {
187
241
  continue;
@@ -194,6 +248,9 @@ function clearUntouchedContactEvents(world, previouslyTouched, touched) {
194
248
  }
195
249
  }
196
250
  function clearUntouchedSensorEvents(world, previouslyTouched, touched) {
251
+ if (previouslyTouched.size === 0) {
252
+ return;
253
+ }
197
254
  for (const entityId of previouslyTouched) {
198
255
  if (touched.has(entityId)) {
199
256
  continue;
@@ -206,6 +263,9 @@ function clearUntouchedSensorEvents(world, previouslyTouched, touched) {
206
263
  }
207
264
  }
208
265
  function clearUntouchedHitEvents(world, previouslyTouched, touched) {
266
+ if (previouslyTouched.size === 0) {
267
+ return;
268
+ }
209
269
  for (const entityId of previouslyTouched) {
210
270
  if (touched.has(entityId)) {
211
271
  continue;
@@ -216,9 +276,6 @@ function clearUntouchedHitEvents(world, previouslyTouched, touched) {
216
276
  }
217
277
  }
218
278
  }
219
- function deleteIfPresent(value) {
220
- value.delete?.();
221
- }
222
279
  function isDestroyed(entity) {
223
280
  return entity.destroyed;
224
281
  }