Skip to content

Commit bb8fd49

Browse files
Docs Update for C++ [2/4] (#4129)
# Description of Changes In order to give reviewers a chance to finish in one session the docs are getting split up for the C++ update. This pass contains the following section update: - Tables # API and ABI breaking changes N/A # Expected complexity level and risk 1 - Just documentation additions # Testing - [x] Manually tested each block
1 parent fc94dd8 commit bb8fd49

10 files changed

Lines changed: 854 additions & 3 deletions

docs/docs/00200-core-concepts/00300-tables.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,24 @@ pub struct Person {
164164
The `pub` modifier on the struct follows normal Rust visibility rules and has no meaning to SpacetimeDB. It controls whether the struct is accessible from other Rust modules in your crate, not whether the table is public to clients. Use the `public` attribute in `#[spacetimedb::table]` to control client visibility.
165165
:::
166166

167+
</TabItem>
168+
<TabItem value="cpp" label="C++">
169+
170+
Register the struct with `SPACETIMEDB_STRUCT`, the table with `SPACETIMEDB_TABLE`, then add field constraints:
171+
172+
```cpp
173+
struct Person {
174+
uint32_t id;
175+
std::string name;
176+
std::string email;
177+
};
178+
SPACETIMEDB_STRUCT(Person, id, name, email)
179+
SPACETIMEDB_TABLE(Person, person, Public)
180+
FIELD_PrimaryKeyAutoInc(person, id)
181+
FIELD_Index(person, name)
182+
FIELD_Unique(person, email)
183+
```
184+
167185
</TabItem>
168186
</Tabs>
169187
@@ -239,6 +257,29 @@ ctx.db.player().insert(Player { /* ... */ });
239257
| `name = player` | `ctx.db.player()` |
240258
| `name = game_session` | `ctx.db.game_session()` |
241259

260+
</TabItem>
261+
<TabItem value="cpp" label="C++">
262+
263+
The accessor name matches the table identifier you pass to `SPACETIMEDB_TABLE`:
264+
265+
```cpp
266+
struct PlayerScores {
267+
uint64_t id;
268+
};
269+
SPACETIMEDB_STRUCT(PlayerScores, id)
270+
SPACETIMEDB_TABLE(PlayerScores, player_scores, Public)
271+
FIELD_PrimaryKeyAutoInc(player_scores, id)
272+
273+
// Accessor matches the table identifier
274+
ctx.db[player_scores].insert(PlayerScores{ /* ... */ });
275+
```
276+
277+
| Table Identifier | Accessor |
278+
|------------------|----------|
279+
| `user` | `ctx.db[user]` |
280+
| `player_scores` | `ctx.db[player_scores]` |
281+
| `game_session` | `ctx.db[game_session]` |
282+
242283
</TabItem>
243284
</Tabs>
244285
@@ -251,6 +292,7 @@ Use idiomatic naming conventions for each language:
251292
| **TypeScript** | snake_case | `'player_score'` | `ctx.db.playerScore` |
252293
| **C#** | PascalCase | `Name = "PlayerScore"` | `ctx.Db.PlayerScore` |
253294
| **Rust** | lower_snake_case | `name = player_score` | `ctx.db.player_score()` |
295+
| **C++** | lower_snake_case | `player_score` | `ctx.db[player_score]` |
254296
255297
These conventions align with each language's standard style guides and make your code feel natural within its ecosystem.
256298
@@ -291,6 +333,25 @@ pub struct User { /* ... */ }
291333
pub struct Secret { /* ... */ }
292334
```
293335

336+
</TabItem>
337+
<TabItem value="cpp" label="C++">
338+
339+
```cpp
340+
struct User {
341+
uint64_t id;
342+
};
343+
SPACETIMEDB_STRUCT(User, id)
344+
SPACETIMEDB_TABLE(User, user, Public)
345+
FIELD_PrimaryKeyAutoInc(user, id)
346+
347+
struct Secret {
348+
uint64_t id;
349+
};
350+
SPACETIMEDB_STRUCT(Secret, id)
351+
SPACETIMEDB_TABLE(Secret, secret, Private)
352+
FIELD_PrimaryKeyAutoInc(secret, id)
353+
```
354+
294355
</TabItem>
295356
</Tabs>
296357
@@ -388,6 +449,33 @@ if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender())
388449
}
389450
```
390451

452+
</TabItem>
453+
<TabItem value="cpp" label="C++">
454+
455+
Apply multiple `SPACETIMEDB_TABLE` macros to the same struct:
456+
457+
```cpp
458+
struct Player {
459+
Identity identity;
460+
int32_t player_id;
461+
std::string name;
462+
};
463+
SPACETIMEDB_STRUCT(Player, identity, player_id, name)
464+
SPACETIMEDB_TABLE(Player, player, Public)
465+
SPACETIMEDB_TABLE(Player, logged_out_player, Private)
466+
FIELD_PrimaryKey(player, identity)
467+
FIELD_PrimaryKey(logged_out_player, identity)
468+
FIELD_UniqueAutoInc(player, player_id)
469+
FIELD_UniqueAutoInc(logged_out_player, player_id)
470+
471+
// Move a row between tables
472+
auto maybe_logged_out = ctx.db[logged_out_player_identity].find(ctx.sender);
473+
if (maybe_logged_out) {
474+
ctx.db[player].insert(*maybe_logged_out);
475+
ctx.db[logged_out_player_identity].delete_by_key(ctx.sender);
476+
}
477+
```
478+
391479
</TabItem>
392480
</Tabs>
393481

docs/docs/00200-core-concepts/00300-tables/00200-column-types.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,29 @@ These optimizations apply across all supported languages.
132132
| Special | `TimeDuration` | Relative duration in microseconds |
133133
| Special | `ScheduleAt` | When a scheduled reducer should execute |
134134

135+
</TabItem>
136+
<TabItem value="cpp" label="C++">
137+
138+
| Category | Type | Description |
139+
|----------|------|-------------|
140+
| Primitive | `bool` | Boolean value |
141+
| Primitive | `std::string` | UTF-8 string |
142+
| Primitive | `float` | 32-bit floating point |
143+
| Primitive | `double` | 64-bit floating point |
144+
| Primitive | `int8_t`, `int16_t`, `int32_t`, `int64_t` | Signed integers (8-bit to 64-bit) |
145+
| Primitive | `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` | Unsigned integers (8-bit to 64-bit) |
146+
| Primitive | `SpacetimeDB::i128`, `SpacetimeDB::i256` | Signed 128-bit and 256-bit integers |
147+
| Primitive | `SpacetimeDB::u128`, `SpacetimeDB::u256` | Unsigned 128-bit and 256-bit integers |
148+
| Composite | `struct` with `SPACETIMEDB_STRUCT` | Product type for nested data |
149+
| Composite | `SPACETIMEDB_ENUM` | Sum type (tagged union) |
150+
| Composite | `std::vector<T>` | Vector of elements |
151+
| Composite | `std::optional<T>` | Optional value |
152+
| Special | `Identity` | Unique identity for authentication |
153+
| Special | `ConnectionId` | Client connection identifier |
154+
| Special | `Timestamp` | Absolute point in time (microseconds since Unix epoch) |
155+
| Special | `TimeDuration` | Relative duration in microseconds |
156+
| Special | `ScheduleAt` | When a scheduled reducer should execute |
157+
135158
</TabItem>
136159
</Tabs>
137160

@@ -289,5 +312,57 @@ pub struct Player {
289312
}
290313
```
291314

315+
</TabItem>
316+
<TabItem value="cpp" label="C++">
317+
318+
```cpp
319+
// Define a nested struct type for coordinates
320+
struct Coordinates {
321+
double x;
322+
double y;
323+
double z;
324+
};
325+
SPACETIMEDB_STRUCT(Coordinates, x, y, z)
326+
327+
// Define unit types for enum variants
328+
SPACETIMEDB_UNIT_TYPE(Active)
329+
SPACETIMEDB_UNIT_TYPE(Inactive)
330+
331+
// Define an enum for status
332+
SPACETIMEDB_ENUM(PlayerStatus,
333+
(Active, Active),
334+
(Inactive, Inactive),
335+
(Suspended, std::string)
336+
)
337+
338+
struct Player {
339+
// Primitive types
340+
uint64_t id;
341+
std::string name;
342+
uint8_t level;
343+
uint32_t experience;
344+
float health;
345+
int64_t score;
346+
bool is_online;
347+
348+
// Composite types
349+
Coordinates position;
350+
PlayerStatus status;
351+
std::vector<uint32_t> inventory;
352+
std::optional<uint64_t> guild_id;
353+
354+
// Special types
355+
Identity owner;
356+
std::optional<ConnectionId> connection;
357+
Timestamp created_at;
358+
TimeDuration play_time;
359+
};
360+
SPACETIMEDB_STRUCT(Player, id, name, level, experience, health, score, is_online,
361+
position, status, inventory, guild_id,
362+
owner, connection, created_at, play_time)
363+
SPACETIMEDB_TABLE(Player, player, Public)
364+
FIELD_PrimaryKeyAutoInc(player, id)
365+
```
366+
292367
</TabItem>
293368
</Tabs>

docs/docs/00200-core-concepts/00300-tables/00210-file-storage.md

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SpacetimeDB can store binary data directly in table columns, making it suitable
1111

1212
## Storing Binary Data Inline
1313

14-
Store binary data using `Vec<u8>` (Rust), `List<byte>` (C#), or `t.array(t.u8())` (TypeScript). This approach keeps data within the database, ensuring it participates in transactions and real-time updates.
14+
Store binary data using `Vec<u8>` (Rust), `List<byte>` (C#), `std::vector<uint8_t>` (C++), or `t.array(t.u8())` (TypeScript). This approach keeps data within the database, ensuring it participates in transactions and real-time updates.
1515

1616
<Tabs groupId="server-language" queryString>
1717
<TabItem value="typescript" label="TypeScript">
@@ -120,6 +120,37 @@ pub fn upload_avatar(
120120
}
121121
```
122122

123+
</TabItem>
124+
<TabItem value="cpp" label="C++">
125+
126+
```cpp
127+
struct UserAvatar {
128+
uint64_t user_id;
129+
std::string mime_type;
130+
std::vector<uint8_t> data; // Binary data stored inline
131+
Timestamp uploaded_at;
132+
};
133+
SPACETIMEDB_STRUCT(UserAvatar, user_id, mime_type, data, uploaded_at)
134+
SPACETIMEDB_TABLE(UserAvatar, user_avatar, Public)
135+
FIELD_PrimaryKey(user_avatar, user_id)
136+
137+
SPACETIMEDB_REDUCER(upload_avatar, ReducerContext ctx,
138+
uint64_t user_id, std::string mime_type, std::vector<uint8_t> data) {
139+
// Delete existing avatar if present
140+
ctx.db[user_avatar_user_id].delete_by_key(user_id);
141+
142+
// Insert new avatar
143+
ctx.db[user_avatar].insert(UserAvatar{
144+
.user_id = user_id,
145+
.mime_type = mime_type,
146+
.data = data,
147+
.uploaded_at = ctx.timestamp,
148+
});
149+
150+
return Ok();
151+
}
152+
```
153+
123154
</TabItem>
124155
</Tabs>
125156
@@ -273,6 +304,41 @@ pub fn register_document(
273304
}
274305
```
275306

307+
</TabItem>
308+
<TabItem value="cpp" label="C++">
309+
310+
```cpp
311+
struct Document {
312+
uint64_t id;
313+
Identity owner_id;
314+
std::string filename;
315+
std::string mime_type;
316+
uint64_t size_bytes;
317+
std::string storage_url; // Reference to external storage
318+
Timestamp uploaded_at;
319+
};
320+
SPACETIMEDB_STRUCT(Document, id, owner_id, filename, mime_type, size_bytes, storage_url, uploaded_at)
321+
SPACETIMEDB_TABLE(Document, document, Public)
322+
FIELD_PrimaryKeyAutoInc(document, id)
323+
FIELD_Index(document, owner_id)
324+
325+
// Called after uploading file to external storage
326+
SPACETIMEDB_REDUCER(register_document, ReducerContext ctx,
327+
std::string filename, std::string mime_type, uint64_t size_bytes, std::string storage_url) {
328+
ctx.db[document].insert(Document{
329+
.id = 0, // auto-increment
330+
.owner_id = ctx.sender,
331+
.filename = filename,
332+
.mime_type = mime_type,
333+
.size_bytes = size_bytes,
334+
.storage_url = storage_url,
335+
.uploaded_at = ctx.timestamp,
336+
});
337+
338+
return Ok();
339+
}
340+
```
341+
276342
</TabItem>
277343
</Tabs>
278344
@@ -735,6 +801,25 @@ pub struct Image {
735801
}
736802
```
737803

804+
</TabItem>
805+
<TabItem value="cpp" label="C++">
806+
807+
```cpp
808+
struct Image {
809+
uint64_t id;
810+
Identity owner_id;
811+
std::vector<uint8_t> thumbnail; // Small preview stored inline
812+
std::string original_url; // Large original in external storage
813+
uint32_t width;
814+
uint32_t height;
815+
Timestamp uploaded_at;
816+
};
817+
SPACETIMEDB_STRUCT(Image, id, owner_id, thumbnail, original_url, width, height, uploaded_at)
818+
SPACETIMEDB_TABLE(Image, image, Public)
819+
FIELD_PrimaryKeyAutoInc(image, id)
820+
FIELD_Index(image, owner_id)
821+
```
822+
738823
</TabItem>
739824
</Tabs>
740825

docs/docs/00200-core-concepts/00300-tables/00230-auto-increment.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ spacetimedb.reducer('add_post', { title: t.string() }, (ctx, { title }) => {
3535

3636
Use the `.autoInc()` method on a column builder.
3737

38+
Auto-increment columns must be integer types: `t.i8()`, `t.u8()`, `t.i16()`, `t.u16()`, `t.i32()`, `t.u32()`, `t.i64()`, `t.u64()`, `t.i128()`, `t.u128()`, `t.i256()`, or `t.u256()`.
39+
3840
</TabItem>
3941
<TabItem value="csharp" label="C#">
4042

@@ -60,6 +62,8 @@ public static void AddPost(ReducerContext ctx, string title)
6062

6163
Use the `[SpacetimeDB.AutoInc]` attribute.
6264

65+
Auto-increment columns must be integer types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `SpacetimeDB.I128`, `SpacetimeDB.U128`, `SpacetimeDB.I256`, or `SpacetimeDB.U256`.
66+
6367
</TabItem>
6468
<TabItem value="rust" label="Rust">
6569

@@ -86,10 +90,35 @@ fn add_post(ctx: &ReducerContext, title: String) -> Result<(), String> {
8690

8791
Use the `#[auto_inc]` attribute.
8892

93+
Auto-increment columns must be integer types: `i8`, `i16`, `i32`, `i64`, `i128`, `u8`, `u16`, `u32`, `u64`, or `u128`.
94+
8995
</TabItem>
90-
</Tabs>
96+
<TabItem value="cpp" label="C++">
97+
98+
```cpp
99+
struct Post {
100+
uint64_t id;
101+
std::string title;
102+
};
103+
SPACETIMEDB_STRUCT(Post, id, title)
104+
SPACETIMEDB_TABLE(Post, post, Public)
105+
FIELD_PrimaryKeyAutoInc(post, id)
106+
107+
SPACETIMEDB_REDUCER(add_post, ReducerContext ctx, std::string title) {
108+
// Pass 0 for the auto-increment field
109+
auto inserted = ctx.db[post].insert(Post{0, title});
110+
// inserted.id now contains the assigned value
111+
LOG_INFO("Created post with id: " + std::to_string(inserted.id));
112+
return Ok();
113+
}
114+
```
115+
116+
Use the `FIELD_PrimaryKeyAutoInc(table, field)` macro after table registration.
91117
92-
Auto-increment columns must be integer types (`u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`, etc.).
118+
Auto-increment columns must be integer types: `int8_t`, `int16_t`, `int32_t`, `int64_t`, `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`, `SpacetimeDB::i128`, `SpacetimeDB::u128`, `SpacetimeDB::i256`, or `SpacetimeDB::u256`.
119+
120+
</TabItem>
121+
</Tabs>
93122
94123
## Trigger Value
95124

0 commit comments

Comments
 (0)