ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpacetimeDB UnrealCPP 代码生成器:Blueprint 兼容性处理与 Unreal 模块绑定生成指南

2026/9/12 5:31:09 拓冰建站 浏览量
SpacetimeDB UnrealCPP 代码生成器:Blueprint 兼容性处理与 Unreal 模块绑定生成指南 SpacetimeDB UnrealCPP 代码生成器Blueprint 兼容性处理与 Unreal 模块绑定生成指南【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读本文基于 SpacetimeDB 仓库中的 UnrealCPP-README.md 及对应的 unrealcpp.rs 源码系统讲解 UnrealCPP 代码生成器如何为 SpacetimeDB 模块生成 Unreal Engine 兼容的 C 绑定以及它如何自动识别 Unreal Blueprint蓝图系统不支持的 C 类型并调整生成策略。读完本文你将掌握哪些 SpacetimeDB 类型无法暴露给蓝图、生成器在表查找函数/Reducer 函数/事件代理/结构体字段等场景下的具体降级行为、Optional 与 Sum Type 等复杂代数类型的生成形态以及如何用 SpacetimeDB CLI 一步生成整套 Unreal 模块绑定。一、UnrealCPP 代码生成器概述UnrealCPP 代码生成器是 SpacetimeDB CLI 代码生成后端之一实现在 unrealcpp.rs共 7000 余行是 codegen crate 中体量最大的后端并在 lib.rs 中以pub mod unrealcpp注册。它面向 Unreal Engine 开发者负责把 SpacetimeDB 模块的 schema 定义翻译成一组 Unreal C 绑定文件覆盖五类核心产物Table classes表类为每张表生成行结构体如FUserType、FMessageType与表操作类如UQuickstartChatMessageTable并提供唯一索引查找、普通 BTree 索引过滤与事件处理入口Reducer classesReducer 类为每个 reducer 生成带参调用函数与参数结构体供 C 与蓝图发起事务调用Type definitions类型定义为模块中的自定义结构体、Optional、Sum Type带载荷枚举、Plain Enum简单枚举生成对应的 USTRUCT/UENUM 定义Client connection 管理类封装连接建立、鉴权与订阅等客户端生命周期逻辑Event delegates事件代理通过DECLARE_DYNAMIC_MULTICAST_DELEGATE_*声明动态多播代理如FSendMessageHandler将 reducer 触发的事件实时推送给客户端。从源码结构看生成器通过UnrealCpp结构体unrealcpp.rs持有module_name、uproject_dir、module_prefix三个关键上下文其中module_prefix是 Unreal 模块名前缀直接决定生成的类名、API 宏与目录结构。二、Blueprint 兼容性的核心规则is_blueprintable()Unreal Engine 的 Blueprint 系统并非所有 C 类型都能暴露尤其是内置整型存在明显的白名单限制。UnrealCPP 生成器将这一约束内建在is_blueprintable()函数中unrealcpp.rs以递归方式判断某个代数类型能否用于蓝图。2.1 蓝图不支持的原始类型以下是无法在 Unreal Blueprint 中使用的类型is_blueprintable()直接返回falseRust 类型C 类型说明i8int8有符号 8 位整数i16int16有符号 16 位整数u16uint16无符号 16 位整数u32uint32无符号 32 位整数u64uint64无符号 64 位整数值得注意的细节是is_blueprintable()中并未把int8列入黑名单——源码只对I8 | I16 | U16 | U32 | U64返回false。也就是说虽然int8在部分 Unreal 版本中也有反射限制但当前仓库的生成器判定标准以源码为准int8int8被当作可蓝图类型处理文档正文中 Blueprint-Compatible 类型列表也把int8、uint8列为可用的类型之一。2.2 蓝图兼容的类型以下类型可以直接在 Blueprint 中使用boolint8、uint8、int16、int32、int64float、doubleFString对应 Rust 的String全部 SpacetimeDB SDK 类型Identity、ConnectionId、Timestamp、TimeDuration、Uuid、ScheduleAt等自定义结构体USTRUCT与枚举UENUMTArrayT当且仅当元素类型T本身可蓝图化源码中AlgebraicTypeUse::Ref(r)分支进一步印证了结构体本身可蓝图化的规则USTRUCT、Sum Type、Plain Enum 作为字段类型时一律视为可蓝图化unrealcpp.rs但结构体内部的字段是否暴露仍需逐字段判断——这正是后续Struct Fields章节降级注释的根源。2.3 两套判定函数的差异生成器中实际上存在两个判定函数容易混淆is_blueprintable()unrealcpp.rs用于函数参数、结构体字段等常规场景。它对数组采取宽松策略——TArrayUSTRUCT永远可蓝图化即使 USTRUCT 内部含不可蓝图字段而TArray基本类型则递归检查元素类型。is_type_blueprintable_for_delegates()unrealcpp.rs专用于 reducer 事件代理参数判定对自定义结构体一律放行所有结构体都可作为代理参数但对Array分支要求元素类型递归可判定。此外还有一个面向 Optional 内部类型的名称级判定辅助函数is_type_name_blueprintable()unrealcpp.rs用于决定 Optional 包装结构的Value字段是否打UPROPERTY。三、各生成场景下的降级行为当检测到不可蓝图类型时生成器不会报错终止而是降级生成仍产出完整可用的 C 代码只是省略UFUNCTION(BlueprintCallable)/UPROPERTY(BlueprintAssignable)等反射标记并插入说明性注释。以下逐一展开。3.1 表查找函数Table Find Functions当表的主键或唯一索引使用uint32这类不可蓝图类型时生成的Find函数形如// NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible FMessageType Find(uint32 Key) { return IdIndexHelper.FindUniqueIndex(Key); }对应的生成逻辑见 unrealcpp.rsis_blueprintable()为真时才输出UFUNCTION(BlueprintCallable, Category SpacetimeDB|{Table}Index)否则输出 NOTE 注释。注意源码中注释使用的是{key_type}即实际 C 类型名uint32与文档示例一致。行为要点函数照常生成只是没有UFUNCTION标记在 C 中完整可用通过内部模板助手FUniqueIndexHelper{RowStruct}, {KeyType}, FTableCache{RowStruct}的FindUniqueIndex(Key)完成查找注释精确列出导致不兼容的类型方便开发者定位并修改模块定义。生成器同时也为非唯一 BTree 索引生成Filter方法与对应的UFUNCTION封装unrealcpp.rs当多列参数中有任一列不可蓝图化时会输出// NOTE: Not exposed to Blueprint because some parameter types are not Blueprint-compatible并跳过反射标记。3.2 Reducer 函数当 reducer 的参数包含uint32、uint64等类型时// NOTE: Not exposed to Blueprint because uint32, uint64 types are not Blueprint-compatible void SendMessage(const FString Text, const uint32 Priority, const uint64 Timestamp);生成逻辑见 unrealcpp.rs在生成调用方法前代码先遍历reducer.params_for_generate.elements收集不可蓝图参数若列表为空则输出UFUNCTION(BlueprintCallable, CategorySpacetimeDB)否则输出 NOTE 注释 普通成员函数。多个不兼容类型在注释中用,拼接。行为要点函数完整生成可在 C 中调用 reducer多个不可蓝图类型会被一次性列出注释形如uint32, uint64 types are not Blueprint-compatible若全部参数可蓝图化则参数类型会进一步通过cpp_ty_fmt_blueprint_compatible()映射为蓝图安全类型若存在不可蓝图参数则保留原生 C 类型。3.3 Reducer 事件代理Event Delegates当 reducer 事件代理参数含不兼容类型时DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams( FSendMessageHandler, const FReducerEventContext, Context, const FString, Text, const uint32, Priority, const uint64, Timestamp ); // NOTE: Not exposed to Blueprint because uint32, uint64 types are not Blueprint-compatible FSendMessageHandler OnSendMessage;生成逻辑见 unrealcpp.rs。代理宏按参数个数映射OneParam到NineParams见 unrealcpp.rsContext固定作为第一个参数。当non_blueprintable_types_for_delegate非空时UPROPERTY(BlueprintAssignable, CategorySpacetimeDB)被替换为 NOTE 注释成员变量On{ReducerName}依旧声明。行为要点代理照常声明可从 C 绑定OnSendMessage.AddDynamic(...)蓝图无法访问该事件无BlueprintAssignable注意源码里代理参数使用的是cpp_ty_fmt_blueprint_compatible()映射后的类型并依据should_pass_by_value_in_delegate()决定按值仅原生标量或按const 引用传递FSpacetimeDB 结构体等复杂类型。参数多于 9 个的边界情况Unreal 动态代理最多支持 9 个参数。当 reducer 参数超过 9 个时生成器改用DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams把参数整体包装进一个F{Reducer}Args结构体unrealcpp.rs此时代理因Args结构体本身是 USTRUCT 而恒可蓝图化但函数层面仍需逐个参数判定。3.4 结构体字段Struct Fields当结构体字段含不可蓝图类型时USTRUCT(BlueprintType) struct MYMODULE_API FSendMessageArgs { GENERATED_BODY() UPROPERTY(BlueprintReadWrite, CategorySpacetimeDB) FString Text; // NOTE: uint32 types cant be used in blueprints uint32 Priority; // NOTE: uint64 types cant be used in blueprints uint64 Timestamp; };行为要点可蓝图字段获得UPROPERTY(BlueprintReadWrite)不可蓝图字段保持普通 C 成员并附带说明注释整个结构体仍是USTRUCT(BlueprintType)可蓝图字段照常被反射系统识别。这意味着包含不可蓝图字段的结构体本身仍能作为函数参数遵循is_blueprintable()中USTRUCT 永远可蓝图化的规则只是其内部不可蓝图字段在蓝图中不可见、不可编辑。四、Optional 类型OptionT的生成SpacetimeDB 的OptionT在 Unreal 中没有原生对应物生成器将其翻译为自定义 USTRUCT存放在Public/ModuleBindings/Optionals/目录下生成路径见 unrealcpp.rs并通过bHasValueValue双字段模拟有值/无值语义USTRUCT(BlueprintType) struct MYMODULE_API FMyModuleOptionalString { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category SpacetimeDB, meta (EditCondition bHasValue)) bool bHasValue false; // Only gets UPROPERTY if the inner type is Blueprint-compatible UPROPERTY(EditAnywhere, BlueprintReadWrite, Category SpacetimeDB, meta (EditCondition bHasValue)) FString Value; // Constructors and helper methods... };4.1 命名与映射规则get_optional_type_name()unrealcpp.rs建立了从内部类型到 Optional 名称的完整映射表内部类型Optional 名称说明boolOptionalBool基本布尔i8~u256OptionalInt8~OptionalUInt256全部整数宽度f32/f64OptionalFloat/OptionalDouble浮点StringOptionalString字符串Identity/ConnectionIdOptionalIdentity/OptionalConnectionId身份类Timestamp/TimeDurationOptionalTimestamp/OptionalTimeDuration时间类Uuid/ScheduleAtOptionalUuid/OptionalScheduleAt唯一 ID / 调度TArrayEOptionalVec{ElementName}数组Ref(自定义类型)Optional{TypeName}自定义结构体OptionOptionTOptionalOptional...嵌套 OptionalResultOk, ErrOptional{ResultName}Result 包装最终生成的类型名为F{ModuleNamePascal}{OptionalName}如FQuickstartChatOptionalString结构体定义由generate_optional_type()unrealcpp.rs输出。4.2 关键行为bHasValue语义布尔标记指示值是否存在IsSet()与Reset()辅助方法分别查询与清除该标记EditCondition联动Value字段的UPROPERTY带meta (EditCondition bHasValue)在编辑器属性面板中只有勾选bHasValue后才能编辑Value按内部类型降级若内部类型不可蓝图化如Optionu32Value字段不输出UPROPERTY仅保留普通成员与// NOTE: ...注释判断依据is_type_name_blueprintable()相等比较生成operator/operator!语义为标记相等且无值或值相等哈希支持生成自定义GetTypeHash组合bHasValue与Value的哈希保证 Optional 可安全用于TSet/TMap序列化生成UE_SPACETIMEDB_OPTIONAL({StructName}, bHasValue, Value)宏unrealcpp.rs与UE_SPACETIMEDB_ENABLE_TARRAY接入 BSATN 序列化体系保证与服务器 wire 格式一致。五、Sum Type带载荷枚举的生成SpacetimeDB 的 sum typeRust 中带 payload 的 enum如enum QueryUpdate { Uncompressed(...), Brotli(...), Gzip(...) }无法直接映射为 UE 枚举。生成器采用UENUM 标签 USTRUCT(TVariant) UBlueprintFunctionLibrary的组合方案实现见autogen_cpp_sum()unrealcpp.rs 起// Tag enum for variant identification UENUM(BlueprintType) enum class ECompressableQueryUpdateTag : uint8 { Uncompressed, Brotli, Gzip }; // Main struct USTRUCT(BlueprintType) struct SPACETIMEDBSDK_API FCompressableQueryUpdateType { GENERATED_BODY() public: FCompressableQueryUpdateType() default; TVariantFQueryUpdateType, TArrayuint8 MessageData; UPROPERTY(BlueprintReadOnly) ECompressableQueryUpdateTag Tag; static FCompressableQueryUpdateType Uncompressed(const FQueryUpdateType Value) { FCompressableQueryUpdateType Obj; Obj.Tag ECompressableQueryUpdateTag::Uncompressed; Obj.MessageData.SetFQueryUpdateType(Value); return Obj; } static FCompressableQueryUpdateType Brotli(const TArrayuint8 Value) { FCompressableQueryUpdateType Obj; Obj.Tag ECompressableQueryUpdateTag::Brotli; Obj.MessageData.SetTArrayuint8(Value); return Obj; } static FCompressableQueryUpdateType Gzip(const TArrayuint8 Value) { FCompressableQueryUpdateType Obj; Obj.Tag ECompressableQueryUpdateTag::Gzip; Obj.MessageData.SetTArrayuint8(Value); return Obj; } // Is* functions bool IsUncompressed() const { return Tag ECompressableQueryUpdateTag::Uncompressed; } // GetAs* functions FQueryUpdateType GetAsUncompressed() const { ensureMsgf(IsUncompressed(), TEXT(MessageData does not hold Uncompressed!)); return MessageData.GetFQueryUpdateType(); } }; // Corresponding blueprint function library for using the sum types UCLASS() class SPACETIMEDBSDK_API UCompressableQueryUpdateBpLib : public UBlueprintFunctionLibrary { GENERATED_BODY() private: UFUNCTION(BlueprintCallable, Category SpacetimeDB|CompressableQueryUpdate) static FCompressableQueryUpdateType Uncompressed(const FQueryUpdateType InValue) { return FCompressableQueryUpdateType::Uncompressed(InValue); } UFUNCTION(BlueprintPure, Category SpacetimeDB|CompressableQueryUpdate) static bool IsUncompressed(const FCompressableQueryUpdateType InValue) { return InValue.IsUncompressed(); } UFUNCTION(BlueprintPure, Category SpacetimeDB|CompressableQueryUpdate) static FQueryUpdateType GetAsUncompressed(const FCompressableQueryUpdateType InValue) { return InValue.GetAsUncompressed(); } // Rest is the same for other variants... }5.1 关键行为UStruct TVariant 组合载荷存储于 UnrealTVariant相比逐一持有所有成员更节省内存本质是 C union 的封装标签枚举E{Name}Tag : uint8标识当前变体配合UPROPERTY(BlueprintReadOnly)暴露给蓝图读取类型安全每个变体拥有独立的静态工厂函数Uncompressed(...)、Brotli(...)与只读访问器IsUncompressed()/GetAsUncompressed()GetAs*内部用ensureMsgf断言当前 Tag 匹配避免错误访问单位变体不带载荷的变体使用FSpacetimeDBUnit类型占位从类型映射表可见Unit FSpacetimeDBUnitunrealcpp.rs蓝图完全可访问生成对应的UBlueprintFunctionLibrary将工厂、判断、取值函数分别包装为BlueprintCallable/BlueprintPure静态函数且按SpacetimeDB|{SumTypeName}分类蓝图节点可直接调用BSATN 支持生成带序列化宏与服务器 wire 格式保持一致相等与哈希生成operator/operator!按 Tag 分支比较各变体载荷与自定义GetTypeHash。六、Plain Enum简单枚举的生成Rust 中仅含 unit 变体无载荷的 enum 会被识别为 Plain Enum直接翻译为标准 Unreal 枚举类实现见autogen_cpp_enum()unrealcpp.rs。Rust 定义#[derive(...)] pub enum Status { Pending, Active, Inactive, Suspended, }生成的 CUENUM(BlueprintType) enum class EStatusType : uint8 { Pending, Active, Inactive, Suspended, };6.1 关键行为轻量 UENUM直接映射为标准enum class底层类型固定为uint8无任何内存开销Blueprint 全兼容UENUM(BlueprintType)使其可用于蓝图下拉选择、switch 节点等命名约定E{Name}Type格式如EStatusType变体统一转为 PascalCase源码中通过.to_case(Case::Pascal)实现unrealcpp.rs类型安全强类型enum class杜绝隐式整数转换序列化生成UE_SPACETIMEDB_ENABLE_TARRAY(E{Name}Type)支持 TArray 与 BSATN。6.2 与 Sum Type 的对比维度Plain EnumSum Type生成形态简单UENUMUStruct TVariant BPLib载荷无有变体可携带数据内存开销无union 语义TVariant 占用最大成员空间蓝图用法直接使用通过 UBlueprintFunctionLibrary 间接构造/访问典型场景状态码、状态机枚举Result、QueryUpdate等多形态数据七、使用 CLI 生成模块绑定7.1 基本命令cargo run --bin spacetimedb-cli -- generate --lang unrealcpp --uproject-dir uproject_directory --module-path module_path --unreal-module-name ModuleName7.2 仓库内示例仓库自带的 Unreal 示例工程位于 sdks/unreal/examples/QuickstartChat含QuickstartChat.uproject与Source/目录对应生成命令cargo run --bin spacetimedb-cli -- generate --lang unrealcpp --uproject-dir crates/sdk-unreal/examples/QuickstartChat --module-path modules/quickstart-chat --unreal-module-name QuickstartChat注示例路径在当前仓库为sdks/unreal/examples/QuickstartChat--module-path指向存放 SpacetimeDB 模块 Rust 源码的目录。7.3 参数说明参数必需说明--lang unrealcpp是选择 UnrealCPP 代码生成后端--uproject-dir是包含 Unreal 工程.uproject文件的目录--module-path是SpacetimeDB 模块源码路径Rust 模块定义--unreal-module-name必填生成类的前缀、API 宏名及绑定文件的落盘模块目录7.4 为什么--unreal-module-name是强制的UnrealCpp结构体通过get_api_macro()unrealcpp.rs生成{MODULE_NAME}_API形式的宏例如QUICKSTARTCHAT_API。该参数必不可少原因有四Unreal API 宏生成的类均以MODULENAME_API宏声明如struct QUICKSTARTCHAT_API FMessageType保证跨模块的 DLL 导出/导入正确链接类名前缀所有 Optional 等生成类以模块名做前缀如FQuickstartChatOptionalString避免多模块命名冲突构建系统集成Unreal 构建系统UBT依赖 API 宏完成模块间符号解析缺失会导致链接失败落盘位置生成的绑定文件需写入Source/{ModuleName}/...对应模块源码目录确保被目标模块编译进同一 DLL。⚠️ 若不提供模块名生成的代码将因缺失 API 宏与命名冲突而无法在 Unreal Engine 中编译。7.5 工程装配行为生成器向 Unreal 工程写入绑定文件的同时还会自动完成工程装配相关逻辑见 unrealcpp.rs 与ensure_module_in_uproject()unrealcpp.rs校验/注入Modules数组读取--uproject-dir下唯一的.uproject文件并解析 JSON若缺少Modules字段则自动补一个空数组若目标模块不在Modules中则注入模块项补齐模块骨架文件自动创建Source/{Module}/{Module}.Build.cs、Source/{Module}/{Module}.cpp、Source/{Module}/{Module}.h模块头中#include Modules/ModuleManager.h并实现模块类。失败条件若.uproject文件缺失、不可读、JSON 格式非法或顶层不是对象、Modules字段不是数组生成会立即失败并给出明确错误如No .uproject file found in ...。八、实现细节与错误信息约定8.1 兼容性检查的递归实现is_blueprintable()的判定递归覆盖全部代数类型分支Primitive黑名单i8/i16/u16/u32/u64之外的原语一律放行ArrayTArrayUSTRUCT/UENUM/PlainEnum恒可蓝图化基本类型数组递归判断元素String/Identity/ConnectionId/Timestamp/TimeDuration/Uuid/ScheduleAt/Unit均返回trueRef结构体、Sum、Plain Enum 引用均视为可蓝图化字段级仍逐字段判断Option递归判定内部类型Result要求Ok与Err两侧同时可蓝图化Never返回false。8.2 错误信息的一致性生成器在遍历 reducer 参数/代理参数/结构体字段时一次性收集所有不兼容类型避免重复循环并在注释中列出。错误信息遵循统一格式实现于 unrealcpp.rs 等处的join(, )单类型uint32 types are not Blueprint-compatible多类型uint32, uint64 types are not Blueprint-compatible这一约定让开发者能一眼看出需要修改模块中的哪些字段类型才能获得完整的 Blueprint 支持。九、实践建议结合上述规则若希望模块函数、事件与结构体字段获得最大化的 Blueprint 覆盖可从以下几点入手优先使用可蓝图整型模块定义中尽量使用i32、i64、u8避免u32/u64/i8/i16/u16确实需要无符号宽整型时明确其只能用于 C 调用链善用 Optional 与 Sum Type 封装OptionT与 sum type 均被翻译为 USTRUCT 形态可以在蓝图中传递对不可蓝图内部类型Value 字段会失去反射标记但整个结构体仍可作为参数遵循注释提示修正模块生成代码中的// NOTE: ... not Blueprint-compatible注释就是最直接的体检报告按注释修改模块类型后重新生成即可恢复 Blueprint 暴露确保 Unreal 工程结构完整--unreal-module-name必须与工程目标模块一致--uproject-dir下必须存在合法的.uproject文件否则生成立即失败保留 SDK 类型不动Identity、Timestamp、Uuid等 SpacetimeDB SDK 内建类型已保证 Blueprint 兼容可作为替代方案减少自定类型的暴露风险。十、关联资源UnrealCPP-README.md本文所依据的官方说明文档unrealcpp.rsUnrealCPP 代码生成后端完整实现is_blueprintable()见 L6286、generate_optional_type()见 L5715、autogen_cpp_sum()见 L6402、autogen_cpp_enum()见 L6373、ensure_module_in_uproject()见 L6962lib.rs代码生成后端注册入口pub mod unrealcppsdks/unreal/examples/QuickstartChatUnreal 示例工程可作为--uproject-dir与生成结果的参考sdks/unrealUnreal SDK 源码与文档包含ModuleBindings/Optionals等生成物所依赖的运行时基础【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考