Skip to content

Commit f8d6d76

Browse files
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](<#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>
1 parent 9667429 commit f8d6d76

209 files changed

Lines changed: 6141 additions & 7262 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-cpp/ARCHITECTURE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ SPACETIMEDB_REDUCER(create_user, ReducerContext ctx, std::string name) {
7777
}
7878

7979
// Success path
80-
ctx.db[user].insert(User{ctx.sender, name, false});
80+
ctx.db[user].insert(User{ctx.sender(), name, false});
8181
return Ok(); // No value needed - just success
8282
}
8383
```
@@ -638,4 +638,4 @@ SpacetimeDB loading → Server-side validation and error reporting
638638
- [Type System Details](README.md#features)
639639
- [Constraint Validation Tests](tests/type-isolation-test/)
640640
- [API Reference](REFERENCE.md)
641-
- [Quick Start Guide](QUICKSTART.md)
641+
- [Quick Start Guide](QUICKSTART.md)

crates/bindings-cpp/QUICKSTART.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ SPACETIMEDB_REDUCER(set_name, ReducerContext ctx, std::string name) {
109109
}
110110

111111
// Find and update the user by identity (primary key)
112-
auto user_row = ctx.db[user_identity].find(ctx.sender);
112+
auto user_row = ctx.db[user_identity].find(ctx.sender());
113113
if (user_row.has_value()) {
114114
auto user = user_row.value();
115115
user.name = validated.value();
@@ -127,7 +127,7 @@ SPACETIMEDB_REDUCER(send_message, ReducerContext ctx, std::string text) {
127127
return Err(validated.error());
128128
}
129129

130-
Message msg{ctx.sender, ctx.timestamp, validated.value()};
130+
Message msg{ctx.sender(), ctx.timestamp, validated.value()};
131131
ctx.db[message].insert(msg);
132132
return Ok();
133133
}
@@ -140,21 +140,21 @@ Lifecycle reducers are special functions called automatically by SpacetimeDB:
140140
```cpp
141141
// Called when a client connects
142142
SPACETIMEDB_CLIENT_CONNECTED(client_connected, ReducerContext ctx) {
143-
auto user_row = ctx.db[user_identity].find(ctx.sender);
143+
auto user_row = ctx.db[user_identity].find(ctx.sender());
144144
if (user_row.has_value()) {
145145
auto user = user_row.value();
146146
user.online = true;
147147
ctx.db[user_identity].update(user);
148148
} else {
149-
User new_user{ctx.sender, std::nullopt, true};
149+
User new_user{ctx.sender(), std::nullopt, true};
150150
ctx.db[user].insert(new_user);
151151
}
152152
return Ok();
153153
}
154154
155155
// Called when a client disconnects
156156
SPACETIMEDB_CLIENT_DISCONNECTED(client_disconnected, ReducerContext ctx) {
157-
auto user_row = ctx.db[user_identity].find(ctx.sender);
157+
auto user_row = ctx.db[user_identity].find(ctx.sender());
158158
if (user_row.has_value()) {
159159
auto user = user_row.value();
160160
user.online = false;
@@ -291,4 +291,4 @@ For more complex examples, see:
291291

292292
---
293293

294-
Ready to learn more? Check out the [C++ Reference Documentation](REFERENCE.md) for detailed API information.
294+
Ready to learn more? Check out the [C++ Reference Documentation](REFERENCE.md) for detailed API information.

crates/bindings-cpp/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ SPACETIMEDB_NAMESPACE(UserRole, "Auth") // Will be "Auth.UserRole" in client co
102102

103103
// User-defined reducer
104104
SPACETIMEDB_REDUCER(add_user, ReducerContext ctx, std::string name, std::string email) {
105-
User user{ctx.sender, name, email}; // id will be auto-generated
105+
User user{ctx.sender(), name, email}; // id will be auto-generated
106106
ctx.db[users].insert(user);
107107
LOG_INFO("Added user: " + name);
108108
return Ok();
109109
}
110110

111111
// Delete user by id (using primary key)
112112
SPACETIMEDB_REDUCER(delete_user, ReducerContext ctx) {
113-
ctx.db[users_identity].delete_by_key(ctx.sender);
113+
ctx.db[users_identity].delete_by_key(ctx.sender());
114114
return Ok();
115115
}
116116

@@ -121,19 +121,19 @@ SPACETIMEDB_INIT(init, ReducerContext ctx) {
121121
}
122122

123123
SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
124-
LOG_INFO("Client connected: " + ctx.sender.to_hex_string());
124+
LOG_INFO("Client connected: " + ctx.sender().to_hex_string());
125125
return Ok();
126126
}
127127

128128
SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
129-
LOG_INFO("Client disconnected: " + ctx.sender.to_hex_string());
129+
LOG_INFO("Client disconnected: " + ctx.sender().to_hex_string());
130130
return Ok();
131131
}
132132

133133
// Define a view for querying data (finds the calling user)
134134
SPACETIMEDB_VIEW(std::optional<User>, find_my_user, Public, ViewContext ctx) {
135135
// Use indexed field to find user by their identity
136-
return ctx.db[users_identity].find(ctx.sender);
136+
return ctx.db[users_identity].find(ctx.sender());
137137
}
138138

139139
// Define a procedure (pure function with return value)

crates/bindings-cpp/REFERENCE.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ SPACETIMEDB_INIT(init, ReducerContext ctx) {
191191
// Called when a client connects
192192
SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
193193
LOG_INFO("Client connected");
194-
// ctx.sender contains the client's Identity
194+
// ctx.sender() contains the client's Identity
195195
return Ok();
196196
}
197197
@@ -213,7 +213,7 @@ SPACETIMEDB_REDUCER(example, ReducerContext ctx, /* params */) {
213213
ctx.db[table_name].insert(record);
214214

215215
// Client identity
216-
Identity client = ctx.sender;
216+
Identity client = ctx.sender();
217217

218218
// Current timestamp
219219
Timestamp now = ctx.timestamp;
@@ -893,7 +893,7 @@ SPACETIMEDB_TABLE(User, user, Public);
893893
FIELD_PrimaryKey(user, id);
894894

895895
SPACETIMEDB_REDUCER(create_user, ReducerContext ctx, std::string name) {
896-
User user{ctx.sender, name}; // ctx.sender is the calling client's identity
896+
User user{ctx.sender(), name}; // ctx.sender() is the calling client's identity
897897
ctx.db[user_id].insert(user);
898898
return Ok();
899899
}
@@ -1138,20 +1138,20 @@ FIELD_Index(user, active);
11381138
// Register new user
11391139
SPACETIMEDB_REDUCER(register_user, ReducerContext ctx, std::string username, std::string email) {
11401140
// Check if user already exists
1141-
auto user_opt = ctx.db[user_identity].find(ctx.sender);
1141+
auto user_opt = ctx.db[user_identity].find(ctx.sender());
11421142
if (user_opt && user_opt->active) {
11431143
return Err("User already registered");
11441144
}
11451145

1146-
User new_user{0, ctx.sender, username, email, ctx.timestamp, true};
1146+
User new_user{0, ctx.sender(), username, email, ctx.timestamp, true};
11471147
ctx.db[user].insert(new_user);
11481148
LOG_INFO("User registered: " + username);
11491149
return Ok();
11501150
}
11511151

11521152
// Update user profile
11531153
SPACETIMEDB_REDUCER(update_profile, ReducerContext ctx, std::string new_username) {
1154-
auto user_opt = ctx.db[user_identity].find(ctx.sender);
1154+
auto user_opt = ctx.db[user_identity].find(ctx.sender());
11551155
if (user_opt && user_opt->active) {
11561156
User updated_user = *user_opt;
11571157
updated_user.username = new_username;
@@ -1164,7 +1164,7 @@ SPACETIMEDB_REDUCER(update_profile, ReducerContext ctx, std::string new_username
11641164

11651165
// Deactivate user
11661166
SPACETIMEDB_REDUCER(deactivate_user, ReducerContext ctx) {
1167-
auto user_opt = ctx.db[user_identity].find(ctx.sender);
1167+
auto user_opt = ctx.db[user_identity].find(ctx.sender());
11681168
if (user_opt && user_opt->active) {
11691169
User updated_user = *user_opt;
11701170
updated_user.active = false;
@@ -1227,7 +1227,7 @@ FIELD_Index(messages, sender);
12271227
12281228
// Create channel
12291229
SPACETIMEDB_REDUCER(create_channel, ReducerContext ctx, std::string name, std::string description, bool is_public) {
1230-
Channel channel{0, name, description, ctx.sender, is_public};
1230+
Channel channel{0, name, description, ctx.sender(), is_public};
12311231
ctx.db[channels].insert(channel);
12321232
LOG_INFO("Channel created: " + name);
12331233
return Ok();
@@ -1249,7 +1249,7 @@ SPACETIMEDB_REDUCER(send_message, ReducerContext ctx, uint32_t channel_id, std::
12491249
return Err("Channel not found");
12501250
}
12511251
1252-
Message message{0, channel_id, ctx.sender, content, ctx.timestamp};
1252+
Message message{0, channel_id, ctx.sender(), content, ctx.timestamp};
12531253
ctx.db[messages].insert(message);
12541254
return Ok();
12551255
}
@@ -1281,4 +1281,4 @@ SPACETIMEDB_REDUCER(update_age, ReducerContext ctx, uint32_t new_age) {
12811281
12821282
---
12831283
1284-
This completes the C++ reference documentation. For more examples and advanced patterns, see the working modules in [`modules/sdk-test-cpp`](../../modules/sdk-test-cpp) and [`modules/module-test-cpp`](../../modules/module-test-cpp).
1284+
This completes the C++ reference documentation. For more examples and advanced patterns, see the working modules in [`modules/sdk-test-cpp`](../../modules/sdk-test-cpp) and [`modules/module-test-cpp`](../../modules/module-test-cpp).

crates/bindings-cpp/include/spacetimedb/procedure_context.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ namespace SpacetimeDB {
5353
* @endcode
5454
*/
5555
struct ProcedureContext {
56+
private:
5657
// Caller's identity - who invoked this procedure
57-
Identity sender;
58-
58+
Identity sender_;
59+
60+
public:
5961
// Timestamp when the procedure was invoked
6062
Timestamp timestamp;
61-
63+
6264
// Connection ID for the caller
6365
// Used to track which client connection initiated this procedure
6466
ConnectionId connection_id;
65-
67+
6668
#ifdef SPACETIMEDB_UNSTABLE_FEATURES
6769
// HTTP client for making external requests
6870
// IMPORTANT: HTTP calls are NOT allowed inside transactions!
@@ -81,7 +83,11 @@ struct ProcedureContext {
8183
ProcedureContext() = default;
8284

8385
ProcedureContext(Identity s, Timestamp t, ConnectionId conn_id)
84-
: sender(s), timestamp(t), connection_id(conn_id) {}
86+
: sender_(s), timestamp(t), connection_id(conn_id) {}
87+
88+
Identity sender() const {
89+
return sender_;
90+
}
8591

8692
/**
8793
* @brief Read the current module's Identity
@@ -197,7 +203,7 @@ struct ProcedureContext {
197203
// Create a ReducerContext for this transaction
198204
// Note: connection_id converted to std::optional
199205
ReducerContext reducer_ctx(
200-
sender,
206+
sender(),
201207
std::optional<ConnectionId>(connection_id),
202208
Timestamp::from_micros_since_epoch(tx_timestamp)
203209
);
@@ -260,7 +266,7 @@ struct ProcedureContext {
260266

261267
// Create a ReducerContext for this transaction
262268
ReducerContext reducer_ctx(
263-
sender,
269+
sender(),
264270
std::optional<ConnectionId>(connection_id),
265271
Timestamp::from_micros_since_epoch(tx_timestamp)
266272
);

crates/bindings-cpp/include/spacetimedb/reducer_context.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ namespace SpacetimeDB {
1717

1818
// Enhanced ReducerContext with database access - matches Rust pattern
1919
struct ReducerContext {
20-
// Core fields - directly accessible like in Rust
21-
Identity sender;
20+
private:
21+
Identity sender_;
22+
23+
public:
24+
// Core fields - sender is exposed via sender() like Rust, other fields remain directly accessible
2225
std::optional<ConnectionId> connection_id;
2326
Timestamp timestamp;
2427

@@ -37,6 +40,10 @@ struct ReducerContext {
3740
mutable uint32_t counter_uuid_ = 0;
3841

3942
public:
43+
Identity sender() const {
44+
return sender_;
45+
}
46+
4047
// Returns the authorization information for the caller of this reducer
4148
const AuthCtx& sender_auth() const {
4249
return sender_auth_;
@@ -110,7 +117,7 @@ struct ReducerContext {
110117
ReducerContext() : sender_auth_(AuthCtx::internal()) {}
111118

112119
ReducerContext(Identity s, std::optional<ConnectionId> cid, Timestamp ts)
113-
: sender(s), connection_id(cid), timestamp(ts),
120+
: sender_(s), connection_id(cid), timestamp(ts),
114121
sender_auth_(AuthCtx::from_connection_id_opt(cid, s)) {}
115122
};
116123

crates/bindings-cpp/include/spacetimedb/reducer_macros.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
* @usage
126126
* ```cpp
127127
* SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
128-
* LOG_INFO("Client connected: " + ctx.sender.to_hex());
128+
* LOG_INFO("Client connected: " + ctx.sender().to_hex());
129129
* return Ok();
130130
* }
131131
* ```
@@ -149,7 +149,7 @@
149149
* @usage
150150
* ```cpp
151151
* SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
152-
* LOG_INFO("Client disconnected: " + ctx.sender.to_hex());
152+
* LOG_INFO("Client disconnected: " + ctx.sender().to_hex());
153153
* return Ok();
154154
* }
155155
* ```

crates/bindings-cpp/include/spacetimedb/tx_context.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,21 @@ struct TxContext {
5353
public:
5454
// Public references to ReducerContext fields for consistent API with Rust
5555
// In Rust, Deref makes tx.db work the same as ctx.db
56-
// In C++, we explicitly expose references to achieve the same ergonomics
56+
// In C++, we explicitly expose references where possible and provide
57+
// accessors for fields exposed as methods on ReducerContext.
5758
DatabaseContext& db;
58-
const Identity& sender;
5959
const Timestamp& timestamp;
6060
const std::optional<ConnectionId>& connection_id;
6161

6262
// Constructor - initializes all reference members
6363
explicit TxContext(ReducerContext& ctx)
6464
: ctx_(ctx),
6565
db(ctx.db),
66-
sender(ctx.sender),
6766
timestamp(ctx.timestamp),
6867
connection_id(ctx.connection_id) {}
6968

7069
// Access to ReducerContext methods
70+
Identity sender() const { return ctx_.sender(); }
7171
const AuthCtx& sender_auth() const { return ctx_.sender_auth(); }
7272
Identity identity() const { return ctx_.identity(); }
7373
StdbRng& rng() const { return ctx_.rng(); }

crates/bindings-cpp/include/spacetimedb/view_context.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,29 @@ namespace SpacetimeDB {
2525
* SPACETIMEDB_VIEW(std::vector<Item>, get_my_items, Public, ViewContext ctx) {
2626
* std::vector<Item> my_items;
2727
* // Filter by caller's identity using indexed field
28-
* for (const auto& item : ctx.db[item_owner].filter(ctx.sender)) {
28+
* for (const auto& item : ctx.db[item_owner].filter(ctx.sender())) {
2929
* my_items.push_back(item);
3030
* }
3131
* return Ok(my_items);
3232
* }
3333
* @endcode
3434
*/
3535
struct ViewContext {
36+
private:
3637
// Caller's identity - who invoked this view
37-
Identity sender;
38-
38+
Identity sender_;
39+
40+
public:
3941
// Read-only database access - no mutations allowed
4042
ReadOnlyDatabaseContext db;
4143

4244
// Constructors
4345
ViewContext() = default;
4446

4547
explicit ViewContext(Identity s)
46-
: sender(s) {}
48+
: sender_(s) {}
49+
50+
Identity sender() const { return sender_; }
4751
};
4852

4953
/**

0 commit comments

Comments
 (0)