Reducers Overzicht
Reducers zijn server-side functies in SpacetimeDB die de game logica uitvoeren.
Wat zijn Reducers?
Reducers zijn:
- Server-side functies die database modificeren
- Atomisch - heel of niet uitgevoerd
- Geautoriseerd - hebben toegang tot caller identity
- Type-safe - Rust type checking
#[reducer]
pub fn my_reducer(ctx: &ReducerContext, param: String) -> Result<(), String> {
let caller = ctx.sender; // Wie roept aan
let now = ctx.timestamp; // Huidige tijd
// Database operaties
ctx.db.my_table().insert(...);
Ok(())
}
Reducer Categorieën
Authenticatie
| Reducer | Beschrijving |
|---|---|
register_player | Nieuwe speler registreren |
register_player_with_pin | Met PIN code |
login_with_pin | Inloggen met PIN |
update_character | Karakter uiterlijk wijzigen |
Connection Lifecycle
| Reducer | Trigger |
|---|---|
on_connect | Client verbindt |
on_disconnect | Client verbreekt |
Positie & Movement
| Reducer | Beschrijving |
|---|---|
update_position | Speler positie updaten |
Chat
| Reducer | Beschrijving |
|---|---|
send_chat | Chat bericht sturen |
Inventory
| Reducer | Beschrijving |
|---|---|
give_container | Container geven aan speler |
add_item_to_container | Item toevoegen |
move_item | Item verplaatsen |
add_resource | Resource toevoegen |
spend_resource | Resource uitgeven |
transfer_currency | Geld overmaken |
Admin
| Reducer | Beschrijving |
|---|---|
add_admin | Admin rechten geven |
create_item_definition | Nieuw item definiëren |
create_container_type | Container type aanmaken |
Reducer Context
Elke reducer krijgt een ReducerContext:
pub struct ReducerContext {
pub sender: Identity, // Wie roept aan
pub timestamp: Timestamp, // Server tijd
pub db: Database, // Database toegang
}
Error Handling
Reducers retourneren Result<(), String>:
#[reducer]
pub fn example(ctx: &ReducerContext) -> Result<(), String> {
// Validatie
if !condition {
return Err("Foutmelding voor client".to_string());
}
// Succes
Ok(())
}
Client Aanroep
Vanuit Godot:
# Simpele aanroep
client.reducers.register_player("username", "Display Name")
# Met error handling
var result = await client.call_reducer("send_chat", ["Hallo!", "global", null])
if result.status == "failed":
print("Fout: ", result.message)
Permissions
Public Reducers
Iedereen kan aanroepen:
#[reducer]
pub fn send_chat(...) -> Result<(), String> {
// Iedereen mag chatten
}
Owner-Only
Alleen resource eigenaar:
#[reducer]
pub fn move_item(ctx: &ReducerContext, container_id: u64, ...) -> Result<(), String> {
let container = ctx.db.player_container().id().find(container_id)
.ok_or("Container not found")?;
// Check ownership
if container.owner != ctx.sender {
return Err("Not your container".to_string());
}
// Proceed...
}
Admin-Only
fn is_admin(ctx: &ReducerContext) -> bool {
ctx.db.admin_user().identity().find(ctx.sender).is_some()
}
#[reducer]
pub fn create_item_definition(ctx: &ReducerContext, ...) -> Result<(), String> {
if !is_admin(ctx) {
return Err("Admin only".to_string());
}
// Admin actie...
}
Best Practices
1. Valideer Input
#[reducer]
pub fn set_username(ctx: &ReducerContext, username: String) -> Result<(), String> {
// Length check
if username.len() < 3 || username.len() > 20 {
return Err("Username must be 3-20 characters".to_string());
}
// Character check
if !username.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err("Username can only contain letters, numbers, underscore".to_string());
}
// Uniqueness check
if ctx.db.player().username().find(&username).is_some() {
return Err("Username already taken".to_string());
}
Ok(())
}
2. Atomische Updates
#[reducer]
pub fn transfer_item(ctx: &ReducerContext, from: u64, to: u64) -> Result<(), String> {
// Beide operaties slagen of geen van beide
let item = ctx.db.container_item().id().find(from)
.ok_or("Item not found")?;
// Remove from source
ctx.db.container_item().id().delete(from);
// Add to destination
ctx.db.container_item().insert(ContainerItem {
container_id: to,
..item
});
Ok(())
}
3. Log Belangrijke Acties
#[reducer]
pub fn delete_player(ctx: &ReducerContext, target: Identity) -> Result<(), String> {
if !is_admin(ctx) {
return Err("Admin only".to_string());
}
log::warn!("Admin {} deleted player {}", ctx.sender, target);
// Delete...
Ok(())
}