Jason Larabie 52b6c66fa1
Add C++ Bindings (#3544)
# Description of Changes

This adds C++ server bindings (/crate/bindings-cpp) to allow writing C++
20 modules.

- Emscripten WASM build system integration with CMake
- Macro-based code generation (SPACETIMEDB_TABLE, SPACETIMEDB_REDUCER,
etc)
- All SpacetimeDB types supported (primitives, Timestamp, Identity,
Uuid, etc)
- Product types via SPACETIMEDB_STRUCT
- Sum types via SPACETIMEDB_ENUM
- Constraints marked with FIELD* macros

# API and ABI breaking changes

None

# Expected complexity level and risk

2 - Doesn't heavily impact any other areas but is complex macro C++
structure to support a similar developer experience, did have a small
impact on init command

# Testing

- [x] modules/module-test-cpp - heavily tested every reducer
- [x] modules/benchmarks-cpp - tested through the standalone (~6x faster
than C#, ~6x slower than Rust)
- [x] modules/sdk-test-cpp
- [x] modules/sdk-test-procedure-cpp
- [x] modules/sdk-test-view-cpp  
- [x] Wrote several test modules myself
- [x] Quickstart smoketest [Currently in progress]
- [ ] Write Blackholio C++ server module

---------

Signed-off-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
2026-02-07 04:26:45 +00:00

208 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""
Compare SpacetimeDB module schemas between Rust and C++ implementations.
This script compares the JSON schema outputs from:
- Rust module: rust-module-schema.json
- C++ module: cpp-module-schema.json or cpp-module-schema-latest.json
Usage:
python3 compare_module_schemas.py
To regenerate schema files:
spacetime describe module-test --json > rust-module-schema.json
spacetime describe module-test-cpp --json > cpp-module-schema.json
"""
import json
import sys
import os
def load_schemas():
"""Load the schema files, trying both cpp-module-schema.json and cpp-module-schema-latest.json"""
try:
with open('rust-module-schema.json', 'r') as f:
rust = json.load(f)
except FileNotFoundError:
print("Error: rust-module-schema.json not found")
print("Generate it with: spacetime describe module-test --json > rust-module-schema.json")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error parsing rust-module-schema.json: {e}")
print("The file may be corrupted or incomplete. Try regenerating it.")
sys.exit(1)
# Try to load C++ schema, checking both possible filenames
cpp = None
for filename in ['cpp-module-schema-latest.json', 'cpp-module-schema.json']:
if os.path.exists(filename):
with open(filename, 'r') as f:
cpp = json.load(f)
print(f"Loaded C++ schema from {filename}")
break
if cpp is None:
print("Error: No C++ schema file found")
print("Generate it with: spacetime describe module-test-cpp --json > cpp-module-schema.json")
sys.exit(1)
return rust, cpp
def normalize_index(idx):
"""Normalize index representation for comparison"""
name = idx.get('name', {})
if isinstance(name, dict):
name = name.get('some', None)
accessor = idx.get('accessor_name', {})
if isinstance(accessor, dict):
accessor = accessor.get('some', None)
algo = idx.get('algorithm', {})
cols = []
if isinstance(algo, dict) and 'BTree' in algo:
cols = algo['BTree']
return {'name': name, 'accessor': accessor, 'columns': cols}
def compare_tables(rust_tables, cpp_tables):
"""Compare table definitions between Rust and C++"""
print("\n=== TABLE COMPARISON ===")
print(f"Rust tables: {len(rust_tables)}")
print(f"C++ tables: {len(cpp_tables)}")
rust_names = set(rust_tables.keys())
cpp_names = set(cpp_tables.keys())
if rust_names != cpp_names:
missing = rust_names - cpp_names
extra = cpp_names - rust_names
if missing:
print(f"❌ Missing in C++: {missing}")
if extra:
print(f"❌ Extra in C++: {extra}")
else:
print("✅ All table names match!")
return rust_names & cpp_names
def compare_indexes(rust_tables, cpp_tables, common_tables):
"""Compare index definitions for common tables"""
print("\n=== INDEX COMPARISON ===")
differences = []
for table_name in sorted(common_tables):
rust_table = rust_tables[table_name]
cpp_table = cpp_tables[table_name]
rust_indexes = [normalize_index(idx) for idx in rust_table.get('indexes', [])]
cpp_indexes = [normalize_index(idx) for idx in cpp_table.get('indexes', [])]
if rust_indexes != cpp_indexes:
differences.append((table_name, rust_indexes, cpp_indexes))
if not differences:
print("✅ All table indexes match perfectly!")
else:
print(f"Found index differences in {len(differences)} table(s):")
for table_name, rust_idx, cpp_idx in differences:
print(f"\n{table_name}:")
for r in rust_idx:
name_str = str(r['name']) if r['name'] is not None else "None"
accessor_str = str(r['accessor']) if r['accessor'] is not None else "None"
print(f" Rust: name={name_str:<30} accessor={accessor_str:<20} cols={r['columns']}")
for c in cpp_idx:
name_str = str(c['name']) if c['name'] is not None else "None"
accessor_str = str(c['accessor']) if c['accessor'] is not None else "None"
print(f" C++: name={name_str:<30} accessor={accessor_str:<20} cols={c['columns']}")
def compare_reducers(rust_reducers, cpp_reducers):
"""Compare reducer definitions between Rust and C++"""
print("\n=== REDUCER COMPARISON ===")
rust_names = {r['name'] for r in rust_reducers}
cpp_names = {r['name'] for r in cpp_reducers}
print(f"Rust reducers: {len(rust_names)}")
print(f"C++ reducers: {len(cpp_names)}")
if rust_names != cpp_names:
missing = rust_names - cpp_names
extra = cpp_names - rust_names
if missing:
print(f"❌ Missing in C++: {missing}")
if extra:
print(f"❌ Extra in C++: {extra}")
else:
print("✅ All reducer names match!")
def check_testf_enum(rust, cpp):
"""Check the TestF enum specifically since it's known to have issues"""
print("\n=== TESTF ENUM COMPARISON ===")
for schema, lang in [(rust, 'Rust'), (cpp, 'C++')]:
for reducer in schema['reducers']:
if reducer['name'] == 'test':
params = reducer.get('params', [])
if len(params) >= 4:
param_type = params[3].get('algebraic_type', {})
if 'Sum' in param_type:
variants = param_type['Sum'].get('variants', [])
print(f"\n{lang} TestF enum:")
for v in variants:
name = v.get('name', 'unnamed')
alg_type = v.get('algebraic_type', {})
if 'Product' in alg_type and alg_type['Product'].get('fields'):
fields = alg_type['Product']['fields']
field_info = []
for f in fields:
field_name = f.get('name', 'unnamed')
field_info.append(field_name)
print(f" - {name}: has payload with fields {field_info}")
else:
print(f" - {name}: unit variant (no payload)")
break
def main():
print("=" * 60)
print("SpacetimeDB Module Schema Comparison")
print("Comparing Rust vs C++ module implementations")
print("=" * 60)
# Load schemas
rust, cpp = load_schemas()
# Extract tables and reducers
rust_tables = {t['name']: t for t in rust['tables']}
cpp_tables = {t['name']: t for t in cpp['tables']}
rust_reducers = rust['reducers']
cpp_reducers = cpp['reducers']
# Run comparisons
common_tables = compare_tables(rust_tables, cpp_tables)
compare_indexes(rust_tables, cpp_tables, common_tables)
compare_reducers(rust_reducers, cpp_reducers)
check_testf_enum(rust, cpp)
# Summary
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
perfect_match = (
len(rust_tables) == len(cpp_tables) and
len(rust_reducers) == len(cpp_reducers) and
set(rust_tables.keys()) == set(cpp_tables.keys()) and
{r['name'] for r in rust_reducers} == {r['name'] for r in cpp_reducers}
)
if perfect_match:
print("✅ Schema structure matches (tables and reducers)")
print("⚠️ Note: There may be minor differences in:")
print(" - Index naming conventions (especially multi-column indexes)")
print(" - TestF enum payload support (C++ currently simplified)")
else:
print("❌ Schema structure has differences - review details above")
if __name__ == "__main__":
main()