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
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! 🙂


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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh yeah this is still confusing, it is ctx.db[table_name_and_field_name] with how the accessors get generated. Maybe I change it to?

- `ctx.db[table]` - Access table for iteration and basic operations, eg. ctx.db[user]
- `ctx.db[table_field]` - Access indexed fields for optimized operations, eg. ctx.db[user_id]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've made the change and going to leave this comment open as it might not be good enough yet

Copy link
Contributor

Choose a reason for hiding this comment

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

If the goal is to simply clarify how it currently works, my personal preference towards camelCase:

- `ctx.db[tableName]` - Access table for iteration and basic operations, eg. ctx.db[user]
- `ctx.db[tableName_fieldName]` - Access indexed fields for optimized operations, eg. ctx.db[user_id]

This is because it allows it to be clear that we are specifically referring to the name of a given table/field, while also not adding any additional special characters, like underscore, that is present in snake_case. The extra underscore get confusing when those are also used as the table to field delimiters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback I like that and it's in!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah this is fine with me for now - is this something we can fix with C++26?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not 100% certain to be honest, the proposal for reflection in C++ 26 doesn't have full code injection, it should help with metaprogramming but I need to play with it before I know if I can resolve this completely to make it more in the style of: ctx.db.user.id or ctx.db.user().id().

Copy link
Contributor

@rekhoff rekhoff left a comment

Choose a reason for hiding this comment

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

Overall LGTM. I very much wish we could use C++26, but given our options, I think the current implementation makes sense. I've reviewed the documentation, skimmed the code, and done some simple local testing. I'd love more eyes on this from core team members, but am happy to sign off approval.

…ter() to return a clean iterator for all use cases

Updated delete_all to work with datastore_delete_by_index_scan_point_bsatn and function correctly
@JasonAtClockwork
Copy link
Contributor Author

Quick update - I didn't love the implementation for the iterator on filter() I had a mix of different uses and was going to move the read-write index to use the same pattern as the View/Multicolumn as the version from Arvikasoft was materializing the entire iterator into a vector.

I really hate the devex of that pattern though as you have to manually loop through the iterator so I've added a small wrapper to make this more clean to go from:

for (auto iter = ctx.db[score_points].filter(range_from(300)); 
     iter != IndexIterator<Score>(); ++iter) {
    const auto& score = *iter;
}

To:

for (const auto& score : ctx.db[score_points].filter(range_from(300)) {
}

@jdetter
Copy link
Collaborator

jdetter commented Jan 30, 2026

@JasonAtClockwork I would take a look at this CI failure, I think it's unlikely that it's a flake because it seems pretty consistent. It might be something introduced by this PR:
image

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.

I've tested this at least with the basic quickstart doc and everything seems to be working. I also looked back and did another pass on the comments that I put down - let me know if you need me to take another look at anything.

I have not done a nit-pick review on the code here since there is quite a lot but I have skimmed it and haven't found anything too concerning. I think it would be better to do a UX review when we go through the follow-up docs PRs anyway. Please request my review when those are ready as well.

Thanks Jason, this is great 👍

Copy link
Contributor

@gefjon gefjon left a comment

Choose a reason for hiding this comment

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

I've only looked at the procedure-related stuff in here, but it looks great - much better than I expected, to be honest. Thanks for all your hard work on this!

Copy link
Collaborator

@joshua-spacetime joshua-spacetime left a comment

Choose a reason for hiding this comment

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

The view code looks correct. sender is a method in the 2.0 rust module api. But as I understand it, this is not meant to be 2.0 compliant. Also this does not include the query builder which will be added separately.

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.

7 participants