The SpacetimeDB Unreal client SDK
The SpacetimeDB client for Unreal Engine contains all the tools you need to build native clients for SpacetimeDB modules using C++ and Blueprint.
Name | Description |
---|---|
Project setup | Configure an Unreal project to use the SpacetimeDB Unreal client SDK. |
Generate module bindings | Use the SpacetimeDB CLI to generate module-specific types and interfaces. |
DbConnection type | A connection to a remote database. |
Context interfaces | Context objects for interacting with the remote database in callbacks. |
Access the client cache | Access to your local view of the database. |
Observe and invoke reducers | Send requests to the database to run reducers, and register callbacks to run when notified of reducers. |
Subscriptions | Subscribe to queries and manage subscription lifecycle. |
Identify a client | Types for identifying users and client connections. |
Project setup
Using the Unreal Engine Plugin
Add the SpacetimeDB Unreal SDK to your project as a plugin. The SDK provides both C++ and Blueprint APIs for connecting to SpacetimeDB modules.
Generate module bindings
Each SpacetimeDB client depends on some bindings specific to your module. Generate the Unreal interface files using the Spacetime CLI. From your project directory, run:
spacetime generate --lang unrealcpp --uproject-dir <uproject_directory> --project-path <module_path> --module-name <module_name>
Replace:
<uproject_directory>
with the path to your Unreal project directory (containing the.uproject
file)<module_path>
with the path to your SpacetimeDB module<module_name>
with the name of your Unreal module, typically the name of the project
Example:
spacetime generate --lang unrealcpp --uproject-dir /path/to/MyGame --project-path /path/to/quickstart-chat --module-name QuickstartChat
This generates module-specific bindings in your project's ModuleBindings
directory.
Type `DbConnection`
A connection to a remote database is represented by the UDbConnection
class. This class is generated per module and contains information about the types, tables, and reducers defined by your module.
Name | Description |
---|---|
Connect to a database | Construct a UDbConnection instance. |
Advance the connection | The connection processes messages automatically via WebSocket callbacks. |
Access tables and reducers | Access the client cache, request reducer invocations, and register callbacks. |
Connect to a database
class UDbConnection
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
static UDbConnectionBuilder* Builder();
};
Construct a UDbConnection
by calling UDbConnection::Builder()
, chaining configuration methods, and finally calling .Build()
. At a minimum, you must specify WithUri
to provide the URI of the SpacetimeDB instance, and WithModuleName
to specify the database's name or identity.
Name | Description |
---|---|
WithUri method | Set the URI of the SpacetimeDB instance hosting the remote database. |
WithModuleName method | Set the name or identity of the remote database. |
WithToken method | Supply a token to authenticate with the remote database. |
WithCompression method | Set the compression method for WebSocket communication. |
OnConnect callback | Register a callback to run when the connection is successfully established. |
OnConnectError callback | Register a callback to run if the connection is rejected or the host is unreachable. |
OnDisconnect callback | Register a callback to run when the connection ends. |
Build method | Finalize configuration and open the connection. |
Method `WithUri`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* WithUri(const FString& InUri);
};
Configure the URI of the SpacetimeDB instance or cluster which hosts the remote module and database.
Method `WithModuleName`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* WithModuleName(const FString& InName);
};
Configure the SpacetimeDB domain name or Identity
of the remote database which identifies it within the SpacetimeDB instance or cluster.
Method `WithToken`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* WithToken(const FString& InToken);
};
Chain a call to .WithToken(token)
to your builder to provide an OpenID Connect compliant JSON Web Token to authenticate with, or to explicitly select an anonymous connection.
Method `WithCompression`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* WithCompression(const ESpacetimeDBCompression& InCompression);
};
Set the compression method for WebSocket communication. Available options:
ESpacetimeDBCompression::None
- No compressionESpacetimeDBCompression::Gzip
- Gzip compression (default)ESpacetimeDBCompression::Brotli
- Brotli compression (not implemented, defaults to Gzip)
Callback `OnConnect`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* OnConnect(FOnConnectDelegate Callback);
};
Chain a call to .OnConnect(callback)
to your builder to register a callback to run when your new UDbConnection
successfully initiates its connection to the remote database. The callback accepts three arguments: a reference to the UDbConnection
, the FSpacetimeDBIdentity
by which SpacetimeDB identifies this connection, and a private access token which can be saved and later passed to WithToken
to authenticate the same user in future connections.
Callback `OnConnectError`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* OnConnectError(FOnConnectErrorDelegate Callback);
};
Chain a call to .OnConnectError(callback)
to your builder to register a callback to run when your connection fails.
Callback `OnDisconnect`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnectionBuilder* OnDisconnect(FOnDisconnectDelegate Callback);
};
Chain a call to .OnDisconnect(callback)
to your builder to register a callback to run when your UDbConnection
disconnects from the remote database, either as a result of a call to Disconnect
or due to an error.
Method `Build`
class UDbConnectionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
UDbConnection* Build();
};
Finalize configuration and open the connection. This creates a WebSocket connection to ws://<uri>/v1/database/<module>/subscribe?compression=<compression>
and begins processing messages.
Advance the connection and process messages
The Unreal SDK processes messages automatically via WebSocket callbacks and with UDbConnection which ultimately inherits from FTickableGameObject. No manual polling or advancement is required. Events are dispatched through the registered delegates.
Access tables and reducers
class UDbConnection
{
UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB")
URemoteTables* Db;
UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB")
URemoteReducers* Reducers;
UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB")
USetReducerFlags* SetReducerFlags;
};
The Db
property provides access to the client cache, the Reducers
property allows invoking reducers, and the SetReducerFlags
property configures reducer behavior.
Context interfaces
Context objects provide access to the database and reducers within callback functions. All context types inherit from FContextBase
.
Name | Description |
---|---|
FContextBase | Base context providing access to Db and Reducers. |
FEventContext | Context for table row event callbacks. |
FReducerEventContext | Context for reducer event callbacks. |
FSubscriptionEventContext | Context for subscription lifecycle callbacks. |
FErrorContext | Context for error callbacks. |
Type `FContextBase`
USTRUCT(BlueprintType)
struct FContextBase
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB")
URemoteTables* Db;
UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB")
URemoteReducers* Reducers;
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
USubscriptionBuilder* SubscriptionBuilder();
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
bool TryGetIdentity(FSpacetimeDBIdentity& OutIdentity) const;
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
FSpacetimeDBConnectionId GetConnectionId() const;
};
Base context providing access to the client cache, reducers, subscription builder, and connection information.
Type `FEventContext`
USTRUCT(BlueprintType)
struct FEventContext : public FContextBase
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB")
FSpacetimeDBEvent Event;
};
Context passed to table row event callbacks (OnInsert, OnUpdate, OnDelete).
Type `FReducerEventContext`
USTRUCT(BlueprintType)
struct FReducerEventContext : public FContextBase
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB")
FReducerEvent Event;
};
Context passed to reducer event callbacks, containing information about the reducer execution.
Type `FSubscriptionEventContext`
USTRUCT(BlueprintType)
struct FSubscriptionEventContext : public FContextBase
{
GENERATED_BODY()
};
Context passed to subscription lifecycle callbacks (OnApplied, OnError).
Type `FErrorContext`
USTRUCT(BlueprintType)
struct FErrorContext : public FContextBase
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB")
FString Error;
};
Context passed to error callbacks, containing error information.
Access the client cache
All context types provide access to the client cache through the .Db
property, which contains generated table classes for each table defined by your module.
Each table defined by a module has a corresponding generated class (e.g., UUserTable
, UMessageTable
) that inherits from URemoteTable
and provides methods for accessing subscribed rows.
Name | Description |
---|---|
URemoteTable | Base class for all generated table classes. |
Unique constraint index access | Seek a subscribed row by the value in its unique or primary key column. |
BTree index access | Seek subscribed rows by the value in its indexed column. |
Type `URemoteTable`
Generated table classes inherit from URemoteTable
and provide the following interface:
Name | Description |
---|---|
Count method | The number of subscribed rows in the table. |
Iter method | Iterate over all subscribed rows in the table. |
OnInsert callback | Register a callback to run whenever a row is inserted into the client cache. |
OnDelete callback | Register a callback to run whenever a row is deleted from the client cache. |
OnUpdate callback | Register a callback to run whenever a subscribed row is replaced with a new version. |
Method `Count`
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
int32 Count() const;
The number of rows of this table resident in the client cache, i.e. the total number which match any subscribed query.
Method `Iter`
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
TArray<RowType> Iter() const;
An iterator over all the subscribed rows in the client cache, i.e. those which match any subscribed query.
Callback `OnInsert`
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnTableInsert,
const FEventContext&, Context,
const RowType&, NewRow);
UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events")
FOnTableInsert OnInsert;
The OnInsert
callback runs whenever a new row is inserted into the client cache, either when applying a subscription or being notified of a transaction.
Callback `OnDelete`
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnTableDelete,
const FEventContext&, Context,
const RowType&, DeletedRow);
UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events")
FOnTableDelete OnDelete;
The OnDelete
callback runs whenever a previously-resident row is deleted from the client cache.
Callback `OnUpdate`
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
FOnTableUpdate,
const FEventContext&, Context,
const RowType&, OldRow,
const RowType&, NewRow);
UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events")
FOnTableUpdate OnUpdate;
The OnUpdate
callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key.
Unique constraint index access
For each unique constraint on a table, its table class has a property which is a unique index handle. This unique index handle has a method .Find(Column value)
. If a Row
with value
in the unique column is resident in the client cache, .Find
returns it. Otherwise it returns null.
Example
Given the following module-side User
definition:
USTRUCT()
struct FUserType
{
GENERATED_BODY()
UPROPERTY()
FSpacetimeDBIdentity Identity; // Unique constraint
// ... other fields
};
a client would lookup a user as follows:
FUserType* FindUser(URemoteTables* Tables, FSpacetimeDBIdentity Id)
{
return Tables->User->Identity->Find(Id);
}
BTree index access
For each btree index defined on a remote table, its corresponding table class has a property which is a btree index handle. This index handle has a method TArray<RowType> Filter(Column value)
which will return Row
s with value
in the indexed Column
, if there are any in the cache.
Example
Given the following module-side Player
definition:
USTRUCT()
struct FPlayerType
{
GENERATED_BODY()
UPROPERTY()
FSpacetimeDBIdentity Id; // Primary key
UPROPERTY()
uint32 Level; // BTree index
// ... other fields
};
a client would count the number of Player
s at a certain level as follows:
int32 CountPlayersAtLevel(URemoteTables* Tables, uint32 Level)
{
return Tables->Player->Level->Filter(Level).Num();
}
Observe and invoke reducers
All context types provide access to reducers through the .Reducers
property, which contains generated methods for invoking reducers defined by the module and registering callbacks.
Each reducer defined by the module has methods on the .Reducers
:
- An invoke method, whose name matches the reducer's name (e.g.,
SendMessage
,SetName
). This requests that the module run the reducer. - A callback registration delegate, whose name is prefixed with
On
(e.g.,OnSendMessage
,OnSetName
). This registers a callback to run whenever we are notified that the reducer ran.
Invoke reducers
class URemoteReducers
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
void SendMessage(const FString& Text);
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
void SetName(const FString& Name);
};
Observe reducer events
class URemoteReducers
{
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnSendMessage,
const FReducerEventContext&, Context,
const FString&, Text);
UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events")
FOnSendMessage OnSendMessage;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnSetName,
const FReducerEventContext&, Context,
const FString&, Name);
UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events")
FOnSetName OnSetName;
};
Reducer flags
class USetReducerFlags
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
void SendMessage(ECallReducerFlags Flag);
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
void SetName(ECallReducerFlags Flag);
};
Configure how much data to receive when a reducer runs:
ECallReducerFlags::FullUpdate
- Receive all table updates (default)ECallReducerFlags::NoUpdate
- Don't receive table updatesECallReducerFlags::LightUpdate
- Receive minimal table updates
Subscriptions
Create subscriptions to receive updates for specific queries using the USubscriptionBuilder
and USubscriptionHandle
classes.
Name | Description |
---|---|
USubscriptionBuilder | Build and configure subscriptions. |
USubscriptionHandle | Manage subscription lifecycle. |
Type `USubscriptionBuilder`
class USubscriptionBuilder
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
USubscriptionBuilder* OnApplied(FOnSubscriptionApplied Callback);
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
USubscriptionBuilder* OnError(FOnSubscriptionError Callback);
UFUNCTION(BlueprintCallable, Category="SpacetimeDB")
USubscriptionHandle* Subscribe(const TArray<FString>& SQL);
UFUNCTION(BlueprintCallable, Category="SpacetimeDB")
USubscriptionHandle* SubscribeToAllTables();
};
Method `OnApplied`
USubscriptionBuilder* OnApplied(FOnSubscriptionApplied Callback);
Register a callback to run when the subscription is successfully applied.
Method `OnError`
USubscriptionBuilder* OnError(FOnSubscriptionError Callback);
Register a callback to run if the subscription fails.
Method `Subscribe`
USubscriptionHandle* Subscribe(const TArray<FString>& SQL);
Subscribe to the provided SQL queries and return a handle for managing the subscription.
Method `SubscribeToAllTables`
USubscriptionHandle* SubscribeToAllTables();
Subscribe to all tables in the module (equivalent to Subscribe({ "SELECT * FROM *" })
).
Type `USubscriptionHandle`
class USubscriptionHandle
{
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
void Unsubscribe();
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
void UnsubscribeThen(FSubscriptionEventDelegate OnEnd);
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
bool IsEnded() const;
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
bool IsActive() const;
UFUNCTION(BlueprintCallable, Category = "SpacetimeDB")
TArray<FString> GetQuerySqls() const;
};
Method `Unsubscribe`
void Unsubscribe();
Immediately cancel the subscription.
Method `UnsubscribeThen`
void UnsubscribeThen(FSubscriptionEventDelegate OnEnd);
Cancel the subscription and invoke the provided callback when complete.
Method `IsEnded`
bool IsEnded() const;
True once the subscription has ended.
Method `IsActive`
bool IsActive() const;
True while the subscription is active.
Method `GetQuerySqls`
TArray<FString> GetQuerySqls() const;
Get the SQL queries associated with this subscription.
Identify a client
Type `FSpacetimeDBIdentity`
A unique public identifier for a client connected to a database. This is a 256-bit value.
USTRUCT(BlueprintType, Category = "SpacetimeDB")
struct FSpacetimeDBIdentity
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FSpacetimeDBUInt256 Value;
// Comparison operators, constructors, etc.
};
Type `FSpacetimeDBConnectionId`
An opaque identifier for a client connection to a database, intended to differentiate between connections from the same Identity. This is a 128-bit value.
USTRUCT(BlueprintType, Category = "SpacetimeDB")
struct FSpacetimeDBConnectionId
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FSpacetimeDBUInt128 Value;
// Comparison operators, constructors, etc.
};
Type `FSpacetimeDBTimestamp`
A point in time, measured in microseconds since the Unix epoch.
USTRUCT(BlueprintType, Category = "SpacetimeDB")
struct FSpacetimeDBTimestamp
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int64 MicrosSinceEpoch;
// Comparison operators, constructors, etc.
};
Example usage
Here's a complete example of connecting to SpacetimeDB, subscribing to tables, and handling events:
// In your Actor's BeginPlay()
void AMyActor::BeginPlay()
{
Super::BeginPlay();
// Setup connection callbacks
FOnConnectDelegate ConnectDelegate;
ConnectDelegate.BindDynamic(this, &AMyActor::OnConnected);
FOnDisconnectDelegate DisconnectDelegate;
DisconnectDelegate.BindDynamic(this, &AMyActor::OnDisconnected);
// Build and connect
Conn = UDbConnection::Builder()
->WithUri(TEXT("127.0.0.1:3000"))
->WithModuleName(TEXT("my-module"))
->OnConnect(ConnectDelegate)
->OnDisconnect(DisconnectDelegate)
->Build();
// Register table callbacks
Conn->Db->User->OnInsert.AddDynamic(this, &AMyActor::OnUserInsert);
Conn->Db->User->OnUpdate.AddDynamic(this, &AMyActor::OnUserUpdate);
Conn->Db->User->OnDelete.AddDynamic(this, &AMyActor::OnUserDelete);
// Register reducer callbacks
Conn->Reducers->OnSendMessage.AddDynamic(this, &AMyActor::OnSendMessage);
Conn->SetReducerFlags->SendMessage(ECallReducerFlags::FullUpdate);
}
void AMyActor::OnConnected(UDbConnection* Connection, FSpacetimeDBIdentity Identity, const FString& Token)
{
// Save token for future connections
UCredentials::SaveToken(Token);
// Subscribe to all tables
USubscriptionHandle* Handle = Connection->SubscriptionBuilder()
->OnApplied(FOnSubscriptionApplied::CreateUObject(this, &AMyActor::OnSubscriptionApplied))
->SubscribeToAllTables();
}
void AMyActor::OnUserInsert(const FEventContext& Context, const FUserType& NewRow)
{
UE_LOG(LogTemp, Log, TEXT("User inserted: %s"), *NewRow.Name);
}
void AMyActor::OnSendMessage(const FReducerEventContext& Context, const FString& Text)
{
UE_LOG(LogTemp, Log, TEXT("Message sent: %s"), *Text);
}
void AMyActor::SendMessage(const FString& Text)
{
if (Conn && Conn->Reducers)
{
Conn->Reducers->SendMessage(Text);
}
}
PreviousUnreal QuickstartNextRust Quickstart