拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpacetimeDB 的 BSATN C++ 库:零依赖二进制序列化/反序列化实战指南

SpacetimeDB 的 BSATN C 库零依赖二进制序列化/反序列化实战指南【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读本文聚焦 SpacetimeDB 仓库中的自包含 C BSATNBinary SpacetimeDB Algebraic Type Notation序列化库它位于crates/bindings-cpp/include/spacetimedb/bsatn/目录。你将了解到如何在 C 客户端中仅引入若干头文件即可完成与 SpacetimeDB 服务端Rust/C#兼容的二进制数据序列化与反序列化、如何用SPACETIMEDB_STRUCT宏零手写代码地接入任意自定义结构体、以及Writer/Reader/AlgebraicType等核心组件的底层设计与文件组织。读完本文你可以直接在自己的 C20 项目中独立使用这套无外部依赖的 BSATN 序列化能力。一、BSATN 是什么跨语言兼容的代数类型二进制格式BSATNBinary SpacetimeDB Algebraic Type Notation二进制 SpacetimeDB 代数类型记法是 SpacetimeDB 用于在网络线上传输结构化数据的二进制序列化格式。它建立在一套代数类型系统之上——即用「和类型Sum对应枚举/标签联合」与「积类型Product对应结构体/元组」的组合来描述任意数据结构。在 bsatn.h 的主入口文件注释中明确写道This provides a complete serialization system compatible with Rust and C#——也就是说这套 C 实现与 SpacetimeDB 的 Rust 核心、C# SDK 保持线上格式一致客户端与服务端可以互操作。同时它也强调提供了AlgebraicType类型元数据系统、序列化 traits 与接口、类型注册表以及支持判别联合discriminated unions的和类型支持。关键设计目标包括支持全部基本类型bool、整数、浮点数、字符串支持容器类型std::vector、std::optional支持通过宏定义的用户自定义结构体支持和类型与判别联合内建 SpacetimeDB 特殊类型Identity、Timestamp、TimeDuration、ConnectionId、Uuid提供类型注册表以承载元数据与 Rust、C# 等语言的跨语言兼容。二、库的使用边界客户端只需一组头文件该目录下的 README 明确界定了 BSATN 库的两种使用场景C 客户端可以完全独立地使用它进行序列化/反序列化不需要任何 SpacetimeDB 模块依赖。你需要的东西What you need本目录下的全部头文件标准 C 库需支持C20——源码中大量使用 concepts 与 requires 约束例如 writer.h 中的write_primitive_le、traits.h 中的HasMemberSerialize概念零外部依赖所有文件只 include 其他 BSATN 头文件或标准 C 库头文件。你不需要的东西What you DONT needITypeRegistrar.h它仅供 SpacetimeDB 模块使用客户端完全不用理会类型注册功能类型注册服务于模块侧的表/Reducer 元数据登记客户端序列化场景不需要本目录之外的任何文件换言之整个库是自包含self-contained的你可以直接把这一个目录拷贝到自己的项目中使用。三、快速上手序列化与反序列化README 给出了一个完整的最小可运行示例这是理解整套 API 的最佳入口。完整代码如下可复制运行#include bsatn/bsatn.h // 定义你的结构体 struct MyData { uint32_t id; std::string name; }; // 定义序列化 traits宏自动生成全部序列化代码 SPACETIMEDB_STRUCT(MyData, id, name) // 序列化 MyData data{42, example}; std::vectoruint8_t buffer; SpacetimeDB::bsatn::Writer writer(buffer); SpacetimeDB::bsatn::serialize(writer, data); // 反序列化 SpacetimeDB::bsatn::Reader reader(buffer); auto result SpacetimeDB::bsatn::deserializeMyData(reader);这段代码揭示了三个核心 API 元素SPACETIMEDB_STRUCT(Type, field1, field2, ...)宏为类型生成完整的bsatn_traits特化Writer向std::vectoruint8_t缓冲区追加字节serialize/deserializeT顶层自由函数模板分发到对应类型的 traits 实现。单字节缓冲区的便捷形式在 serialization.h 中还提供了两个便捷辅助函数适合一次性「值 → 字节」或「字节 → 值」的转换using namespace SpacetimeDB::bsatn; // 序列化为字节向量 std::vectoruint8_t bytes to_bytes(MyData{42, example}); // 从字节向量反序列化 MyData back from_bytesMyData(bytes);以及用 C20 参数包实现的批量序列化Writer writer; serialize_all(writer, 42, hello, true, 3.14); // 一次性写入多个值四、SPACETIMEDB_STRUCT宏零手写代码的底层原理README 只展示了宏的用法而它的实现机制对理解整个库至关重要。macros.h 中宏展开的核心逻辑是为类型特化SpacetimeDB::bsatn::bsatn_traitsType生成三个静态成员函数template struct SpacetimeDB::bsatn::bsatn_traitsType { static void serialize(SpacetimeDB::bsatn::Writer w, const Type v) { SPACETIMEDB_SERIALIZE_FIELDS(v, w, __VA_ARGS__) } static Type deserialize(SpacetimeDB::bsatn::Reader r) { Type v; SPACETIMEDB_DESERIALIZE_FIELDS(v, r, __VA_ARGS__) return v; } static SpacetimeDB::bsatn::AlgebraicType algebraic_type() { return SpacetimeDB::Internal::LazyTypeRegistrarType::getOrRegister( []() - SpacetimeDB::bsatn::AlgebraicType { SpacetimeDB::bsatn::ProductTypeBuilder builder; SPACETIMEDB_REGISTER_FIELDS(Type, builder, __VA_ARGS__) // ...构建 ProductType }); } };也就是说宏实际替你生成了与下面等价的三段逻辑serialize按字段声明顺序依次调用bsatn::serialize(w, v.field)deserialize按字段顺序依次调用bsatn::deserializeFieldType(r)并填充algebraic_type通过ProductTypeBuilder登记每个字段的名字与类型产出用于模式schema描述/注册的ProductType。traits.h 中对这套机制有精确的文档化说明开发者永远不需要手写bsatn_serialize()或bsatn_deserialize()。traits.h同时定义了三个核心 C20 概念用于在编译期检测类型能力概念检测内容HasMemberSerialize类型是否具有t.bsatn_serialize(writer)成员方法HasStaticDeserialize类型是否具有静态T::bsatn_deserialize(reader)方法HasAlgebraicType是否特化了algebraic_type_ofT::get()可返回AlgebraicTypebsatn_traits主模板就是围绕这三个概念的if constexpr分发若有成员方法则调用之否则触发static_assert编译错误提示保证错误的类型在编译期即被拦截。枚举与std::variant的自动支持primitive_traits.h 中有一个泛型枚举特化任何enum class如enum class MyEnum : uint8_t都会自动以底层类型序列化无需额外声明templatetypename T requires std::is_enum_vT struct bsatn_traitsT { // 委托给底层类型underlying type的序列化实现 };此外bsatn_traitsstd::variantTs...在 traits.h 中实现为1 字节 tag0 表示第一个备选类型1 表示第二个……后跟当前活跃备选类型的载荷。注意备选类型的声明顺序就是 tag 编号重排会破坏线上格式兼容性。五、Writer与Reader的底层实现Writer小端序字节流写入器writer.h 中的Writer类维护一个内部std::vectoruint8_t缓冲区并支持两种构造方式Writer()使用内部自有缓冲区Writer(std::vectoruint8_t buffer)复用外部缓冲区所有写入直接追加到外部向量。写入模型是「先写长度前缀再写原始字节」。例如字符串与字节数组的编码均为inline void write_string(const std::string value) { write_u32_le(static_castuint32_t(value.size())); // 4 字节长度前缀 write_bytes_raw(value.data(), value.size()); // 原始字节 }所有多字节整数都遵循小端序little-endianwrite_u16_le/write_u32_le/write_u64_le以及 128/256 位整数write_u128_le按 low、high 两个 64 位字依次写出。write_primitive_le模板通过 C20 的requires (std::is_arithmetic_vT sizeof(T) 1)约束仅接受算术类型单字节类型走write_u8。值得注意的 SpacetimeDB 特有约定std::optional的判别字节是Some 0、None 1与标准 Rust 的 Option 判别相反。源码注释明确标注了这一点// SpacetimeDB uses non-standard Option discriminants: // Some 0, None 1 (reversed from standard Rust) if (opt_value.has_value()) { write_u8(0); // Some 0 (SpacetimeDB convention) serialize(*this, *opt_value); } else { write_u8(1); // None 1 (SpacetimeDB convention) }Reader带边界检查的顺序读取器reader.h 中的Reader持有一对指针current_ptr/end_ptr提供三种构造方式原始指针 长度、std::spanconst uint8_t、std::vectoruint8_t统一以uint8_t字节为操作单位。每个读取方法都会先调用私有的check_available(size_t)做越界检查不足时直接std::abort()终止避免越界读导致的未定义行为read_bool()还会校验值必须为 0 或 1。read_string()/read_bytes()先读 4 字节长度前缀再拷贝对应字节。Reader还提供is_eos()是否已读尽与remaining_bytes()剩余字节数两个实用的流状态查询接口。容器读取方面read_optionalT读取 1 字节 tag 后决定返回std::nullopt还是继续反序列化Tread_vectorT先读uint32_t长度并reserve再逐元素反序列化。反序列化的分发入口是deserializeT自由函数reader.h它统一转发到deserializerT::deserialize(r)。deserializer主模板默认委托给bsatn_traitsT并为所有基本类型、std::string、std::vectoruint8_t、std::optionalT、std::vectorT以及Identity/ConnectionId提供了显式特化。六、容器与和类型的序列化规格std::vectorT在 traits.h 中bsatn_traitsstd::vectorT的编码规则为[uint32 长度] [元素 0] [元素 1] ... [元素 N-1]嵌套容器如std::vectorstd::vectorint自动递归工作。代数类型层面数组始终内联Array 类型直接嵌入父类型不注册到类型空间这与 Rust 行为一致。std::optionalT编码规则为[1 字节判别] [如果 SomeT 的值] 判别 0x00 Some(value)0x01 Nonetraits.h 文档给出了直观的字节示例std::optionaluint32_t has_value 42; // 序列化结果: [00] [2A 00 00 00] // ^Some ^value42 std::optionaluint32_t empty; // 序列化结果: [01] // ^Nonestd::monostateUnit 类型bsatn_traitsstd::monostate序列化时不产生任何字节反序列化恒返回默认构造的std::monostate对应 SpacetimeDB 的 Unit 类型常用于枚举中的空载荷变体如SPACETIMEDB_ENUM中的Unit变体。七、SpacetimeDB 特殊类型带语义标签的元类型README 强调库内建了 Identity、ConnectionId、Timestamp、TimeDuration 等特殊类型它们以「带特定 tag 的字段」形式序列化以保持兼容性。type_extensions.h 定义了五个特殊类型的字段标签常量特殊类型标签tag底层数据Identity__identity__U25632 字节ConnectionId__connection_id__U12816 字节Timestamp__timestamp_micros_since_unix_epoch__I64微秒级时间戳TimeDuration__time_duration_micros__I64微秒级时长Uuid__uuid__U12816 字节源码注释对这些类型给出了明确的定义Identity256 位标识符IDENTITY_SIZE 32字节提供to_hex_string()、to_string()与比较运算符ConnectionId基于u128的连接标识此前曾用uint64_t导致运行时序列化崩溃源码注释记录了这次修复见 types.hTimestamp自 Unix 纪元以来的微秒数int64_t提供from_micros_since_epoch、from_millis_since_epoch、from_seconds_since_epoch与now()工厂方法timestamp.hTimeDuration微秒级时长int64_t可与 Timestamp 进行加减运算。在类型元数据层面这些特殊类型由 type_extensions.h 的special_types命名空间中的工厂函数构造它们表现为单字段的 ProductType字段名即语义标签。源码强调特殊类型一律内联传递真实类型而非 Ref 引用以匹配 Rust 实现。此外库还提供 128/256 位大整数类型u128/i128/u256/i256types.h均支持小端序 BSATN 编解码与十进制字符串互转ResultT, E则是一个结构性和类型含oktag 0与errtag 1两个变体。八、类型系统AlgebraicType 与类型标签algebraic_type.h 实现了与 Rust/C# 对齐的代数类型元数据系统用于描述任意类型的结构。核心是enum class AlgebraicTypeTag : uint8_t共 20 个标签标签值含义Ref0对另一类型的引用Sum1和类型标签联合/枚举Product2积类型结构体/元组Array3数组String4UTF-8 字符串Bool5布尔I8/U86/78 位有/无符号整数I16/U168/916 位有/无符号整数I32/U3210/1132 位有/无符号整数I64/U6412/1364 位有/无符号整数I128/U12814/15128 位有/无符号整数I256/U25616/17256 位有/无符号整数F32/F6418/1932/64 位浮点AlgebraicType类内部用std::variant存储类型专属数据Ref 存uint32_t类型 ID、Sum/Product/Array 存对应的 schema 智能指针、基本类型存std::monostate并提供了工厂方法模板化的primitiveAlgebraicTypeTag()生成基本类型make_product/make_sum/Array/Ref生成复合类型Unit()生成空积类型对应std::monostate或 Rust 的()。配套的数据结构包括ProductType有序元素集合每个ProductTypeElement含可选字段名 完整AlgebraicTypeSumTypeSchema变体集合每个SumTypeVariant含名称 完整AlgebraicTypeArrayType元素类型algebraic_type_ofT模板为基本类型与容器类型提供特化是 schema 元数据生成的入口。traits.h中的ProductTypeBuilder与SumTypeBuilder是仅供宏内部使用的构建器前者逐字段登记(名字, 类型)后者登记 Unit 变体用于SPACETIMEDB_ENUM。特殊类型识别由is_special_type/get_special_type_kind完成——空 Product 判定为 Unit、空 Sum 判定为 Never、单字段带标签的 Product 判定为 Identity/ConnectionId 等、双变体 Sum 按变体名判定为 ScheduleAt/Option/Result。九、类型注册为什么客户端可以忽略 ITypeRegistrarREADME 的架构说明指出ITypeRegistrar.h是「可选类型注册」的接口客户端可以完全忽略——它只被 SpacetimeDB 模块使用保留在此是为了避免循环依赖并保持架构整洁。从源码结构看类型注册服务于模块侧的 schema 登记如SPACETIMEDB_TABLE宏需要把表结构注册进数据库类型空间而纯客户端序列化只需要「字节 ↔ 值」的转换并不需要向任何注册表登记类型。SPACETIMEDB_STRUCT宏生成代码中虽然调用了LazyTypeRegistrarType::getOrRegister用于循环引用检测与惰性登记但这属于模块链路的一部分对于只做序列化往返的客户端场景Writer/Reader与serialize/deserialize已经足够。十、文件结构与工程组织README 给出了清晰的目录划分完整文件清单如下相对仓库根目录入口头文件bsatn.h包含全部核心组件是唯一需要显式 include 的入口核心三件套reader.hReader反序列化类与deserializer特化writer.hWriter序列化类与serialize重载serialization.h顶层serialize/deserialize概念约束版本与to_bytes/from_bytes/serialize_all辅助函数类型系统algebraic_type.hAlgebraicType、ProductType、SumTypeSchema、ArrayType及algebraic_type_ofTtraits.hbsatn_traits主模板、C20 概念、容器/变体特化、类型构建器primitive_traits.h基本类型 traits 特化与泛型枚举支持特殊类型types.hIdentity、ConnectionId、u128/i128/u256/i256、Option/Vec别名、BsatnSerializer旧式序列化器timestamp.h 与 time_duration.h时间类型uuid.hUUID 类型type_extensions.h特殊类型标签常量、special_types工厂、特殊类型 traits 特化合并自 special_types.h 与 extended_types.h必须先于 traits.h 引入types_impl.hSpacetimeDB 类型的 BSATN 方法实现避免循环依赖工具与和类型size_calculator.hSizeWriter/SizeCalculator在不真正序列化的情况下统计字节数对应 Rust 的 CountWriter 功能sum_type.hSumTypeTs...封装类对应旧名Sum与Optionresult.hResultT, E类型schedule_at.h 与 schedule_at_impl.hScheduleAt类型及其实现模块专用ITypeRegistrar.h客户端可忽略。大小预计算SizeCalculatorserialization.h 中还有一个值得注意的编译期能力static_bsatn_sizeT()可返回类型序列化后的静态字节数要求类型满足HasStaticSize。与之配套的运行时方案是 size_calculator.h 中的SizeWriter——它实现了与Writer相同的接口但不存储任何字节仅累加size_计数可用于在分配缓冲区前预知序列化长度。例如字符串总是按「4 字节长度前缀 数据长度」计数。十一、架构设计要点小结从 README 的架构说明与源码实现可以归纳出本库的四个设计原则自包含Self-contained全部文件只依赖标准 C 库与彼此可直接整体拷贝使用是bindings-cpp客户端 SDK 中可独立取用的子库跨语言一致Cross-language compatible小端序、Some0/None1的 Option 判别、特殊类型的语义标签、AlgebraicTypeTag枚举等均与 Rust/C# 实现严格对齐确保线上字节流互通编译期安全Compile-time safetyC20 conceptsSerializable、Deserializable、HasMemberSerialize等把绝大多数类型错误拦截在编译期错误的类型会触发带提示信息的static_assert清晰的分层Clean layering宏用户入口→ traits分发机制→ Writer/Reader字节层→ AlgebraicType元数据层ITypeRegistrar单独隔离以规避循环依赖客户端与模块两种消费场景各取所需。如果你的目标是纯客户端数据交换仅需引入bsatn.h、定义结构体并调用SPACETIMEDB_STRUCT宏即可获得与 SpacetimeDB 服务端完全兼容的二进制序列化能力如果进一步需要为模块贡献 schema 元数据则可以在该库之上继续使用模块侧的注册与表宏体系。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门