diff --git a/.gitignore b/.gitignore index 32bf1df4385..2c3b68ea18b 100644 --- a/.gitignore +++ b/.gitignore @@ -224,5 +224,9 @@ new.json # Temporary templates dir for CLI's init command /crates/cli/.templates +# C++ build data +modules/benchmarks-cpp/build/ +modules/module-test-cpp/build/ + # Symlinked output from `nix build` result diff --git a/crates/bindings-cpp/.gitignore b/crates/bindings-cpp/.gitignore new file mode 100644 index 00000000000..a5309e6b906 --- /dev/null +++ b/crates/bindings-cpp/.gitignore @@ -0,0 +1 @@ +build*/ diff --git a/crates/bindings-cpp/ARCHITECTURE.md b/crates/bindings-cpp/ARCHITECTURE.md new file mode 100644 index 00000000000..bbc84d0b7c3 --- /dev/null +++ b/crates/bindings-cpp/ARCHITECTURE.md @@ -0,0 +1,483 @@ +# SpacetimeDB C++ SDK Architecture + +## Overview + +The SpacetimeDB C++ SDK provides a sophisticated compile-time/runtime hybrid system for building database modules in C++ that compile to WebAssembly (WASM) and run inside the SpacetimeDB database. This document describes the architectural components, type registration flow, and key differences from other language SDKs. + +## Core Architecture Principles + +### 1. Hybrid Compile-Time/Runtime System +- **Compile-time validation**: C++20 concepts and static assertions catch constraint violations before compilation +- **Runtime registration**: __preinit__ functions execute during WASM module load to register types and metadata +- **Nominal type system**: Types identified by their declared names, not structural analysis +- **Error detection**: Multi-layer validation system from compile-time through module publishing + +### 2. Priority-Ordered Initialization System +The SDK uses a numbered __preinit__ function system to ensure correct initialization order: + +``` +__preinit__01_ - Clear global state (first) +__preinit__10_ - Field registration +__preinit__19_ - Auto-increment integration registration +__preinit__20_ - Table and lifecycle reducer registration +__preinit__21_ - Field constraints +__preinit__25_ - Row level security filters +__preinit__30_ - User reducers +__preinit__99_ - Type validation and error detection (last) +``` + +## Detailed Type Registration Flow + +### Phase 1: Compile-Time Validation + +**Location**: Template instantiation during compilation + +**Components**: +- **C++20 Concepts** (`filterable_value_concept.h`, `table_with_constraints.h`): + ```cpp + template + concept FilterableValue = + std::integral || + std::same_as || + std::same_as || + // ... other filterable types + + template + concept AutoIncrementable = + std::same_as || + std::same_as || + // ... integer types only + ``` + +- **Static Assertions** in FIELD_ macros: + ```cpp + #define FIELD_Unique(table_name, field_name) \ + static_assert([]() constexpr { \ + using FieldType = decltype(std::declval().field_name); \ + static_assert(FilterableValue, \ + "Field cannot have Unique constraint - type is not filterable."); \ + return true; \ + }(), "Constraint validation for " #table_name "." #field_name); + ``` + +**Validation Coverage**: +- ✅ AutoIncrement constraints (only integer types) +- ✅ Index/Unique/PrimaryKey constraints (only filterable types) +- ✅ Type compatibility with BSATN serialization +- ✅ Template parameter validation + +**Error Output**: Clear compile-time error messages with specific guidance + +### Phase 2: Runtime Registration (__preinit__ functions) + +**Location**: WASM module load, before any user code executes + +#### 2.1 Global State Initialization (__preinit__01_) +```cpp +extern "C" __attribute__((export_name("__preinit__01_clear_global_state"))) +void __preinit__01_clear_global_state() { + ClearV9Module(); // Reset module definition + getV9TypeRegistration().clear(); // Reset type registry +} +``` + +#### 2.2 Component Registration (__preinit__10-30_) +Generated by macros during preprocessing: + +**Table Registration** (__preinit__20_): +```cpp +SPACETIMEDB_TABLE(User, users, Public) +// Generates: +extern "C" __attribute__((export_name("__preinit__20_register_table_User_line_42"))) +void __preinit__20_register_table_User_line_42() { + SpacetimeDb::Module::RegisterTable("users", true); +} +``` + +**Field Constraints** (__preinit__21_): +```cpp +FIELD_PrimaryKey(users, id); +// Generates: +extern "C" __attribute__((export_name("__preinit__21_field_constraint_users_id_line_43"))) +void __preinit__21_field_constraint_users_id_line_43() { + getV9Builder().AddFieldConstraint("users", "id", FieldConstraint::PrimaryKey); +} +``` + +**Auto-Increment Integration Registration** (__preinit__19_): +Auto-increment fields require special handling during `insert()` operations. When SpacetimeDB processes an auto-increment insert, it returns only the generated column values (not the full row) in BSATN format. The C++ SDK uses a registry-based integration system to properly handle these generated values and update the user's row object. + +```cpp +FIELD_PrimaryKeyAutoInc(users, id); +// Generates both constraint registration AND auto-increment integration: + +// 1. Auto-increment integration function (unique per field via __LINE__) +namespace SpacetimeDb { namespace detail { + static void autoinc_integrate_47(User& row, SpacetimeDb::bsatn::Reader& reader) { + using FieldType = decltype(std::declval().id); + FieldType generated_value = SpacetimeDb::bsatn::deserialize(reader); + row.id = generated_value; // Update field with generated ID + } +}} + +// 2. Registration function to register the integrator +extern "C" __attribute__((export_name("__preinit__19_autoinc_register_47"))) +void __preinit__19_autoinc_register_47() { + SpacetimeDb::detail::get_autoinc_integrator() = + &SpacetimeDb::detail::autoinc_integrate_47; +} +``` + +**Runtime Integration Process**: +When `insert()` is called on a table with auto-increment fields: +1. SDK serializes and sends the row to SpacetimeDB +2. SpacetimeDB processes the insert and generates the auto-increment value(s) +3. SpacetimeDB returns a buffer containing only the generated column values in BSATN format +4. SDK calls the registered integrator function to update the original row with generated values +5. `insert()` returns the updated row with the correct generated ID + +This system enables users to immediately access generated IDs: +```cpp +SPACETIMEDB_REDUCER(create_user, ReducerContext ctx, std::string name) { + User user{0, name, true}; // id=0 will be auto-generated + User inserted_user = ctx.db[users].insert(user); // Returns user with generated ID + LOG_INFO("Created user with ID: " + std::to_string(inserted_user.id)); +} +``` + +**Reducer Registration** (__preinit__30_): +```cpp +SPACETIMEDB_REDUCER(add_user, ReducerContext ctx, std::string name) +// Generates registration function that captures parameter types and creates dispatch handler +``` + +#### 2.3 Multiple Primary Key Detection +During constraint registration, track primary keys per table: +```cpp +// In V9Builder::AddFieldConstraint +if (constraint == FieldConstraint::PrimaryKey) { + if (table_has_primary_key[table_name]) { + SetMultiplePrimaryKeyError(table_name); // Set global error flag + } + table_has_primary_key[table_name] = true; +} +``` + +### Phase 3: Type System Registration + +**Component**: V9TypeRegistration system (`v9_type_registration.h`) + +**Core Principle**: Only user-defined structs and enums get registered in the typespace. Primitives, arrays, Options, and special types are always inlined. + +**Registration Flow**: +```cpp +class V9TypeRegistration { + AlgebraicType registerType(const bsatn::AlgebraicType& bsatn_type, + const std::string& explicit_name = "", + const std::type_info* cpp_type = nullptr) { + // 1. Check if primitive → return inline + if (isPrimitive(bsatn_type)) return convertPrimitive(bsatn_type); + + // 2. Check if array → return inline Array with recursive element processing + if (bsatn_type.tag() == bsatn::AlgebraicTypeTag::Array) + return convertArray(bsatn_type); + + // 3. Check if Option → return inline Sum structure + if (isOptionType(bsatn_type)) return convertOption(bsatn_type); + + // 4. Check if special type → return inline Product structure + if (isSpecialType(bsatn_type)) return convertSpecialType(bsatn_type); + + // 5. User-defined type → register in typespace, return Ref + return registerUserDefinedType(bsatn_type, explicit_name, cpp_type); + } +}; +``` + +**Circular Reference Detection**: +```cpp +// Track types currently being registered +std::unordered_set types_being_registered_; + +AlgebraicType registerUserDefinedType(...) { + if (types_being_registered_.contains(type_name)) { + setError("Circular reference detected in type: " + type_name); + return createErrorType(); + } + types_being_registered_.insert(type_name); + // ... process type ... + types_being_registered_.erase(type_name); +} +``` + +### Phase 4: Validation and Error Detection (__preinit__99_) + +**Location**: Final preinit function - runs after all registration is complete + +**Error Detection**: +```cpp +extern "C" __attribute__((export_name("__preinit__99_validate_types"))) +void __preinit__99_validate_types() { + // 1. Check for circular reference errors + if (g_circular_ref_error) { + createErrorModule("ERROR_CIRCULAR_REFERENCE_" + g_circular_ref_type_name); + return; + } + + // 2. Check for multiple primary key errors + if (g_multiple_primary_key_error) { + createErrorModule("ERROR_MULTIPLE_PRIMARY_KEYS_" + g_multiple_primary_key_table_name); + return; + } + + // 3. Check for type registration errors + if (getV9TypeRegistration().hasError()) { + createErrorModule("ERROR_TYPE_REGISTRATION_" + sanitize(error_message)); + return; + } +} +``` + +**Error Module Creation**: When errors are detected, the normal module is replaced with a special error module containing an invalid type reference. When SpacetimeDB tries to resolve the type, it fails with an error message that includes the descriptive error type name. + +### Phase 5: Module Description Export + +**Function**: `__describe_module__()` - Called by SpacetimeDB after preinit functions complete + +**Process**: +1. Serialize the completed V9 module definition +2. Include typespace (all registered types) +3. Include tables with constraints +4. Include reducers with parameter types +5. Include named type exports +6. Return binary module description + +## Namespace Qualification System + +### Overview +The C++ SDK provides a unique compile-time namespace qualification system for enum types, allowing better organization in generated client code without affecting server-side C++ usage. + +### Architecture Components + +#### 1. Compile-Time Namespace Storage +**Location**: `enum_macro.h` - namespace_info template specialization + +```cpp +namespace SpacetimeDb::detail { + // Primary template - no namespace by default + template + struct namespace_info { + static constexpr const char* value = nullptr; + }; +} + +// SPACETIMEDB_NAMESPACE macro creates specialization +#define SPACETIMEDB_NAMESPACE(EnumType, NamespacePrefix) \ + namespace SpacetimeDb::detail { \ + template<> \ + struct namespace_info { \ + static constexpr const char* value = NamespacePrefix; \ + }; \ + } +``` + +#### 2. LazyTypeRegistrar Integration +**Location**: `v9_type_registration.h` - Compile-time namespace detection + +```cpp +template +class LazyTypeRegistrar { + static bsatn::AlgebraicType getOrRegister(...) { + std::string qualified_name = type_name; + + // Compile-time check for namespace information + if constexpr (requires { SpacetimeDb::detail::namespace_info::value; }) { + constexpr const char* namespace_prefix = + SpacetimeDb::detail::namespace_info::value; + if (namespace_prefix != nullptr) { + qualified_name = std::string(namespace_prefix) + "." + type_name; + } + } + + // Register with qualified name + type_index_ = getV9TypeRegistration().registerAndGetIndex( + algebraic_type, qualified_name, &typeid(T)); + } +}; +``` + +#### 3. Type Registration with Namespaces +When an enum with namespace qualification is registered: +1. SPACETIMEDB_ENUM defines the enum and its BSATN traits +2. SPACETIMEDB_NAMESPACE adds compile-time metadata +3. LazyTypeRegistrar detects namespace at compile-time +4. Type is registered with qualified name (e.g., "Auth.UserRole") +5. Client generators recognize the namespace structure + +### Design Rationale + +**Why Separate Macros?** +- Clean separation of concerns: enum definition vs. namespace qualification +- Optional feature - enums work without namespaces +- Non-intrusive - doesn't modify the enum type itself +- Compile-time only - zero runtime overhead + +**Why Template Specialization?** +- Type-safe association between enum and namespace +- Compile-time resolution - no runtime lookups +- Works with C++20 concepts and if constexpr +- No memory overhead - constexpr strings + +### Comparison with Other Approaches + +**Alternative 1: Preinit Runtime Modification** (Rejected) +- Would require modifying types after registration +- Complex synchronization with type registry +- Runtime overhead for namespace lookup + +**Alternative 2: Embedded in SPACETIMEDB_ENUM** (Rejected) +- Would complicate the macro syntax +- Makes namespace mandatory rather than optional +- Harder to add namespaces to existing code + +**Current Approach Benefits**: +- Clean, modular design +- Zero runtime cost +- Optional and backwards-compatible +- Easy to understand and maintain + +## Key Differences from Rust and C# SDKs + +### 1. Type Registration Approach + +**Rust SDK**: +- Derive macros automatically generate type registration code +- Compile-time code generation using procedural macros +- Direct integration with Rust's type system +- Option types automatically inlined by macro system + +**C# SDK**: +- Reflection-based runtime type discovery +- Attribute-based configuration +- Dynamic type registration during module initialization +- .NET type system integration + +**C++ SDK**: +- Template-based compile-time validation with runtime registration +- Macro-generated __preinit__ functions for ordered initialization +- Manual type registration via SPACETIMEDB_STRUCT macros +- Hybrid approach combining compile-time safety with runtime flexibility + +### 2. Constraint Validation + +**Rust SDK**: +- Procedural macros generate compile-time validation +- Type system automatically enforces valid constraints +- No runtime constraint checking needed + +**C# SDK**: +- Runtime validation using reflection +- Attributes specify constraints, validated during registration +- Dynamic error reporting + +**C++ SDK**: +- **Three-layer validation system**: + 1. **Compile-time**: C++20 concepts and static assertions + 2. **Registration-time**: Multiple primary key detection + 3. **Module load**: preinit_99_ comprehensive validation +- Most sophisticated error detection of all SDKs + +### 3. Error Handling Strategy + +**Rust SDK**: +- Compile-time errors prevent building invalid modules +- Type system prevents most runtime errors +- Standard Rust error messages + +**C# SDK**: +- Runtime exceptions with detailed error messages +- Graceful error handling with exception propagation +- .NET debugging tools integration + +**C++ SDK**: +- **Error module replacement strategy**: + - Invalid modules replaced with special error modules + - Error type names embed descriptive information + - SpacetimeDB server provides clear error messages + - Comprehensive error categorization and reporting + +### 4. Type System Philosophy + +**Rust SDK**: +- "If it compiles, it works" - maximum compile-time validation +- Leverages Rust's ownership and type system +- Minimal runtime overhead + +**C# SDK**: +- "Flexibility with safety" - runtime validation with rich error messages +- Leverages .NET reflection and attributes +- Dynamic type discovery + +**C++ SDK**: +- **"Validate early, validate often"** - multi-layer validation system +- Combines C++20 compile-time features with runtime checks +- Nominal type system with explicit registration +- Optimized for catching errors at the earliest possible phase + +## Memory Management and Performance + +### Compile-Time Optimizations +- Template specialization eliminates runtime overhead +- Constexpr evaluations reduce WASM binary size +- Zero-cost abstractions for type-safe database access + +### Runtime Efficiency +- Minimal allocation during type registration +- Efficient binary serialization with BSATN +- Optimized field accessors with index caching + +### WASM Constraints +- 16MB initial memory limit (configurable) +- No dynamic memory growth during module registration +- Careful memory management in preinit functions + +## Development Workflow Integration + +### Error Detection Timeline +``` +Developer writes code + ↓ +C++ compilation → Compile-time validation (concepts, static_assert) + ↓ +Emscripten WASM build → Template instantiation validation + ↓ +Module publishing → Runtime validation (__preinit__99_) + ↓ +SpacetimeDB loading → Server-side validation and error reporting +``` + +### Debugging Support +- **Compile-time**: Clear error messages with field/constraint guidance +- **Build-time**: Template instantiation error reporting +- **Runtime**: Comprehensive logging with error categorization +- **Server-side**: Descriptive error module names for easy diagnosis + +## Future Architecture Considerations + +### Potential Improvements +1. **Unified Validation**: Move more validation to compile-time using concepts +2. **Better Error Recovery**: Partial module loading with isolated error handling +3. **Performance Optimization**: Reduce template instantiation overhead +4. **Enhanced Debugging**: Source location tracking for runtime errors + +### Scalability +- Type registration system scales linearly with module complexity +- Preinit function count grows with table/reducer count but remains manageable +- Memory usage is predictable and bounded + +## Related Documentation + +- [Type System Details](README.md#features) +- [Constraint Validation Tests](tests/type-isolation-test/) +- [API Reference](REFERENCE.md) +- [Quick Start Guide](QUICKSTART.md) \ No newline at end of file diff --git a/crates/bindings-cpp/CMakeLists.txt b/crates/bindings-cpp/CMakeLists.txt new file mode 100644 index 00000000000..782a351a525 --- /dev/null +++ b/crates/bindings-cpp/CMakeLists.txt @@ -0,0 +1,82 @@ +# SpacetimeDB C++ Module Library CMake Configuration + +cmake_minimum_required(VERSION 3.15) +project(SpacetimeDBCppModuleLibrary CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Module Library source files +set(LIBRARY_SOURCES + src/abi/module_exports.cpp + src/abi/wasi_shims.cpp + src/internal/Module.cpp + src/internal/AlgebraicType.cpp # Required for V9 autogen types + src/internal/v9_builder.cpp # V9 incremental module builder + src/internal/v9_type_registration.cpp # Unified type registration system + # src/library/index_management.cpp # Disabled due to conflicts with index_ops.h + # src/type_registry.cpp # REMOVED - TypeRegistry system eliminated +) + +# Create the Module Library +add_library(spacetimedb_cpp_library STATIC ${LIBRARY_SOURCES}) + +# Set include directories +target_include_directories(spacetimedb_cpp_library + PUBLIC + $ + $ +) + +# Create an alias target for better namespacing +add_library(spacetimedb::spacetimedb_cpp_library ALIAS spacetimedb_cpp_library) + +# Set compile options if building for WASM +if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + target_compile_options(spacetimedb_cpp_library PRIVATE + -O2 # Optimize for performance + -fno-exceptions # Disable exceptions for WASM compatibility + -ffunction-sections # Place each function in its own section + -fdata-sections # Place each data item in its own section + -Wall -Wextra # Enable warnings + ) + # Note: -g0 should be set in the final executable's linker flags, not here + # This allows debugging the library during development if needed +endif() + +# Export compile commands for better IDE support +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Testing configuration +option(BUILD_TESTS "Build the test suite" ON) + +if(BUILD_TESTS AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + enable_testing() + + # Add test executable + add_executable(test_bsatn tests/main.cpp tests/module_library_unit_tests.cpp) + + # Link against the module library + target_link_libraries(test_bsatn PRIVATE spacetimedb_cpp_library) + + # Set C++20 standard for tests + target_compile_features(test_bsatn PRIVATE cxx_std_20) + + # Add test to CTest + add_test(NAME bsatn_tests COMMAND test_bsatn) + + # Add verbose test variant + add_test(NAME bsatn_tests_verbose COMMAND test_bsatn -v) + + # Set test properties + set_tests_properties(bsatn_tests PROPERTIES + TIMEOUT 30 + LABELS "unit" + ) + + set_tests_properties(bsatn_tests_verbose PROPERTIES + TIMEOUT 30 + LABELS "unit;verbose" + ) +endif() \ No newline at end of file diff --git a/crates/bindings-cpp/DEVELOP.md b/crates/bindings-cpp/DEVELOP.md new file mode 100644 index 00000000000..0d4a761d00d --- /dev/null +++ b/crates/bindings-cpp/DEVELOP.md @@ -0,0 +1,597 @@ +# C++ SDK Development Roadmap: Leveraging Modern C++ Standards + +This document explores how upgrading to C++23 and C++26 could fundamentally transform the SpacetimeDB C++ SDK, moving from runtime registration to compile-time type system integration and eliminating most macro usage. + +## Current Architecture: Why So Many Macros? + +### The Fundamental Problem: No Compile-Time Reflection in C++20 + +Without reflection, the compiler cannot: +- Enumerate struct fields automatically +- Determine field types and names +- Discover which types should be tables +- Generate serialization code +- Build type relationships + +This forces us into a multi-layer macro system with runtime registration: + +### Layer 1: SPACETIMEDB_STRUCT - Manual Field Enumeration + +```cpp +struct User { + uint32_t id; + std::string name; + std::string email; +}; +SPACETIMEDB_STRUCT(User, id, name, email) +``` + +**Why we need it:** +- C++ has no way to iterate over struct fields at compile-time +- We must manually list every field for serialization +- The macro generates a `bsatn_traits` specialization that knows how to serialize/deserialize + +**What it generates:** +```cpp +template<> struct bsatn_traits { + static AlgebraicType algebraic_type() { + // Builds Product type with fields [id, name, email] + } + static void serialize(Writer& w, const User& val) { + w.write(val.id); w.write(val.name); w.write(val.email); + } +}; +``` + +**Why it can't be compile-time:** We can't discover the fields without listing them explicitly. + +### Layer 2: SPACETIMEDB_TABLE - Runtime Table Registration + +```cpp +SPACETIMEDB_TABLE(User, users, Public) +``` + +**Why we need it separately from STRUCT:** +- A struct might be used as a table, a reducer parameter, or a nested type +- Not all structs are tables - some are just data types +- Table registration needs additional metadata (name, access level) + +**What it generates:** +```cpp +extern "C" __attribute__((export_name("__preinit__20_register_table_User"))) +void __preinit__20_register_table_User() { + Module::RegisterTable("users", true); +} +// Plus a tag type for clean syntax: ctx.db[users] +``` + +**Why it must be runtime:** The module schema must be built dynamically when the WASM module loads, as we can't generate a complete module description at compile-time. + +### Layer 3: FIELD_ Macros - Constraint Registration + +```cpp +FIELD_PrimaryKey(users, id); +FIELD_Unique(users, email); +FIELD_Index(users, name); +``` + +**Why we need separate macros per constraint:** +- Each field can have multiple constraints +- Constraints must be registered AFTER the table (priority ordering) +- Some constraints need compile-time validation (C++20 concepts) +- Can't be part of SPACETIMEDB_TABLE because we need the table to exist first + +**What they generate:** +```cpp +// Compile-time validation +static_assert(FilterableValue, "Primary keys must be filterable"); + +// Runtime registration +extern "C" __attribute__((export_name("__preinit__21_field_constraint_users_id"))) +void __preinit__21_field_constraint_users_id() { + AddFieldConstraint("users", "id", FieldConstraint::PrimaryKey); +} +``` + +**Why the split:** Compile-time validation via concepts, but runtime registration for module schema. + +### Layer 4: __preinit__ Priority System - Ordered Initialization + +```cpp +__preinit__01_ - Clear global state +__preinit__10_ - Field registration +__preinit__20_ - Table registration +__preinit__21_ - Constraints (must come after tables) +__preinit__30_ - Reducers +__preinit__99_ - Validation +``` + +**Why we need priority ordering:** +- Tables must exist before constraints can be added +- Types must be registered before they're referenced +- Validation must happen after everything else +- WebAssembly's linear memory model requires deterministic initialization + +**Why it's runtime:** WASM modules are initialized linearly, and we need to build the module schema during this initialization phase. + +### Layer 5: Namespace Qualification - Compile-Time Metadata + +```cpp +SPACETIMEDB_ENUM(UserRole, Admin, Moderator, Member) +SPACETIMEDB_NAMESPACE(UserRole, "Auth") // Separate macro for namespace +``` + +**Why we need a separate macro:** +- Namespace is optional metadata, not core to the enum definition +- Must work with existing enum definitions without modification +- Needs to associate compile-time string with type + +**What it generates:** +```cpp +namespace SpacetimeDb::detail { + template<> struct namespace_info { + static constexpr const char* value = "Auth"; + }; +} +``` + +**Why it's compile-time but still needs a macro:** +- C++20 has no way to attach metadata to types without explicit specialization +- Template specialization requires knowing the type name +- LazyTypeRegistrar uses `if constexpr` to detect namespace at compile-time + +### Layer 6: __describe_module__ - Final Runtime Assembly + +```cpp +extern "C" const uint8_t* __describe_module__() { + // Serialize the complete module built by __preinit__ functions + return Module::serialize(); +} +``` + +**Why it's needed:** +- SpacetimeDB server calls this to get the module schema +- Must return a binary description of all tables, types, reducers +- Can only be built after all __preinit__ functions have run + +**The fundamental limitation:** Without compile-time reflection, we cannot know at compile-time: +- Which types are tables +- What fields each struct has +- What constraints apply to each field +- The complete type dependency graph +- What namespace qualifications are applied + +### The Cascade Effect + +This creates a cascade of limitations: + +1. **No automatic serialization** → Need SPACETIMEDB_STRUCT macro +2. **No type discovery** → Need explicit SPACETIMEDB_TABLE macro +3. **No field introspection** → Need separate FIELD_ macros +4. **No compile-time module generation** → Need runtime __preinit__ system +5. **No static validation** → Need runtime validation in __preinit__99 + +Each macro exists because C++20 lacks the reflection capabilities to do this work automatically. The runtime registration exists because we can't build a complete module description at compile-time without knowing what types exist and their relationships. + +## Current Architecture Limitations (Summary) + +The current C++20 SDK relies on: +- **Runtime registration** via `__preinit__` functions (because no compile-time type discovery) +- **Heavy macro usage** for type and table registration (because no reflection) +- **Runtime error detection** in `__preinit__99_validate_types` (because incomplete compile-time info) +- **Manual serialization** through SPACETIMEDB_STRUCT macros (because no field enumeration) +- **String-based type identification** requiring explicit registration (because no compile-time type identity) + +## C++23 Improvements + +### 1. Deducing `this` for Zero-Cost Field Accessors + +**Current approach:** +```cpp +template +class TypedFieldAccessor : public TableAccessor { + FieldType TableType::*member_ptr_; + // Complex inheritance hierarchy +}; +``` + +**C++23 approach:** +```cpp +struct TableAccessor { + template + auto filter(this Self&& self, auto&& predicate) { + // Deduce table type from self, no inheritance needed + return self.table_.filter(std::forward(predicate)); + } +}; +``` + +**Benefits:** +- Eliminate accessor class hierarchy +- Perfect forwarding throughout +- Reduced template instantiation overhead + +### 2. `if consteval` for Hybrid Compile/Runtime Validation + +**Current approach:** +```cpp +// Static assertions in macros +static_assert(FilterableValue, "Error message"); +// Plus runtime validation in __preinit__99 +``` + +**C++23 approach:** +```cpp +template +constexpr auto validate_constraint() { + if consteval { + // Compile-time path: full validation + static_assert(FilterableValue); + return compile_time_type_id(); + } else { + // Runtime fallback for dynamic types + return runtime_type_registry::get(); + } +} +``` + +**Benefits:** +- Single validation function works at compile-time or runtime +- Better error messages with source locations +- Gradual migration path from runtime to compile-time + +### 3. `std::expected` for Error Propagation + +**Current approach:** +```cpp +// Global error flags +static bool g_multiple_primary_key_error = false; +static std::string g_multiple_primary_key_table_name = ""; +``` + +**C++23 approach:** +```cpp +template +using RegistrationResult = std::expected; + +constexpr RegistrationResult register_table() { + if (/* multiple primary keys detected */) + return std::unexpected(RegistrationError::MultiplePrimaryKeys); + return TypeId{...}; +} +``` + +**Benefits:** +- Type-safe error handling +- Composable error propagation +- No global state needed + +### 4. `constexpr std::unique_ptr` for Compile-Time Type Trees + +**Current approach:** +```cpp +// Runtime type tree building +std::vector types; +types.push_back(...); +``` + +**C++23 approach:** +```cpp +constexpr auto build_type_tree() { + std::unique_ptr root = std::make_unique(); + // Build entire type hierarchy at compile time + return root; +} + +constexpr auto type_tree = build_type_tree(); +``` + +**Benefits:** +- Complete type system known at compile-time +- Zero runtime allocation +- Enables compile-time validation of entire module + +### 5. `std::ranges` for Cleaner Type Processing + +**Current approach:** +```cpp +// Manual iteration and filtering +std::vector types; +for (const auto& type : all_types) { + if (isPrimitive(type)) continue; + if (isOptionType(type)) continue; + types.push_back(processType(type)); +} +``` + +**C++23 approach:** +```cpp +// Declarative pipeline with ranges +auto process_types() { + return all_types + | std::views::filter(not_primitive) + | std::views::filter(not_option) + | std::views::transform(processType) + | std::ranges::to(); +} + +// Better: lazy evaluation for type checking +auto valid_types = registered_types + | std::views::filter([](auto& t) { return validate_type(t).has_value(); }); +``` + +**Benefits:** +- More declarative and readable type processing +- Lazy evaluation reduces memory usage +- Composable validation pipelines +- Better separation of filtering logic from processing + +### 6. `std::mdspan` for Table Data Access + +**Current approach:** +```cpp +// Custom iterators and accessors +for (const auto& row : table) { } +``` + +**C++23 approach:** +```cpp +template +using TableView = std::mdspan()>>; + +// Direct columnar access +auto names = table_view[std::full_extent, name_column]; +``` + +**Benefits:** +- Standard library support for multi-dimensional access +- Optimized for columnar operations +- Compatible with parallel algorithms + +## C++26 Transformative Features + +### 1. Static Reflection (P2996) - Complete Macro Elimination + +**Current approach:** +```cpp +SPACETIMEDB_STRUCT(User, id, name, email) +SPACETIMEDB_TABLE(User, users, Public) +FIELD_PrimaryKey(users, id); +SPACETIMEDB_ENUM(UserRole, Admin, Moderator, Member) +SPACETIMEDB_NAMESPACE(UserRole, "Auth") // Separate macro for namespace +``` + +**C++26 approach:** +```cpp +struct [[spacetimedb::table("users", public)]] User { + [[spacetimedb::primary_key]] uint32_t id; + [[spacetimedb::unique]] std::string email; + std::string name; +}; + +enum class [[spacetimedb::namespace("Auth")]] UserRole { + Admin, Moderator, Member +}; + +// Automatic registration via reflection +template requires has_spacetimedb_table_attr +consteval void register_table() { + constexpr auto members = ^T::members(); + for (constexpr auto member : members) { + if constexpr (has_attribute) { + register_primary_key(member.name(), member.type()); + } + } +} + +// Automatic namespace detection via reflection +template +consteval std::string get_qualified_name() { + if constexpr (has_attribute<^T, spacetimedb::namespace>) { + return get_attribute<^T, spacetimedb::namespace>() + "." + name_of(^T); + } + return name_of(^T); +} +``` + +**Benefits:** +- **Zero macros needed** +- Natural C++ syntax with attributes +- Complete type information at compile-time +- Automatic serialization without SPACETIMEDB_STRUCT + +### 2. Contracts for Constraint Validation + +**Current approach:** +```cpp +static_assert(FilterableValue, "Field cannot have Index constraint"); +``` + +**C++26 approach:** +```cpp +template +void add_index_constraint(T TableType::*field) + [[pre: FilterableValue]] + [[pre: !has_existing_index(field)]] +{ + // Contract violations become compile-time or runtime errors + // depending on evaluation context +} +``` + +**Benefits:** +- Declarative constraint specification +- Automatic error messages from contract violations +- Works for both compile-time and runtime validation + +### 3. Pattern Matching for Type Dispatch + +**Current approach:** +```cpp +switch(type.tag()) { + case AlgebraicTypeTag::Product: ... + case AlgebraicTypeTag::Sum: ... + // Manual casting and handling +} +``` + +**C++26 approach:** +```cpp +inspect(type) { + p => serialize_product(p), + s => serialize_sum(s), + [auto elem_type] => serialize_array(elem_type), +