Skip to content

Conversation

@JasonAtClockwork
Copy link
Contributor

@JasonAtClockwork JasonAtClockwork commented Oct 30, 2025

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

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

@JasonAtClockwork JasonAtClockwork marked this pull request as ready for review January 21, 2026 15:57
@JasonAtClockwork
Copy link
Contributor Author

/update-llm-benchmark

@clockwork-labs-bot
Copy link
Collaborator

LLM Benchmark Results (ci-quickfix)

Language Mode Category Tests Passed Task Pass %
Rust rustdoc_json basics 20/27 68.1% ⬆️ +23.6%
Rust rustdoc_json schema 26/34 75.3% ⬆️ +48.8%
Rust rustdoc_json total 46/61 71.4% ⬆️ +35.1%
Rust docs basics 5/27 11.1%
Rust docs schema 8/34 20.5%
Rust docs total 13/61 15.4%
C# docs basics 27/27 100.0% ⬆️ +38.9%
C# docs schema 25/34 73.7% ⬆️ +42.2%
C# docs total 52/61 88.0% ⬆️ +40.4%

Compared against master branch baseline

Generated at: 2026-01-21T23:43:33.764Z

Failure Analysis (click to expand)

Benchmark Failure Analysis

Generated from: /__w/SpacetimeDB/SpacetimeDB/tools/xtask-llm-benchmark/../../docs/llms/docs-benchmark-details.json

Summary

  • Total failures analyzed: 34

Analysis of SpacetimeDB Benchmark Failures

This document provides an analysis of the failures in SpacetimeDB benchmark tests, organized by language and failure type. Each entry includes the generated code, the expected golden example, the error message, an explanation of the differences, the root cause of the issue, and specific recommendations for documentation changes.


Rust / rustdoc_json Failures

1. Compile/Publish Errors

t_002_scheduled_table

Generated Code:

use spacetimedb::{reducer, table, ReducerContext, ScheduleAt, Table};

#[table(name = tick_timer, scheduled(reducer = tick, column = scheduled_at))]
pub struct TickTimer {
    #[primary_key]
    #[auto_inc]
    scheduled_id: u64,
    scheduled_at: ScheduleAt,
}

#[reducer(init)]
pub fn init(ctx: &ReducerContext) {
    let tbl = ctx.db.tick_timer();
    match tbl.count() {
        0 => {
            tbl.insert(TickTimer {
                scheduled_id: 0,
                scheduled_at: ScheduleAt::repeat_micros(50_000),
            });
        }
        n if n > 1 => {
            let mut keep_one = true;
            for row in tbl.iter() {
                if keep_one {
                    keep_one = false;
                } else {
                    tbl.scheduled_id().delete(&row.scheduled_id);
                }
            }
        }
        _ => {}
    }
}

Expected (golden):

use spacetimedb::{reducer, table, ReducerContext, ScheduleAt, Table};
use std::time::Duration;

#[table(name = tick_timer, scheduled(tick))]
pub struct TickTimer {
    #[primary_key]
    #[auto_inc]
    pub scheduled_id: u64,
    pub scheduled_at: ScheduleAt,
}

#[reducer]
pub fn tick(_ctx: &ReducerContext, _row: TickTimer) -> Result<(), String> {
    Ok(())
}

#[reducer(init)]
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
    ctx.db.tick_timer().insert(TickTimer {
        scheduled_id: 0,
        scheduled_at: ScheduleAt::Interval(Duration::from_millis(50).into()),
    });
    Ok(())
}

Error: publish_error: spacetime publish failed (exit=1)

Explanation of Difference:

  • The generated code incorrectly uses ScheduleAt::repeat_micros(50_000) instead of ScheduleAt::Interval(Duration::from_millis(50).into()).
  • The function signatures for reducers in the generated code do not include returning a Result.

Root Cause:

  • The documentation may not clearly delineate the expected syntax for the scheduled attribute or the return type for reducers.

Recommendation:

  • Update documentation to provide clear examples for scheduled attributes using different time formats and specify that reducers should return Result types.

t_008_index_lookup

Generated Code:

use spacetimedb::{ReducerContext, Table};

#[spacetimedb::table(name = user)]
pub struct User {
    #[primary_key]
    id: i32,
    name: String,
    age: i32,
    active: bool,
}

#[spacetimedb::table(name = result)]
pub struct ResultRow {
    #[primary_key]
    id: i32,
    name: String,
}

#[spacetimedb::reducer]
pub fn lookup_user_name(ctx: &ReducerContext, id: i32) {
    if let Some(u) = ctx.db.user().id().find(id) {
        let row = ResultRow { id: u.id, name: u.name.clone() };
        if ctx.db.result().try_insert(row.clone()).is_err() {
            ctx.db.result().id().update(row);
        }
    }
}

Expected (golden):

use spacetimedb::{reducer, table, ReducerContext, Table};

#[table(name = user)]
pub struct User {
    #[primary_key]
    pub id: i32,
    pub name: String,
    pub age: i32,
    pub active: bool,
}

#[table(name = result)]
pub struct ResultRow {
    #[primary_key]
    pub id: i32,
    pub name: String,
}

#[reducer]
pub fn lookup_user_name(ctx: &ReducerContext, id: i32) {
    if let Some(u) = ctx.db.user().id().find(id) {
        ctx.db.result().insert(ResultRow { id: u.id, name: u.name });
    }
}

Error: publish_error: spacetime publish failed (exit=1)

Explanation of Difference:

  • The generated code does not publicize the fields in the User and ResultRow structs.
  • Incorrectly attempts to use the .try_insert method instead of the .insert method.

Root Cause:

  • Documentation may lack emphasis on the necessity of field visibility (using pub) and the correct methods for database insertion.

Recommendation:

  • Update the code examples in the documentation to show public fields in structs and specify the correct methods for inserting records.

Additional Recommendations for All Failures

  1. Common Structure: All failure analysis should present the structure in a consistent manner for easy scanning and understanding by developers.

  2. Version Tags: Each example should indicate the version of SpacetimeDB the examples pertain to, as APIs may evolve over time.

  3. Error Handling: Documentation should emphasize the importance of error handling in all reducer functions to ensure robustness.

  4. Clear API Guides: Include clear guidelines on key usage patterns for attribute macros such as #[table] and #[reducer], including common pitfalls.

  5. Time Handling Guidelines: Provide explicit examples related to the different ways to manage time intervals (like Duration vs. microseconds).

This structured approach will assist developers in quickly diagnosing and resolving their issues when working with SpacetimeDB.

clockwork-labs-bot and others added 6 commits January 21, 2026 23:44
# Conflicts:
#	docs/llms/docs-benchmark-analysis.md
#	docs/llms/docs-benchmark-comment.md
#	docs/llms/docs-benchmark-details.json
#	docs/llms/docs-benchmark-summary.json
…ement for devs to add procedures header manually to match the rest of the architecture (single header)
…ype to hold the error string as std::variant doesn't allow duplicate types
…an just Rust; as well as fixed bugs with the framework from running tests
Comment on lines +50 to +53
#URL "https://www.spacetimedb.com/cpp/spacetimedb-cpp-sdk-1.11.2.zip"
URL "https://www.rodentgames.com/spacetimedb/spacetimedb-cpp-sdk-1.11.2.zip"
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
#URL_HASH SHA256=E10503F57AA3ADB10ADD82ECF2E0F3C1107B5C141FA5E157D174C30B71071434
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be addressed before merging.

Copy link
Collaborator

@jdetter jdetter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments/questions to get started here. There is a lot to review here so I'll do another pass later today.

The main thing I would bring up is that we are referring to this as the "C++ SDK" which I think we should rebrand to the "C++ bindings". My reason is that the other bindings libraries all refer to themselves as bindings, not an SDK. We typically use the nomenclature "SDK" to mean the client SDKs, not the module side bindings.

I fear that if we start referring to this as our C++ SDK this will both confuse users and confuse the LLM training especially if we add a C++ client SDK in the future. If you want feedback from the team on this it might be a good topic to bring up during standup.

Let me know when you're ready for another pass, I'm really excited that this seems like it's working so far and I'm excited for the future with C++26! 🙂

@@ -0,0 +1,731 @@
SPACETIMEDB BUSINESS SOURCE LICENSE AGREEMENT
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a symlink to the base license, as an example:

boppy@geralt:~/clockwork/SpacetimeDB$ ls -al crates/client-api/LICENSE
lrwxrwxrwx 1 boppy boppy 22 Jan 14 17:12 crates/client-api/LICENSE -> ../../licenses/BSL.txt

git clone https://github.com/emscripten-core/emsdk.git /opt/emsdk
cd /opt/emsdk
./emsdk install latest
./emsdk activate latest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by cloning the repo we're just going to be testing against the latest master all the time - is that fine or should we pin to a known working version? I fear this test could start failing based on whatever is on emsdk master.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds 29 seconds to the CI time - might be worth caching this step eventually. It depends on how fast Tyler gets the new smoktests running. Probably leave a TODO on this.

```cpp
#include <spacetimedb.h>
using namespace SpacetimeDb;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's probably too late to change this, but how much work would it be to make this SpacetimeDB instead of SpacetimeDb?


**Build errors**: Ensure you have the latest Emscripten SDK and are using `emcmake cmake`

**Module not found**: Check that SpacetimeDB is running with `spacetime server status`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacetime server status is not a command

## License
Apache License 2.0 - See LICENSE file in the root directory. No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What license is this referring to? Certainly the license for this crate is BSL right? Why does this say Apache2?

@@ -0,0 +1,1290 @@
# SpacetimeDB C++ Module Reference
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this become our API reference doc?

@@ -0,0 +1,293 @@
# SpacetimeDB C++ Module Quickstart
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This becomes our quickstart chat example in the docs, right?


SpacetimeDB C++ uses a unique accessor pattern:
- `ctx.db[table_name]` - Access table for iteration and basic operations
- `ctx.db[table_field]` - Access indexed fields for optimized operations (generated symbol named `tableName_fieldName`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit weird, shouldn't it be something like this instead:

ctx.db[table_name].table_field

What if 2 tables have the same indexed field name?

JasonAtClockwork and others added 2 commits January 27, 2026 09:55
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
Signed-off-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
Signed-off-by: Jason Larabie <jason@clockworklabs.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants