Update Unreal SDK to websocket 2.0 (#4497)

# Description of Changes

- Updated the Unreal SDK and generated Unreal bindings for the websocket
2.0 protocol/model
  - Reworked DbConnectionBase to handle the updated message shapes
- Switched subscription handling over to new message types and
QuerySetId
- Updated reducer to ReducerResult, removal of callbacks, and set
reducer flags
  - Added event table support
- Baked in multi-module support replacing [the old
PR](<https://github.com/clockworklabs/SpacetimeDB/pull/3417>)
- Added functionality to generate module support for multiple folders in
the Unreal project (add <module>.Build.cs, <module>.h, <module>.cpp)
using the --module-name
- Add new configuration option for spacetime generate to handle module
prefix
 - Regenerated Unreal Blackholio/TestClient/QuickstartChat bindings
   - Rebuilt Unreal Blackholio's consume entity to use event tables 
 - Updated migration documentation
 - Updated the version bump tool to impact C++

# API and ABI breaking changes

- Unreal websocket/message handling updated to the new protocol
- Unreal generation now expects a real .uproject target and will stop
immediately if project
    metadata is invalid instead of continuing past setup issues.

# Expected complexity level and risk

3 - A large set of changes to update the websocket/message handling
along with heavy codegen changes to handle multi-module support

# Testing

Test coverage of the Unreal SDK will need expansion in a future ticket
once our issues with flakiness on CI is resolved.

- [x] Updated Unreal Blackholio 
- [x] Ran full Unreal SDK test suite
- [x] Built new test project using the new `--module-prefix` 
- [x] Run through Unreal Blackholio (C++ and Blueprint)
- [x] Rebuilt Unreal Blackholio with multi-module, and duplicate
generated module testing side-by-side modules that would overlap

# Review Question(s)
- [x] Updates to `spacetime init` have made the tutorial a little
confusing with pathing for the Unreal Blackholio tutorial. To fix though
we'd have to update all the commands to be more explicit, or update the
tutorial `spacetime init` to use `--project-path .` to keep pathing
simpler, thoughts?

---------

Signed-off-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: Ryan <r.ekhoff@clockworklabs.io>
This commit is contained in:
Jason Larabie
2026-03-18 14:14:06 -07:00
committed by GitHub
parent 9667429be8
commit f8d6d76ee4
209 changed files with 5877 additions and 6998 deletions
+9 -9
View File
@@ -217,7 +217,7 @@ FIELD_Default(table_with_defaults, active, true)
// View to find the player associated with the calling identity
SPACETIMEDB_VIEW(std::optional<Player>, my_player, Public, ViewContext ctx) {
return ctx.db[player_identity].find(ctx.sender);
return ctx.db[player_identity].find(ctx.sender());
}
// =============================================================================
@@ -287,7 +287,7 @@ SPACETIMEDB_REDUCER(log_module_identity, ReducerContext ctx) {
// Complex test reducer with multiple parameters
SPACETIMEDB_REDUCER(test, ReducerContext ctx, TestAlias arg, TestB arg2, TestC arg3, TestF arg4) {
LOG_INFO("BEGIN");
LOG_INFO("sender: " + ctx.sender.to_string());
LOG_INFO("sender: " + ctx.sender().to_string());
LOG_INFO("timestamp: " + ctx.timestamp.to_string());
LOG_INFO("bar: " + arg2.foo);
@@ -550,8 +550,8 @@ SPACETIMEDB_REDUCER(test_btree_index_args, ReducerContext ctx) {
// Test reducer for assertions
SPACETIMEDB_REDUCER(assert_caller_identity_is_module_identity, ReducerContext ctx) {
LOG_INFO("Sender: " + ctx.sender.to_string() + " Identity: " + ctx.identity().to_string());
if (ctx.sender != ctx.identity()) {
LOG_INFO("Sender: " + ctx.sender().to_string() + " Identity: " + ctx.identity().to_string());
if (ctx.sender() != ctx.identity()) {
LOG_ERROR("Assertion failed: caller identity does not match module identity");
} else {
LOG_INFO("Assertion passed: caller identity matches module identity");
@@ -639,14 +639,14 @@ SPACETIMEDB_REDUCER(test_jwt_auth, ReducerContext ctx) {
LOG_INFO("JWT Identity: " + identity.to_string());
// Compare with caller identity
LOG_INFO("Caller Identity: " + ctx.sender.to_string());
LOG_INFO("Caller Identity: " + ctx.sender().to_string());
// Verify that get_caller_identity returns the same as ctx.sender
// Verify that get_caller_identity returns the same as ctx.sender()
auto caller_identity = auth.get_caller_identity();
if (caller_identity == ctx.sender) {
LOG_INFO("get_caller_identity matches ctx.sender");
if (caller_identity == ctx.sender()) {
LOG_INFO("get_caller_identity matches ctx.sender()");
} else {
LOG_ERROR("get_caller_identity does NOT match ctx.sender");
LOG_ERROR("get_caller_identity does NOT match ctx.sender()");
}
} else {
LOG_INFO("No JWT present (anonymous or scheduled reducer)");