#ifndef SPACETIMEDB_MODULE_IMPL_H #define SPACETIMEDB_MODULE_IMPL_H /** * SpacetimeDB C++ bindings - Module Implementation * * Core module system implementation with optimized type handling, * table registration, and reducer management. * * Architecture: * - Type traits for compile-time type detection * - BSATN serialization utilities * - Unified table and reducer registration * - V9 builder integration for constraints */ #include "Module.h" #include "field_registration.h" #include "../table_with_constraints.h" #include #include "bsatn_adapters.h" #include #include #include #include "autogen/Lifecycle.g.h" #include "autogen/RawConstraintDefV9.g.h" #include "autogen/RawUniqueConstraintDataV9.g.h" #include "autogen/RawIndexDefV9.g.h" #include "autogen/RawIndexAlgorithm.g.h" #include "v9_builder.h" #include #include #include #include #include #include #include #include #include namespace SpacetimeDB { namespace Internal { // ============================================================================= // TYPE TRAITS // ============================================================================= // Detect unit structs template struct is_unit_struct_type { private: template static auto test(int) -> decltype(U::__is_unit_type__, std::bool_constant{}); template static std::false_type test(...); public: static constexpr bool value = decltype(test(0))::value; }; template inline constexpr bool is_unit_struct_v = is_unit_struct_type::value; // Detect vector types template struct is_vector_type : std::false_type {}; template struct is_vector_type> : std::true_type {}; // Detect optional types template struct is_optional : std::false_type {}; template struct is_optional> : std::true_type {}; // Check for big integer types template constexpr bool is_big_integer_v = std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; // Check if type needs registry registration template constexpr bool needs_type_registration_v = (!std::is_arithmetic_v && !std::is_same_v && !is_big_integer_v) || requires { bsatn::bsatn_traits::algebraic_type(); }; // Get BSATN tag for primitive types template struct type_id { static constexpr uint8_t value = []() { if constexpr (std::is_same_v) return 4; else if constexpr (std::is_same_v) return 5; else if constexpr (std::is_same_v) return 6; else if constexpr (std::is_same_v) return 7; else if constexpr (std::is_same_v) return 8; else if constexpr (std::is_same_v) return 9; else if constexpr (std::is_same_v) return 10; else if constexpr (std::is_same_v) return 11; else if constexpr (std::is_same_v) return 12; else if constexpr (std::is_same_v) return 17; else if constexpr (std::is_same_v) return 18; else if constexpr (std::is_same_v) return 19; else return 0; // Complex type }(); }; // Check for BSATN traits template struct has_bsatn_traits : std::false_type {}; template struct has_bsatn_traits::deserialize(std::declval()))>> : std::true_type {}; template constexpr bool has_bsatn_traits_v = has_bsatn_traits::value; // Check if type can be written inline template constexpr bool is_basic_inlineable_v = std::is_arithmetic_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; // ============================================================================= // BINARY I/O UTILITIES // ============================================================================= inline void write_u32(std::vector& buf, uint32_t val) { bsatn::Writer writer(buf); writer.write_u32_le(val); } inline void write_string(std::vector& buf, const std::string& str) { bsatn::Writer writer(buf); writer.write_string(str); } inline uint8_t read_u8(uint32_t source) { BytesSource src{source}; BytesSourceReader reader(src); return reader.read_u8(); } inline uint32_t read_u32(uint32_t source) { BytesSource src{source}; BytesSourceReader reader(src); return reader.read_u32_le(); } // ============================================================================= // TABLE REGISTRATION // ============================================================================= // Apply constraints to table with optimized field lookup inline void apply_table_constraints(RawModuleDef::Table& table, const std::vector& constraints) { if (constraints.empty() || table.fields.empty()) return; // Build field name lookup map std::unordered_map field_indices; field_indices.reserve(table.fields.size()); for (size_t i = 0; i < table.fields.size(); ++i) { field_indices.emplace(table.fields[i].name, static_cast(i)); } // Pre-allocate constraint vectors std::vector unique_fields, indexed_fields, autoinc_fields; unique_fields.reserve(constraints.size()); indexed_fields.reserve(constraints.size()); autoinc_fields.reserve(constraints.size()); // Process constraints in single pass for (const auto& constraint : constraints) { if (constraint.field_name == nullptr) continue; auto field_it = field_indices.find(constraint.field_name); if (field_it == field_indices.end()) continue; const uint16_t field_idx = field_it->second; const auto constraint_flags = constraint.constraints; if (constraint_flags == FieldConstraint::PrimaryKey || constraint_flags == FieldConstraint::PrimaryKeyAuto) { table.primary_key = field_idx; unique_fields.push_back(field_idx); indexed_fields.push_back(field_idx); } else if (constraint_flags == FieldConstraint::Unique || constraint_flags == FieldConstraint::Identity) { unique_fields.push_back(field_idx); indexed_fields.push_back(field_idx); } else if (has_constraint(constraint_flags, FieldConstraint::Indexed)) { indexed_fields.push_back(field_idx); } if (has_constraint(constraint_flags, FieldConstraint::AutoInc)) { autoinc_fields.push_back(field_idx); } } // Sort and deduplicate auto sort_and_dedupe = [](std::vector& vec) { if (!vec.empty()) { std::sort(vec.begin(), vec.end()); vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); } }; sort_and_dedupe(unique_fields); sort_and_dedupe(indexed_fields); sort_and_dedupe(autoinc_fields); // Move into table table.unique_columns = std::move(unique_fields); table.indexed_columns = std::move(indexed_fields); table.autoinc_columns = std::move(autoinc_fields); } // Extract fields from type and populate table structure template void add_fields_for_type(RawModuleDef::Table& table) { auto algebraic_type = bsatn::bsatn_traits::algebraic_type(); if (algebraic_type.tag() != bsatn::AlgebraicTypeTag::Product) { return; } const auto& product = algebraic_type.as_product(); const size_t field_count = product.elements.size(); table.fields.reserve(field_count); // Type-specific storage for field names static std::map> type_field_storage; auto& field_names = type_field_storage[&typeid(T)]; field_names.clear(); field_names.reserve(field_count); // Update global descriptors auto& global_descriptors = get_table_descriptors(); auto& descriptor = global_descriptors[&typeid(T)]; descriptor.fields.clear(); descriptor.fields.reserve(field_count); // Process fields for (size_t i = 0; i < field_count; ++i) { const auto& element = product.elements[i]; std::string field_name = element.name.has_value() ? element.name.value() : "field_" + std::to_string(i); field_names.push_back(std::move(field_name)); FieldInfo field; field.name = field_names[i].c_str(); field.offset = i * sizeof(void*); field.size = sizeof(void*); field.type_id = 0; field.serialize = [](std::vector&, const void*) {}; table.fields.push_back(field); FieldDescriptor global_field; global_field.name = field_names[i]; global_field.offset = field.offset; global_field.size = field.size; global_field.write_type = [](std::vector&) {}; global_field.get_algebraic_type = []() { return bsatn::AlgebraicType::U32(); }; global_field.serialize = [](std::vector&, const void*) {}; global_field.get_type_name = []() { return std::string(); }; descriptor.fields.push_back(std::move(global_field)); } } // Unified table registration - single implementation template void Module::RegisterTableInternalImpl(const char* name, bool is_public, const std::vector& constraints) { RawModuleDef::Table table; table.name = name; table.is_public = is_public; table.type = &typeid(T); add_fields_for_type(table); if (!constraints.empty()) { apply_table_constraints(table, constraints); } // V9 registration - always register tables with V9Builder getV9Builder().RegisterTable(name, is_public); table.serialize = [](std::vector& buf, const void* obj) { auto& module_def = GetModuleDef(); auto it = module_def.table_indices.find(&typeid(T)); if (it == module_def.table_indices.end()) return; const auto& table = module_def.tables[it->second]; for (const auto& field : table.fields) { field.serialize(buf, obj); } }; GetModuleDef().AddTable(std::move(table)); } // Overload for tables without constraints template void Module::RegisterTableInternalImpl(const char* name, bool is_public) { RegisterTableInternalImpl(name, is_public, {}); } // ============================================================================= // ARGUMENT DESERIALIZATION // ============================================================================= template T read_arg(uint32_t source) { BytesSource src{source}; if constexpr (is_unit_struct_v) { return T{}; } else if constexpr (!needs_type_registration_v) { BytesSourceReader reader(src); if constexpr (std::is_same_v) { return reader.read_bool(); } else if constexpr (std::is_same_v) { return reader.read_u8(); } else if constexpr (std::is_same_v) { return reader.read_u16_le(); } else if constexpr (std::is_same_v) { return reader.read_u32_le(); } else if constexpr (std::is_same_v) { return reader.read_u64_le(); } else if constexpr (std::is_same_v) { return reader.read_i8(); } else if constexpr (std::is_same_v) { return reader.read_i16_le(); } else if constexpr (std::is_same_v) { return reader.read_i32_le(); } else if constexpr (std::is_same_v) { return reader.read_i64_le(); } else if constexpr (std::is_same_v) { return reader.read_f32_le(); } else if constexpr (std::is_same_v) { return reader.read_f64_le(); } else if constexpr (std::is_same_v) { return reader.read_string(); } else { static_assert(!sizeof(T), "Unsupported primitive type"); } } else if constexpr (has_bsatn_traits_v) { std::vector buffer; constexpr size_t CHUNK_SIZE = 256; uint8_t chunk[CHUNK_SIZE]; while (true) { size_t requested = CHUNK_SIZE; size_t actual = requested; FFI::bytes_source_read(src, chunk, &actual); if (actual == 0) break; buffer.insert(buffer.end(), chunk, chunk + actual); if (actual < requested) break; } bsatn::Reader reader(buffer); return bsatn::bsatn_traits::deserialize(reader); } else { static_assert(std::is_default_constructible_v, "Type must be default constructible or have BSATN traits"); return T{}; } } // Read multiple arguments as tuple template auto read_args_tuple(uint32_t args_source) { BytesSource src{args_source}; // Handle single unit struct specially if constexpr (sizeof...(Args) == 1) { using FirstArg = std::tuple_element_t<0, std::tuple>; if constexpr (is_unit_struct_v) { return std::make_tuple(FirstArg{}); } } if constexpr ((has_bsatn_traits_v || ...)) { std::vector buffer; constexpr size_t CHUNK_SIZE = 256; uint8_t chunk[CHUNK_SIZE]; while (true) { size_t requested = CHUNK_SIZE; size_t actual = requested; FFI::bytes_source_read(src, chunk, &actual); if (actual == 0) break; buffer.insert(buffer.end(), chunk, chunk + actual); if (actual < requested) break; } bsatn::Reader reader(buffer); return std::make_tuple(bsatn::bsatn_traits::deserialize(reader)...); } else { BytesSourceReader reader(src); return std::make_tuple([&reader]() -> Args { if constexpr (std::is_same_v) { return reader.read_u32_le(); } else if constexpr (std::is_same_v) { return reader.read_i32_le(); } else if constexpr (std::is_same_v) { return reader.read_u64_le(); } else if constexpr (std::is_same_v) { return reader.read_i64_le(); } else if constexpr (std::is_same_v) { return reader.read_bool(); } else if constexpr (std::is_same_v) { return reader.read_string(); } else { return bsatn::bsatn_traits::deserialize(reader); } }()...); } } // ============================================================================= // REDUCER WRAPPERS // ============================================================================= // Lifecycle reducer wrapper template void builtin_reducer_wrapper(Func func, ReducerContext& ctx, uint64_t sender_0, uint64_t sender_1, uint64_t sender_2, uint64_t sender_3) { std::array senderBytes{}; memcpy(senderBytes.data(), &sender_0, sizeof(uint64_t)); memcpy(senderBytes.data() + 8, &sender_1, sizeof(uint64_t)); memcpy(senderBytes.data() + 16, &sender_2, sizeof(uint64_t)); memcpy(senderBytes.data() + 24, &sender_3, sizeof(uint64_t)); Identity sender(senderBytes); if constexpr (std::is_invocable_v) { func(ctx); } else if constexpr (std::is_invocable_v) { func(ctx, sender); } } // Generic reducer wrapper template void spacetimedb_reducer_wrapper(void (*func)(ReducerContext, Args...), ReducerContext& ctx, uint32_t args_source) { if constexpr (sizeof...(Args) == 0) { func(ctx); } else { auto args_tuple = read_args_tuple(args_source); std::apply([&](auto&&... args) { func(ctx, std::forward(args)...); }, args_tuple); } } // ============================================================================= // TYPE SERIALIZATION // ============================================================================= // Write algebraic type inline (for special types and primitives) template void write_algebraic_type_inline(std::vector& buf) { if constexpr (is_optional::value) { using inner_type = typename T::value_type; buf.push_back(1); // AlgebraicType::Sum write_u32(buf, 2); // 2 variants buf.push_back(0); // Has name write_string(buf, "some"); write_algebraic_type_inline(buf); buf.push_back(0); // Has name write_string(buf, "none"); buf.push_back(2); // AlgebraicType::Product (empty) write_u32(buf, 0); // 0 elements } else if constexpr (is_vector_type::value) { using element_type = typename T::value_type; buf.push_back(3); // AlgebraicType::Array write_algebraic_type_inline(buf); } else if constexpr (std::is_same_v) { buf.push_back(10); } else if constexpr (std::is_same_v) { buf.push_back(11); } else if constexpr (std::is_same_v) { buf.push_back(4); } else if constexpr (std::is_same_v) { buf.push_back(5); } else if constexpr (std::is_same_v) { buf.push_back(6); } else if constexpr (std::is_same_v) { buf.push_back(7); } else if constexpr (std::is_same_v) { buf.push_back(8); } else if constexpr (std::is_same_v) { buf.push_back(9); } else if constexpr (std::is_same_v) { buf.push_back(12); } else if constexpr (std::is_same_v) { buf.push_back(13); } else if constexpr (std::is_same_v) { buf.push_back(18); } else if constexpr (std::is_same_v) { buf.push_back(19); } else if constexpr (std::is_same_v) { buf.push_back(15); } else if constexpr (std::is_same_v) { buf.push_back(17); } else if constexpr (std::is_same_v) { buf.push_back(14); } else if constexpr (std::is_same_v) { buf.push_back(16); } else if constexpr (std::is_same_v) { buf.push_back(2); // AlgebraicType::Product write_u32(buf, 1); // 1 field buf.push_back(0); // Some (has name) write_string(buf, bsatn::IDENTITY_TAG); buf.push_back(17); // AlgebraicType::U256 } else if constexpr (std::is_same_v) { buf.push_back(2); // AlgebraicType::Product write_u32(buf, 1); // 1 field buf.push_back(0); // Some (has name) write_string(buf, bsatn::CONNECTION_ID_TAG); buf.push_back(15); // AlgebraicType::U128 } else if constexpr (std::is_same_v) { buf.push_back(2); // AlgebraicType::Product write_u32(buf, 1); // 1 field buf.push_back(0); // Some (has name) write_string(buf, bsatn::TIMESTAMP_TAG); buf.push_back(12); // AlgebraicType::I64 } else if constexpr (std::is_same_v) { buf.push_back(2); // AlgebraicType::Product write_u32(buf, 1); // 1 field buf.push_back(0); // Some (has name) write_string(buf, bsatn::TIME_DURATION_TAG); buf.push_back(12); // AlgebraicType::I64 } else if constexpr (std::is_enum_v) { buf.push_back(4); // String fallback for complex enums } else { buf.push_back(4); // AlgebraicType::String fallback } } // ============================================================================= // REDUCER REGISTRATION // ============================================================================= inline std::optional get_lifecycle_for_name(const std::string& name) { if (name == "init") return Lifecycle::Init; if (name == "client_connected") return Lifecycle::OnConnect; if (name == "client_disconnected") return Lifecycle::OnDisconnect; return std::nullopt; } // Unified reducer registration template void RegisterReducerUnified(const std::string& name, void (*func)(ReducerContext, Args...), std::optional lifecycle = std::nullopt, const std::vector& param_names = {}) { RawModuleDef::Reducer reducer; reducer.name = name; reducer.lifecycle = lifecycle; reducer.handler = [func](ReducerContext& ctx, uint32_t args) { spacetimedb_reducer_wrapper(func, ctx, args); }; if constexpr (sizeof...(Args) == 0) { reducer.write_params = [](std::vector& buf) { write_u32(buf, 0); }; reducer.param_names = {}; } else { reducer.param_names = param_names; } // V9 registration { auto& v9_builder = getV9Builder(); std::vector param_types; std::vector param_cpp_types; std::vector param_type_names; if constexpr (sizeof...(Args) > 0) { (param_types.push_back(bsatn::bsatn_traits::algebraic_type()), ...); (param_cpp_types.push_back(&typeid(Args)), ...); param_type_names.resize(sizeof...(Args)); } v9_builder.AddV9Reducer( name, param_types, param_names, param_cpp_types, param_type_names, lifecycle ); } Module::GetModuleDef().AddReducer(std::move(reducer)); } // Lifecycle reducer registration inline void RegisterLifecycleReducer(const std::string& name, std::optional lifecycle, std::function handler) { auto& v9_builder = getV9Builder(); v9_builder.AddV9Reducer( name, {}, {}, {}, {}, lifecycle ); RawModuleDef::Reducer reducer; reducer.name = name; reducer.lifecycle = lifecycle; reducer.handler = handler; reducer.write_params = [](std::vector& buf) { write_u32(buf, 0); }; Module::GetModuleDef().AddReducer(std::move(reducer)); } template void Module::RegisterReducerInternalImpl(const std::string& name, void (*func)(ReducerContext, Args...)) { RegisterReducerUnified(name, func, get_lifecycle_for_name(name)); } template void Module::RegisterReducerInternalWithNames(const std::string& name, void (*func)(ReducerContext, Args...), const std::vector& param_names) { RegisterReducerUnified(name, func, get_lifecycle_for_name(name), param_names); } inline void Module::RegisterInitReducer(void (*func)(ReducerContext)) { RegisterLifecycleReducer("init", Lifecycle::Init, [func](ReducerContext& ctx, uint32_t) { func(ctx); }); } inline void Module::RegisterClientConnectedReducer(void (*func)(ReducerContext, Identity)) { RegisterLifecycleReducer("client_connected", Lifecycle::OnConnect, [func](ReducerContext& ctx, uint32_t) { func(ctx, ctx.sender); }); } inline void Module::RegisterClientDisconnectedReducer(void (*func)(ReducerContext, Identity)) { RegisterLifecycleReducer("client_disconnected", Lifecycle::OnDisconnect, [func](ReducerContext& ctx, uint32_t) { func(ctx, ctx.sender); }); } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= template void register_table_type(const char* name, bool is_public) { Module::RegisterTableInternalImpl(name, is_public); } // Optimized parameter name parsing inline std::vector parse_parameter_names(const std::string& params_str) { std::vector names; size_t pos = 0; bool first_param = true; while (pos < params_str.length()) { size_t comma_pos = params_str.find(',', pos); if (comma_pos == std::string::npos) comma_pos = params_str.length(); std::string param = params_str.substr(pos, comma_pos - pos); // Skip ReducerContext if (first_param) { first_param = false; pos = comma_pos + 1; continue; } // Trim whitespace size_t start = param.find_first_not_of(" \t"); if (start != std::string::npos) { param = param.substr(start); size_t end = param.find_last_not_of(" \t"); if (end != std::string::npos) { param = param.substr(0, end + 1); } } // Extract parameter name size_t last_space = param.find_last_of(" \t"); if (last_space != std::string::npos) { std::string param_name = param.substr(last_space + 1); size_t name_end = param_name.find_first_of("&*[]"); if (name_end != std::string::npos) { param_name = param_name.substr(0, name_end); } names.push_back(std::move(param_name)); } pos = comma_pos + 1; } return names; } } // namespace Internal } // namespace SpacetimeDB #endif // SPACETIMEDB_MODULE_IMPL_H