box2d-wasm

repository·master·Indexed 43 days ago

https://github.com/birch-san/box2d-wasm

A high-performance WebAssembly port of Box2D v2.4 with TypeScript support. It provides a physics engine for web and Node.js applications, featuring detailed TypeScript declarations generated from WebIDL bindings and support for both CommonJS and ES modules.

Tokens
13.2K
Snippets
45
Records
62
Agent score
54%

What's inside box2d-wasm

  1. Overview of box2d-wasm aims and features

    master

    box2d-wasm is a WebAssembly port of the Box2D physics engine designed to improve upon the original box2d.js. Key features and design goals include:

    • Modern Box2D Support: Supports Box2D v2.4.0+ and its CMake build system.
    • TypeScript First: Provides TypeScript declarations for the WebIDL interface, making TypeScript declarations the primary source of documentation for consumers.
    • Modern Tooling: Focuses on a smooth development experience, aiming to eliminate manual copying of build artifacts and providing demonstrations of how to consume the library via TypeScript.
    • Optimized Builds: Prioritizes smaller build sizes over features like source maps that can conflict with link-time optimizations.
  2. Use the integration-test package to validate box2d-wasm changes

    master
    The integration-test package is a Svelte web application bundled with Rollup designed to validate changes made to box2d-wasm. It demonstrates a realistic integration of box2d-wasm into a modern web environment using npm, TypeScript, ES imports, and a UI framework.
  3. Understand Box2D memory re-use and heap behavior

    master

    Understanding how Box2D manages its internal memory helps prevent confusion regarding perceived memory leaks:

    • Heap Growth: Using new calls malloc(), which may grow the WASM heap. Methods like b2World#CreateBody() also use internal allocators that may grow the heap.
    • Heap Shrinking: Calling destroy() calls free(), which shrinks the heap.
    • Memory Re-use: When you call b2World#DestroyBody(), Box2D's small block allocator designates that memory as reusable but does not shrink the WASM heap. Subsequent calls to b2World#CreateBody() may reuse this existing memory instead of growing the heap further.

    Best Practice: Because Box2D re-uses memory blocks, avoid assigning custom properties directly to objects received via factory functions (like b2World#CreateBody()). Use the official user data management patterns instead.

  4. Compare box2d-wasm with box2d.js

    master

    If you are deciding between box2d-wasm and the original box2d.js, consider these key differences:

    • Box2D Version: box2d-wasm supports Box2D v2.4.1, whereas box2d.js is limited to v2.3.1.
    • TypeScript Support: box2d-wasm includes TypeScript declarations; box2d.js does not.
    • Module Formats: box2d-wasm supports both UMD and ES modules. box2d.js only supports UMD.
    • Runtime Formats: box2d-wasm uses WebAssembly (WASM) but has dropped support for asm.js to favor modern standards.
    • API Surface: While the WebIDL bindings are similar, box2d-wasm adds updates for Box2D v2.4.1 and exposes additional array properties and collision functions.
    • Size: box2d-wasm is slightly larger (~537 kB uncompressed) due to the newer features and TypeScript support.
  5. Understand TARGET_TYPE build modes

    master

    The TARGET_TYPE environment variable controls the compilation profile used for C++ to LLVM IR and LLVM IR to WASM stages:

    • Debug: Optimized for fast compilation to allow for rapid local development iteration.
    • Release: Optimized for high performance; results in longer compile times but better runtime performance.
    • RelWithDebInfo: A Release build that includes debug source-maps and has Closure optimizations disabled.
  6. How to safely manage metadata for Box2D objects

    master

    In box2d-wasm, you cannot safely assign custom properties directly to the JS objects returned by the API (e.g., b2Body). Because Emscripten reuses JS objects for recycled WASM memory, properties assigned to a destroyed object will persist and appear on the next object that occupies that same memory address, leading to data corruption and logic errors.

    To manage metadata safely, you must store it externally to the Box2D JS objects using one of the following patterns:

    Use getPointer(object) to retrieve the memory address and use that as a key in a standard JavaScript object or Map. This is the most robust method.

    2. Using the JS Object as a Key

    Since Box2D object references are reused, you can use the object itself as a key in a metadata object. This is simpler but relies on the same object-reuse behavior.

    3. Using Prototypical Inheritance (Hydration Pattern)

    If you want the metadata to appear as if it lives on the Box2D object, you can create a wrapper using Object.setPrototypeOf. This allows you to access both your custom metadata and the original Box2D methods on the same reference.

    Important: Regardless of the method chosen, you must manually delete the metadata when you call world.DestroyBody(object) to prevent memory leaks and stale data.

    /**
     * Recommended: Using pointers as keys
     */
    const metadata = {}
    const body = world.CreateBody(bd);
    
    // Store metadata using the pointer
    metadata[getPointer(body)] = {
      favouriteDessert: 'pie'
    };
    
    // Cleanup
    world.DestroyBody(body);
    delete metadata[getPointer(body)];
    
    /**
     * Alternative: Prototypical inheritance (Hydration)
     */
    const hydratedInstances = {}
    
    // Create a 'hydrated' version where metadata is on the prototype
    hydratedInstances[body] = Object.setPrototypeOf({
      favouriteDessert: 'pie'
    }, body);
    
    // Access both metadata and Box2D methods
    console.log(hydratedInstances[body].favouriteDessert);
    console.log(hydratedInstances[body].GetMass());
    
    // Cleanup
    world.DestroyBody(body);
    delete hydratedInstances[body];
  7. Getting started with box2d-wasm usage

    master

    After installation, you can begin using box2d-wasm by following these guides:

    • Integrating box2d-wasm into your application: Learn how to import and initialize the library.
    • Make a falling box: A basic tutorial that prints object coordinates to the console.
    • Draw the falling box: A tutorial on visualizing the physics simulation.

    For more complex implementations, refer to the project's demos. Note that box2d-wasm is designed to be broadly compatible with box2d.js code examples, as it binds Box2D functionality in a similar manner.

  8. Integrate box2d-wasm using the Modern approach

    master

    The Modern approach is designed for plain webpages that support ES modules. It uses npm and standard ES imports.

    Key characteristics:

    • No build system or TypeScript required.
    • Imports code as an ES module: <script src="demo.js" type="module" />.
    • Imports Box2D.js via ES import: import Box2DFactory from 'box2d-wasm';.
    • Uses JSDoc comments to provide TypeScript-like type benefits without a build step.
    import Box2DFactory from 'box2d-wasm';
    <script src="demo.js" type="module" />
  9. Integrate box2d-wasm into your project

    master

    Integrating box2d-wasm requires handling an asynchronous factory function that downloads a .wasm file via the browser's fetch() API.

    Core Requirements

    1. Asynchronous Initialization: You must call the factory function and wait for the returned Promise to resolve to access the box2D instance.
    2. WASM File Hosting: You must serve the Box2D.wasm file from a location accessible to the browser. It is highly recommended to serve this file with the MIME type application/wasm.
    3. File Locations: You can find the necessary distribution files in node_modules/box2d-wasm/dist/es/:
      • Box2D.js
      • Box2D.wasm

    Customizing WASM Location

    If your Box2D.wasm file is stored in a different directory than the default, use the locateFile option in the factory call to control the URL construction.

    import Box2DFactory from './Box2D.js';
    
    Box2DFactory({
      /**
       * Modify this logic if your Box2D.wasm lives in a different directory.
       */
      locateFile: (url, scriptDirectory) => `${scriptDirectory}${url}`
    }).then(box2D => {
      // finished downloading Box2D.wasm
      console.log(box2D);
    });