SpacetimeDB C++ 快速上手:5 分钟用 C++ 编写可编译为 WebAssembly 的服务端模块
2026/9/13 9:54:37 网站建设 项目流程

SpacetimeDB C++ 快速上手:5 分钟用 C++ 编写可编译为 WebAssembly 的服务端模块

【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB

本篇指南基于 SpacetimeDB 官方 C++ 快速入门文档展开,完整覆盖从安装 Emscripten 工具链、创建basic-cpp模板项目,到编写表(Table)与 Reducer、并用 CLI 实时调用与查询的完整流程。读完本文,你将掌握使用 C++20 编写 SpacetimeDB 服务端模块的核心套路,并理解spacetime dev背后由 CMake +emcc驱动编译为 WebAssembly 的真实工作链路,可直接上手搭建自己的 C++ 后端模块。

前置条件

在开始之前,请确认本机已具备以下环境:

依赖版本要求用途
SpacetimeDB CLI最新稳定版创建项目、启动本地服务、构建发布模块、生成客户端绑定
Emscripten SDK4.0.21 及以上提供emcc编译器与 Emscripten CMake 工具链文件
CMake3.20 及以上驱动 C++ 模块的配置与构建
make / ninja任一后端CMake 的构建后端
C++20 工具链(宿主)C++20语法编译,最终通过 Emscripten 交叉编译为 WASM 目标

安装完 Emscripten SDK 后,必须执行对应的emsdk_env脚本(PowerShell 或 Bash),使emcc以及 CMake 工具链文件进入PATH,否则后续的spacetime build无法找到编译器。

第一步:安装并激活 Emscripten

SpacetimeDB 的 C++ 模块最终会被编译为 WebAssembly(WASM)运行在服务端,因此需要 Emscripten 提供的emcc编译器。推荐使用 4.0.21 及以上版本,按如下方式安装并激活环境:

# 从你的 emsdk 目录执行(下载/clone 之后) # Windows PowerShell ./emsdk install 4.0.21 ./emsdk activate 4.0.21 ./emsdk_env.ps1 # macOS/Linux ./emsdk install 4.0.21 ./emsdk activate 4.0.21 source ./emsdk_env.sh

激活后建议先验证工具链是否可用:

emcc --version cmake --version

从 CLI 的源码实现看,spacetime build在构建 C++ 模块前会依次检查emcccmakeemcmake是否存在于PATH(见 crates/cli/src/tasks/cpp.rs)。其中 Windows 平台会查找emcc.batcmake.exe,macOS/Linux 则查找无后缀的可执行文件;任一工具缺失都会直接报错中止构建。因此这一步的"激活环境"并非可选项,而是构建流程的硬性前置。

第二步:创建你的 C++ 项目

SpacetimeDB CLI 提供了模板驱动的开发流程。spacetime dev会为你完成一整条链路:启动本地服务器、构建并发布模块(内部封装了 CMake +emcc)、生成客户端绑定代码:

spacetime dev --template basic-cpp

该命令的核心是--template basic-cpp,对应的模板位于仓库的 templates/basic-cpp 目录。如果你更喜欢手动控制构建过程,也可以直接驱动 CMake +emcc(可参考模板内的 CMakeLists.txt),但官方推荐路径始终是spacetime build/spacetime dev,因为前者会替你处理好工具链检查、WASM 链接参数、输出产物定位等一系列细节。

CLI 是如何构建 C++ 模块的

spacetime dev最终会调用 CLI 内部的build_cpp逻辑(见 crates/cli/src/tasks/mod.rs 中ModuleLanguage::Cpp => build_cpp(...)的分支)。从 crates/cli/src/tasks/cpp.rs 可以还原出它的实际执行步骤:

  1. 校验emcc/cmake/emcmake三个可执行文件;
  2. 执行emcmake cmake -S . -B build -DCMAKE_BUILD_TYPE=<Debug|Release>完成配置(spacetime dev走 Debug,发布构建走 Release);
  3. 执行cmake --build build --config <build_type> --parallel进行编译链接;
  4. 递归扫描build/目录,取修改时间最新的.wasm文件作为模块产物(避免误用旧缓存)。

理解了这一链路,你就知道为什么"必须先激活 Emscripten 环境":emcmake是 Emscripten 提供的 CMake 包装器,缺少它整个配置阶段都无法启动。

第三步:探索项目结构

spacetime dev --template basic-cpp生成的项目结构如下:

my-spacetime-app/ ├── spacetimedb/ # 你的 C++ 模块 │ ├── CMakeLists.txt │ └── src/ │ └── lib.cpp # 服务端逻辑 ├── Cargo.toml └── src/ ├── module_bindings/ # 自动生成的 Rust 客户端绑定 └── main.rs # Rust 客户端应用

其中关键文件与真实仓库模板的对应关系为:

  • 服务端模块:lib.cpp 是模块的入口,表结构与所有 Reducer 都写在这里;CMakeLists.txt 负责把lib.cpp编译为 WASM 并链接 C++ 绑定 SDK。
  • 客户端绑定src/module_bindings/由 CLI 根据模块 schema 自动生成(见 templates/basic-cpp/src/module_bindings/mod.rs 顶部的 "AUTOMATICALLY GENERATED BY SPACETIMEDB" 注释,内含person_table.rsperson_type.rsadd_reducer.rssay_hello_reducer.rs等文件)。直接修改这些文件是无效的,改动表结构应回到lib.cpp重新构建生成。
  • Rust 客户端:main.rs 演示了连接数据库、订阅person表、注册on_insert回调的完整客户端写法,依赖的spacetimedb-sdk版本在 Cargo.toml 中声明。

CMakeLists.txt 关键配置速览

模板的 CMakeLists.txt 值得细读,它揭示了 C++ 模块的编译细节:

  • SPACETIMEDB_CPP_VERSION:SDK 版本选择器,MAJOR.MINOR(如2.10)会拉取release/MAJOR.MINOR分支的最新补丁,MAJOR.MINOR.PATCH则锁定vMAJOR.MINOR.PATCH精确标签;
  • SPACETIMEDB_CPP_REF:直接覆盖 Git ref(如release/latestmainv1.12.0);
  • SPACETIMEDB_CPP_DIR:指向本地 C++ 绑定的 clone 目录,用于绑定开发场景(会跳过 FetchContent 网络拉取);
  • 标准设为 C++20(CMAKE_CXX_STANDARD 20);
  • 在 Emscripten 平台下追加-fno-exceptions-O2 -g0编译选项,并链接-sSTANDALONE_WASM=1--no-entry-sINITIAL_MEMORY=16MB等 WASM 专用参数,导出_malloc_free___describe_module_____call_reducer__等运行时入口函数。

第四步:理解表(Table)与 Reducer

模板自带的lib.cpp展示了 SpacetimeDB C++ 模块最核心的两个抽象——(服务端持久化数据)与Reducer(客户端可远程调用的服务端函数)。模板包含一张Person表和两个 Reducer:add用于插入数据,say_hello用于遍历并打印日志:

#include "spacetimedb.h" using namespace SpacetimeDB; struct Person { std::string name; }; SPACETIMEDB_STRUCT(Person, name) SPACETIMEDB_TABLE(Person, person, Public) SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name) { ctx.db[person].insert(Person{name}); return Ok(); } SPACETIMEDB_REDUCER(say_hello, ReducerContext ctx) { for (const auto& person : ctx.db[person]) { LOG_INFO("Hello, " + person.name + "!"); } LOG_INFO("Hello, World!"); return Ok(); }

三个宏的职责划分

这段代码中的三个宏构成了 C++ 模块的基本骨架(完整参考见 skills/cpp-server/SKILL.md):

  • SPACETIMEDB_STRUCT(Person, name):把 C++ struct 注册为 SpacetimeDB 的行类型(product type);
  • SPACETIMEDB_TABLE(Person, person, Public):把该类型注册为一张表,第二个参数person是访问器名(accessor),后续通过ctx.db[person]访问;第三个参数Public/Private控制表是否对客户端可见;还可以传入第四个参数true将其声明为事件表(event table);
  • SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name):注册一个可被客户端调用的 Reducer,第一个参数是 reducer 名称(即调用时使用的名字),第二个参数是上下文ctx,其余是 reducer 入参。

Reducer 的上下文与返回值约定

  • 所有 Reducer 都以ReducerContext ctx开头,通过ctx访问ctx.db[表访问器](表访问)、ctx.sender()(调用者 Identity)、ctx.timestamp(确定性时间戳)、ctx.rng()(确定性随机数)等;
  • 每个 Reducer 必须返回ReducerResult:成功用Ok(),失败用Err("错误信息")Err会把错误信息返回给调用方;
  • 表字段约束通过字段宏声明,例如FIELD_PrimaryKey(accessor, field)(主键)、FIELD_PrimaryKeyAutoInc(accessor, field)(自增主键,插入时传 0)、FIELD_Unique(accessor, field)(唯一约束)、FIELD_Index(accessor, field)(B 树索引,启用.filter()查询)。

生命周期钩子

模板的lib.cpp中还包含三个常用的生命周期钩子(真实模板源码见 templates/basic-cpp/spacetimedb/src/lib.cpp):

  • SPACETIMEDB_INIT(init, ReducerContext ctx):模块首次发布时调用,适合做初始化逻辑;
  • SPACETIMEDB_CLIENT_CONNECTED(identity_connected, ReducerContext ctx):每当新客户端连接时调用;
  • SPACETIMEDB_CLIENT_DISCONNECTED(identity_disconnected, ReducerContext ctx):每当客户端断开时调用。

第五步:用 CLI 测试你的模块

spacetime dev会在前台运行并持续监听。打开另一个终端,进入项目目录,即可通过 CLI 直接调用 Reducer、执行 SQL 查询并查看日志:

cd my-spacetime-app # 插入一个人(调用 add reducer) spacetime call add Alice # 查询 person 表 spacetime sql "SELECT * FROM person" # 调用 say_hello,向所有人打招呼 spacetime call say_hello # 查看模块日志 spacetime logs

预期行为:

  • spacetime call add Alice会在person表插入一行{ name: "Alice" }
  • spacetime sql "SELECT * FROM person"返回当前表内全部数据;
  • spacetime call say_hello遍历表内每一行并打出Hello, <name>!日志,随后输出一行Hello, World!
  • spacetime logs展示模块的标准输出日志,包括上面所有LOG_INFO的内容。

与此同时,如果你保持main.rs的 Rust 客户端在运行,它订阅了person表并注册了on_insert回调,插入新行时终端还会打印New person: Alice,这直观展示了"服务端表变更实时推送至客户端"的订阅机制(见 templates/basic-cpp/src/main.rs)。

注意事项与常见问题

使用本地 SDK 克隆

默认情况下,CMake 通过 FetchContent 从远端拉取 C++ 绑定 SDK。如果你要使用本仓库内的本地绑定(仓库内即包含 crates/bindings-cpp 的完整 C++ 绑定源码),可以在运行spacetime dev/spacetime build之前设置环境变量SPACETIMEDB_CPP_SDK_DIR指向本地目录:

# Bash / macOS / Linux export SPACETIMEDB_CPP_SDK_DIR=/path/to/SpacetimeDB/crates/bindings-cpp # Windows PowerShell $env:SPACETIMEDB_CPP_SDK_DIR="E:\SpacetimeDB\crates\bindings-cpp"

该变量最终会落入 CMake 的SPACETIMEDB_CPP_DIR逻辑(见 templates/basic-cpp/spacetimedb/CMakeLists.txt),从而跳过 FetchContent 网络拉取,直接以add_subdirectory方式链接本地绑定,适合 SDK 二次开发场景。

异常处理被禁用

模板构建出的 WASM 模块默认禁用异常(编译选项-fno-exceptions,同时链接-sDISABLE_EXCEPTION_CATCHING=1)。这意味着模块代码中不应依赖 C++ 异常机制来传递错误,错误处理应遵循 SpacetimeDB 的约定——在 Reducer 中返回Err(...),并通过ReducerResult传播给调用方。

emcc 找不到怎么办

如果执行spacetime dev/spacetime build时提示emcc未找到,说明 Emscripten 环境未激活或PATH未持久化。重新运行对应的emsdk_env脚本(Windows 用emsdk_env.ps1,macOS/Linux 用source ./emsdk_env.sh)以填充环境变量后重试。CLI 在 Windows 上会额外通过cmd /C包装命令来兼容emcc.bat/emcmake.cmd这类批处理可执行文件,这一点在 crates/cli/src/tasks/cpp.rs 有明确实现。

延伸:从模板走向真实项目

模板只是起点。如果要在真实项目中使用 C++ 编写 SpacetimeDB 模块,可以结合仓库内 skills/cpp-server/SKILL.md 这份完整的 SDK 参考继续深入,它覆盖了以下常用能力:

  • 更丰富的字段约束FIELD_PrimaryKey/FIELD_PrimaryKeyAutoInc/FIELD_Unique/FIELD_Index/FIELD_NamedMultiColumnIndex(多列索引),以及基于索引的filter("Alice")、范围查询range_inclusive(18, 65)等;
  • CRUD 操作ctx.db[table].insert(...).find(key)(返回std::optional)、.filter(...).count()、查找后修改再update(*e)delete_by_key(...)
  • 自定义类型SPACETIMEDB_UNIT_TYPE/SPACETIMEDB_ENUM定义枚举(sum type);
  • 定时表(Scheduled Tables):通过SPACETIMEDB_SCHEDULE宏声明定时表,用ScheduleAt::time(...)做一次性触发、ScheduleAt::interval(...)做周期触发;
  • 鉴权与时间ctx.sender()校验调用者身份、ctx.timestamp获取确定性服务端时间戳、ctx.sender_auth()读取 JWT claims;
  • 日志分级LOG_INFO/LOG_WARN/LOG_ERROR/LOG_DEBUG/LOG_PANIC

仓库内还有完整的多语言客户端示例与测试(如 crates/bindings-cpp/tests),以及基于 C++ 的完整示例模块 modules/benchmarks-cpp、demo/Blackholio/server-cpp 等,可以作为阅读源码、理解真实用法的参考。

【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询