Loading...
Loading...
Compare original and translation side by side
Tested with: SpacetimeDB runtime 1.11.x,crate 1.1.xspacetimedb
已测试版本: SpacetimeDB runtime 1.11.x,crate 1.1.xspacetimedb
// WRONG — these macros/attributes don't exist
#[spacetimedb::table] // Use #[table] after importing
#[spacetimedb::reducer] // Use #[reducer] after importing
#[derive(Table)] // Tables use #[table] attribute, not derive
#[derive(Reducer)] // Reducers use #[reducer] attribute
// WRONG — SpacetimeType on tables
#[derive(SpacetimeType)] // DO NOT use on #[table] structs!
#[table(name = my_table)]
pub struct MyTable { ... }
// WRONG — mutable context
pub fn my_reducer(ctx: &mut ReducerContext, ...) { } // Should be &ReducerContext
// WRONG — table access without parentheses
ctx.db.player // Should be ctx.db.player()
ctx.db.player.find(id) // Should be ctx.db.player().id().find(&id)// 错误示例 — 这些宏/属性不存在
#[spacetimedb::table] // 导入后请使用#[table]
#[spacetimedb::reducer] // 导入后请使用#[reducer]
#[derive(Table)] // 表使用#[table]属性,而非derive
#[derive(Reducer)] // Reducer使用#[reducer]属性
// 错误示例 — 为表添加SpacetimeType
#[derive(SpacetimeType)] // 请勿在#[table]结构体上使用!
#[table(name = my_table)]
pub struct MyTable { ... }
// 错误示例 — 可变上下文
pub fn my_reducer(ctx: &mut ReducerContext, ...) { } // 应该使用&ReducerContext
// 错误示例 — 不带括号的表访问
ctx.db.player // 应该是ctx.db.player()
ctx.db.player.find(id) // 应该是ctx.db.player().id().find(&id)// CORRECT IMPORTS
use spacetimedb::{table, reducer, Table, ReducerContext, Identity, Timestamp};
use spacetimedb::SpacetimeType; // Only for custom types, NOT tables
// CORRECT TABLE — no SpacetimeType derive!
#[table(name = player, public)]
pub struct Player {
#[primary_key]
pub id: u64,
pub name: String,
}
// CORRECT REDUCER — immutable context reference
#[reducer]
pub fn create_player(ctx: &ReducerContext, name: String) {
ctx.db.player().insert(Player { id: 0, name });
}
// CORRECT TABLE ACCESS — methods with parentheses
let player = ctx.db.player().id().find(&player_id);// 正确导入
use spacetimedb::{table, reducer, Table, ReducerContext, Identity, Timestamp};
use spacetimedb::SpacetimeType; // 仅用于自定义类型,不用于表
// 正确的表定义 — 无需派生SpacetimeType!
#[table(name = player, public)]
pub struct Player {
#[primary_key]
pub id: u64,
pub name: String,
}
// 正确的Reducer — 不可变上下文引用
#[reducer]
pub fn create_player(ctx: &ReducerContext, name: String) {
ctx.db.player().insert(Player { id: 0, name });
}
// 正确的表访问 — 使用带括号的方法
let player = ctx.db.player().id().find(&player_id);SpacetimeType#[table]&ReducerContext&mut ReducerContextTablectx.db.player()ctx.db.player#[table]SpacetimeType&ReducerContext&mut ReducerContextTablectx.db.player()ctx.db.player| Wrong | Right | Error |
|---|---|---|
| Remove it — macro handles this | Conflicting derive macros |
| | "no field |
| | Must access via index |
| | Wrong context type |
Missing | Add import | "no method named |
| | String literals not allowed |
Missing | Add | Clients can't subscribe |
| | Wrong attribute path |
| Network/filesystem in reducer | Use procedures instead | Sandbox violation |
| Panic for expected errors | Return | WASM instance destroyed |
| 错误写法 | 正确写法 | 错误原因 |
|---|---|---|
在 | 移除该派生 — 宏会自动处理 | 派生宏冲突 |
| | "类型中不存在字段 |
| | 必须通过索引访问 |
| | 上下文类型错误 |
缺少 | 添加该导入 | "不存在名为 |
| | 不允许使用字符串字面量 |
表上缺少 | 添加 | 客户端无法订阅 |
| 导入后使用 | 属性路径错误 |
| 在reducer中进行网络/文件系统操作 | 使用procedure替代 | 沙箱违规 |
| 预期错误触发panic | 返回 | WASM实例被销毁 |
| Wrong | Right | Error |
|---|---|---|
| Wrong crate name | | Dependency not found |
| Manual event loop | Use | Async issues |
| 错误写法 | 正确写法 | 错误原因 |
|---|---|---|
| 错误的crate名称 | | 依赖未找到 |
| 手动事件循环 | 使用 | 异步问题 |
SpacetimeType#[table]Table&ReducerContext&mut ReducerContextctx.db.table()ctx.db.tablectx.random()ctx.rngrandpublic#[table]SpacetimeTypeTable&ReducerContext&mut ReducerContextctx.db.table()ctx.db.tablectx.random()ctx.rngrandpublic[package]
name = "my-module"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
spacetimedb = "1.0"
log = "0.4"crate-type = ["cdylib"][package]
name = "my-module"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
spacetimedb = "1.0"
log = "0.4"crate-type = ["cdylib"]use spacetimedb::{ReducerContext, Table};use spacetimedb::{Identity, Timestamp, ConnectionId, ScheduleAt};
use spacetimedb::sats::{i256, u256}; // For 256-bit integersuse spacetimedb::{ReducerContext, Table};use spacetimedb::{Identity, Timestamp, ConnectionId, ScheduleAt};
use spacetimedb::sats::{i256, u256}; // 用于256位整数#[spacetimedb::table]#[spacetimedb::table]#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
#[auto_inc]
id: u64,
name: String,
score: u32,
}#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
#[auto_inc]
id: u64,
name: String,
score: u32,
}| Attribute | Description |
|---|---|
| Required. The table name used in |
| Makes table visible to clients via subscriptions |
| Creates a schedule table that triggers the named reducer |
| Creates a multi-column index |
| 属性 | 描述 |
|---|---|
| 必填项。在 |
| 使表可通过订阅对客户端可见 |
| 创建一个调度表,触发指定名称的reducer |
| 创建多列索引 |
| Attribute | Description |
|---|---|
| Unique identifier for the row (one per table max) |
| Enforces uniqueness, enables |
| Auto-generates unique integer values when inserting 0 |
| Creates a B-tree index for efficient lookups |
| Default value for migrations (must be const-evaluable) |
| 属性 | 描述 |
|---|---|
| 行的唯一标识符(每个表最多一个) |
| 强制唯一性,启用 |
| 插入0时自动生成唯一整数值 |
| 创建B树索引以提高查询效率 |
| 迁移时的默认值(必须是可常量计算的) |
u8u16u32u64u128u256i8i16i32i64i128i256f32f64boolStringIdentityConnectionIdTimestampUuidScheduleAtVec<T>Option<T>Result<T, E>#[derive(SpacetimeType)]u8u16u32u64u128u256i8i16i32i64i128i256f32f64boolStringIdentityConnectionIdTimestampUuidScheduleAtVec<T>Option<T>Result<T, E>#[derive(SpacetimeType)]// Insert and get the auto-generated ID
let row = ctx.db.task().insert(Task {
id: 0, // Placeholder for auto_inc
owner_id: ctx.sender,
title: "New task".to_string(),
created_at: ctx.timestamp,
});
let new_id = row.id; // Get the actual ID// 插入并获取自动生成的ID
let row = ctx.db.task().insert(Task {
id: 0, // auto_inc会填充该值
owner_id: ctx.sender,
title: "New task".to_string(),
created_at: ctx.timestamp,
});
let new_id = row.id; // 获取实际生成的IDpublic| Scenario | Pattern |
|---|---|
| Everyone sees all rows | |
| Users see only their data | Private table + row-level security |
public| 场景 | 实现方式 |
|---|---|
| 所有人可见所有行 | |
| 用户仅能看到自己的数据 | 私有表 + 行级安全性 |
// No public flag — only server can read
#[table(name = secret_data)]
pub struct SecretData { ... }// 无public标识 — 仅服务器可读取
#[table(name = secret_data)]
pub struct SecretData { ... }// Use row-level security for per-user visibility
#[table(name = player_data, public)]
#[rls(filter = |ctx, row| row.owner_id == ctx.sender)]
pub struct PlayerData {
#[primary_key]
pub id: u64,
pub owner_id: Identity,
pub data: String,
}true// 使用行级安全性实现用户级可见性
#[table(name = player_data, public)]
#[rls(filter = |ctx, row| row.owner_id == ctx.sender)]
pub struct PlayerData {
#[primary_key]
pub id: u64,
pub owner_id: Identity,
pub data: String,
}true#[spacetimedb::reducer]
pub fn create_player(ctx: &ReducerContext, name: String) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
ctx.db.player().insert(Player {
id: 0, // auto_inc assigns the value
name,
score: 0,
});
Ok(())
}#[spacetimedb::reducer]
pub fn create_player(ctx: &ReducerContext, name: String) -> Result<(), String> {
if name.is_empty() {
return Err("名称不能为空".to_string());
}
ctx.db.player().insert(Player {
id: 0, // auto_inc会分配值
name,
score: 0,
});
Ok(())
}&ReducerContextSpacetimeType()Result<(), String>Result<(), E>E: DisplayErrTableuse spacetimedb::Table;&ReducerContextSpacetimeType()Result<(), String>Result<(), E>E: DisplayErrTableuse spacetimedb::Table;ReducerContextReducerContext#[spacetimedb::reducer]
pub fn example(ctx: &ReducerContext) {
// Database access
let _table = ctx.db.player();
// Caller identity (always present)
let caller: Identity = ctx.sender;
// Connection ID (None for scheduled/system reducers)
let conn: Option<ConnectionId> = ctx.connection_id;
// Invocation timestamp
let when: Timestamp = ctx.timestamp;
// Module's own identity
let module_id: Identity = ctx.identity();
// Random number generation (deterministic)
let random_val: u32 = ctx.random();
// UUID generation
let uuid = ctx.new_uuid_v4().unwrap(); // Random UUID
let uuid = ctx.new_uuid_v7().unwrap(); // Timestamp-based UUID
// Check if caller is internal (scheduled reducer)
if ctx.sender_auth().is_internal() {
// Called by scheduler, not external client
}
}#[spacetimedb::reducer]
pub fn example(ctx: &ReducerContext) {
// 数据库访问
let _table = ctx.db.player();
// 调用者身份(始终存在)
let caller: Identity = ctx.sender;
// 连接ID(调度/系统Reducer为None)
let conn: Option<ConnectionId> = ctx.connection_id;
// 调用时间戳
let when: Timestamp = ctx.timestamp;
// 模块自身的身份
let module_id: Identity = ctx.identity();
// 随机数生成(确定性)
let random_val: u32 = ctx.random();
// UUID生成
let uuid = ctx.new_uuid_v4().unwrap(); // 随机UUID
let uuid = ctx.new_uuid_v7().unwrap(); // 基于时间戳的UUID
// 检查调用者是否为内部(调度Reducer)
if ctx.sender_auth().is_internal() {
// 由调度器调用,而非外部客户端
}
}// Insert returns the row with auto_inc values populated
let player = ctx.db.player().insert(Player {
id: 0, // auto_inc fills this
name: "Alice".to_string(),
score: 100,
});
log::info!("Created player with id: {}", player.id);// 插入操作返回填充了auto_inc值的行
let player = ctx.db.player().insert(Player {
id: 0, // auto_inc会填充该值
name: "Alice".to_string(),
score: 100,
});
log::info!("创建玩家,ID为:{}", player.id);// find() returns Option<RowType>
if let Some(player) = ctx.db.player().id().find(123) {
log::info!("Found: {}", player.name);
}// find()返回Option<RowType>
if let Some(player) = ctx.db.player().id().find(123) {
log::info!("找到玩家:{}", player.name);
}// filter() returns an iterator
for player in ctx.db.player().name().filter("Alice") {
log::info!("Player {}: score {}", player.id, player.score);
}
// Range queries (Rust range syntax)
for player in ctx.db.player().score().filter(50..=100) {
log::info!("{} has score {}", player.name, player.score);
}// filter()返回迭代器
for player in ctx.db.player().name().filter("Alice") {
log::info!("玩家{}:分数{}", player.id, player.score);
}
// 范围查询(Rust范围语法)
for player in ctx.db.player().score().filter(50..=100) {
log::info!("{}的分数为{}", player.name, player.score);
}update()if let Some(mut player) = ctx.db.player().id().find(123) {
player.score += 10;
ctx.db.player().id().update(player);
}update()if let Some(mut player) = ctx.db.player().id().find(123) {
player.score += 10;
ctx.db.player().id().update(player);
}// Delete by unique key
ctx.db.player().id().delete(&123);
// Delete by indexed column (returns count)
let deleted = ctx.db.player().name().delete("Alice");
log::info!("Deleted {} rows", deleted);
// Delete by range
ctx.db.player().score().delete(..50); // Delete all with score < 50// 通过唯一键删除
ctx.db.player().id().delete(&123);
// 通过索引列删除(返回删除行数)
let deleted = ctx.db.player().name().delete("Alice");
log::info!("删除了{}行", deleted);
// 通过范围删除
ctx.db.player().score().delete(..50); // 删除所有分数<50的行for player in ctx.db.player().iter() {
log::info!("{}: {}", player.name, player.score);
}
// Count rows
let total = ctx.db.player().count();for player in ctx.db.player().iter() {
log::info!("{}:{}", player.name, player.score);
}
// 统计行数
let total = ctx.db.player().count();#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
id: u64,
#[index(btree)]
level: u32,
name: String,
}#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
id: u64,
#[index(btree)]
level: u32,
name: String,
}#[spacetimedb::table(
name = score,
public,
index(name = by_player_level, btree(columns = [player_id, level]))
)]
pub struct Score {
player_id: u32,
level: u32,
points: i64,
}#[spacetimedb::table(
name = score,
public,
index(name = by_player_level, btree(columns = [player_id, level]))
)]
pub struct Score {
player_id: u32,
level: u32,
points: i64,
}// Prefix match (first column only)
for score in ctx.db.score().by_player_level().filter(&123u32) {
log::info!("Level {}: {} points", score.level, score.points);
}
// Full match
for score in ctx.db.score().by_player_level().filter((123u32, 5u32)) {
log::info!("Points: {}", score.points);
}
// Prefix with range on second column
for score in ctx.db.score().by_player_level().filter((123u32, 1u32..=10u32)) {
log::info!("Level {}: {} points", score.level, score.points);
}// 前缀匹配(仅第一列)
for score in ctx.db.score().by_player_level().filter(&123u32) {
log::info!("等级{}:{}分", score.level, score.points);
}
// 完全匹配
for score in ctx.db.score().by_player_level().filter((123u32, 5u32)) {
log::info!("分数:{}", score.points);
}
// 前缀+第二列范围查询
for score in ctx.db.score().by_player_level().filter((123u32, 1u32..=10u32)) {
log::info!("等级{}:{}分", score.level, score.points);
}#[spacetimedb::table(name = user_profile, public)]
pub struct UserProfile {
#[primary_key]
identity: Identity,
display_name: String,
created_at: Timestamp,
}
#[spacetimedb::reducer]
pub fn create_profile(ctx: &ReducerContext, display_name: String) -> Result<(), String> {
// Check if profile already exists
if ctx.db.user_profile().identity().find(ctx.sender).is_some() {
return Err("Profile already exists".to_string());
}
ctx.db.user_profile().insert(UserProfile {
identity: ctx.sender,
display_name,
created_at: ctx.timestamp,
});
Ok(())
}#[spacetimedb::table(name = user_profile, public)]
pub struct UserProfile {
#[primary_key]
identity: Identity,
display_name: String,
created_at: Timestamp,
}
#[spacetimedb::reducer]
pub fn create_profile(ctx: &ReducerContext, display_name: String) -> Result<(), String> {
// 检查是否已存在该用户的资料
if ctx.db.user_profile().identity().find(ctx.sender).is_some() {
return Err("该用户资料已存在".to_string());
}
ctx.db.user_profile().insert(UserProfile {
identity: ctx.sender,
display_name,
created_at: ctx.timestamp,
});
Ok(())
}#[spacetimedb::reducer]
pub fn update_my_profile(ctx: &ReducerContext, new_name: String) -> Result<(), String> {
// Only allow users to update their own profile
if let Some(mut profile) = ctx.db.user_profile().identity().find(ctx.sender) {
profile.display_name = new_name;
ctx.db.user_profile().identity().update(profile);
Ok(())
} else {
Err("Profile not found".to_string())
}
}#[spacetimedb::reducer]
pub fn update_my_profile(ctx: &ReducerContext, new_name: String) -> Result<(), String> {
// 仅允许用户更新自己的资料
if let Some(mut profile) = ctx.db.user_profile().identity().find(ctx.sender) {
profile.display_name = new_name;
ctx.db.user_profile().identity().update(profile);
Ok(())
} else {
Err("用户资料未找到".to_string())
}
}#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
log::info!("Database initializing...");
// Set up default data
if ctx.db.config().count() == 0 {
ctx.db.config().insert(Config {
key: "version".to_string(),
value: "1.0.0".to_string(),
});
}
Ok(())
}#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
log::info!("数据库初始化中...");
// 设置默认数据
if ctx.db.config().count() == 0 {
ctx.db.config().insert(Config {
key: "version".to_string(),
value: "1.0.0".to_string(),
});
}
Ok(())
}#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) -> Result<(), String> {
log::info!("Client connected: {}", ctx.sender);
// connection_id is guaranteed to be Some
let conn_id = ctx.connection_id.unwrap();
// Create or update user session
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
ctx.db.user().identity().update(User { online: true, ..user });
} else {
ctx.db.user().insert(User {
identity: ctx.sender,
online: true,
name: None,
});
}
Ok(())
}#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) -> Result<(), String> {
log::info!("客户端已连接:{}", ctx.sender);
// connection_id此时一定为Some
let conn_id = ctx.connection_id.unwrap();
// 创建或更新用户会话
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
ctx.db.user().identity().update(User { online: true, ..user });
} else {
ctx.db.user().insert(User {
identity: ctx.sender,
online: true,
name: None,
});
}
Ok(())
}#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) -> Result<(), String> {
log::info!("Client disconnected: {}", ctx.sender);
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
ctx.db.user().identity().update(User { online: false, ..user });
}
Ok(())
}#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) -> Result<(), String> {
log::info!("客户端已断开连接:{}", ctx.sender);
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
ctx.db.user().identity().update(User { online: false, ..user });
}
Ok(())
}use spacetimedb::ScheduleAt;
use std::time::Duration;
#[spacetimedb::table(name = game_tick_schedule, scheduled(game_tick))]
pub struct GameTickSchedule {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
scheduled_at: ScheduleAt,
}
#[spacetimedb::reducer]
fn game_tick(ctx: &ReducerContext, schedule: GameTickSchedule) {
// Verify this is an internal call (from scheduler)
if !ctx.sender_auth().is_internal() {
log::warn!("External call to scheduled reducer rejected");
return;
}
// Game logic here
log::info!("Game tick at {:?}", ctx.timestamp);
}use spacetimedb::ScheduleAt;
use std::time::Duration;
#[spacetimedb::table(name = game_tick_schedule, scheduled(game_tick))]
pub struct GameTickSchedule {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
scheduled_at: ScheduleAt,
}
#[spacetimedb::reducer]
fn game_tick(ctx: &ReducerContext, schedule: GameTickSchedule) {
// 验证是否为内部调用(来自调度器)
if !ctx.sender_auth().is_internal() {
log::warn!("拒绝外部调用调度Reducer");
return;
}
// 游戏逻辑写在这里
log::info!("游戏Tick触发于{:?}", ctx.timestamp);
}#[spacetimedb::reducer]
fn start_game_loop(ctx: &ReducerContext) {
// Schedule game tick every 100ms
ctx.db.game_tick_schedule().insert(GameTickSchedule {
scheduled_id: 0,
scheduled_at: ScheduleAt::Interval(Duration::from_millis(100).into()),
});
}#[spacetimedb::reducer]
fn start_game_loop(ctx: &ReducerContext) {
// 每100ms调度一次游戏Tick
ctx.db.game_tick_schedule().insert(GameTickSchedule {
scheduled_id: 0,
scheduled_at: ScheduleAt::Interval(Duration::from_millis(100).into()),
});
}#[spacetimedb::reducer]
fn schedule_reminder(ctx: &ReducerContext, delay_secs: u64) {
let run_at = ctx.timestamp + Duration::from_secs(delay_secs);
ctx.db.reminder_schedule().insert(ReminderSchedule {
scheduled_id: 0,
scheduled_at: ScheduleAt::Time(run_at),
message: "Time's up!".to_string(),
});
}#[spacetimedb::reducer]
fn schedule_reminder(ctx: &ReducerContext, delay_secs: u64) {
let run_at = ctx.timestamp + Duration::from_secs(delay_secs);
ctx.db.reminder_schedule().insert(ReminderSchedule {
scheduled_id: 0,
scheduled_at: ScheduleAt::Time(run_at),
message: "时间到了!".to_string(),
});
}undefinedundefined
```rust
use spacetimedb::{procedure, ProcedureContext};
// Simple procedure
#[procedure]
fn add_numbers(_ctx: &mut ProcedureContext, a: u32, b: u32) -> u64 {
a as u64 + b as u64
}
// Procedure with database access
#[procedure]
fn save_external_data(ctx: &mut ProcedureContext, url: String) -> Result<(), String> {
// HTTP request (allowed in procedures, not reducers)
let data = fetch_from_url(&url)?;
// Database access requires explicit transaction
ctx.try_with_tx(|tx| {
tx.db.external_data().insert(ExternalData {
id: 0,
content: data,
});
Ok(())
})?;
Ok(())
}
```rust
use spacetimedb::{procedure, ProcedureContext};
// 简单Procedure
#[procedure]
fn add_numbers(_ctx: &mut ProcedureContext, a: u32, b: u32) -> u64 {
a as u64 + b as u64
}
// 带数据库访问的Procedure
#[procedure]
fn save_external_data(ctx: &mut ProcedureContext, url: String) -> Result<(), String> {
// HTTP请求(允许在Procedure中执行,Reducer中不行)
let data = fetch_from_url(&url)?;
// 数据库访问需要显式事务
ctx.try_with_tx(|tx| {
tx.db.external_data().insert(ExternalData {
id: 0,
content: data,
});
Ok(())
})?;
Ok(())
}| Reducers | Procedures |
|---|---|
| |
Direct | Must use |
| No HTTP/network | HTTP allowed |
| No return values | Can return data |
| Reducers | Procedures |
|---|---|
| |
直接访问 | 必须使用 |
| 不允许HTTP/网络操作 | 允许HTTP操作 |
| 无返回值 | 可以返回数据 |
#[spacetimedb::reducer]
pub fn transfer_credits(
ctx: &ReducerContext,
to_user: Identity,
amount: u32,
) -> Result<(), String> {
let sender = ctx.db.user().identity().find(ctx.sender)
.ok_or("Sender not found")?;
if sender.credits < amount {
return Err("Insufficient credits".to_string());
}
// Perform transfer...
Ok(())
}#[spacetimedb::reducer]
pub fn transfer_credits(
ctx: &ReducerContext,
to_user: Identity,
amount: u32,
) -> Result<(), String> {
let sender = ctx.db.user().identity().find(ctx.sender)
.ok_or("发送方未找到")?;
if sender.credits < amount {
return Err("余额不足".to_string());
}
// 执行转账操作...
Ok(())
}#[spacetimedb::reducer]
pub fn process_data(ctx: &ReducerContext, data: Vec<u8>) {
// This should never happen - indicates a bug
assert!(!data.is_empty(), "Unexpected empty data");
// Use expect for operations that should always succeed
let parsed = parse_data(&data).expect("Failed to parse data");
}#[spacetimedb::reducer]
pub fn process_data(ctx: &ReducerContext, data: Vec<u8>) {
// 这永远不应该发生 — 表示存在Bug
assert!(!data.is_empty(), "意外的空数据");
// 对于应该始终成功的操作,使用expect
let parsed = parse_data(&data).expect("数据解析失败");
}#[derive(SpacetimeType)]use spacetimedb::SpacetimeType;
#[derive(SpacetimeType)]
pub enum PlayerStatus {
Active,
Idle,
Away,
}
#[derive(SpacetimeType)]
pub struct Position {
x: f32,
y: f32,
z: f32,
}
#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
id: u64,
status: PlayerStatus,
position: Position,
}#[derive(SpacetimeType)]use spacetimedb::SpacetimeType;
#[derive(SpacetimeType)]
pub enum PlayerStatus {
Active,
Idle,
Away,
}
#[derive(SpacetimeType)]
pub struct Position {
x: f32,
y: f32,
z: f32,
}
#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
id: u64,
status: PlayerStatus,
position: Position,
}#[spacetimedb::table]#[spacetimedb::table(name = online_player, public)]
#[spacetimedb::table(name = offline_player)]
pub struct Player {
#[primary_key]
identity: Identity,
name: String,
}
#[spacetimedb::reducer]
fn player_logout(ctx: &ReducerContext) {
if let Some(player) = ctx.db.online_player().identity().find(ctx.sender) {
ctx.db.offline_player().insert(player.clone());
ctx.db.online_player().identity().delete(&ctx.sender);
}
}#[spacetimedb::table]#[spacetimedb::table(name = online_player, public)]
#[spacetimedb::table(name = offline_player)]
pub struct Player {
#[primary_key]
identity: Identity,
name: String,
}
#[spacetimedb::reducer]
fn player_logout(ctx: &ReducerContext) {
if let Some(player) = ctx.db.online_player().identity().find(ctx.sender) {
ctx.db.offline_player().insert(player.clone());
ctx.db.online_player().identity().delete(&ctx.sender);
}
}logspacetime logs <database>log::trace!("Detailed trace info");
log::debug!("Debug information");
log::info!("General information");
log::warn!("Warning message");
log::error!("Error occurred");println!eprintln!dbg!logspacetime logs <database>log::trace!("详细跟踪信息");
log::debug!("调试信息");
log::info!("常规信息");
log::warn!("警告消息");
log::error!("发生错误");println!eprintln!dbg!#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
identity: Identity,
name: Option<String>,
online: bool,
last_seen: Timestamp,
}
#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) {
match ctx.db.player().identity().find(ctx.sender) {
Some(player) => {
ctx.db.player().identity().update(Player {
online: true,
last_seen: ctx.timestamp,
..player
});
}
None => {
ctx.db.player().insert(Player {
identity: ctx.sender,
name: None,
online: true,
last_seen: ctx.timestamp,
});
}
}
}
#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) {
if let Some(player) = ctx.db.player().identity().find(ctx.sender) {
ctx.db.player().identity().update(Player {
online: false,
last_seen: ctx.timestamp,
..player
});
}
}#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
identity: Identity,
name: Option<String>,
online: bool,
last_seen: Timestamp,
}
#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) {
match ctx.db.player().identity().find(ctx.sender) {
Some(player) => {
ctx.db.player().identity().update(Player {
online: true,
last_seen: ctx.timestamp,
..player
});
}
None => {
ctx.db.player().insert(Player {
identity: ctx.sender,
name: None,
online: true,
last_seen: ctx.timestamp,
});
}
}
}
#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) {
if let Some(player) = ctx.db.player().identity().find(ctx.sender) {
ctx.db.player().identity().update(Player {
online: false,
last_seen: ctx.timestamp,
..player
});
}
}#[spacetimedb::table(name = counter)]
pub struct Counter {
#[primary_key]
name: String,
value: u64,
}
#[spacetimedb::reducer]
fn create_invoice(ctx: &ReducerContext, amount: u64) -> Result<(), String> {
let mut counter = ctx.db.counter().name().find(&"invoice".to_string())
.unwrap_or(Counter { name: "invoice".to_string(), value: 0 });
counter.value += 1;
ctx.db.counter().name().update(counter.clone());
ctx.db.invoice().insert(Invoice {
invoice_number: counter.value,
amount,
});
Ok(())
}#[spacetimedb::table(name = counter)]
pub struct Counter {
#[primary_key]
name: String,
value: u64,
}
#[spacetimedb::reducer]
fn create_invoice(ctx: &ReducerContext, amount: u64) -> Result<(), String> {
let mut counter = ctx.db.counter().name().find(&"invoice".to_string())
.unwrap_or(Counter { name: "invoice".to_string(), value: 0 });
counter.value += 1;
ctx.db.counter().name().update(counter.clone());
ctx.db.invoice().insert(Invoice {
invoice_number: counter.value,
amount,
});
Ok(())
}#[spacetimedb::table(name = admin)]
pub struct Admin {
#[primary_key]
identity: Identity,
}
#[spacetimedb::reducer]
fn admin_action(ctx: &ReducerContext) -> Result<(), String> {
if ctx.db.admin().identity().find(ctx.sender).is_none() {
return Err("Not authorized".to_string());
}
// Admin-only logic here
Ok(())
}#[spacetimedb::table(name = admin)]
pub struct Admin {
#[primary_key]
identity: Identity,
}
#[spacetimedb::reducer]
fn admin_action(ctx: &ReducerContext) -> Result<(), String> {
if ctx.db.admin().identity().find(ctx.sender).is_none() {
return Err("未授权访问".to_string());
}
// 仅管理员可执行的逻辑
Ok(())
}undefinedundefinedundefinedundefinedctx.random()ctx.new_uuid_*()ctx.random()ctx.new_uuid_*()