diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index fe576a38dce..cfd9d34acc5 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -1084,13 +1084,25 @@ pub struct ProcedureContext { /// Methods for performing HTTP requests. pub http: crate::http::HttpClient, - // TODO: Add rng? + // TODO: Change rng? // Complex and requires design because we may want procedure RNG to behave differently from reducer RNG, // as it could actually be seeded by OS randomness rather than a deterministic source. + #[cfg(feature = "rand08")] + rng: std::cell::OnceCell, } #[cfg(feature = "unstable")] impl ProcedureContext { + fn new(sender: Identity, connection_id: Option, timestamp: Timestamp) -> Self { + Self { + sender, + timestamp, + connection_id, + http: http::HttpClient {}, + #[cfg(feature = "rand08")] + rng: std::cell::OnceCell::new(), + } + } /// Read the current module's [`Identity`]. pub fn identity(&self) -> Identity { // Hypothetically, we *could* read the module identity out of the system tables. diff --git a/crates/bindings/src/rng.rs b/crates/bindings/src/rng.rs index c289ef7744f..4066abfeb20 100644 --- a/crates/bindings/src/rng.rs +++ b/crates/bindings/src/rng.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "unstable")] +use crate::ProcedureContext; use crate::{rand, ReducerContext}; use core::cell::UnsafeCell; use core::marker::PhantomData; @@ -49,6 +51,51 @@ impl ReducerContext { } } +#[cfg(feature = "unstable")] +impl ProcedureContext { + /// Generates a random value. + /// + /// Similar to [`rand::random()`], but using [`StdbRng`] instead. + /// + /// See also [`ProcedureContext::rng()`]. + #[cfg(feature = "unstable")] + pub fn random(&self) -> T + where + Standard: Distribution, + { + Standard.sample(&mut self.rng()) + } + + /// Retrieve the random number generator for this procedure transaction, + /// seeded by the timestamp of the procedure call. + /// + /// If you only need a single random value, you can use [`ProcedureContext::random()`]. + /// + /// # Examples + /// ```no_run + /// # #[cfg(target_arch = "wasm32")] mod demo { + /// use spacetimedb::{procedure, ProcedureContext}; + /// use rand::Rng; + /// + /// #[spacetimedb::procedure] + /// fn rng_demo(ctx: &spacetimedb::ProcedureContext) { + /// // Can be used in method chaining style: + /// let digit = ctx.rng().gen_range(0..=9); + /// + /// // Or, cache locally for reuse: + /// let mut rng = ctx.rng(); + /// let floats: Vec = rng.sample_iter(rand::distributions::Standard).collect(); + /// } + /// # } + /// ``` + /// + /// For more information, see [`StdbRng`] and [`rand::Rng`]. + #[cfg(feature = "unstable")] + pub fn rng(&self) -> &StdbRng { + self.rng.get_or_init(|| StdbRng::seed_from_ts(self.timestamp)) + } +} + /// A reference to the random number generator for this reducer call. /// /// An instance can be obtained via [`ReducerContext::rng()`]. Import diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index d3fbb079ffd..602abb62ed6 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -1034,12 +1034,7 @@ extern "C" fn __call_procedure__( let timestamp = Timestamp::from_micros_since_unix_epoch(timestamp as i64); // Assemble the `ProcedureContext`. - let ctx = ProcedureContext { - connection_id: conn_id, - sender, - timestamp, - http: crate::http::HttpClient {}, - }; + let ctx = ProcedureContext::new(sender, conn_id, timestamp); // Grab the list of procedures, which is populated by the preinit functions. let procedures = PROCEDURES.get().unwrap();