diff --git a/src/engine.rs b/src/engine.rs index ff25fa34..34ed2ddb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -519,8 +519,8 @@ pub struct State { /// Embedded module resolver. #[cfg(not(feature = "no_module"))] pub resolver: Option>, - /// Cached lookup values for function hashes. - pub functions_caches: StaticVec< + /// Functions resolution cache. + fn_resolution_caches: StaticVec< HashMap< NonZeroU64, Option<(CallableFunction, Option)>, @@ -535,6 +535,40 @@ impl State { pub fn is_global(&self) -> bool { self.scope_level == 0 } + /// Get the current functions resolution cache. + pub fn fn_resolution_cache( + &self, + ) -> Option< + &HashMap< + NonZeroU64, + Option<(CallableFunction, Option)>, + StraightHasherBuilder, + >, + > { + self.fn_resolution_caches.last() + } + /// Get a mutable reference to the current functions resolution cache. + pub fn fn_resolution_cache_mut( + &mut self, + ) -> &mut HashMap< + NonZeroU64, + Option<(CallableFunction, Option)>, + StraightHasherBuilder, + > { + if self.fn_resolution_caches.is_empty() { + self.fn_resolution_caches + .push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder)); + } + self.fn_resolution_caches.last_mut().unwrap() + } + /// Push an empty functions resolution cache onto the stack and make it current. + pub fn push_fn_resolution_cache(&mut self) { + self.fn_resolution_caches.push(Default::default()); + } + /// Remove the current functions resolution cache and make the last one current. + pub fn pop_fn_resolution_cache(&mut self) { + self.fn_resolution_caches.pop(); + } } /// _(INTERNALS)_ A type containing all the limits imposed by the [`Engine`]. @@ -1874,9 +1908,11 @@ impl Engine { Stmt::Import(_, _, _) => { // When imports list is modified, clear the functions lookup cache if _has_imports { - state.functions_caches.last_mut().map(|c| c.clear()); + state.fn_resolution_caches.last_mut().map(|c| c.clear()); } else if restore { - state.functions_caches.push(Default::default()); + state + .fn_resolution_caches + .push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder)); _has_imports = true; } } @@ -1890,7 +1926,7 @@ impl Engine { scope.rewind(prev_scope_len); if _has_imports { // If imports list is modified, pop the functions lookup cache - state.functions_caches.pop(); + state.fn_resolution_caches.pop(); } mods.truncate(prev_mods_len); state.scope_level -= 1; diff --git a/src/engine_api.rs b/src/engine_api.rs index 26268ba8..ebecaa40 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1527,15 +1527,16 @@ impl Engine { ast: &'a AST, level: usize, ) -> Result> { - let state = &mut State { - source: ast.clone_source(), - #[cfg(not(feature = "no_module"))] - resolver: ast.resolver(), - ..Default::default() - }; + let mut state: State = Default::default(); + state.source = ast.clone_source(); + #[cfg(not(feature = "no_module"))] + { + state.resolver = ast.resolver(); + } + let statements = ast.statements(); let lib = &[ast.lib()]; - self.eval_global_statements(scope, mods, state, statements, lib, level) + self.eval_global_statements(scope, mods, &mut state, statements, lib, level) } /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. @@ -1598,15 +1599,15 @@ impl Engine { ast: &AST, ) -> Result<(), Box> { let mods = &mut (&self.global_sub_modules).into(); - let state = &mut State { - source: ast.clone_source(), - #[cfg(not(feature = "no_module"))] - resolver: ast.resolver(), - ..Default::default() - }; + let mut state: State = Default::default(); + state.source = ast.clone_source(); + #[cfg(not(feature = "no_module"))] + { + state.resolver = ast.resolver(); + } let statements = ast.statements(); let lib = &[ast.lib()]; - self.eval_global_statements(scope, mods, state, statements, lib, 0)?; + self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?; Ok(()) } /// Call a script function defined in an [`AST`] with multiple arguments. diff --git a/src/fn_call.rs b/src/fn_call.rs index 9b56b55d..ffe88602 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -175,15 +175,11 @@ impl Engine { ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state, pos)?; - // Check if function access already in the cache - if state.functions_caches.is_empty() { - state.functions_caches.push(Default::default()); - } + let source = state.source.clone(); + // Check if function access already in the cache let func = &*state - .functions_caches - .last_mut() - .unwrap() + .fn_resolution_cache_mut() .entry(hash_fn) .or_insert_with(|| { // Search for the native function @@ -208,7 +204,7 @@ impl Engine { }) }); - if let Some((func, source)) = func { + if let Some((func, src)) = func { assert!(func.is_native()); // Calling pure function but the first argument is a reference? @@ -216,12 +212,7 @@ impl Engine { backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); // Run external function - let source = if source.is_none() { - state.source.as_ref() - } else { - source.as_ref() - } - .map(|s| s.as_str()); + let source = src.as_ref().or_else(|| source.as_ref()).map(|s| s.as_str()); let result = if func.is_plugin_fn() { func.get_plugin_fn() .call((self, fn_name, source, mods, lib).into(), args) @@ -405,7 +396,7 @@ impl Engine { let unified_lib = if let Some(ref env_lib) = fn_def.lib { unified = true; - state.functions_caches.push(Default::default()); + state.push_fn_resolution_cache(); lib_merged = Default::default(); lib_merged.push(env_lib.as_ref()); lib_merged.extend(lib.iter().cloned()); @@ -477,7 +468,7 @@ impl Engine { state.scope_level = orig_scope_level; if unified { - state.functions_caches.pop(); + state.pop_fn_resolution_cache(); } result @@ -515,13 +506,13 @@ impl Engine { // Check if it is already in the cache if let Some(state) = state.as_mut() { if let Some(hash) = hash_script { - match state.functions_caches.last().map_or(None, |c| c.get(&hash)) { + match state.fn_resolution_cache().map_or(None, |c| c.get(&hash)) { Some(v) => return v.is_some(), None => (), } } if let Some(hash) = hash_fn { - match state.functions_caches.last().map_or(None, |c| c.get(&hash)) { + match state.fn_resolution_cache().map_or(None, |c| c.get(&hash)) { Some(v) => return v.is_some(), None => (), } @@ -545,24 +536,10 @@ impl Engine { if !r { if let Some(state) = state.as_mut() { if let Some(hash) = hash_script { - if state.functions_caches.is_empty() { - state.functions_caches.push(Default::default()); - } - state - .functions_caches - .last_mut() - .unwrap() - .insert(hash, None); + state.fn_resolution_cache_mut().insert(hash, None); } if let Some(hash) = hash_fn { - if state.functions_caches.is_empty() { - state.functions_caches.push(Default::default()); - } - state - .functions_caches - .last_mut() - .unwrap() - .insert(hash, None); + state.fn_resolution_cache_mut().insert(hash, None); } } } @@ -653,14 +630,8 @@ impl Engine { let hash_script = hash_script.unwrap(); // Check if function access already in the cache - if state.functions_caches.is_empty() { - state.functions_caches.push(Default::default()); - } - let (func, source) = state - .functions_caches - .last_mut() - .unwrap() + .fn_resolution_cache_mut() .entry(hash_script) .or_insert_with(|| { lib.iter() @@ -822,14 +793,12 @@ impl Engine { } // Evaluate the AST - let new_state = &mut State { - source: state.source.clone(), - operations: state.operations, - ..Default::default() - }; + let mut new_state: State = Default::default(); + new_state.source = state.source.clone(); + new_state.operations = state.operations; let result = - self.eval_global_statements(scope, mods, new_state, ast.statements(), lib, level); + self.eval_global_statements(scope, mods, &mut new_state, ast.statements(), lib, level); state.operations = new_state.operations; result