Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
High-performance WebAssembly modules written in Rust for accelerating performance-critical operations with SIMD optimizations.
Web Engine leverages WebAssembly (WASM) to accelerate performance-critical operations that benefit from native code execution. Our WASM modules are written in Rust and compiled using wasm-bindgen, providing near-native performance for transforms, physics, culling, and network compression.
Execute compute-intensive operations at speeds approaching native C/C++
Leverage SIMD instructions for parallel data processing
Rust's memory safety guarantees prevent common bugs and crashes
Efficient data sharing between JavaScript and WASM via TypedArrays
The WASM package (@web-engine-dev/wasm) contains optimized implementations for transform computation, physics simulation, frustum culling, octree spatial partitioning, and network batch compression.
Web Engine's WASM modules follow a carefully designed architecture that maximizes performance while maintaining clean JavaScript integration:
Build Optimizations
-O3 (maximum optimization), --enable-simd (SIMD instructions), -ffm (fast float math), and LTO (Link Time Optimization) for minimal binary size and maximum runtime performance.SIMD-accelerated transform hierarchy computation using the glam math library. Processes entity transforms in batch with O(n) complexity:
import init, {update_world_transforms,update_quaternions_from_euler,compute_transform_matrices,} from '@web-engine-dev/wasm';// Initialize WASM module once at startupawait init();// Update world transforms for entire entity hierarchy// Processes parent-child relationships recursively in O(n) timeupdate_world_transforms(localPositions, // Float32Array: [x, y, z] per entitylocalRotations, // Float32Array: [x, y, z] Euler angles per entitylocalScales, // Float32Array: [x, y, z] per entitylocalQuaternions, // Float32Array: [x, y, z, w] per entityparents, // Uint32Array: parent entity ID per entityentityIds, // Uint32Array: entity IDs being processedoutWorldPositions, // Float32Array: output world positionsoutWorldRotations, // Float32Array: output world rotationsoutWorldScales, // Float32Array: output world scalesoutWorldQuaternions // Float32Array: output world quaternions);// Convert Euler angles to quaternions (batch operation)update_quaternions_from_euler(rotations, // Float32Array: [x, y, z] Euler angles (radians)quaternions, // Float32Array: output [x, y, z, w] quaternionsentityIds // Uint32Array: entity IDs);// Compute 4x4 matrices for instanced renderingcompute_transform_matrices(positions, // Float32Array: world positionsrotations, // Float32Array: Euler rotationsscales, // Float32Array: scale vectorsquaternions, // Float32Array: quaternions (preferred)entityIds, // Uint32Array: entity IDsmatrices // Float32Array: output 4x4 matrices (16 floats each));
Physics simulation bridge using Rapier3D, Rust's high-performance physics engine. Provides rigid body dynamics, collision detection, and character controllers:
import { PhysicsWorld } from '@web-engine-dev/wasm';// Create physics worldconst physics = new PhysicsWorld();// Bulk add bodies (single FFI call vs n calls)physics.add_bodies_batch(entityIds, // Uint32Array: entity IDspositions, // Float32Array: initial positions [x, y, z]isDynamicArray // Uint8Array: 0 = static, 1 = dynamic);// Update body properties in batchphysics.update_bodies_batch(entityIds, // Uint32Array: entity IDs to updatemasses, // Float32Array: mass per entityfrictions, // Float32Array: friction coefficientsrestitutions // Float32Array: restitution (bounciness));// Step simulationphysics.step(deltaTime);// Sync physics state back to ECS (SIMD-accelerated)const syncedCount = physics.sync_states_simd_bulk(positions, // Float32Array: output positionsrotations, // Float32Array: output rotationsentityIds, // Uint32Array: entity IDsvelocities, // Float32Array: output velocitiesangularVelocities, // Float32Array: output angular velocitiestrue, // use_velocitiestrue // use_angular_velocities);// Raycast with shapecast supportconst hit = physics.raycast(originX, originY, originZ,directionX, directionY, directionZ,maxDistance);if (hit) {console.log(`Hit entity ${hit.eid} at distance ${hit.toi}`);}
Spatial Hash Optimization
Accelerated frustum culling and octree spatial partitioning for visibility determination:
import { OctreeBvh, SpatialGrid, cull_entities } from '@web-engine-dev/wasm';// Create octree for hierarchical cullingconst octree = new OctreeBvh(100000, // max entities8 // max depth);// Rebuild octree from entity dataoctree.rebuild(positions, // Float32Array: [x, y, z] per entityradii, // Float32Array: bounding sphere radius per entityentityIds, // Uint32Array: entity IDscount // number: entity count);// Frustum cull with front-to-back traversalconst visibleCount = octree.frustum_cull(viewProjMatrix, // Float32Array: 4x4 view-projection matrixcameraPos, // Float32Array: [x, y, z] camera positionoutVisibility, // Uint8Array: visibility mask (indexed by EID)outVisibleIds // Uint32Array: ordered list of visible entity IDs);console.log(`Visible: ${visibleCount} / ${count}`);// Get octree statisticsconst stats = octree.stats;console.log(`Nodes: ${stats.node_count}, Leaves: ${stats.leaf_count}`);console.log(`Depth: ${stats.max_depth}, Avg occupancy: ${stats.average_leaf_occupancy}`);// Alternative: Simple frustum culling without octreecull_entities(positions, // Float32Array: entity positionsradii, // Float32Array: bounding radiicount, // number: entity countviewProjMatrix, // Float32Array: view-projection matrixoutVisibility // Uint8Array: output visibility mask);
SIMD-accelerated network packet compression for multiplayer state synchronization. Uses delta compression and quantization to minimize bandwidth:
import { BatchCompressionWASM, CompressionConfig } from '@web-engine-dev/wasm';// Create compression instanceconst compressor = new BatchCompressionWASM(10000); // max entities// Configure compression thresholdsconst config: CompressionConfig = {enable_simd: true,position_threshold: 0.01, // 1cm position changerotation_threshold: 0.001, // ~0.06 degree rotation changevelocity_threshold: 0.1, // 10cm/s velocity changequantization_scale: 1000.0 // quantize to millimeters};// Compress entity state batchconst result = compressor.compress_batch(currentPositions, // Float32Array: current frame positionspreviousPositions, // Float32Array: previous frame positionscurrentRotations, // Float32Array: current frame quaternionspreviousRotations, // Float32Array: previous frame quaternionscurrentVelocities, // Float32Array: current frame velocitiespreviousVelocities, // Float32Array: previous frame velocitiesconfig);console.log(`Compression: ${result.compression_ratio.toFixed(2)}x`);console.log(`Size: ${result.compressed_size} / ${result.uncompressed_size} bytes`);console.log(`Time: ${result.processing_time_ms.toFixed(2)}ms`);console.log(`SIMD ops: ${result.simd_operations}`);// Low-level delta computation (SIMD-accelerated)const changedCount = compressor.compute_position_deltas_simd(currentPositions,previousPositions,deltaOutput,threshold);
The WASM package ships with pre-built binaries, but you can rebuild from source if needed:
# Install Rust toolchaincurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh# Install wasm-packcargo install wasm-pack# Add wasm32 targetrustup target add wasm32-unknown-unknown
# Navigate to WASM packagecd packages/wasm# Build optimized WASM binary (production)pnpm run build# Build with SIMD support (requires browser support)pnpm run build:simd# Development build (faster compilation, larger binary)wasm-pack build --target web --dev# Run Rust testscargo test# Run WASM tests in headless browserwasm-pack test --headless --firefox
The WASM build is configured in Cargo.toml with aggressive optimizations:
[profile.release]opt-level = 3 # Maximum optimizationlto = true # Link Time Optimizationcodegen-units = 1 # Single codegen unit for better optimizationpanic = "abort" # Smaller binary sizestrip = true # Strip debug symbols[package.metadata.wasm-pack.profile.release]wasm-opt = ["-O3", # Maximum optimization"--enable-simd", # SIMD instructions"-ffm", # Fast float math"--precompute-propagate" # Aggressive constant propagation]
WASM modules use shared memory for zero-copy data transfer between JavaScript and Rust:
// Allocate data in JavaScript (you own this memory)const positions = new Float32Array(entityCount * 3);const matrices = new Float32Array(entityCount * 16);// Pass to WASM (zero-copy, shares memory)compute_transform_matrices(positions, // Rust sees this as &[f32]rotations,scales,quaternions,entityIds,matrices // Rust sees this as &mut [f32]);// Data is modified in-place, no copies madeconsole.log(matrices[0]); // Updated by WASM// WASM objects need manual disposalconst octree = new OctreeBvh(10000, 8);// ... use octree ...octree.free(); // Release WASM memory
Memory Ownership
PhysicsWorld, call .free() when done to release WASM-allocated memory.| Operation | Complexity | Performance | Notes |
|---|---|---|---|
| Transform hierarchy update | O(n) | ~0.1ms / 10k entities | SIMD-accelerated, single-pass DFS |
| Quaternion conversion | O(n) | ~0.05ms / 10k entities | Vectorized batch operation |
| Physics step | O(n log n) | ~2ms / 10k bodies | Spatial hash broad-phase |
| Frustum culling | O(n) | ~0.3ms / 10k entities | Simple sphere tests |
| Octree frustum cull | O(log n) | ~0.1ms / 10k entities | Hierarchical early-out |
| Batch compression | O(n) | ~0.5ms / 1k entities | SIMD delta + quantization |
Benchmarks performed on: Apple M1, Chrome 120, SIMD enabled. Real-world performance varies by entity complexity and data distribution.
WebAssembly SIMD provides 128-bit vector operations for parallel data processing. Web Engine's WASM modules are compiled with SIMD support:
// Check SIMD support at runtimeconst simdSupported = WebAssembly.validate(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11]));if (simdSupported) {console.log('WASM SIMD available - using optimized code paths');} else {console.log('WASM SIMD unavailable - using scalar fallback');}// Most WASM functions automatically use SIMD when available// Some modules provide explicit control:const config = {enable_simd: simdSupported,// ... other config};
The WASM package includes generated TypeScript definitions from wasm-bindgen:
// Auto-generated TypeScript definitionsexport function update_world_transforms(local_positions: Float32Array,local_rotations: Float32Array,local_scales: Float32Array,local_quaternions: Float32Array,parents: Uint32Array,entity_ids: Uint32Array,out_world_positions: Float32Array,out_world_rotations: Float32Array,out_world_scales: Float32Array,out_world_quaternions: Float32Array): void;export class PhysicsWorld {constructor();free(): void;step(dt: number): void;add_body(eid: number, x: number, y: number, z: number, is_dynamic: boolean): void;// ... all methods fully typed}export class OctreeBvh {constructor(max_entities: number, max_depth: number);free(): void;rebuild(positions: Float32Array, radii: Float32Array, entity_ids: Uint32Array, count: number): void;frustum_cull(view_proj_matrix: Float32Array, camera_pos: Float32Array, out_visibility: Uint8Array, out_visible_ids: Uint32Array): number;readonly stats: OctreeStats;}
WASM modules include panic hooks for better error messages in browser console:
import init, { wasm_version } from '@web-engine-dev/wasm';try {await init();console.log(`WASM version: ${wasm_version()}`);} catch (error) {console.error('WASM initialization failed:', error);}// WASM panics are caught and displayed in console with full Rust backtrace// Example panic output:// panicked at 'index out of bounds: the len is 1000 but the index is 1001', src/transform.rs:42:5// Enable source maps for better debugging (development builds only)// Production builds are stripped and optimized
Development vs Production
wasm-pack build --dev during development for faster compile times and better error messages. Production builds with pnpm run build are 2-3x smaller but strip debug info.// Initialize WASM once at app startupimport init from '@web-engine-dev/wasm';let wasmInitialized = false;export async function initializeWasm() {if (wasmInitialized) return;try {await init();wasmInitialized = true;console.log('WASM initialized successfully');} catch (error) {console.error('Failed to initialize WASM:', error);throw error;}}// Call before using any WASM functionsawait initializeWasm();
// ❌ Bad: Multiple FFI calls (slow)for (let i = 0; i < entities.length; i++) {physics.add_body(entities[i], x[i], y[i], z[i], true);}// ✅ Good: Single batch call (fast)physics.add_bodies_batch(entityIds, positions, isDynamicArray);// Minimize FFI boundary crossings// Each JS -> WASM call has overhead (~10-50ns)// Process data in large batches to amortize this cost
// ❌ Bad: Allocate new arrays every framefunction update() {const positions = new Float32Array(count * 3);const matrices = new Float32Array(count * 16);compute_transform_matrices(positions, rotations, scales, quaternions, entityIds, matrices);}// ✅ Good: Reuse pre-allocated buffersconst positions = new Float32Array(MAX_ENTITIES * 3);const matrices = new Float32Array(MAX_ENTITIES * 16);function update() {compute_transform_matrices(positions, rotations, scales, quaternions, entityIds, matrices);}// Zero allocations in the hot path