Module Structuur
De organisatie van de SpacetimeDB Rust module.
Bestandsstructuur
server/
├── Cargo.toml # Rust dependencies
├── Cargo.lock # Locked versions
└── src/
└── lib.rs # Hoofd module bestand
Cargo.toml
[package]
name = "milenas-treehouse"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
spacetimedb = "1.0"
log = "0.4"
[profile.release]
opt-level = "s" # Optimaliseer voor size
lto = true # Link-time optimization
lib.rs Structuur
Het hoofdbestand is georganiseerd in secties:
// === IMPORTS ===
use spacetimedb::{
table, reducer,
Identity, Timestamp,
ReducerContext,
SpacetimeType,
};
// === ENUMS ===
#[derive(SpacetimeType, Clone, Debug)]
pub enum ItemCategory {
Food,
Seed,
Tool,
Decoration,
}
// === TABELLEN ===
// -- Spelers --
#[table(name = player, public)]
pub struct Player { ... }
#[table(name = player_stats, public)]
pub struct PlayerStats { ... }
// -- Inventory --
#[table(name = item_definition, public)]
pub struct ItemDefinition { ... }
#[table(name = container_item, public)]
pub struct ContainerItem { ... }
// -- Farming --
#[table(name = farm_plot, public)]
pub struct FarmPlot { ... }
// ... meer secties
// === HELPER FUNCTIES ===
fn is_admin(ctx: &ReducerContext) -> bool { ... }
fn get_player(ctx: &ReducerContext) -> Option<Player> { ... }
// === REDUCERS ===
// -- Authenticatie --
#[reducer]
pub fn register_player(...) { ... }
// -- Speler Acties --
#[reducer]
pub fn update_position(...) { ... }
// -- Inventory --
#[reducer]
pub fn add_item(...) { ... }
// -- Admin --
#[reducer]
pub fn admin_give_item(...) { ... }
Sectie Details
1. Enums
Custom types voor categorieën en statussen:
#[derive(SpacetimeType, Clone, Debug, PartialEq)]
pub enum ItemCategory {
Food,
Seed,
Tool,
Container,
Decoration,
Material,
Quest,
Special,
}
#[derive(SpacetimeType, Clone, Debug)]
pub enum CropState {
Planted,
Growing,
Ready,
Withered,
}
2. Tabellen per Domein
Spelers
#[table(name = player, public)]
pub struct Player {
#[primary_key]
pub identity: Identity,
#[unique]
pub username: String,
pub display_name: String,
pub created_at: Timestamp,
pub last_seen: Timestamp,
}
#[table(name = player_stats, public)]
pub struct PlayerStats {
#[primary_key]
pub identity: Identity,
pub level: u32,
pub experience: u64,
pub play_time_minutes: u32,
}
#[table(name = player_position, public)]
pub struct PlayerPosition {
#[primary_key]
pub identity: Identity,
pub x: f32,
pub y: f32,
pub scene: String,
}
Inventory
#[table(name = item_definition, public)]
pub struct ItemDefinition {
#[primary_key]
pub item_id: String,
pub category: ItemCategory,
pub display_name: String,
pub description: String,
pub icon_path: String,
pub max_stack: u32,
pub is_tradeable: bool,
}
#[table(name = player_container, public)]
pub struct PlayerContainer {
#[primary_key]
#[auto_inc]
pub id: u64,
pub owner: Identity,
pub container_type: String,
pub slot_index: u8,
}
#[table(name = container_item, public)]
pub struct ContainerItem {
#[primary_key]
#[auto_inc]
pub id: u64,
pub container_id: u64,
pub slot: u8,
pub item_id: String,
pub quantity: u32,
}
Resources
#[table(name = player_resource, public)]
pub struct PlayerResource {
#[primary_key]
#[auto_inc]
pub id: u64,
pub owner: Identity,
pub resource_type: String,
pub amount: u64,
}
3. Helper Functies
Herbruikbare logica:
/// Check of caller een admin is
fn is_admin(ctx: &ReducerContext) -> bool {
ctx.db.admin_user()
.identity()
.find(&ctx.sender)
.is_some()
}
/// Haal speler op voor huidige caller
fn get_player(ctx: &ReducerContext) -> Option<Player> {
ctx.db.player()
.identity()
.find(&ctx.sender)
}
/// Haal resource amount op
fn get_resource_amount(ctx: &ReducerContext, resource_type: &str) -> u64 {
ctx.db.player_resource()
.owner()
.filter(&ctx.sender)
.find(|r| r.resource_type == resource_type)
.map(|r| r.amount)
.unwrap_or(0)
}
4. Reducers per Categorie
// === AUTHENTICATIE ===
#[reducer]
pub fn register_player(ctx: &ReducerContext, username: String) -> Result<(), String> {
// Validatie
if username.len() < 3 || username.len() > 20 {
return Err("Username must be 3-20 characters".to_string());
}
// Check of username al bestaat
if ctx.db.player().username().find(&username).is_some() {
return Err("Username already taken".to_string());
}
// Maak speler aan
ctx.db.player().insert(Player {
identity: ctx.sender,
username: username.clone(),
display_name: username,
created_at: ctx.timestamp,
last_seen: ctx.timestamp,
});
// Initialiseer stats
ctx.db.player_stats().insert(PlayerStats {
identity: ctx.sender,
level: 1,
experience: 0,
play_time_minutes: 0,
});
// Geef starter items
give_starter_pack(ctx);
Ok(())
}
// === POSITIE ===
#[reducer]
pub fn update_position(
ctx: &ReducerContext,
x: f32,
y: f32,
scene: String
) -> Result<(), String> {
// Update of insert positie
if let Some(pos) = ctx.db.player_position().identity().find(&ctx.sender) {
ctx.db.player_position().identity().update(PlayerPosition {
identity: ctx.sender,
x, y, scene,
});
} else {
ctx.db.player_position().insert(PlayerPosition {
identity: ctx.sender,
x, y, scene,
});
}
Ok(())
}
Toekomstige Modularisatie
Voor grotere projecten kan de module opgesplitst worden:
server/src/
├── lib.rs # Re-exports
├── tables/
│ ├── mod.rs
│ ├── player.rs
│ ├── inventory.rs
│ └── farming.rs
├── reducers/
│ ├── mod.rs
│ ├── auth.rs
│ ├── player.rs
│ └── admin.rs
└── helpers/
├── mod.rs
└── validation.rs
Huidige Aanpak
Voorlopig houden we alles in één lib.rs voor simpliciteit. Modularisatie is voor later als de codebase groeit.