Skip to content

feat: add table accessor without lifetime#5055

Open
onx2 wants to merge 1 commit into
clockworklabs:masterfrom
onx2:chore/table-accessor-for-downstream
Open

feat: add table accessor without lifetime#5055
onx2 wants to merge 1 commit into
clockworklabs:masterfrom
onx2:chore/table-accessor-for-downstream

Conversation

@onx2
Copy link
Copy Markdown
Contributor

@onx2 onx2 commented May 16, 2026

Description of Changes

Adds a lifetime-aware TableAccessor trait to the Rust SDK and updates Rust codegen to emit a generated accessor marker type for each table.

Generated bindings now include a marker type like:

pub struct PlayerPositionTableAccessor;

impl __sdk::TableAccessor<super::RemoteTables> for PlayerPositionTableAccessor {
    type Row = PlayerPositionRow;
    type Handle<'db> = PlayerPositionTableHandle<'db>;

    fn get<'db>(db: &'db super::RemoteTables) -> Self::Handle<'db> {
        db.player_position()
    }
}

The existing generated table access methods remain unchanged:

fn player_position(&self) -> PlayerPositionTableHandle<'_>;

Motivation

Generated table handles are lifetime-scoped to RemoteTables, which makes it difficult for downstream crates to expose ergonomic generic APIs over generated table accessors. In particular, a method like RemoteTables::player_position returns a handle whose type depends on the borrow lifetime of RemoteTables, which is hard to represent in higher-order APIs.

The existing lifetime is not mechanically required by the current handle storage model: generated table handles own an SDK TableHandle<Row>, which holds shared connection/cache state rather than borrowing from RemoteTables. However, the lifetime does express the conceptual scope of a handle relative to the database view that produced it.

This PR preserves that scoped API contract instead of removing the lifetime, and adds a generated marker type so generic downstream APIs can name table accessors without erasing or weakening the lifetime relationship.

This enables APIs such as:

.add_table::<PlayerPositionTableAccessor>()

instead of requiring closure-based registration everywhere.

API and ABI breaking changes

None.

This is additive and should not break existing generated bindings usage. Existing code such as the below continues to work unchanged:

ctx.db.player_position().on_insert(...)

The new TableAccessor trait is exported from both the SDK root and __codegen, and generated marker types are added alongside existing generated table handles and access traits.

Expected complexity level and risk

Complexity: 2/5

The change is small and additive, but it touches Rust SDK codegen and generated binding shape. The main risk is introducing generated code that depends on the new SDK trait, so generated bindings and the SDK version must match.

This does not alter table handle ownership, callback behavior, or existing access methods.

Testing

  • Ran cargo check -p spacetimedb-sdk.
  • Regenerated Rust bindings for my game.
  • Verified generated bindings include *TableAccessor marker types.
  • Used generated accessors from downstream local bevy_stdb.
  • Ran cargo check -p bevy_stdb against the local SDK.

ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}}

/// Lifetime-aware accessor marker for the table `{table_name}`.
Copy link
Copy Markdown
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 sure why there are lifetimes on the currently generated code, it doesn't seem like they are necessary 🤔 If someone could give me some more info on that I'd appreciate it!

Nevertheless, I didn't want to introduce breaking changes so this is simply additive. If lifetimes aren't needed in the code, then I'd like to see them eventually removed in the next breaking release.

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.

1 participant