mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 15:49:35 -04:00
[2.0 Breaking]: Remove sender field from [Reducer|View|Procedure]Context (#4208)
# Description of Changes Makes the `sender` field on `ViewContext` private and exposes a `sender()` method. Does the same for `ReducerContext` and `ProcedureContext`. The purpose of this change: So that the host can determine if/when a view invokes or reads the `sender`. Currently, because `sender` is a field, the host assumes that it is always read. This means views must be materialized per client, even if the view doesn't actually depend on `sender`, resulting in data duplication. The initial solution for this problem was `AnonymousViewContext` which doesn't have a `sender` field. The better solution is to make `sender` a method so that it can call into the host and record when it's actually invoked. Note, this patch only updates the module API, so the current implementation does not change. `ViewContext` views are still duplicated across clients. Changing this requires a new host syscall and for `sender()` to invoke that syscall. This however is backwards compatible and can be done anytime after the module APIs for the other languages (C#, TypeScript, C++) are updated. Also note that `ReducerContext` and `ProcedureContext` were updated purely for consistency. There are currently no plans to track reads of `sender` in these contexts. # API and ABI breaking changes Breaks the rust module api. # Expected complexity level and risk 1 # Testing N/A
This commit is contained in:
@@ -22,23 +22,23 @@ fn init(ctx: &ReducerContext) {
|
||||
|
||||
#[reducer(client_connected)]
|
||||
fn client_connected(ctx: &ReducerContext) {
|
||||
let existing_user = ctx.db.offline_user().identity().find(ctx.sender);
|
||||
let existing_user = ctx.db.offline_user().identity().find(ctx.sender());
|
||||
if let Some(user) = existing_user {
|
||||
ctx.db.user().insert(user);
|
||||
ctx.db.offline_user().identity().delete(ctx.sender);
|
||||
ctx.db.offline_user().identity().delete(ctx.sender());
|
||||
return;
|
||||
}
|
||||
ctx.db.offline_user().insert(User {
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
has_incremented_count: 0,
|
||||
});
|
||||
}
|
||||
|
||||
#[reducer(client_disconnected)]
|
||||
fn client_disconnected(ctx: &ReducerContext) -> Result<(), String> {
|
||||
let existing_user = ctx.db.user().identity().find(ctx.sender).ok_or("User not found")?;
|
||||
let existing_user = ctx.db.user().identity().find(ctx.sender()).ok_or("User not found")?;
|
||||
ctx.db.offline_user().insert(existing_user);
|
||||
ctx.db.user().identity().delete(ctx.sender);
|
||||
ctx.db.user().identity().delete(ctx.sender());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ fn increment_counter(ctx: &ReducerContext) -> Result<(), String> {
|
||||
counter.count += 1;
|
||||
ctx.db.counter().id().update(counter);
|
||||
|
||||
let mut user = ctx.db.user().identity().find(ctx.sender).ok_or("User not found")?;
|
||||
let mut user = ctx.db.user().identity().find(ctx.sender()).ok_or("User not found")?;
|
||||
user.has_incremented_count += 1;
|
||||
ctx.db.user().identity().update(user);
|
||||
|
||||
|
||||
@@ -608,7 +608,7 @@ struct PlayerAndLevel {
|
||||
// At-most-one row: return Option<T>
|
||||
#[view(name = my_player, public)]
|
||||
fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
ctx.db.player().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
// Multiple rows: return Vec<T>
|
||||
|
||||
@@ -662,7 +662,7 @@ pub use spacetimedb_bindings_macro::table;
|
||||
///
|
||||
/// #[reducer]
|
||||
/// fn scheduled(ctx: &ReducerContext, args: ScheduledArgs) -> Result<(), String> {
|
||||
/// if ctx.sender != ctx.identity() {
|
||||
/// if ctx.sender() != ctx.identity() {
|
||||
/// return Err("Reducer `scheduled` may not be invoked by clients, only via scheduling.".into());
|
||||
/// }
|
||||
/// // Reducer body...
|
||||
@@ -707,7 +707,7 @@ pub use spacetimedb_bindings_macro::reducer;
|
||||
/// #[procedure]
|
||||
/// fn return_value(ctx: &mut ProcedureContext, arg: MyArgument) -> MyReturnValue {
|
||||
/// MyReturnValue {
|
||||
/// a: format!("Hello, {}", ctx.sender),
|
||||
/// a: format!("Hello, {}", ctx.sender()),
|
||||
/// b: ctx.timestamp,
|
||||
/// }
|
||||
/// }
|
||||
@@ -816,13 +816,13 @@ pub use spacetimedb_bindings_macro::procedure;
|
||||
/// // A view that selects at most one row from a table
|
||||
/// #[view(name = my_player, public)]
|
||||
/// fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
/// ctx.db.player().identity().find(ctx.sender)
|
||||
/// ctx.db.player().identity().find(ctx.sender())
|
||||
/// }
|
||||
///
|
||||
/// // An example of column projection
|
||||
/// #[view(name = my_player_id, public)]
|
||||
/// fn my_player_id(ctx: &ViewContext) -> Option<PlayerId> {
|
||||
/// ctx.db.player().identity().find(ctx.sender).map(|Player { id, .. }| PlayerId { id })
|
||||
/// ctx.db.player().identity().find(ctx.sender()).map(|Player { id, .. }| PlayerId { id })
|
||||
/// }
|
||||
///
|
||||
/// // An example that is analogous to a semijoin in sql
|
||||
@@ -894,7 +894,7 @@ impl Default for AnonymousViewContext {
|
||||
/// The other is [`AnonymousViewContext`].
|
||||
/// Use this type if the view depends on the caller's identity.
|
||||
pub struct ViewContext {
|
||||
pub sender: Identity,
|
||||
sender: Identity,
|
||||
pub db: LocalReadOnly,
|
||||
pub from: QueryBuilder,
|
||||
}
|
||||
@@ -907,6 +907,11 @@ impl ViewContext {
|
||||
from: QueryBuilder {},
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Identity` of the client that invoked the view.
|
||||
pub fn sender(&self) -> Identity {
|
||||
self.sender
|
||||
}
|
||||
}
|
||||
|
||||
/// The context that any reducer is provided with.
|
||||
@@ -924,7 +929,7 @@ impl ViewContext {
|
||||
#[non_exhaustive]
|
||||
pub struct ReducerContext {
|
||||
/// The `Identity` of the client that invoked the reducer.
|
||||
pub sender: Identity,
|
||||
sender: Identity,
|
||||
|
||||
/// The time at which the reducer was started.
|
||||
pub timestamp: Timestamp,
|
||||
@@ -1014,6 +1019,11 @@ impl ReducerContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Identity` of the client that invoked the reducer.
|
||||
pub fn sender(&self) -> Identity {
|
||||
self.sender
|
||||
}
|
||||
|
||||
/// Returns the authorization information for the caller of this reducer.
|
||||
pub fn sender_auth(&self) -> &AuthCtx {
|
||||
&self.sender_auth
|
||||
@@ -1124,7 +1134,7 @@ impl Deref for TxContext {
|
||||
#[cfg(feature = "unstable")]
|
||||
pub struct ProcedureContext {
|
||||
/// The `Identity` of the client that invoked the procedure.
|
||||
pub sender: Identity,
|
||||
sender: Identity,
|
||||
|
||||
/// The time at which the procedure was started.
|
||||
pub timestamp: Timestamp,
|
||||
@@ -1162,6 +1172,12 @@ impl ProcedureContext {
|
||||
counter_uuid: Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Identity` of the client that invoked the procedure.
|
||||
pub fn sender(&self) -> Identity {
|
||||
self.sender
|
||||
}
|
||||
|
||||
/// Read the current module's [`Identity`].
|
||||
pub fn identity(&self) -> Identity {
|
||||
// Hypothetically, we *could* read the module identity out of the system tables.
|
||||
|
||||
@@ -10,18 +10,17 @@ pub struct ConnectedClient {
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
fn on_connect(ctx: &ReducerContext) {
|
||||
ctx.db.connected_client().insert(ConnectedClient {
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
connection_id: ctx.connection_id.expect("sender connection id unset"),
|
||||
});
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer(client_disconnected)]
|
||||
fn on_disconnect(ctx: &ReducerContext) {
|
||||
let sender_identity = &ctx.sender;
|
||||
let sender_identity = &ctx.sender();
|
||||
let sender_connection_id = ctx.connection_id.as_ref().expect("sender connection id unset");
|
||||
let match_client = |row: &ConnectedClient| {
|
||||
&row.identity == sender_identity && &row.connection_id == sender_connection_id
|
||||
};
|
||||
let match_client =
|
||||
|row: &ConnectedClient| &row.identity == sender_identity && &row.connection_id == sender_connection_id;
|
||||
if let Some(client) = ctx.db.connected_client().iter().find(match_client) {
|
||||
ctx.db.connected_client().delete(client);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ pub struct Users {
|
||||
pub fn add_user(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.users().insert(Users {
|
||||
name,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@ pub struct Users {
|
||||
}
|
||||
|
||||
#[spacetimedb::client_visibility_filter]
|
||||
const USER_FILTER: spacetimedb::Filter =
|
||||
spacetimedb::Filter::Sql("SELECT * FROM users WHERE identity = :sender");
|
||||
const USER_FILTER: spacetimedb::Filter = spacetimedb::Filter::Sql("SELECT * FROM users WHERE identity = :sender");
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn add_user(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.users().insert(Users {
|
||||
name,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ pub struct Users {
|
||||
}
|
||||
|
||||
#[spacetimedb::client_visibility_filter]
|
||||
const USER_FILTER: spacetimedb::Filter = spacetimedb::Filter::Sql(
|
||||
"SELECT * FROM users WHERE identity = :sender"
|
||||
);
|
||||
const USER_FILTER: spacetimedb::Filter = spacetimedb::Filter::Sql("SELECT * FROM users WHERE identity = :sender");
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn add_user(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.users().insert(Users { name, identity: ctx.sender });
|
||||
ctx.db.users().insert(Users {
|
||||
name,
|
||||
identity: ctx.sender(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ pub struct PlayerState {
|
||||
|
||||
#[spacetimedb::view(name = my_player, public)]
|
||||
pub fn my_player(ctx: &ViewContext) -> Option<PlayerState> {
|
||||
ctx.db.player_state().identity().find(ctx.sender)
|
||||
ctx.db.player_state().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn insert_player(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.player_state().insert(PlayerState {
|
||||
name,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ pub fn init(ctx: &ReducerContext) -> Result<(), String> {
|
||||
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender) {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender()) {
|
||||
ctx.db.player().insert(player.clone());
|
||||
ctx.db.logged_out_player().identity().delete(&player.identity);
|
||||
|
||||
@@ -157,7 +157,7 @@ pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
}
|
||||
} else {
|
||||
ctx.db.player().try_insert(Player {
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
player_id: 0,
|
||||
name: String::new(),
|
||||
})?;
|
||||
@@ -167,10 +167,15 @@ pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
|
||||
#[spacetimedb::reducer(client_disconnected)]
|
||||
pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
let player = ctx.db.player().identity().find(&ctx.sender).ok_or("Player not found")?;
|
||||
let player = ctx
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
let player_id = player.player_id;
|
||||
ctx.db.logged_out_player().insert(player);
|
||||
ctx.db.player().identity().delete(&ctx.sender);
|
||||
ctx.db.player().identity().delete(&ctx.sender());
|
||||
|
||||
// Move any circles from the arena into logged out tables
|
||||
for circle in ctx.db.circle().player_id().filter(&player_id) {
|
||||
@@ -187,7 +192,7 @@ pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
#[spacetimedb::reducer]
|
||||
pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||
log::info!("Creating player with name {}", name);
|
||||
let mut player: Player = ctx.db.player().identity().find(ctx.sender).ok_or("")?;
|
||||
let mut player: Player = ctx.db.player().identity().find(ctx.sender()).ok_or("")?;
|
||||
let player_id = player.player_id;
|
||||
player.name = name;
|
||||
ctx.db.player().identity().update(player);
|
||||
@@ -234,7 +239,7 @@ pub fn respawn(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("No such player found")?;
|
||||
|
||||
spawn_player_initial_circle(ctx, player.player_id)?;
|
||||
@@ -248,7 +253,7 @@ pub fn suicide(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("No such player found")?;
|
||||
|
||||
for circle in ctx.db.circle().player_id().filter(&player.player_id) {
|
||||
@@ -260,7 +265,12 @@ pub fn suicide(ctx: &ReducerContext) -> Result<(), String> {
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn update_player_input(ctx: &ReducerContext, direction: DbVector2) -> Result<(), String> {
|
||||
let player = ctx.db.player().identity().find(&ctx.sender).ok_or("Player not found")?;
|
||||
let player = ctx
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
for mut circle in ctx.db.circle().player_id().filter(&player.player_id) {
|
||||
circle.direction = direction.normalized();
|
||||
circle.speed = direction.magnitude().clamp(0.0, 1.0);
|
||||
@@ -467,7 +477,7 @@ pub fn player_split(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Sender has no player")?;
|
||||
let circles: Vec<Circle> = ctx.db.circle().player_id().filter(&player.player_id).collect();
|
||||
let mut circle_count = circles.len() as i32;
|
||||
|
||||
@@ -451,7 +451,7 @@ A view can be written in Rust like so:
|
||||
```rust
|
||||
#[spacetimedb::view(name = my_player, public)]
|
||||
fn my_player(ctx: &spacetimedb::ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
ctx.db.player().identity().find(ctx.sender())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ Add to `spacetimedb/src/lib.rs`:
|
||||
#[reducer]
|
||||
pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||
let name = validate_name(name)?;
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender()) {
|
||||
ctx.db.user().identity().update(User { name: Some(name), ..user });
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -439,7 +439,7 @@ pub fn send_message(ctx: &ReducerContext, text: String) -> Result<(), String> {
|
||||
let text = validate_message(text)?;
|
||||
log::info!("{}", text);
|
||||
ctx.db.message().insert(Message {
|
||||
sender: ctx.sender,
|
||||
sender: ctx.sender(),
|
||||
text,
|
||||
sent: ctx.timestamp,
|
||||
});
|
||||
@@ -547,12 +547,12 @@ Add to `spacetimedb/src/lib.rs`:
|
||||
```rust server
|
||||
#[reducer(client_connected)]
|
||||
pub fn client_connected(ctx: &ReducerContext) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender()) {
|
||||
ctx.db.user().identity().update(User { online: true, ..user });
|
||||
} else {
|
||||
ctx.db.user().insert(User {
|
||||
name: None,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
online: true,
|
||||
});
|
||||
}
|
||||
@@ -560,10 +560,10 @@ pub fn client_connected(ctx: &ReducerContext) {
|
||||
|
||||
#[reducer(client_disconnected)]
|
||||
pub fn identity_disconnected(ctx: &ReducerContext) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender()) {
|
||||
ctx.db.user().identity().update(User { online: false, ..user });
|
||||
} else {
|
||||
log::warn!("Disconnect event for unknown user with identity {:?}", ctx.sender);
|
||||
log::warn!("Disconnect event for unknown user with identity {:?}", ctx.sender());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -345,7 +345,7 @@ public static void Debug(ReducerContext ctx)
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
pub fn debug(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("This reducer was called by {}.", ctx.sender);
|
||||
log::debug!("This reducer was called by {}.", ctx.sender());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -463,7 +463,7 @@ Next let's connect our client to our database. Let's start by modifying our `deb
|
||||
```rust
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("{} just connected.", ctx.sender);
|
||||
log::debug!("{} just connected.", ctx.sender());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@@ -391,7 +391,7 @@ Next, modify your `connect` reducer and add a new `disconnect` reducer below it:
|
||||
```rust
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender) {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender()) {
|
||||
ctx.db.player().insert(player.clone());
|
||||
ctx.db
|
||||
.logged_out_player()
|
||||
@@ -399,7 +399,7 @@ pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.delete(&player.identity);
|
||||
} else {
|
||||
ctx.db.player().try_insert(Player {
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
player_id: 0,
|
||||
name: String::new(),
|
||||
})?;
|
||||
@@ -413,11 +413,11 @@ pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
let player_id = player.player_id;
|
||||
ctx.db.logged_out_player().insert(player);
|
||||
ctx.db.player().identity().delete(&ctx.sender);
|
||||
ctx.db.player().identity().delete(&ctx.sender());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ pub fn update_player_input(ctx: &ReducerContext, direction: DbVector2) -> Result
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
for mut circle in ctx.db.circle().player_id().filter(&player.player_id) {
|
||||
circle.direction = direction.normalized();
|
||||
@@ -218,7 +218,7 @@ pub fn update_player_input(ctx: &ReducerContext, direction: DbVector2) -> Result
|
||||
}
|
||||
```
|
||||
|
||||
This is a simple reducer that takes the movement input from the client and applies them to all circles that that player controls. Note that it is not possible for a player to move another player's circles using this reducer, because the `ctx.sender` value is not set by the client. Instead `ctx.sender` is set by SpacetimeDB after it has authenticated that sender. You can rest assured that the caller has been authenticated as that player by the time this reducer is called.
|
||||
This is a simple reducer that takes the movement input from the client and applies them to all circles that that player controls. Note that it is not possible for a player to move another player's circles using this reducer, because the `ctx.sender()` value is not set by the client. Instead `ctx.sender()` is set by SpacetimeDB after it has authenticated that sender. You can rest assured that the caller has been authenticated as that player by the time this reducer is called.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
@@ -343,7 +343,7 @@ public static void Debug(ReducerContext ctx)
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
pub fn debug(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("This reducer was called by {}.", ctx.sender);
|
||||
log::debug!("This reducer was called by {}.", ctx.sender());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -459,7 +459,7 @@ Next let's connect our client to our database. Let's start by modifying our `deb
|
||||
```rust
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("{} just connected.", ctx.sender);
|
||||
log::debug!("{} just connected.", ctx.sender());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@@ -384,7 +384,7 @@ Next, modify your `connect` reducer and add a new `disconnect` reducer below it:
|
||||
```rust
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender) {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender()) {
|
||||
ctx.db.player().insert(player.clone());
|
||||
ctx.db
|
||||
.logged_out_player()
|
||||
@@ -392,7 +392,7 @@ pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.delete(&player.identity);
|
||||
} else {
|
||||
ctx.db.player().try_insert(Player {
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
player_id: 0,
|
||||
name: String::new(),
|
||||
})?;
|
||||
@@ -406,11 +406,11 @@ pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
let player_id = player.player_id;
|
||||
ctx.db.logged_out_player().insert(player);
|
||||
ctx.db.player().identity().delete(&ctx.sender);
|
||||
ctx.db.player().identity().delete(&ctx.sender());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -499,7 +499,7 @@ const START_PLAYER_MASS: i32 = 15;
|
||||
#[spacetimedb::reducer]
|
||||
pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||
log::info!("Creating player with name {}", name);
|
||||
let mut player: Player = ctx.db.player().identity().find(ctx.sender).ok_or("")?;
|
||||
let mut player: Player = ctx.db.player().identity().find(ctx.sender()).ok_or("")?;
|
||||
let player_id = player.player_id;
|
||||
player.name = name;
|
||||
ctx.db.player().identity().update(player);
|
||||
@@ -589,11 +589,11 @@ pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
let player_id = player.player_id;
|
||||
ctx.db.logged_out_player().insert(player);
|
||||
ctx.db.player().identity().delete(&ctx.sender);
|
||||
ctx.db.player().identity().delete(&ctx.sender());
|
||||
|
||||
// Remove any circles from the arena
|
||||
for circle in ctx.db.circle().player_id().filter(&player_id) {
|
||||
|
||||
@@ -207,7 +207,7 @@ pub fn update_player_input(ctx: &ReducerContext, direction: DbVector2) -> Result
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.find(&ctx.sender())
|
||||
.ok_or("Player not found")?;
|
||||
for mut circle in ctx.db.circle().player_id().filter(&player.player_id) {
|
||||
circle.direction = direction.normalized();
|
||||
@@ -218,7 +218,7 @@ pub fn update_player_input(ctx: &ReducerContext, direction: DbVector2) -> Result
|
||||
}
|
||||
```
|
||||
|
||||
This is a simple reducer that takes the movement input from the client and applies them to all circles that that player controls. Note that it is not possible for a player to move another player's circles using this reducer, because the `ctx.sender` value is not set by the client. Instead `ctx.sender` is set by SpacetimeDB after it has authenticated that sender. You can rest assured that the caller has been authenticated as that player by the time this reducer is called.
|
||||
This is a simple reducer that takes the movement input from the client and applies them to all circles that that player controls. Note that it is not possible for a player to move another player's circles using this reducer, because the `ctx.sender()` value is not set by the client. Instead `ctx.sender()` is set by SpacetimeDB after it has authenticated that sender. You can rest assured that the caller has been authenticated as that player by the time this reducer is called.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -477,7 +477,7 @@ use spacetimedb::{view, ViewContext};
|
||||
// Return single row
|
||||
#[view(name = my_player, public)]
|
||||
fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
ctx.db.player().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
// Return multiple rows
|
||||
@@ -522,7 +522,7 @@ ctx.Rng // Random number generator
|
||||
|
||||
```rust
|
||||
ctx.db // Database access
|
||||
ctx.sender // Identity of caller
|
||||
ctx.sender() // Identity of caller
|
||||
ctx.connection_id // Option<ConnectionId>
|
||||
ctx.timestamp // Timestamp
|
||||
ctx.identity() // Module's identity
|
||||
|
||||
+8
-8
@@ -46,7 +46,7 @@ fn create_character(ctx: &ReducerContext, class: Class, nickname: String) {
|
||||
"Creating new level 1 {class:?} named {nickname}",
|
||||
);
|
||||
ctx.db.character().insert(Character {
|
||||
player_id: ctx.sender,
|
||||
player_id: ctx.sender(),
|
||||
nickname,
|
||||
level: 1,
|
||||
class,
|
||||
@@ -57,7 +57,7 @@ fn find_character_for_player(ctx: &ReducerContext) -> Character {
|
||||
ctx.db
|
||||
.character()
|
||||
.player_id()
|
||||
.find(ctx.sender)
|
||||
.find(ctx.sender())
|
||||
.expect("Player has not created a character")
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ fn choose_alliance(ctx: &ReducerContext, alliance: Alliance) {
|
||||
"Setting {}'s alliance to {:?} for player {}",
|
||||
character.nickname,
|
||||
alliance,
|
||||
ctx.sender,
|
||||
ctx.sender(),
|
||||
);
|
||||
update_character(
|
||||
ctx,
|
||||
@@ -194,18 +194,18 @@ When a new player creates a character, we'll make rows in both tables for them.
|
||||
fn create_character(ctx: &ReducerContext, class: Class, nickname: String) {
|
||||
log::info!(
|
||||
"Creating new level 1 {class:?} named {nickname} for player {}",
|
||||
ctx.sender,
|
||||
ctx.sender(),
|
||||
);
|
||||
|
||||
ctx.db.character().insert(Character {
|
||||
player_id: ctx.sender,
|
||||
player_id: ctx.sender(),
|
||||
nickname: nickname.clone(),
|
||||
level: 1,
|
||||
class,
|
||||
});
|
||||
|
||||
ctx.db.character_v2().insert(CharacterV2 {
|
||||
player_id: ctx.sender,
|
||||
player_id: ctx.sender(),
|
||||
nickname,
|
||||
level: 1,
|
||||
class,
|
||||
@@ -218,7 +218,7 @@ We'll update our helper functions so that they operate on `character_v2` rows. I
|
||||
|
||||
```rust
|
||||
fn find_character_for_player(ctx: &ReducerContext) -> CharacterV2 {
|
||||
if let Some(character) = ctx.db.character_v2().player_id().find(ctx.sender) {
|
||||
if let Some(character) = ctx.db.character_v2().player_id().find(ctx.sender()) {
|
||||
// Already migrated; just return the new player.
|
||||
return character;
|
||||
}
|
||||
@@ -228,7 +228,7 @@ fn find_character_for_player(ctx: &ReducerContext) -> CharacterV2 {
|
||||
.db
|
||||
.character()
|
||||
.player_id()
|
||||
.find(ctx.sender)
|
||||
.find(ctx.sender())
|
||||
.expect("Player has not created a character");
|
||||
|
||||
ctx.db.character_v2().insert(CharacterV2 {
|
||||
|
||||
+2
-2
@@ -174,7 +174,7 @@ pub struct Player {
|
||||
#[reducer]
|
||||
fn update_score(ctx: &ReducerContext, new_score: u32) {
|
||||
// Get the caller's identity
|
||||
let caller = ctx.sender;
|
||||
let caller = ctx.sender();
|
||||
|
||||
// Find and update their player record
|
||||
if let Some(mut player) = ctx.db.player().identity().find(caller) {
|
||||
@@ -290,7 +290,7 @@ pub struct ScheduledTask {
|
||||
#[reducer]
|
||||
fn send_reminder(ctx: &ReducerContext, task: ScheduledTask) {
|
||||
// Only allow the scheduler (module identity) to call this
|
||||
if ctx.sender != ctx.identity() {
|
||||
if ctx.sender() != ctx.identity() {
|
||||
panic!("This reducer can only be called by the scheduler");
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ public static void OnConnect(ReducerContext ctx)
|
||||
```rust
|
||||
#[reducer(client_connected)]
|
||||
pub fn on_connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::info!("Client connected: {}", ctx.sender);
|
||||
log::info!("Client connected: {}", ctx.sender());
|
||||
|
||||
// ctx.connection_id is guaranteed to be Some(...)
|
||||
let conn_id = ctx.connection_id.unwrap();
|
||||
@@ -139,7 +139,7 @@ pub fn on_connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
// Initialize client session
|
||||
ctx.db.sessions().try_insert(Session {
|
||||
connection_id: conn_id,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
connected_at: ctx.timestamp,
|
||||
})?;
|
||||
|
||||
@@ -198,7 +198,7 @@ public static void OnDisconnect(ReducerContext ctx)
|
||||
```rust
|
||||
#[reducer(client_disconnected)]
|
||||
pub fn on_disconnect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::info!("Client disconnected: {}", ctx.sender);
|
||||
log::info!("Client disconnected: {}", ctx.sender());
|
||||
|
||||
// ctx.connection_id is guaranteed to be Some(...)
|
||||
let conn_id = ctx.connection_id.unwrap();
|
||||
@@ -230,6 +230,6 @@ Reducers can be triggered at specific times using schedule tables. See [Schedule
|
||||
|
||||
:::info Scheduled Reducer Context
|
||||
Scheduled reducer calls originate from SpacetimeDB itself, not from a client. Therefore:
|
||||
- `ctx.sender` will be the module's own identity
|
||||
- `ctx.sender()` will be the module's own identity
|
||||
- `ctx.connection_id` will be `None`/`null`/`undefined`
|
||||
:::
|
||||
|
||||
@@ -55,7 +55,7 @@ spacetimedb.view(
|
||||
{ name: 'my_player', public: true },
|
||||
t.option(players.rowType),
|
||||
(ctx) => {
|
||||
const row = ctx.db.players.identity.find(ctx.sender);
|
||||
const row = ctx.db.players.identity.find(ctx.sender());
|
||||
return row ?? undefined;
|
||||
}
|
||||
);
|
||||
@@ -197,7 +197,7 @@ pub struct PlayerAndLevel {
|
||||
// At-most-one row: return Option<T>
|
||||
#[view(name = my_player, public)]
|
||||
fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
ctx.db.player().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
// Multiple rows: return Vec<T>
|
||||
@@ -232,7 +232,7 @@ Views can return either `Option<T>` for at-most-one row or `Vec<T>` for multiple
|
||||
|
||||
Views use one of two context types:
|
||||
|
||||
- **`ViewContext`**: Provides access to the caller's `Identity` through `ctx.sender`. Use this when the view depends on who is querying it.
|
||||
- **`ViewContext`**: Provides access to the caller's `Identity` through `ctx.sender()`. Use this when the view depends on who is querying it.
|
||||
- **`AnonymousViewContext`**: Does not provide caller information. Use this when the view produces the same results regardless of who queries it.
|
||||
|
||||
Both contexts provide read-only access to tables and indexes through `ctx.db`.
|
||||
@@ -243,7 +243,7 @@ The choice between `ViewContext` and `AnonymousViewContext` has significant perf
|
||||
|
||||
**Anonymous views can be shared across all subscribers.** When a view uses `AnonymousViewContext`, SpacetimeDB knows the result is the same for every client. The database can materialize the view once and serve that same result to all subscribers. When the underlying data changes, it recomputes the view once and broadcasts the update to everyone.
|
||||
|
||||
**Per-user views require separate computation for each subscriber.** When a view uses `ViewContext`, the result depends on `ctx.sender`, so each client potentially sees different data. SpacetimeDB must compute and track the view separately for each subscriber. With 1,000 connected users, that's 1,000 separate view computations and 1,000 separate sets of change tracking.
|
||||
**Per-user views require separate computation for each subscriber.** When a view uses `ViewContext` and invokes `ctx.sender()`, each client potentially sees different data. SpacetimeDB must compute and track the view separately for each subscriber. With 1,000 connected users, that's 1,000 separate view computations and 1,000 separate sets of change tracking.
|
||||
|
||||
**Prefer `AnonymousViewContext` when possible.** Design your views to be caller-independent when the use case allows. For example:
|
||||
|
||||
@@ -294,7 +294,7 @@ public static Player? MyPlayer(ViewContext ctx)
|
||||
// Per-user: each client sees their own player
|
||||
#[view(name = my_player, public)]
|
||||
fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(&ctx.sender)
|
||||
ctx.db.player().identity().find(&ctx.sender())
|
||||
}
|
||||
```
|
||||
|
||||
@@ -541,7 +541,7 @@ fn entities_in_origin_chunk(ctx: &AnonymousViewContext) -> Vec<Entity> {
|
||||
// Per-user: returns entities in the chunk the player is currently in
|
||||
#[view(name = entities_in_my_chunk, public)]
|
||||
fn entities_in_my_chunk(ctx: &ViewContext) -> Vec<Entity> {
|
||||
let Some(player) = ctx.db.player().identity().find(&ctx.sender) else {
|
||||
let Some(player) = ctx.db.player().identity().find(&ctx.sender()) else {
|
||||
return vec![];
|
||||
};
|
||||
let Some(chunk) = ctx.db.player_chunk().player_id().find(&player.id) else {
|
||||
|
||||
@@ -382,7 +382,7 @@ ctx.db.player().insert(Player { /* ... */ });
|
||||
ctx.db.logged_out_player().insert(Player { /* ... */ });
|
||||
|
||||
// Move a row between tables
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender) {
|
||||
if let Some(player) = ctx.db.logged_out_player().identity().find(&ctx.sender()) {
|
||||
ctx.db.player().insert(player.clone());
|
||||
ctx.db.logged_out_player().identity().delete(&player.identity);
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ pub fn register_document(
|
||||
) {
|
||||
ctx.db.document().insert(Document {
|
||||
id: 0, // auto-increment
|
||||
owner_id: ctx.sender,
|
||||
owner_id: ctx.sender(),
|
||||
filename,
|
||||
mime_type,
|
||||
size_bytes,
|
||||
|
||||
@@ -330,7 +330,7 @@ Views can only access table data through indexed lookups, not by scanning all ro
|
||||
|
||||
### Filtering Rows by Caller
|
||||
|
||||
Use views with `ViewContext` to return only the rows that belong to the caller. The view accesses the caller's identity through `ctx.sender` and uses it to look up rows via an index.
|
||||
Use views with `ViewContext` to return only the rows that belong to the caller. The view accesses the caller's identity through `ctx.sender()` and uses it to look up rows via an index.
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
@@ -426,8 +426,8 @@ pub struct Message {
|
||||
#[spacetimedb::view(name = my_messages, public)]
|
||||
fn my_messages(ctx: &ViewContext) -> Vec<Message> {
|
||||
// Look up messages by index where caller is sender or recipient
|
||||
let sent: Vec<_> = ctx.db.message().sender().filter(&ctx.sender).collect();
|
||||
let received: Vec<_> = ctx.db.message().recipient().filter(&ctx.sender).collect();
|
||||
let sent: Vec<_> = ctx.db.message().sender().filter(&ctx.sender()).collect();
|
||||
let received: Vec<_> = ctx.db.message().recipient().filter(&ctx.sender()).collect();
|
||||
sent.into_iter().chain(received).collect()
|
||||
}
|
||||
```
|
||||
@@ -574,7 +574,7 @@ pub struct PublicUserProfile {
|
||||
#[spacetimedb::view(name = my_profile, public)]
|
||||
fn my_profile(ctx: &ViewContext) -> Option<PublicUserProfile> {
|
||||
// Look up the caller's account by their identity (unique index)
|
||||
let user = ctx.db.user_account().identity().find(&ctx.sender)?;
|
||||
let user = ctx.db.user_account().identity().find(&ctx.sender())?;
|
||||
Some(PublicUserProfile {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
@@ -726,7 +726,7 @@ pub struct Colleague {
|
||||
#[spacetimedb::view(name = my_colleagues, public)]
|
||||
fn my_colleagues(ctx: &ViewContext) -> Vec<Colleague> {
|
||||
// Find the caller's employee record by identity (unique index)
|
||||
let Some(me) = ctx.db.employee().identity().find(&ctx.sender) else {
|
||||
let Some(me) = ctx.db.employee().identity().find(&ctx.sender()) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ pub fn process_data(ctx: &ReducerContext, value: u32) -> Result<(), String> {
|
||||
return Err("Value cannot be zero".to_string());
|
||||
}
|
||||
|
||||
log::debug!("Debug information: ctx.sender = {:?}", ctx.sender);
|
||||
log::debug!("Debug information: ctx.sender = {:?}", ctx.sender());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -220,7 +220,7 @@ use spacetimedb::log;
|
||||
pub fn transfer_credits(ctx: &ReducerContext, to_user: u64, amount: u32) -> Result<(), String> {
|
||||
log::info!(
|
||||
"Credit transfer: from={:?}, to={}, amount={}",
|
||||
ctx.sender,
|
||||
ctx.sender(),
|
||||
to_user,
|
||||
amount
|
||||
);
|
||||
|
||||
@@ -206,7 +206,7 @@ impl Foo<'_> {
|
||||
|
||||
#[spacetimedb::view(name = my_player, public)]
|
||||
fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
ctx.db.player().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -267,7 +267,7 @@ fn log_module_identity(ctx: &ReducerContext) {
|
||||
#[spacetimedb::reducer]
|
||||
pub fn test(ctx: &ReducerContext, arg: TestAlias, arg2: TestB, arg3: TestC, arg4: TestF) -> anyhow::Result<()> {
|
||||
log::info!("BEGIN");
|
||||
log::info!("sender: {:?}", ctx.sender);
|
||||
log::info!("sender: {:?}", ctx.sender());
|
||||
log::info!("timestamp: {:?}", ctx.timestamp);
|
||||
log::info!("bar: {:?}", arg2.foo);
|
||||
|
||||
@@ -468,7 +468,7 @@ fn test_btree_index_args(ctx: &ReducerContext) {
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
fn assert_caller_identity_is_module_identity(ctx: &ReducerContext) {
|
||||
let caller = ctx.sender;
|
||||
let caller = ctx.sender();
|
||||
let owner = ctx.identity();
|
||||
if caller != owner {
|
||||
panic!("Caller {caller} is not the owner {owner}");
|
||||
|
||||
@@ -22,10 +22,10 @@ pub struct Disconnected {
|
||||
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn identity_connected(ctx: &ReducerContext) {
|
||||
ctx.db.connected().insert(Connected { identity: ctx.sender });
|
||||
ctx.db.connected().insert(Connected { identity: ctx.sender() });
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer(client_disconnected)]
|
||||
pub fn identity_disconnected(ctx: &ReducerContext) {
|
||||
ctx.db.disconnected().insert(Disconnected { identity: ctx.sender });
|
||||
ctx.db.disconnected().insert(Disconnected { identity: ctx.sender() });
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ fn delete_player(ctx: &ReducerContext, identity: Identity) {
|
||||
|
||||
#[reducer]
|
||||
pub fn move_player(ctx: &ReducerContext, dx: i32, dy: i32) {
|
||||
let my_player = ctx.db.player().identity().find(ctx.sender).unwrap_or_else(|| {
|
||||
let my_player = ctx.db.player().identity().find(ctx.sender()).unwrap_or_else(|| {
|
||||
ctx.db.player().insert(Player {
|
||||
entity_id: 0,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
})
|
||||
});
|
||||
match ctx.db.player_location().entity_id().find(my_player.entity_id) {
|
||||
@@ -82,7 +82,7 @@ pub fn move_player(ctx: &ReducerContext, dx: i32, dy: i32) {
|
||||
|
||||
#[view(name = my_player, public)]
|
||||
fn my_player(ctx: &ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
ctx.db.player().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
#[view(name = my_player_and_level, public)]
|
||||
@@ -90,7 +90,7 @@ fn my_player_and_level(ctx: &ViewContext) -> Option<PlayerAndLevel> {
|
||||
ctx.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(ctx.sender)
|
||||
.find(ctx.sender())
|
||||
.and_then(|Player { entity_id, identity }| {
|
||||
ctx.db
|
||||
.player_level()
|
||||
@@ -119,7 +119,7 @@ pub fn nearby_players(ctx: &ViewContext) -> Vec<PlayerLocation> {
|
||||
ctx.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(ctx.sender)
|
||||
.find(ctx.sender())
|
||||
.and_then(|my_player| ctx.db.player_location().entity_id().find(my_player.entity_id))
|
||||
.iter()
|
||||
.flat_map(|my_loc| {
|
||||
|
||||
@@ -637,25 +637,27 @@ fn delete_pk_u32_insert_pk_u32_two(ctx: &ReducerContext, n: u32, data: i32) -> a
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
fn insert_caller_one_identity(ctx: &ReducerContext) -> anyhow::Result<()> {
|
||||
ctx.db.one_identity().insert(OneIdentity { i: ctx.sender });
|
||||
ctx.db.one_identity().insert(OneIdentity { i: ctx.sender() });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
fn insert_caller_vec_identity(ctx: &ReducerContext) -> anyhow::Result<()> {
|
||||
ctx.db.vec_identity().insert(VecIdentity { i: vec![ctx.sender] });
|
||||
ctx.db.vec_identity().insert(VecIdentity { i: vec![ctx.sender()] });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
fn insert_caller_unique_identity(ctx: &ReducerContext, data: i32) -> anyhow::Result<()> {
|
||||
ctx.db.unique_identity().insert(UniqueIdentity { i: ctx.sender, data });
|
||||
ctx.db
|
||||
.unique_identity()
|
||||
.insert(UniqueIdentity { i: ctx.sender(), data });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
fn insert_caller_pk_identity(ctx: &ReducerContext, data: i32) -> anyhow::Result<()> {
|
||||
ctx.db.pk_identity().insert(PkIdentity { i: ctx.sender, data });
|
||||
ctx.db.pk_identity().insert(PkIdentity { i: ctx.sender(), data });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const USER_FILTER: spacetimedb::Filter = spacetimedb::Filter::Sql(
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn add_user(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.users().insert(Users { name, identity: ctx.sender });
|
||||
ctx.db.users().insert(Users { name, identity: ctx.sender() });
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -88,7 +88,7 @@ pub struct Users {
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn add_user(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.users().insert(Users { name, identity: ctx.sender });
|
||||
ctx.db.users().insert(Users { name, identity: ctx.sender() });
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@@ -609,12 +609,12 @@ pub struct PlayerState {
|
||||
|
||||
#[spacetimedb::view(name = my_player, public)]
|
||||
pub fn my_player(ctx: &ViewContext) -> Option<PlayerState> {
|
||||
ctx.db.player_state().identity().find(ctx.sender)
|
||||
ctx.db.player_state().identity().find(ctx.sender())
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn insert_player(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.player_state().insert(PlayerState { name, identity: ctx.sender });
|
||||
ctx.db.player_state().insert(PlayerState { name, identity: ctx.sender() });
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@@ -112,14 +112,14 @@ pub struct ConnectedClient {
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
fn on_connect(ctx: &ReducerContext) {
|
||||
ctx.db.connected_client().insert(ConnectedClient {
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
connection_id: ctx.connection_id.expect("sender connection id unset"),
|
||||
});
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer(client_disconnected)]
|
||||
fn on_disconnect(ctx: &ReducerContext) {
|
||||
let sender_identity = &ctx.sender;
|
||||
let sender_identity = &ctx.sender();
|
||||
let sender_connection_id = ctx.connection_id.as_ref().expect("sender connection id unset");
|
||||
let match_client = |row: &ConnectedClient| {
|
||||
&row.identity == sender_identity && &row.connection_id == sender_connection_id
|
||||
|
||||
@@ -26,8 +26,8 @@ fn validate_name(name: String) -> Result<String, String> {
|
||||
#[spacetimedb::reducer]
|
||||
pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||
let name = validate_name(name)?;
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
|
||||
log::info!("User {} sets name to {name}", ctx.sender);
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender()) {
|
||||
log::info!("User {} sets name to {name}", ctx.sender());
|
||||
ctx.db.user().identity().update(User {
|
||||
name: Some(name),
|
||||
..user
|
||||
@@ -52,9 +52,9 @@ pub fn send_message(ctx: &ReducerContext, text: String) -> Result<(), String> {
|
||||
// - Rate-limit messages per-user.
|
||||
// - Reject messages from unnamed user.
|
||||
let text = validate_message(text)?;
|
||||
log::info!("User {}: {text}", ctx.sender);
|
||||
log::info!("User {}: {text}", ctx.sender());
|
||||
ctx.db.message().insert(Message {
|
||||
sender: ctx.sender,
|
||||
sender: ctx.sender(),
|
||||
text,
|
||||
sent: ctx.timestamp,
|
||||
});
|
||||
@@ -67,7 +67,7 @@ pub fn init(_ctx: &ReducerContext) {}
|
||||
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn identity_connected(ctx: &ReducerContext) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender()) {
|
||||
// If this is a returning user, i.e. we already have a `User` with this `Identity`,
|
||||
// set `online: true`, but leave `name` and `identity` unchanged.
|
||||
ctx.db.user().identity().update(User { online: true, ..user });
|
||||
@@ -76,7 +76,7 @@ pub fn identity_connected(ctx: &ReducerContext) {
|
||||
// which is online, but hasn't set a name.
|
||||
ctx.db.user().insert(User {
|
||||
name: None,
|
||||
identity: ctx.sender,
|
||||
identity: ctx.sender(),
|
||||
online: true,
|
||||
});
|
||||
}
|
||||
@@ -84,11 +84,11 @@ pub fn identity_connected(ctx: &ReducerContext) {
|
||||
|
||||
#[spacetimedb::reducer(client_disconnected)]
|
||||
pub fn identity_disconnected(ctx: &ReducerContext) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
|
||||
if let Some(user) = ctx.db.user().identity().find(ctx.sender()) {
|
||||
ctx.db.user().identity().update(User { online: false, ..user });
|
||||
} else {
|
||||
// This branch should be unreachable,
|
||||
// as it doesn't make sense for a client to disconnect without connecting first.
|
||||
log::warn!("Disconnect event for unknown user with identity {:?}", ctx.sender);
|
||||
log::warn!("Disconnect event for unknown user with identity {:?}", ctx.sender());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user