Skip to content

Commit 6aa1239

Browse files
authored
Capabilities API (#86)
1 parent 7d75e9f commit 6aa1239

7 files changed

Lines changed: 343 additions & 129 deletions

File tree

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,18 +173,31 @@ Use the `StdbPlugin` builder methods to register table bindings during app setup
173173

174174
Each method eagerly registers the internal Bevy message channels for the row type and stores a deferred binding that runs whenever a connection becomes active.
175175

176+
The `add_*` methods are semantic convenience APIs. For capability-based registration, use `bind` or the direct `bind_insert`, `bind_delete`, `bind_update`, and `bind_insert_update` methods. Unsupported capabilities fail at compile time; duplicate bindings panic during plugin configuration with the accessor and capability in the error.
177+
176178
| Method | Use when |
177179
|---|---|
178-
| `add_table` | Table has a primary key — exposes insert, update, delete, and insert-or-update message readers |
179-
| `add_table_without_pk` | Table has no primary key — exposes insert and delete message readers |
180-
| `add_event_table` | Append-only log table — exposes insert message readers |
181-
| `add_view` | Server-computed virtual table — exposes insert and delete message readers |
180+
| `add_table::<T>` | Table has a primary key — exposes insert, update, delete, and insert-or-update message readers |
181+
| `add_table_without_pk::<T>` | Table has no primary key — exposes insert and delete message readers |
182+
| `add_event_table::<T>` | Append-only log table — exposes insert message readers |
183+
| `add_view::<T>` | Server-computed virtual table — exposes insert and delete message readers |
184+
| `bind::<T>`, `bind_*::<T>` | Explicit control — exposes specific message readers |
182185

183186
```rust
187+
// Semantic convenience APIs.
184188
.add_table::<PlayerInfoTableAccessor>()
185189
.add_table_without_pk::<WorldClockTableAccessor>()
186190
.add_event_table::<DamageEventsTableAccessor>()
187191
.add_view::<NearbyMonstersTableAccessor>()
192+
193+
// Capability-based API.
194+
.bind::<PlayerInfoTableAccessor>([
195+
TableCapability::insert(),
196+
TableCapability::delete(),
197+
TableCapability::update(),
198+
TableCapability::insert_update(),
199+
])
200+
.bind_insert::<DamageEventsTableAccessor>()
188201
```
189202

190203
## Reading table events

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,6 @@ pub mod prelude {
107107
plugin::StdbPlugin,
108108
set::StdbSet,
109109
subscription::StdbSubscriptions,
110+
table::TableCapability,
110111
};
111112
}

src/plugin.rs

Lines changed: 99 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ use crate::{
44
message::RowEvent,
55
set::StdbSet,
66
subscription::{SubscriptionsInitializer, SubscriptionsPlugin},
7-
table::{
8-
StdbTablePlugin, TableBindCallback, TableRegistrationCallback, bind_insert, bind_table,
9-
bind_table_without_pk, register_event_table, register_table, register_table_without_pk,
10-
register_view,
11-
},
7+
table::{TableCapability, TableRegistry},
128
};
139
use bevy_app::{App, Plugin, PreStartup, PreUpdate};
1410
use bevy_ecs::prelude::{IntoScheduleConfigs, Message};
@@ -63,8 +59,7 @@ pub struct StdbPlugin<
6359
driver: Option<ConnectionDriver<C>>,
6460
reconnect_options: Option<StdbReconnectOptions>,
6561
subscriptions_initializer: Option<Arc<SubscriptionsInitializer>>,
66-
table_registrations: Vec<Arc<TableRegistrationCallback>>,
67-
table_bindings: Vec<Arc<TableBindCallback<C>>>,
62+
table_registry: TableRegistry<C, M>,
6863
channel_registrations: Vec<Arc<ChannelRegistrationCallback>>,
6964
}
7065

@@ -81,8 +76,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
8176
driver: None,
8277
reconnect_options: None,
8378
subscriptions_initializer: None,
84-
table_registrations: Vec::new(),
85-
table_bindings: Vec::new(),
79+
table_registry: TableRegistry::default(),
8680
channel_registrations: Vec::new(),
8781
}
8882
}
@@ -243,14 +237,98 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
243237
self
244238
}
245239

240+
/// Registers table event capabilities for a generated table accessor.
241+
///
242+
/// Each capability constructor validates the corresponding SDK trait at
243+
/// compile time. Duplicate accessor/capability pairs panic with a precise
244+
/// error when this method is called.
245+
///
246+
/// # Example
247+
///
248+
/// ```ignore
249+
/// .bind::<PlayerTableAccessor>([
250+
/// TableCapability::insert(),
251+
/// TableCapability::delete(),
252+
/// TableCapability::update(),
253+
/// TableCapability::insert_update(),
254+
/// ])
255+
/// ```
256+
pub fn bind<TTable>(
257+
mut self,
258+
capabilities: impl IntoIterator<Item = TableCapability<C, M, TTable>>,
259+
) -> Self
260+
where
261+
TTable: 'static,
262+
{
263+
self.table_registry.bind(capabilities);
264+
self
265+
}
266+
267+
/// Binds insert messages for a generated table accessor.
268+
pub fn bind_insert<TTable>(self) -> Self
269+
where
270+
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
271+
TTable::Row: Send + Sync + Clone + InModule + 'static,
272+
RowEvent<TTable::Row>: Send + Sync,
273+
for<'db> TTable::Handle<'db>: TableLike<
274+
Row = TTable::Row,
275+
EventContext = <<TTable::Row as InModule>::Module as SpacetimeModule>::EventContext,
276+
> + WithInsert,
277+
{
278+
self.bind([TableCapability::<C, M, TTable>::insert()])
279+
}
280+
281+
/// Binds delete messages for a generated table accessor.
282+
pub fn bind_delete<TTable>(self) -> Self
283+
where
284+
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
285+
TTable::Row: Send + Sync + Clone + InModule + 'static,
286+
RowEvent<TTable::Row>: Send + Sync,
287+
for<'db> TTable::Handle<'db>: TableLike<
288+
Row = TTable::Row,
289+
EventContext = <<TTable::Row as InModule>::Module as SpacetimeModule>::EventContext,
290+
> + WithDelete,
291+
{
292+
self.bind([TableCapability::<C, M, TTable>::delete()])
293+
}
294+
295+
/// Binds update messages for a generated table accessor.
296+
pub fn bind_update<TTable>(self) -> Self
297+
where
298+
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
299+
TTable::Row: Send + Sync + Clone + InModule + 'static,
300+
RowEvent<TTable::Row>: Send + Sync,
301+
for<'db> TTable::Handle<'db>: TableLike<
302+
Row = TTable::Row,
303+
EventContext = <<TTable::Row as InModule>::Module as SpacetimeModule>::EventContext,
304+
> + WithUpdate,
305+
{
306+
self.bind([TableCapability::<C, M, TTable>::update()])
307+
}
308+
309+
/// Binds insert-update messages for a generated table accessor.
310+
pub fn bind_insert_update<TTable>(self) -> Self
311+
where
312+
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
313+
TTable::Row: Send + Sync + Clone + InModule + 'static,
314+
RowEvent<TTable::Row>: Send + Sync,
315+
for<'db> TTable::Handle<'db>: TableLike<
316+
Row = TTable::Row,
317+
EventContext = <<TTable::Row as InModule>::Module as SpacetimeModule>::EventContext,
318+
> + WithInsert
319+
+ WithUpdate,
320+
{
321+
self.bind([TableCapability::<C, M, TTable>::insert_update()])
322+
}
323+
246324
/// Registers a table with a primary key.
247325
///
248326
/// # Example
249327
///
250328
/// ```ignore
251329
/// .add_table::<PlayerTableAccessor>()
252330
/// ```
253-
pub fn add_table<TTable>(mut self) -> Self
331+
pub fn add_table<TTable>(self) -> Self
254332
where
255333
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
256334
TTable::Row: Send + Sync + Clone + InModule + 'static,
@@ -262,12 +340,10 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
262340
+ WithDelete
263341
+ WithUpdate,
264342
{
265-
self.table_registrations
266-
.push(Arc::new(register_table::<TTable::Row>));
267-
self.table_bindings.push(Arc::new(|world, db| {
268-
bind_table::<TTable::Row, _>(world, TTable::get(db));
269-
}));
270-
self
343+
self.bind_insert::<TTable>()
344+
.bind_delete::<TTable>()
345+
.bind_update::<TTable>()
346+
.bind_insert_update::<TTable>()
271347
}
272348

273349
/// Registers a table without a primary key.
@@ -277,7 +353,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
277353
/// ```ignore
278354
/// .add_table_without_pk::<NearbyMonstersTableAccessor>()
279355
/// ```
280-
pub fn add_table_without_pk<TTable>(mut self) -> Self
356+
pub fn add_table_without_pk<TTable>(self) -> Self
281357
where
282358
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
283359
TTable::Row: Send + Sync + Clone + InModule + 'static,
@@ -288,12 +364,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
288364
> + WithInsert
289365
+ WithDelete,
290366
{
291-
self.table_registrations
292-
.push(Arc::new(register_table_without_pk::<TTable::Row>));
293-
self.table_bindings.push(Arc::new(|world, db| {
294-
bind_table_without_pk::<TTable::Row, _>(world, TTable::get(db));
295-
}));
296-
self
367+
self.bind_insert::<TTable>().bind_delete::<TTable>()
297368
}
298369

299370
/// Registers a view.
@@ -303,7 +374,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
303374
/// ```ignore
304375
/// .add_view::<CharacterSelectionScreenViewTableAccessor>()
305376
/// ```
306-
pub fn add_view<TTable>(mut self) -> Self
377+
pub fn add_view<TTable>(self) -> Self
307378
where
308379
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
309380
TTable::Row: Send + Sync + Clone + InModule + 'static,
@@ -314,12 +385,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
314385
> + WithInsert
315386
+ WithDelete,
316387
{
317-
self.table_registrations
318-
.push(Arc::new(register_view::<TTable::Row>));
319-
self.table_bindings.push(Arc::new(|world, db| {
320-
bind_table_without_pk::<TTable::Row, _>(world, TTable::get(db));
321-
}));
322-
self
388+
self.bind_insert::<TTable>().bind_delete::<TTable>()
323389
}
324390

325391
/// Registers an event table.
@@ -329,7 +395,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
329395
/// ```ignore
330396
/// .add_event_table::<LogEventsTableAccessor>()
331397
/// ```
332-
pub fn add_event_table<TTable>(mut self) -> Self
398+
pub fn add_event_table<TTable>(self) -> Self
333399
where
334400
TTable: TableAccessor<C::DbView> + Send + Sync + 'static,
335401
TTable::Row: Send + Sync + Clone + InModule + 'static,
@@ -339,12 +405,7 @@ impl<C: DbConnection<Module = M> + DbContext + Send + Sync, M: SpacetimeModule<D
339405
EventContext = <<TTable::Row as InModule>::Module as SpacetimeModule>::EventContext,
340406
> + WithInsert,
341407
{
342-
self.table_registrations
343-
.push(Arc::new(register_event_table::<TTable::Row>));
344-
self.table_bindings.push(Arc::new(|world, db| {
345-
bind_insert::<TTable::Row, _>(world, &TTable::get(db));
346-
}));
347-
self
408+
self.bind_insert::<TTable>()
348409
}
349410

350411
/// Registers a bridged message channel for `T`.
@@ -515,9 +576,6 @@ impl<
515576
compression: self.compression.unwrap_or_default(),
516577
});
517578

518-
app.add_plugins(StdbTablePlugin::<C, M>::new(
519-
self.table_bindings.clone(),
520-
self.table_registrations.clone(),
521-
));
579+
app.add_plugins(self.table_registry.plugin());
522580
}
523581
}

src/table/bind.rs

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,6 @@ use spacetimedb_sdk::__codegen::{
77
AbstractEventContext, InModule, SpacetimeModule, TableLike, WithDelete, WithInsert, WithUpdate,
88
};
99

10-
pub(crate) fn bind_table<TRow, TTable>(world: &World, table: TTable)
11-
where
12-
TRow: Send + Sync + Clone + InModule + 'static,
13-
RowEvent<TRow>: Send + Sync,
14-
TTable: TableLike<
15-
Row = TRow,
16-
EventContext = <<TRow as InModule>::Module as SpacetimeModule>::EventContext,
17-
> + WithInsert
18-
+ WithDelete
19-
+ WithUpdate,
20-
{
21-
bind_insert::<TRow, TTable>(world, &table);
22-
bind_delete::<TRow, TTable>(world, &table);
23-
bind_update::<TRow, TTable>(world, &table);
24-
bind_insert_update::<TRow, TTable>(world, &table);
25-
}
26-
27-
pub(crate) fn bind_table_without_pk<TRow, TTable>(world: &World, table: TTable)
28-
where
29-
TRow: Send + Sync + Clone + InModule + 'static,
30-
RowEvent<TRow>: Send + Sync,
31-
TTable: TableLike<
32-
Row = TRow,
33-
EventContext = <<TRow as InModule>::Module as SpacetimeModule>::EventContext,
34-
> + WithInsert
35-
+ WithDelete,
36-
{
37-
bind_insert::<TRow, TTable>(world, &table);
38-
bind_delete::<TRow, TTable>(world, &table);
39-
}
40-
4110
pub(crate) fn bind_insert<TRow, TTable>(world: &World, table: &TTable)
4211
where
4312
TRow: Send + Sync + Clone + InModule + 'static,
@@ -57,7 +26,7 @@ where
5726
});
5827
}
5928

60-
fn bind_delete<TRow, TTable>(world: &World, table: &TTable)
29+
pub(crate) fn bind_delete<TRow, TTable>(world: &World, table: &TTable)
6130
where
6231
TRow: Send + Sync + Clone + InModule + 'static,
6332
RowEvent<TRow>: Send + Sync,
@@ -76,7 +45,7 @@ where
7645
});
7746
}
7847

79-
fn bind_update<TRow, TTable>(world: &World, table: &TTable)
48+
pub(crate) fn bind_update<TRow, TTable>(world: &World, table: &TTable)
8049
where
8150
TRow: Send + Sync + Clone + InModule + 'static,
8251
RowEvent<TRow>: Send + Sync,
@@ -96,7 +65,7 @@ where
9665
});
9766
}
9867

99-
fn bind_insert_update<TRow, TTable>(world: &World, table: &TTable)
68+
pub(crate) fn bind_insert_update<TRow, TTable>(world: &World, table: &TTable)
10069
where
10170
TRow: Send + Sync + Clone + InModule + 'static,
10271
RowEvent<TRow>: Send + Sync,

0 commit comments

Comments
 (0)