crates/codegen/src/UnrealCPP-README.md
This document provides information about the UnrealCPP code generator (unrealcpp.rs) and its Blueprint compatibility handling.
The UnrealCPP code generator creates Unreal Engine-compatible C++ bindings for SpacetimeDB modules, including:
Unreal Engine's Blueprint system has limitations on which C++ types can be exposed. The code generator automatically handles this by detecting incompatible types and adjusting the generated code accordingly.
The following types cannot be used in Unreal Engine Blueprints:
int8 (signed 8-bit integer)int16 (signed 16-bit integer)uint16 (unsigned 16-bit integer)uint32 (unsigned 32-bit integer)uint64 (unsigned 64-bit integer)These types can be used in Blueprints:
boolint8, uint8, int16, int32, int64float, doubleFStringAll SpacetimeDB SDK typesTArray<T> (if T is Blueprint-compatible)When a table has a primary key or unique index with an unsupported type like uint32. In generated code you'll see:
Generated Code:
// NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible
FMessageType Find(uint32 Key)
{
return IdIndexHelper.FindUniqueIndex(Key);
}
Behavior:
UFUNCTION(BlueprintCallable)When a reducer has parameters with unsupported types like uint32 and uint64:
Generated Code:
// NOTE: Not exposed to Blueprint because uint32, uint64 types are not Blueprint-compatible
void SendMessage(const FString& Text, const uint32& Priority, const uint64& Timestamp);
Behavior:
UFUNCTION(BlueprintCallable)When a reducer's event delegate has unsupported parameter types:
Generated Code:
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;
Behavior:
UPROPERTY(BlueprintAssignable)When struct fields have unsupported types:
Generated Code:
USTRUCT(BlueprintType)
struct MYMODULE_API FSendMessageArgs
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB")
FString Text;
// NOTE: uint32 types can't be used in blueprints
uint32 Priority;
// NOTE: uint64 types can't be used in blueprints
uint64 Timestamp;
};
Behavior:
UPROPERTY(BlueprintReadWrite)USTRUCT(BlueprintType) for the supported fieldsSpacetimeDB optional types (Option<T>) are generated as custom Unreal structs with special handling:
Generated Structure:
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...
};
Behavior:
Public/ModuleBindings/Optionals/ directorybHasValue field indicates whether a value is presentValue field is only editable when bHasValue is true (using EditCondition)Option<u32>), the Value field won't have UPROPERTYGetTypeHash implementation for proper map/set supportUE_SPACETIMEDB_OPTIONAL macroSpacetimeDB sum types (Rust enums with variants) are generated as UStructs + TVarint + BlueprintFunctionLibrary for Blueprint compatibility:
Generated Structure:
// 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;
TVariant<FQueryUpdateType, TArray<uint8>> MessageData;
UPROPERTY(BlueprintReadOnly)
ECompressableQueryUpdateTag Tag;
static FCompressableQueryUpdateType Uncompressed(const FQueryUpdateType& Value)
{
FCompressableQueryUpdateType Obj;
Obj.Tag = ECompressableQueryUpdateTag::Uncompressed;
Obj.MessageData.Set<FQueryUpdateType>(Value);
return Obj;
}
static FCompressableQueryUpdateType Brotli(const TArray<uint8>& Value)
{
FCompressableQueryUpdateType Obj;
Obj.Tag = ECompressableQueryUpdateTag::Brotli;
Obj.MessageData.Set<TArray<uint8>>(Value);
return Obj;
}
static FCompressableQueryUpdateType Gzip(const TArray<uint8>& Value)
{
FCompressableQueryUpdateType Obj;
Obj.Tag = ECompressableQueryUpdateTag::Gzip;
Obj.MessageData.Set<TArray<uint8>>(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.Get<FQueryUpdateType>();
}
};
// 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...
}
Key Behaviors:
FSpacetimeDBUnit typeSpacetimeDB plain enums (Rust enums with only unit variants, no payloads) are generated as simple Unreal Engine enums:
Rust Definition:
#[derive(...)]
pub enum Status {
Pending,
Active,
Inactive,
Suspended,
}
Generated Code:
UENUM(BlueprintType)
enum class EStatusType : uint8
{
Pending,
Active,
Inactive,
Suspended,
};
Key Behaviors:
uint8 backing typeUENUM(BlueprintType) makes it fully accessible in BlueprintsE[Name]Type format (e.g., EStatusType)Usage in Generated Code:
EStatusType in struct fields, function parameters, etc.Comparison with Sum Types:
UENUM with no payload → lightweight, direct Blueprint usageUStructs with TVarint payload → union based, indirect Blueprint usage through BPLibTo generate UnrealCPP bindings for your SpacetimeDB module, use the SpacetimeDB CLI:
cargo run --bin spacetimedb-cli -- generate --lang unrealcpp --uproject-dir <uproject_directory> --module-path <module_path> --unreal-module-name <ModuleName>
cargo run --bin spacetimedb-cli -- generate --lang unrealcpp --uproject-dir crates/sdk-unreal/examples/QuickstartChat --module-path modules/quickstart-chat --unreal-module-name QuickstartChat
--lang unrealcpp: Specifies the UnrealCPP code generator--uproject-dir: Directory containing your Unreal project's .uproject file--module-path: Path to your SpacetimeDB module source code--unreal-module-name: Required - Name used for generated classes, API prefix and putting generated module bindings in the correct Module's SourceThe --unreal-module-name parameter is mandatory for UnrealCPP generation because:
MODULENAME_API macros (e.g., QUICKSTARTCHAT_API) for proper DLL export/import in Unreal EngineFQuickstartChatOptionalString)⚠️ IMPORTANT: Without the module name, the generated code would not compile in Unreal Engine due to missing API macros and naming conflicts.
When generating into an Unreal project, the code generator also:
Modules array in the .uproject file.Source/<Module>/<Module>.Build.cs, Source/<Module>/<Module>.cpp, and Source/<Module>/<Module>.h files.If the .uproject file is missing, unreadable, malformed, or has an invalid Modules field, generation fails immediately.
The Blueprint compatibility checking is implemented in the is_blueprintable() function, which recursively checks:
The code generator collects incompatible types during the first parameter iteration to avoid duplicate loops and provides specific error messages listing exactly which types are causing Blueprint incompatibility.
All error messages follow a consistent format:
"uint32 types are not Blueprint-compatible""uint32, uint64 types are not Blueprint-compatible"This makes it clear to developers exactly which types need to be changed for Blueprint compatibility.