diff --git a/CHANGELOG.md b/CHANGELOG.md index f73ee408..3a606c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,11 @@ New features * `Scope` is now serializable and deserializable via `serde`. +### Store and recreate `NativeCallContext` + +* A convenient API is added to store a `NativeCallContext` into a new `NativeCallContextStore` type. +* This allows a `NativeCallContext` to be stored and recreated later on. + ### Call native Rust functions in `NativeCallContext` * `NativeCallContext::call_native_fn` is added to call registered native Rust functions only. diff --git a/src/func/native.rs b/src/func/native.rs index 912f9d79..63823ecd 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -72,22 +72,51 @@ pub struct NativeCallContext<'a> { /// Function source, if any. source: Option<&'a str>, /// The current [`GlobalRuntimeState`], if any. - global: Option<&'a GlobalRuntimeState<'a>>, + global: Option<&'a GlobalRuntimeState>, /// The current stack of loaded [modules][Module]. - lib: &'a [&'a Module], + lib: &'a [Shared], /// [Position] of the function call. pos: Position, /// The current nesting level of function calls. level: usize, } +/// _(internals)_ Context of a native Rust function call. +/// Exported under the `internals` feature only. +#[cfg(feature = "internals")] +#[derive(Debug, Clone)] +pub struct NativeCallContextStore { + /// Name of function called. + pub fn_name: String, + /// Function source, if any. + pub source: Option, + /// The current [`GlobalRuntimeState`], if any. + pub global: GlobalRuntimeState, + /// The current stack of loaded [modules][Module]. + pub lib: StaticVec>, + /// [Position] of the function call. + pub pos: Position, + /// The current nesting level of function calls. + pub level: usize, +} + +#[cfg(feature = "internals")] +impl NativeCallContextStore { + /// Create a [`NativeCallContext`] from a [`NativeCallContextClone`]. + #[inline(always)] + #[must_use] + pub fn create_context<'a>(&'a self, engine: &'a Engine) -> NativeCallContext<'a> { + NativeCallContext::from_stored_data(engine, self) + } +} + impl<'a> From<( &'a Engine, &'a str, Option<&'a str>, - &'a GlobalRuntimeState<'a>, - &'a [&Module], + &'a GlobalRuntimeState, + &'a [Shared], Position, usize, )> for NativeCallContext<'a> @@ -99,7 +128,7 @@ impl<'a> &'a str, Option<&'a str>, &'a GlobalRuntimeState, - &'a [&Module], + &'a [Shared], Position, usize, ), @@ -116,9 +145,9 @@ impl<'a> } } -impl<'a> From<(&'a Engine, &'a str, &'a [&'a Module])> for NativeCallContext<'a> { +impl<'a> From<(&'a Engine, &'a str, &'a [Shared])> for NativeCallContext<'a> { #[inline(always)] - fn from(value: (&'a Engine, &'a str, &'a [&Module])) -> Self { + fn from(value: (&'a Engine, &'a str, &'a [Shared])) -> Self { Self { engine: value.0, fn_name: value.1, @@ -140,7 +169,7 @@ impl<'a> NativeCallContext<'a> { )] #[inline(always)] #[must_use] - pub fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self { + pub fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [Shared]) -> Self { Self { engine, fn_name, @@ -164,7 +193,7 @@ impl<'a> NativeCallContext<'a> { fn_name: &'a str, source: Option<&'a str>, global: &'a GlobalRuntimeState, - lib: &'a [&Module], + lib: &'a [Shared], pos: Position, level: usize, ) -> Self { @@ -178,6 +207,39 @@ impl<'a> NativeCallContext<'a> { level, } } + + /// _(internals)_ Create a [`NativeCallContext`] from a [`NativeCallContextClone`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline] + #[must_use] + pub fn from_stored_data(engine: &'a Engine, context: &'a NativeCallContextStore) -> Self { + Self { + engine, + fn_name: &context.fn_name, + source: context.source.as_ref().map(String::as_str), + global: Some(&context.global), + lib: &context.lib, + pos: context.pos, + level: context.level, + } + } + /// _(internals)_ Store this [`NativeCallContext`] into a [`NativeCallContextClone`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline] + #[must_use] + pub fn store_data(&self) -> NativeCallContextStore { + NativeCallContextStore { + fn_name: self.fn_name.to_string(), + source: self.source.map(|s| s.to_string()), + global: self.global.unwrap().clone(), + lib: self.lib.iter().cloned().collect(), + pos: self.pos, + level: self.level, + } + } + /// The current [`Engine`]. #[inline(always)] #[must_use] @@ -246,14 +308,14 @@ impl<'a> NativeCallContext<'a> { /// in reverse order (i.e. parent namespaces are iterated after child namespaces). #[inline] pub fn iter_namespaces(&self) -> impl Iterator { - self.lib.iter().copied() + self.lib.iter().map(|m| m.as_ref()) } /// _(internals)_ The current stack of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] #[must_use] - pub const fn namespaces(&self) -> &[&Module] { + pub const fn namespaces(&self) -> &[Shared] { self.lib } /// Call a function inside the call context with the provided arguments. diff --git a/tests/modules.rs b/tests/modules.rs index f18d9aa7..babc862a 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -2,7 +2,7 @@ use rhai::{ module_resolvers::{DummyModuleResolver, StaticModuleResolver}, Dynamic, Engine, EvalAltResult, FnNamespace, FnPtr, ImmutableString, Module, NativeCallContext, - ParseError, ParseErrorType, Scope, Shared, INT, + ParseError, ParseErrorType, Scope, INT, }; #[test] @@ -546,44 +546,13 @@ fn test_module_context() -> Result<(), Box> { engine.register_fn( "calc", |context: NativeCallContext, fp: FnPtr| -> Result> { - // Store fields for later use let engine = context.engine(); - let fn_name = context.fn_name().to_string(); - let source = context.source().map(|s| s.to_string()); - let global = context.global_runtime_state().unwrap().clone(); - let pos = context.position(); - let call_level = context.call_level(); - // Store the paths of the stack of call modules up to this point - let modules_list: Vec = context - .iter_namespaces() - .map(|m| m.id().unwrap_or("testing")) - .filter(|id| !id.is_empty()) - .map(|id| id.to_string()) - .collect(); + // Store context for later use - requires the 'internals' feature + let context_data = context.store_data(); - // Recreate the 'NativeCallContext' - requires the 'internals' feature - let mut libraries = Vec::>::new(); - - for path in modules_list { - // Recreate the stack of call modules by resolving each path with - // the module resolver. - let module = engine.module_resolver().resolve(engine, None, &path, pos)?; - - libraries.push(module); - } - - let lib: Vec<&Module> = libraries.iter().map(|m| m.as_ref()).collect(); - - let new_context = NativeCallContext::new_with_all_fields( - engine, - &fn_name, - source.as_ref().map(String::as_str), - &global, - &lib, - pos, - call_level, - ); + // Recreate the 'NativeCallContext' + let new_context = context_data.create_context(engine); fp.call_within_context(&new_context, (41 as INT,)) },