This project demonstrates real-time multiplayer synchronization using SpacetimeDB (v1.1.1) as the backend and a web client built with React, TypeScript, and React Three Fiber. It includes server-side physics simulation using the Rapier engine.
-
SpacetimeDB Server (
/server):- Written in Rust using SpacetimeDB modules.
- Integrates the Rapier 3D physics engine (
rapier3dv0.19) for server-side simulation. - Defines tables:
Entity (id: u32): Basic entity identifier.EntityPhysics (entity_id, rb_handle_index, ..., co_handle_generation): Stores Rapier handle parts for physics bodies.EntityTransform (entity_id, x, y, z): Stores entity position, updated by the physics engine.PhysicsTickTimer: Schedules the physics update loop.
- Physics simulation runs on a fixed interval (currently 16ms) via a scheduled reducer (
process_physics_tick). - Includes a static
Mutex-guardedPhysicsStateto hold Rapier world components (RigidBodySet,ColliderSet, etc.). idis manually generated (max_id + 1).- Exposes reducers:
spawn(x, y, z): Creates a single dynamic sphere entity with a Rapier rigid body and collider, initially positioned high up.spawn_exploding_spheres(): Creates 100 small sphere entities at the origin with random outward velocities.reset_simulation(): Deletes all entities and their corresponding physics objects.
- Uses
logcrate for server-side logging (viewable viaspacetime logs spacetime | cator thespacetime startterminal). - Uses
randcrate (viactx.rng()) for deterministic randomness in reducers.
-
Client (
/client):- Built with React, TypeScript, and Vite.
- Uses
@react-three/fiberand@react-three/dreifor 3D rendering. - Uses
@react-three/rapierto mirror server-side physics for visual representation (gravity, collisions, etc. are simulated client-side based on server state updates). - Connects to the SpacetimeDB backend using generated client code (
/client/src/generated). - Subscribes to
EntityandEntityTransformtables. - Displays entities based on
EntityTransformdata as red spheres within a<Physics>context. - Includes buttons to call:
spawn: Creates one sphere.spawn_exploding_spheres: Triggers the 100-sphere explosion.reset_simulation: Clears the simulation.
- Uses
@clockworklabs/spacetimedb-sdk(v1.1.0).
-
Development Environment:
- Includes VS Code tasks (
.vscode/tasks.json) for common client/server actions. - Includes a
clean_publish.shscript for rebuilding the server, deleting the local DB, publishing the module, and regenerating client code. .gitignoreis configured for Rust and Node projects.- SpacetimeDB CLI installed via script to
~/.local/bin/spacetime.
- Includes VS Code tasks (
- Short-term:
- Allow client input to influence physics (e.g., applying forces).
- Refine visual representation and physics parameters.
- Long-term:
- Add more complex interactions (deletion via clicking?).
- Implement authentication and player representation.
- Explore deployment options (e.g., SpacetimeDB Cloud).
Prerequisites:
- Rust toolchain (
rustup,cargo). - Node.js and npm.
- SpacetimeDB CLI (v1.1.1). If not installed, run:
Note: The CLI installs to
curl -sSf https://install.spacetimedb.com | sh~/.local/bin/spacetimeby default.
Setup & Run Sequence:
-
Install Dependencies:
# Server (Rust) cd server cargo build cd .. # Client (Node) cd client npm install cd ..
-
Start SpacetimeDB Server (Manual Recommended):
- IMPORTANT: Running
spacetime startvia VS Code tasks can be unreliable (see Known Issues). It's recommended to run it manually in a dedicated terminal. - If running from an AppImage environment (like Cursor), you must unset the
APPIMAGEenvironment variable first.
# In a separate terminal: unset APPIMAGE && /home/ksollner/.local/bin/spacetime start
(Leave this terminal running)
- IMPORTANT: Running
-
Publish & Generate Client Code (Using Script Recommended):
- This needs to be done while the server from Step 2 is running.
- The
clean_publish.shscript handles build, delete, publish, and generate steps.
# Make sure script is executable: chmod +x clean_publish.sh bash ./clean_publish.sh- Alternatively, run manually (requires
unset APPIMAGEprefix if in AppImage env):# Publish /home/ksollner/.local/bin/spacetime publish --project-path ./server spacetime # Generate cd server /home/ksollner/.local/bin/spacetime generate --lang typescript --out-dir ../client/src/generated cd ..
-
Run Client Dev Server:
cd client npm run dev- Open the URL provided by Vite (usually
http://localhost:5173) in your browser.
- Open the URL provided by Vite (usually
Tasks are provided for convenience but have limitations (see Known Issues).
Client: dev: Runsnpm run devin/client.Client: build: Runsnpm run buildin/client.Server: start: Attempts to runspacetime start. (Currently unreliable, use manual start).Server: publish: Attempts to publish the module. (Requires manual server start).
Note: Server tasks use the absolute path /home/ksollner/.local/bin/spacetime and include the unset APPIMAGE workaround.
spacetime startvia VS Code Tasks: Running the "Server: start" task often fails silently (exit code 1) even with correct paths and workarounds.- Workaround: Run
unset ARGV0 && unset APPIMAGE && /home/ksollner/.local/bin/spacetime startmanually in a separate terminal.
- Workaround: Run
- AppImage Environment Conflict (
APPIMAGE,ARGV0): When running VS Code/Cursor as an AppImage, environment variables set by the AppImage interfere with thespacetimeCLI andcargo(viarustupproxy).APPIMAGE: Causesspacetimeinternal command dispatching issues ("multicall binary" error).ARGV0: Causescargo build(and potentially otherrustuptools) to fail with an "unknown proxy name" error.- Workaround: Prefix all relevant commands (
cargo build,spacetime start,spacetime publish,spacetime generate) withunset ARGV0 && unset APPIMAGE &&when running them from within the AppImage environment. The provided tasks in.vscode/tasks.jsonalready include this combined workaround. Theclean_publish.shscript does not currently include this, so run it from a standard terminal if using AppImage.
- Client SDK Typing: Using the
@clockworklabs/spacetimedb-sdkdirectly without generated code is difficult due to complex types and builder patterns.- Solution: Always run
spacetime generate(or useclean_publish.sh) after publishing the module and use the generated types/classes from/client/src/generated.
- Solution: Always run
- Entity ID Generation: Using
#[auto_inc]on the primary key caused internal SpacetimeDB errors duringpublish.- Solution: The server now uses manual ID generation by querying the max existing ID + 1.
- Wasm Time Limitations: Standard Rust timing functions like
std::time::Instantare not available in the Wasm environment used by SpacetimeDB modules. Usectx.timestampfor basic timing information within reducers.
Pull requests and issues are welcome!