| Crates.io | spacetime_tiled |
| lib.rs | spacetime_tiled |
| version | 0.1.0 |
| created_at | 2025-10-01 08:39:58.03363+00 |
| updated_at | 2025-10-01 08:39:58.03363+00 |
| description | SpacetimeDB integration for loading Tiled map editor files |
| homepage | https://github.com/jbuehler23/spacetime_tiled |
| repository | https://github.com/jbuehler23/spacetime_tiled |
| max_upload_size | |
| id | 1862275 |
| size | 104,839 |
Load Tiled maps into SpacetimeDB for multiplayer games.
This library parses TMX files and stores them in SpacetimeDB tables. Since SpacetimeDB modules run in WASM without filesystem access, it includes an in-memory XML parser that works with embedded map data or content sent from clients.
Add to your server module's Cargo.toml:
[dependencies]
spacetimedb = "1.4.0"
spacetime_tiled = "0.1"
[lib]
crate-type = ["cdylib"]
In your server code:
use spacetimedb::{reducer, ReducerContext};
pub use spacetime_tiled::*;
#[reducer]
pub fn load_map(ctx: &ReducerContext) -> Result<(), String> {
// Embed the TMX file at compile time
const MAP_DATA: &str = include_str!("../assets/map.tmx");
// Parse and store in database
load_tmx_map_from_str(ctx, "level1", MAP_DATA)?;
Ok(())
}
Then call the reducer after publishing:
spacetime build
spacetime publish my-game
spacetime call my-game load_map
SpacetimeDB modules run in WASM sandboxes with no filesystem access. You can't just std::fs::read_to_string("map.tmx") in a reducer - it'll fail with "operation not supported on this platform".
This library provides two solutions:
load_tmx_map_from_str() - Parses TMX XML in-memory using quick-xml. Works in WASM. Use this.load_tmx_map() - Uses the tiled crate's file loader. Doesn't work in WASM. Only useful for testing outside SpacetimeDB.The library defines six tables:
All tables are indexed for querying by map_id or layer_id.
Option 1: Embedded maps (recommended)
#[reducer]
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
const OVERWORLD: &str = include_str!("../maps/overworld.tmx");
const DUNGEON: &str = include_str!("../maps/dungeon.tmx");
load_tmx_map_from_str(ctx, "overworld", OVERWORLD)?;
load_tmx_map_from_str(ctx, "dungeon", DUNGEON)?;
Ok(())
}
Option 2: Client-uploaded maps
#[reducer]
pub fn upload_map(ctx: &ReducerContext, name: String, tmx: String) -> Result<(), String> {
load_tmx_map_from_str(ctx, &name, &tmx)?;
Ok(())
}
Then from your client: spacetime call my-game upload_map '{"name": "custom", "tmx": "<?xml version..."}'
use spacetimedb::{reducer, ReducerContext, Table};
#[reducer]
pub fn check_collision(ctx: &ReducerContext, x: u32, y: u32) -> Result<bool, String> {
// Get collision layer (assuming it's layer 1)
let tile = ctx.db.tiled_tile()
.iter()
.find(|t| t.layer_id == 1 && t.x == x && t.y == y);
// Non-zero GID = collision tile
Ok(tile.map_or(false, |t| t.gid != 0))
}
#[reducer]
pub fn get_spawn_points(ctx: &ReducerContext) -> Result<(), String> {
let spawns: Vec<_> = ctx.db.tiled_object()
.iter()
.filter(|obj| obj.obj_type == "spawn")
.collect();
for spawn in spawns {
log::info!("Spawn at ({}, {}): {}", spawn.x, spawn.y, spawn.name);
}
Ok(())
}
The Rust client SDK requires an event loop to process messages. Without it, subscriptions never apply and you won't get any data.
use spacetimedb_sdk::{DbContext, Table};
use module_bindings::*;
fn main() {
let conn = DbConnection::builder()
.with_uri("http://localhost:3000")
.with_module_name("my-game")
.build()
.unwrap();
conn.subscription_builder()
.on_applied(on_sub_applied)
.subscribe([
"SELECT * FROM tiled_map",
"SELECT * FROM tiled_layer",
"SELECT * FROM tiled_tile",
]);
// THIS IS REQUIRED - starts processing messages
conn.run_threaded();
// Wait a moment for subscription to apply
std::thread::sleep(Duration::from_millis(500));
// Now you can query data
let maps: Vec<_> = conn.db.tiled_map().iter().collect();
println!("Loaded {} maps", maps.len());
}
fn on_sub_applied(ctx: &module_bindings::SubscriptionEventContext) {
println!("Subscription applied!");
// Initial data is available here
}
Important: You must import the table accessor traits:
use module_bindings::{
tiled_map_table::TiledMapTableAccess,
tiled_layer_table::TiledLayerTableAccess,
tiled_tile_table::TiledTileTableAccess,
// ... etc
};
Check out examples/simple_game/ for a complete working example with:
To run it:
cd examples/simple_game/server
spacetime build
spacetime publish simple-game
spacetime call simple-game load_demo_map
cd ../client
spacetime generate --lang rust --out-dir src/module_bindings --project-path ../server
cargo run
You forgot to call conn.run_threaded(). The SDK doesn't process messages automatically - you have to start an event loop.
You're trying to use load_tmx_map() in a WASM module. Use load_tmx_map_from_str() instead with include_str!().
Missing trait imports. The generated bindings define traits like TiledMapTableAccess that you need to import to use .tiled_map() methods.
use module_bindings::tiled_map_table::TiledMapTableAccess;
The zstd-sys dependency needs LLVM/clang for WASM compilation. Install LLVM and add it to your PATH, or use WSL/Linux.
Licensed under either of:
at your option.
Contributions are welcome! See CONTRIBUTING.md for guidelines.
Areas that need help: