Files
Jason Larabie f8d6d76ee4 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>
2026-03-18 21:14:06 +00:00

167 lines
6.4 KiB
C++

#pragma once
#include "spacetimedb/bsatn/types.h"
#include "spacetimedb/reducer_context.h"
#include "spacetimedb/internal/Module.h"
#include "spacetimedb/internal/v10_builder.h"
#include "spacetimedb/macros.h"
#include <string>
#include <vector>
#include <sstream>
// Note: parseParameterNames() is now in macros.h for reuse across reducers and views
/**
* @brief Unified SPACETIMEDB_REDUCER macro for defining SpacetimeDB reducers
*
* This macro provides a clean, consistent syntax for defining reducers with
* automatic registration in the SpacetimeDB module system.
*
* Reducers now return SpacetimeDB::ReducerResult to support Result-based
* error handling matching Rust's Result<(), E> pattern.
*
* @usage
* ```cpp
* // Reducer with no extra parameters
* SPACETIMEDB_REDUCER(my_reducer, ReducerContext ctx) {
* if (some_error) {
* return Err("Error message");
* }
* ctx.db.table<MyTable>("my_table").insert(MyTable{});
* return Ok();
* }
*
* // Reducer with parameters
* SPACETIMEDB_REDUCER(my_reducer, ReducerContext ctx, uint32_t id, std::string name) {
* if (id == 0) {
* return Err("ID must be non-zero");
* }
* ctx.db.table<MyTable>("my_table").insert(MyTable{id, name});
* return Ok();
* }
* ```
*
* @param name The name of the reducer function
* @param ctx_param Must be ReducerContext ctx
* @param ... Additional parameters (optional)
*
* @details
* The macro generates:
* 1. A function declaration and definition with ReducerResult return type
* 2. A preinit registration function that registers the reducer with SpacetimeDB
*
* The first parameter must always be `ReducerContext ctx`. Additional parameters
* can be any types that support BSATN serialization.
*/
#undef SPACETIMEDB_REDUCER
#define SPACETIMEDB_REDUCER(name, ctx_param, ...) \
/* Forward declaration - returns ReducerResult */ \
SpacetimeDB::ReducerResult name(ctx_param __VA_OPT__(,) __VA_ARGS__); \
\
/* Preinit registration function */ \
/* This function is called during module initialization to register the reducer */ \
__attribute__((export_name("__preinit__30_reducer_" #name))) \
extern "C" void CONCAT(_spacetimedb_preinit_register_, name)() { \
/* Parse parameter names from the stringified parameter list */ \
std::string param_list = #__VA_ARGS__; \
std::vector<std::string> param_names = \
SpacetimeDB::Internal::parseParameterNames(param_list); \
/* Register the reducer with the unified V9Builder system */ \
SpacetimeDB::Internal::getV10Builder().RegisterReducer(#name, name, param_names); \
} \
\
/* The actual reducer function definition - returns ReducerResult */ \
SpacetimeDB::ReducerResult name(ctx_param __VA_OPT__(,) __VA_ARGS__)
#define SPACETIMEDB_REDUCER_NAMED(name, canonical_name, ctx_param, ...) \
SpacetimeDB::ReducerResult name(ctx_param __VA_OPT__(,) __VA_ARGS__); \
__attribute__((export_name("__preinit__30_reducer_" #name))) \
extern "C" void CONCAT(_spacetimedb_preinit_register_, name)() { \
std::string param_list = #__VA_ARGS__; \
std::vector<std::string> param_names = \
SpacetimeDB::Internal::parseParameterNames(param_list); \
SpacetimeDB::Internal::getV10Builder().RegisterReducer(#name, name, param_names); \
SpacetimeDB::Module::RegisterExplicitFunctionName(#name, canonical_name); \
} \
SpacetimeDB::ReducerResult name(ctx_param __VA_OPT__(,) __VA_ARGS__)
// -----------------------------------------------------------------------------
// Lifecycle Reducer Macros
// -----------------------------------------------------------------------------
// Use unified macro system from macro_helpers.h
/**
* @brief Macro for defining an init reducer
*
* Init reducers are called when the module is first initialized.
* They require an explicit ReducerContext parameter and return ReducerResult.
*
* @usage
* ```cpp
* SPACETIMEDB_INIT(my_init, ReducerContext ctx) {
* ctx.db.table<MyTable>().insert({...});
* return Ok();
* }
* ```
*/
#ifdef SPACETIMEDB_INIT
#undef SPACETIMEDB_INIT
#endif
#define SPACETIMEDB_INIT(function_name, ctx_param) \
SpacetimeDB::ReducerResult function_name(ctx_param); \
__attribute__((export_name("__preinit__20_reducer_init"))) \
extern "C" void CONCAT(_preinit_register_init_reducer_, function_name)() { \
::SpacetimeDB::Internal::getV10Builder().RegisterLifecycleReducer(#function_name, function_name, ::SpacetimeDB::Internal::Lifecycle::Init); \
} \
SpacetimeDB::ReducerResult function_name(ctx_param)
/**
* @brief Macro for defining a client_connected reducer
*
* Client connected reducers require an explicit ReducerContext parameter and return ReducerResult.
*
* @usage
* ```cpp
* SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
* LOG_INFO("Client connected: " + ctx.sender().to_hex());
* return Ok();
* }
* ```
*/
#ifdef SPACETIMEDB_CLIENT_CONNECTED
#undef SPACETIMEDB_CLIENT_CONNECTED
#endif
#define SPACETIMEDB_CLIENT_CONNECTED(function_name, ctx_param) \
SpacetimeDB::ReducerResult function_name(ctx_param); \
__attribute__((export_name("__preinit__20_reducer_client_connected"))) \
extern "C" void CONCAT(_preinit_register_client_connected_, function_name)() { \
::SpacetimeDB::Internal::getV10Builder().RegisterLifecycleReducer(#function_name, function_name, ::SpacetimeDB::Internal::Lifecycle::OnConnect); \
} \
SpacetimeDB::ReducerResult function_name(ctx_param)
/**
* @brief Macro for defining a client_disconnected reducer
*
* Client disconnected reducers require an explicit ReducerContext parameter and return ReducerResult.
*
* @usage
* ```cpp
* SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
* LOG_INFO("Client disconnected: " + ctx.sender().to_hex());
* return Ok();
* }
* ```
*/
#ifdef SPACETIMEDB_CLIENT_DISCONNECTED
#undef SPACETIMEDB_CLIENT_DISCONNECTED
#endif
#define SPACETIMEDB_CLIENT_DISCONNECTED(function_name, ctx_param) \
SpacetimeDB::ReducerResult function_name(ctx_param); \
__attribute__((export_name("__preinit__20_reducer_client_disconnected"))) \
extern "C" void CONCAT(_preinit_register_client_disconnected_, function_name)() { \
::SpacetimeDB::Internal::getV10Builder().RegisterLifecycleReducer(#function_name, function_name, ::SpacetimeDB::Internal::Lifecycle::OnDisconnect); \
} \
SpacetimeDB::ReducerResult function_name(ctx_param)