Ga naar hoofdinhoud

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.