Ga naar hoofdinhoud

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

ReducerBeschrijving
register_playerNieuwe speler registreren
register_player_with_pinMet PIN code
login_with_pinInloggen met PIN
update_characterKarakter uiterlijk wijzigen

Connection Lifecycle

ReducerTrigger
on_connectClient verbindt
on_disconnectClient verbreekt

Positie & Movement

ReducerBeschrijving
update_positionSpeler positie updaten

Chat

ReducerBeschrijving
send_chatChat bericht sturen

Inventory

ReducerBeschrijving
give_containerContainer geven aan speler
add_item_to_containerItem toevoegen
move_itemItem verplaatsen
add_resourceResource toevoegen
spend_resourceResource uitgeven
transfer_currencyGeld overmaken

Admin

ReducerBeschrijving
add_adminAdmin rechten geven
create_item_definitionNieuw item definiëren
create_container_typeContainer 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(())
}

Volgende