@vworlds/box2d3-wasm 0.0.0-stage → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Alex Birch & Erik Sombroek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,302 @@
1
- # Temporary Holding Version
1
+ # @vworlds/box2d3-wasm
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Box2D](https://github.com/erincatto/box2d) v3 compiled to WebAssembly for browsers and Node.js,
4
+ with two ways to call it:
5
+
6
+ - a flat, C-style [embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html)
7
+ API (`b2CreateWorld`, `b2World_Step`, `b2Body_GetPosition`, ...), and
8
+ - a zero-allocation **fast path** (`b2x_*` raw exports and a small helper, `@vworlds/box2d3-wasm/fastpath`)
9
+ that skips embind's marshalling for the calls a game makes every frame.
10
+
11
+ Both run the same Box2D code. Results are bit-identical to
12
+ [`box2d3-wasm` 5.2.0](https://www.npmjs.com/package/box2d3-wasm) in every scene of
13
+ `npm run test:stock`.
14
+
15
+ ## Origin
16
+
17
+ This is a fork of [Birch-san/box2d3-wasm](https://github.com/Birch-san/box2d3-wasm) by Alex Birch and
18
+ Erik Sombroek, which wraps Erin Catto's [Box2D](https://github.com/erincatto/box2d). The upstream
19
+ project is no longer maintained; this one is maintained by vworlds and its consumer is the
20
+ vecs ECS library.
21
+
22
+ What differs from upstream 5.2.0:
23
+
24
+ - **Flat C API only.** The object-oriented classes (`World`, `Body`, `Shape`, the joint classes, ...)
25
+ are gone, and so is the box2cpp submodule.
26
+ - **No threading.** No `TaskSystem`, no `b2CreateThreadedWorld`, no pthread build, no
27
+ `SharedArrayBuffer` requirement. Memory is not shared.
28
+ - **No debug draw.** No `b2DebugDraw`, `b2World_Draw` or the `./utils/*` export.
29
+ - **The fast path** (below), plus a small patch to Box2D (`patches/box2d/`) that adds a runtime switch
30
+ for its profile timers.
31
+
32
+ ## Install
33
+
34
+ ```sh
35
+ npm install @vworlds/box2d3-wasm
36
+ ```
37
+
38
+ The package is not published yet. Until it is, build it from source (see [Build](#build)), run
39
+ `npm pack` and install the tarball. It is ESM only.
40
+
41
+ ## Usage
42
+
43
+ The flat API mirrors Box2D's C API; see Box2D's documentation for what each function does.
44
+
45
+ ```js
46
+ import Box2DFactory from "@vworlds/box2d3-wasm";
47
+
48
+ const b2 = await Box2DFactory();
49
+
50
+ const worldDef = b2.b2DefaultWorldDef();
51
+ worldDef.gravity.y = -10;
52
+ const worldId = b2.b2CreateWorld(worldDef);
53
+ worldDef.delete();
54
+
55
+ const bodyDef = b2.b2DefaultBodyDef();
56
+ bodyDef.type = b2.b2BodyType.b2_dynamicBody;
57
+ bodyDef.position.y = 10;
58
+ const bodyId = b2.b2CreateBody(worldId, bodyDef);
59
+ bodyDef.delete();
60
+
61
+ const shapeDef = b2.b2DefaultShapeDef();
62
+ shapeDef.density = 1;
63
+ const box = b2.b2MakeBox(0.5, 0.5);
64
+ b2.b2CreatePolygonShape(bodyId, shapeDef, box);
65
+ shapeDef.delete();
66
+
67
+ for (let i = 0; i < 60; i++) b2.b2World_Step(worldId, 1 / 60, 4);
68
+
69
+ const p = b2.b2Body_GetPosition(bodyId);
70
+ console.log(p.x, p.y); // 0 4.979168891906738
71
+ p.delete();
72
+
73
+ b2.b2DestroyWorld(worldId);
74
+ ```
75
+
76
+ Objects that embind returns or that you construct (`b2WorldDef`, `b2Vec2`, ...) are handles into
77
+ wasm memory; call `.delete()` when you are done with them.
78
+
79
+ ### Fast path
80
+
81
+ Ids cross as plain integers, scalars cross directly and structs are written into wasm memory you
82
+ own. After each step one call exports the pose of every body that moved.
83
+
84
+ ```js
85
+ import Box2DFactory from "@vworlds/box2d3-wasm";
86
+ import { createFastPath } from "@vworlds/box2d3-wasm/fastpath";
87
+
88
+ const b2 = await Box2DFactory();
89
+ const fp = createFastPath(b2);
90
+
91
+ // Worlds, bodies and shapes are created through the flat embind API.
92
+ const worldDef = b2.b2DefaultWorldDef();
93
+ worldDef.gravity.y = -10;
94
+ const worldId = b2.b2CreateWorld(worldDef);
95
+ worldDef.delete();
96
+ const bodyDef = b2.b2DefaultBodyDef();
97
+ bodyDef.type = b2.b2BodyType.b2_dynamicBody;
98
+ const bodyId = b2.b2CreateBody(worldId, bodyDef);
99
+ bodyDef.delete();
100
+ const shapeDef = b2.b2DefaultShapeDef();
101
+ shapeDef.density = 1;
102
+ b2.b2CreatePolygonShape(bodyId, shapeDef, b2.b2MakeBox(0.5, 0.5));
103
+ shapeDef.delete();
104
+
105
+ // Packed ids for the raw calls.
106
+ const world = fp.world(worldId);
107
+ const index1 = bodyId.index1;
108
+ const rev = fp.rev(bodyId);
109
+
110
+ const moves = fp.createMoveExporter(256);
111
+ const M = fp.layout.move;
112
+ for (let i = 0; i < 60; i++) {
113
+ fp.module._b2x_World_Step(world, 1 / 60, 4);
114
+ const count = moves.exportMoves(world, 0); // also refreshes the typed-array views
115
+ for (let n = 0, base = moves.base; n < count; n++, base += moves.stride) {
116
+ const y = fp.f32[base + (M.y >> 2)];
117
+ if (i === 59) console.log("y", y); // y -5.020830154418945
118
+ }
119
+ }
120
+
121
+ // A struct result goes into memory you own.
122
+ const out = fp.alloc(64);
123
+ fp.module._b2x_Body_GetPose(index1, rev, out);
124
+ console.log(fp.f32[(out + fp.layout.pose.y) >> 2]);
125
+ fp.free(out);
126
+ ```
127
+
128
+ The full reference (exports, memory-growth rule, layouts, events, profile timers) is in
129
+ [docs/fastpath.md](docs/fastpath.md).
130
+
131
+ ## Flavours
132
+
133
+ `dist/` holds two builds of the same code:
134
+
135
+ | flavour | built with | used when |
136
+ |---|---|---|
137
+ | `deluxe` | WebAssembly SIMD (`-msimd128 -msse2`) | the runtime validates a SIMD module |
138
+ | `compat` | `BOX2D_DISABLE_SIMD=ON` | otherwise |
139
+
140
+ `dist/entry.mjs` (the `"."` export) checks `WebAssembly.validate` on a tiny SIMD module and
141
+ imports one of them. In the scenes of `npm run test:stock` both flavours produce the same hashes.
142
+ Both carry the fast path, and `@vworlds/box2d3-wasm/fastpath` works with either.
143
+
144
+ ## Build
145
+
146
+ Requirements: emsdk **4.0.18**, TypeScript 5 as `tsc` (`emcc --emit-tsd` breaks on TypeScript 6),
147
+ cmake, make, git, gawk, Node.js (and Python 3 for `npm run gen`, `gen:check` and `check:bindings`). The output depends on the emsdk version; do not change it together
148
+ with anything else.
149
+
150
+ ```sh
151
+ git clone --recurse-submodules https://github.com/vworlds/box2d3-wasm
152
+ cd box2d3-wasm
153
+ ```
154
+
155
+ **With Docker** (nothing else to install):
156
+
157
+ ```sh
158
+ npm run build:docker # builds scripts/Dockerfile into an image on first use, then builds in it
159
+ ```
160
+
161
+ **With a local emsdk** (activated in the shell, `source emsdk_env.sh`):
162
+
163
+ ```sh
164
+ npm run build
165
+ ```
166
+
167
+ Either writes `dist/`. `FLAVOURS="deluxe"` limits the build to one flavour; `TARGET_TYPE=Debug` or
168
+ `RelWithDebInfo` builds those instead of Release. Intermediate output goes to `build/`.
169
+
170
+ The build applies `patches/box2d/*.patch` to the `box2d` submodule, so the submodule shows as
171
+ modified afterwards; that is expected. It does not depend on where the repository is checked out:
172
+ the same commit built at two different paths gives byte-identical files in `dist/`.
173
+
174
+ ## Test
175
+
176
+ ```sh
177
+ npm install # once; fetches box2d3-wasm 5.2.0 for test:stock
178
+ npm test # fast-path differential test + bindings test (both flavours) + integration test, on dist/
179
+ npm run test:stock # step-by-step hashes equal to box2d3-wasm 5.2.0, embind and fast path
180
+ npm run gen:check # the generated fast-path code is current
181
+ npm run check:bindings # every C function is bound or excepted (see API coverage)
182
+ npm run test:release # the release-version script (scripts/release-version.test.mjs)
183
+ npm run check:pack # npm pack would publish only the expected files (needs dist/)
184
+ ```
185
+
186
+ `npm run gen` regenerates `src/fastpath_body.gen.cpp` and `src/fastpath_body.gen.d.ts` from
187
+ `box2d/include/box2d/box2d.h`.
188
+
189
+ ## API coverage
190
+
191
+ Rule: **every function of Box2D's public C API (the `B2_API` declarations in `box2d.h`, `types.h`,
192
+ `collision.h`, `math_functions.h`, `base.h`) is callable through the flat embind API** under its C
193
+ name, except the ones listed with a reason in
194
+ [scripts/bindings-exceptions.txt](scripts/bindings-exceptions.txt). The fast path is an optional
195
+ shortcut to some of them and does not count towards the rule.
196
+
197
+ Today that is 397 of 428 declared functions bound; the 31 exceptions are:
198
+
199
+ - debug draw (`b2World_Draw`, `b2DefaultDebugDraw`), removed on purpose;
200
+ - allocator and assert hooks (`b2SetAllocator`, `b2SetAssertFcn`, `b2InternalAssertFcn`), which take
201
+ C function pointers;
202
+ - `b2GetMillisecondsAndReset` (in/out `uint64_t*`; use `b2GetTicks` and `b2GetMilliseconds`),
203
+ `b2ShapeDistance` (needs the `b2DistanceInput`/`b2DistanceOutput`/`b2Simplex` value classes);
204
+ - the 22 `b2DynamicTree_*` functions (a standalone broadphase tree with raw pointers and callbacks);
205
+ - `b2World_DumpMemoryStats` (writes a file; the build has no file system, so the call throws);
206
+ - `b2Chain_GetSurfaceMaterialCount` (declared in `box2d.h` but not defined in the pinned Box2D).
207
+
208
+ Conventions for the awkward ones:
209
+
210
+ - **User data** (`b2World_/b2Body_/b2Shape_/b2Joint_` `SetUserData`/`GetUserData`) is an unsigned
211
+ 32-bit **integer** stored in the `void*`, not an object. It is the same storage as
212
+ `_b2x_Body_SetUserData` / `_b2x_Shape_SetUserData`, so both APIs see the same value; keep your own
213
+ table if you need to attach objects.
214
+ - **Names**: `b2Body_SetName(id, string)` / `b2Body_GetName(id)` (Box2D keeps at most 31 characters).
215
+ - **Out-parameters** come back as value objects: `b2Joint_GetConstraintTuning(id)` returns
216
+ `{hertz, dampingRatio}`, `b2DistanceJoint_GetSpringForceRange(id)` returns
217
+ `{lowerForce, upperForce}`.
218
+ - **`uint64_t`** values (`b2Shape_SetUserMaterial`, `b2GetTicks`) are `BigInt`.
219
+ - `b2ContactData` gained a `contactId` property, which `b2Contact_GetData` and `b2Contact_IsValid`
220
+ take.
221
+ - Callbacks: bound are the world queries `b2World_OverlapAABB`, `b2World_OverlapShape`,
222
+ `b2World_CastRay`, `b2World_CastShape`, `b2World_CollideMover` (a JS function per call),
223
+ `b2World_SetCustomFilterCallback`, `b2World_SetPreSolveCallback` (free the returned handle with
224
+ `b2World_DeleteCallback`) and `b2World_SetFrictionCallback` / `b2World_SetRestitutionCallback`.
225
+ Not bound: the task system and the allocator/assert hooks above, and debug draw.
226
+
227
+ `npm run check:bindings` enforces the rule. It parses the `B2_API` declarations (comments and
228
+ preprocessor lines stripped, multi-line declarations handled) and the `function("...")` names in
229
+ `src/*.cpp`, prints the totals per header, and exits 1 when a declared function is neither bound nor
230
+ excepted, when an exception or a Box2D-looking binding names a function the headers no longer
231
+ declare (so a Box2D update that removes or renames one is noticed), or when an exception is also
232
+ bound. Lines starting with `+` in the exceptions file list package helpers that have no C twin
233
+ (`b2Body_GetPointer`, ...). Run it after changing `glue.cpp` or moving the Box2D submodule.
234
+
235
+ ## Demo
236
+
237
+ `demo/` is a small Phaser 4 site that runs this package on the main thread (flat embind API to create
238
+ bodies, the `b2x_` fast path to step and read poses): a pile, a joints scene, sensors and hit events, and
239
+ 3500 always-moving bodies. It loads whichever flavour `entry.mjs` picks and shows it in the stats readout.
240
+ It imports the package from this repository's `dist/`, so build the package first.
241
+
242
+ ```sh
243
+ npm run build # the package, into dist/
244
+ npm --prefix demo ci # once
245
+ npm run demo:dev # http://localhost:5173
246
+ npm run demo:build # static site in demo/dist-site/
247
+ ```
248
+
249
+ The demo is not part of the published package (it has its own `package.json`). CI builds it and deploys
250
+ `demo/dist-site/` to GitHub Pages from `master`. See [demo/README.md](demo/README.md).
251
+
252
+ ## CI
253
+
254
+ `.github/workflows/ci.yml` runs on pull requests to `dev` and `master` and on pushes to `dev`. It
255
+ builds both flavours once, then runs `gen:check`, `check:bindings`, `test:release`, `npm test`, `npm run test:stock`,
256
+ a check of the `npm pack` content, the demo build and the vecs test suites (physics, phaser-server,
257
+ phaser) against the vecs commit pinned in `ci/vecs-ref`. The vecs repository is private: the suites
258
+ need the `VECS_READ_SECRET` secret and are skipped, with a warning, when it is missing.
259
+ `.github/workflows/vecs-nightly.yml` runs the same suites against vecs's `dev` every night and opens
260
+ a pull request that moves the pin when they pass. To run them yourself:
261
+
262
+ ```sh
263
+ npm pack
264
+ scripts/run-vecs-suites.sh <vecs-checkout> vworlds-box2d3-wasm-*.tgz
265
+ ```
266
+
267
+ ## Releasing
268
+
269
+ Merge `dev` into `master`. `.github/workflows/publish.yml` builds, tests, and publishes
270
+ `@vworlds/box2d3-wasm` to npm with provenance. If the version in `package.json` is not on npm yet it
271
+ is released as is, otherwise the patch version is bumped (`scripts/release-version.mjs`, `--dry-run`
272
+ shows the decision), committed to `master` with `[skip ci]` and tagged `v<version>`. For a minor or
273
+ major release, set the version in `package.json` on `dev` first. `pages.yml` deploys the demo to
274
+ GitHub Pages on the same push. [docs/releasing.md](docs/releasing.md) lists the one-time setup.
275
+
276
+ ## Repository layout
277
+
278
+ | path | contents |
279
+ |---|---|
280
+ | `src/` | `glue.cpp` (embind bindings), `fastpath.cpp` and `fastpath_body.gen.cpp` (`b2x_` exports), `fastpath.mjs` / `.d.ts` (helper), `entry.mjs` / `.d.ts` (flavour selection), `Box2D.template.d.ts` |
281
+ | `scripts/` | `build.sh`, `build-in-docker.sh`, `Dockerfile`, `gen_fastpath.py`, `check_bindings.py`, `bindings-exceptions.txt`, `modify_emscripten_dts.awk`, `release-version.mjs` (+ test), `check-pack.mjs`, `run-vecs-suites.sh` |
282
+ | `.github/`, `ci/` | workflows, the build action, `vecs-ref` (the pinned vecs commit) |
283
+ | `patches/box2d/` | the patch applied to the Box2D submodule |
284
+ | `box2d/` | Box2D (git submodule) |
285
+ | `tests/` | `fastpath-differential.mjs`, `bindings.mjs`, `integration.mjs`, `stock-differential/` |
286
+ | `demo/` | the Phaser 4 demo site (own `package.json`, not published) |
287
+ | `docs/` | [fastpath.md](docs/fastpath.md), [vecs-migration.md](docs/vecs-migration.md), [box2d-update.md](docs/box2d-update.md) |
288
+ | `dist/`, `build/` | build output (not committed) |
289
+
290
+ ## Documentation
291
+
292
+ - [docs/fastpath.md](docs/fastpath.md): the fast-path API.
293
+ - [docs/vecs-migration.md](docs/vecs-migration.md): moving vecs-physics from embind to the fast path.
294
+ - [docs/box2d-update.md](docs/box2d-update.md): what to do when moving the Box2D submodule.
295
+ - [docs/releasing.md](docs/releasing.md): releases, CI secrets and the one-time repository setup.
296
+ - [patches/README.md](patches/README.md): the Box2D patch.
297
+ - [tests/stock-differential/README.md](tests/stock-differential/README.md): what the stock comparison proves.
298
+
299
+ ## Licence
300
+
301
+ [MIT](LICENSE). Box2D is MIT licensed too (Erin Catto), and parts of the build output come from
302
+ Emscripten and wasm-feature-detect; see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
@@ -0,0 +1,65 @@
1
+ # Third-party notices
2
+
3
+ This package contains or is built from the following third-party works. The package's own licence
4
+ is in [LICENSE](LICENSE).
5
+
6
+ ## Box2D
7
+
8
+ The WebAssembly modules in `dist/` are compiled from [Box2D](https://github.com/erincatto/box2d)
9
+ v3 (the `box2d` submodule of the source repository), with one small patch (see `patches/`).
10
+
11
+ ```
12
+ MIT License
13
+
14
+ Copyright (c) 2022 Erin Catto
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
33
+ ```
34
+
35
+ ## box2d3-wasm
36
+
37
+ This package began as a fork of [Birch-san/box2d3-wasm](https://github.com/Birch-san/box2d3-wasm)
38
+ by Alex Birch and Erik Sombroek, which is MIT licensed; its copyright notice is kept in
39
+ [LICENSE](LICENSE).
40
+
41
+ ## wasm-feature-detect
42
+
43
+ `dist/entry.mjs` contains the SIMD validation expression (a small WebAssembly module as a byte
44
+ array) from [wasm-feature-detect](https://github.com/GoogleChromeLabs/wasm-feature-detect), which
45
+ is licensed under the Apache License, Version 2.0. The notice that accompanies it in the source:
46
+
47
+ ```
48
+ Copyright 2019 Google Inc. All Rights Reserved.
49
+ Licensed under the Apache License, Version 2.0 (the "License");
50
+ you may not use this file except in compliance with the License.
51
+ You may obtain a copy of the License at
52
+ http://www.apache.org/licenses/LICENSE-2.0
53
+ Unless required by applicable law or agreed to in writing, software
54
+ distributed under the License is distributed on an "AS IS" BASIS,
55
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
56
+ See the License for the specific language governing permissions and
57
+ limitations under the License.
58
+ ```
59
+
60
+ ## Emscripten output
61
+
62
+ The `.mjs` and `.wasm` files in `dist/` are produced by [Emscripten](https://emscripten.org/) and
63
+ include parts of its runtime, its C and C++ standard libraries and embind. Those parts are
64
+ distributed under the licences of the Emscripten project and the libraries it ships (see
65
+ https://github.com/emscripten-core/emscripten/blob/main/LICENSE).