Change HashMap to BTreeMap.

This commit is contained in:
Stephen Chung 2021-03-23 12:13:53 +08:00
parent 7a0032fc89
commit f70225ca1d
19 changed files with 139 additions and 214 deletions

View File

@ -4,9 +4,21 @@ Rhai Release Notes
Version 0.19.15 Version 0.19.15
=============== ===============
This version replaces internal usage of `HashMap` with `BTreeMap` in many cases, which should result
in speed improvements because a `BTreeMap` is faster when the number of items held is small.
This also translates to the Rhai object map type, `Map`, which used to be an alias to `HashMap` and
is now aliased to `BTreeMap` instead. This change is due to the fact that, in the vast majority of
object map usages, the number of properties held is small.
`HashMap` and `BTreeMap` have almost identical public API's so this change is unlikely to break a
lot of existing code.
Breaking changes Breaking changes
---------------- ----------------
* `Map` is now an alias to `BTreeMap` instead of `HashMap`. This is because most object maps used have few properties.
* The traits `RegisterFn` and `RegisterResultFn` are removed. `Engine::register_fn` and `Engine::register_result_fn` are now implemented directly on `Engine`. * The traits `RegisterFn` and `RegisterResultFn` are removed. `Engine::register_fn` and `Engine::register_result_fn` are now implemented directly on `Engine`.
* `FnPtr::call_dynamic` now takes `&NativeCallContext` instead of consuming it. * `FnPtr::call_dynamic` now takes `&NativeCallContext` instead of consuming it.
* All `Module::set_fn_XXX` methods are removed, in favor of `Module::set_native_fn`. * All `Module::set_fn_XXX` methods are removed, in favor of `Module::set_native_fn`.
@ -16,6 +28,7 @@ Breaking changes
Enhancements Enhancements
------------ ------------
* Replaced most `HashMap` usage with `BTreeMap` for better performance when the number of items is small.
* `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type. * `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type.
* `#[rhai_fn(return_raw)]` can now return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type instead of `Result<Dynamic, Box<EvalAltResult>>`. * `#[rhai_fn(return_raw)]` can now return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type instead of `Result<Dynamic, Box<EvalAltResult>>`.

View File

@ -40,7 +40,7 @@ internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
metadata = ["serde", "serde_json"] # enables exporting functions metadata to JSON metadata = ["serde", "serde_json"] # enables exporting functions metadata to JSON
no_std = ["smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash/compile-time-rng"] no_std = ["smallvec/union", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"]
# compiling for WASM # compiling for WASM
wasm-bindgen = ["instant/wasm-bindgen"] wasm-bindgen = ["instant/wasm-bindgen"]
@ -63,12 +63,6 @@ default_features = false
features = ["alloc"] features = ["alloc"]
optional = true optional = true
[dependencies.hashbrown]
version = "0.11"
default-features = false
features = ["ahash", "nightly", "inline-more"]
optional = true
[dependencies.serde] [dependencies.serde]
version = "1.0" version = "1.0"
default_features = false default_features = false

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::BTreeMap;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
@ -238,8 +238,8 @@ pub fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error>
}) })
} }
let mut renames = HashMap::<String, proc_macro2::Span>::new(); let mut renames = BTreeMap::<String, proc_macro2::Span>::new();
let mut fn_defs = HashMap::<String, proc_macro2::Span>::new(); let mut fn_defs = BTreeMap::<String, proc_macro2::Span>::new();
for item_fn in fns.iter() { for item_fn in fns.iter() {
if !item_fn.params().name.is_empty() || item_fn.params().special != FnSpecialAccess::None { if !item_fn.params().name.is_empty() || item_fn.params().special != FnSpecialAccess::None {

View File

@ -6,6 +6,7 @@ use crate::module::NamespaceRef;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::BTreeMap,
fmt, fmt,
hash::Hash, hash::Hash,
num::NonZeroUsize, num::NonZeroUsize,
@ -15,7 +16,6 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
use crate::token::Token; use crate::token::Token;
use crate::utils::{HashableHashMap, StraightHasherBuilder};
use crate::{ use crate::{
Dynamic, FnNamespace, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT, Dynamic, FnNamespace, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT,
}; };
@ -866,14 +866,7 @@ pub enum Stmt {
/// `if` expr `{` stmt `}` `else` `{` stmt `}` /// `if` expr `{` stmt `}` `else` `{` stmt `}`
If(Expr, Box<(StmtBlock, StmtBlock)>, Position), If(Expr, Box<(StmtBlock, StmtBlock)>, Position),
/// `switch` expr `{` literal or _ `=>` stmt `,` ... `}` /// `switch` expr `{` literal or _ `=>` stmt `,` ... `}`
Switch( Switch(Expr, Box<(BTreeMap<u64, StmtBlock>, StmtBlock)>, Position),
Expr,
Box<(
HashableHashMap<u64, StmtBlock, StraightHasherBuilder>,
StmtBlock,
)>,
Position,
),
/// `while` expr `{` stmt `}` /// `while` expr `{` stmt `}`
While(Expr, Box<StmtBlock>, Position), While(Expr, Box<StmtBlock>, Position),
/// `do` `{` stmt `}` `while`|`until` expr /// `do` `{` stmt `}` `while`|`until` expr
@ -1594,20 +1587,14 @@ impl Expr {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Array(x, _) if self.is_constant() => { Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(crate::stdlib::cmp::max( let mut arr = Array::with_capacity(x.len());
crate::engine::TYPICAL_ARRAY_SIZE,
x.len(),
));
arr.extend(x.iter().map(|v| v.get_constant_value().unwrap())); arr.extend(x.iter().map(|v| v.get_constant_value().unwrap()));
Dynamic(Union::Array(Box::new(arr), AccessMode::ReadOnly)) Dynamic(Union::Array(Box::new(arr), AccessMode::ReadOnly))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::Map(x, _) if self.is_constant() => { Self::Map(x, _) if self.is_constant() => {
let mut map = Map::with_capacity(crate::stdlib::cmp::max( let mut map = Map::new();
crate::engine::TYPICAL_MAP_SIZE,
x.len(),
));
map.extend( map.extend(
x.iter() x.iter()
.map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())), .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())),

View File

@ -61,6 +61,7 @@ fn main() {
let mut engine = Engine::new(); let mut engine = Engine::new();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
{ {
// Set a file module resolver without caching // Set a file module resolver without caching
let mut resolver = rhai::module_resolvers::FileModuleResolver::new(); let mut resolver = rhai::module_resolvers::FileModuleResolver::new();

View File

@ -812,8 +812,8 @@ impl Dynamic {
/// A [`Vec<T>`][Vec] does not get automatically converted to an [`Array`], but will be a generic /// A [`Vec<T>`][Vec] does not get automatically converted to an [`Array`], but will be a generic
/// restricted trait object instead, because [`Vec<T>`][Vec] is not a supported standard type. /// restricted trait object instead, because [`Vec<T>`][Vec] is not a supported standard type.
/// ///
/// Similarly, passing in a [`HashMap<String, T>`][std::collections::HashMap] will not get a [`Map`] /// Similarly, passing in a [`HashMap<String, T>`][std::collections::HashMap] or
/// but a trait object. /// [`BTreeMap<String, T>`][std::collections::BTreeMap] will not get a [`Map`] but a trait object.
/// ///
/// # Examples /// # Examples
/// ///
@ -1696,6 +1696,7 @@ impl<T: Variant + Clone> crate::stdlib::iter::FromIterator<T> for Dynamic {
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_std"))]
impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collections::HashMap<K, T>> impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collections::HashMap<K, T>>
for Dynamic for Dynamic
{ {
@ -1712,6 +1713,23 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collectio
)) ))
} }
} }
#[cfg(not(feature = "no_object"))]
impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collections::BTreeMap<K, T>>
for Dynamic
{
#[inline(always)]
fn from(value: crate::stdlib::collections::BTreeMap<K, T>) -> Self {
Self(Union::Map(
Box::new(
value
.into_iter()
.map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(),
),
AccessMode::ReadWrite,
))
}
}
impl From<FnPtr> for Dynamic { impl From<FnPtr> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FnPtr) -> Self { fn from(value: FnPtr) -> Self {

View File

@ -14,7 +14,7 @@ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::{HashMap, HashSet}, collections::{BTreeMap, BTreeSet},
fmt, format, fmt, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
@ -23,7 +23,7 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
use crate::syntax::CustomSyntax; use crate::syntax::CustomSyntax;
use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::utils::get_hasher;
use crate::{ use crate::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared,
StaticVec, StaticVec,
@ -32,15 +32,9 @@ use crate::{
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::{calc_fn_hash, stdlib::iter::empty, Array}; use crate::{calc_fn_hash, stdlib::iter::empty, Array};
#[cfg(not(feature = "no_index"))]
pub const TYPICAL_ARRAY_SIZE: usize = 8; // Small arrays are typical
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; use crate::Map;
#[cfg(not(feature = "no_object"))]
pub const TYPICAL_MAP_SIZE: usize = 8; // Small maps are typical
pub type Precedence = NonZeroU8; pub type Precedence = NonZeroU8;
/// _(INTERNALS)_ A stack of imported [modules][Module]. /// _(INTERNALS)_ A stack of imported [modules][Module].
@ -501,7 +495,7 @@ pub struct FnResolutionCacheEntry {
} }
/// A function resolution cache. /// A function resolution cache.
pub type FnResolutionCache = HashMap<u64, Option<FnResolutionCacheEntry>, StraightHasherBuilder>; pub type FnResolutionCache = BTreeMap<u64, Option<FnResolutionCacheEntry>>;
/// _(INTERNALS)_ A type that holds all the current states of the [`Engine`]. /// _(INTERNALS)_ A type that holds all the current states of the [`Engine`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -540,9 +534,7 @@ impl State {
/// Get a mutable reference to the current function resolution cache. /// Get a mutable reference to the current function resolution cache.
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution_caches.0.is_empty() { if self.fn_resolution_caches.0.is_empty() {
self.fn_resolution_caches self.fn_resolution_caches.0.push(BTreeMap::new());
.0
.push(HashMap::with_capacity_and_hasher(64, StraightHasherBuilder));
} }
self.fn_resolution_caches.0.last_mut().unwrap() self.fn_resolution_caches.0.last_mut().unwrap()
} }
@ -711,21 +703,21 @@ pub struct Engine {
/// A collection of all modules loaded into the global namespace of the Engine. /// A collection of all modules loaded into the global namespace of the Engine.
pub(crate) global_modules: StaticVec<Shared<Module>>, pub(crate) global_modules: StaticVec<Shared<Module>>,
/// A collection of all sub-modules directly loaded into the Engine. /// A collection of all sub-modules directly loaded into the Engine.
pub(crate) global_sub_modules: HashMap<ImmutableString, Shared<Module>>, pub(crate) global_sub_modules: BTreeMap<ImmutableString, Shared<Module>>,
/// A module resolution service. /// A module resolution service.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>, pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
/// A hashmap mapping type names to pretty-print names. /// A map mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: BTreeMap<String, String>,
/// A hashset containing symbols to disable. /// A set of symbols to disable.
pub(crate) disabled_symbols: HashSet<String>, pub(crate) disabled_symbols: BTreeSet<String>,
/// A hashmap containing custom keywords and precedence to recognize. /// A map containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: HashMap<String, Option<Precedence>>, pub(crate) custom_keywords: BTreeMap<String, Option<Precedence>>,
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>, pub(crate) custom_syntax: BTreeMap<ImmutableString, CustomSyntax>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
pub(crate) resolve_var: Option<OnVarCallback>, pub(crate) resolve_var: Option<OnVarCallback>,
@ -1682,8 +1674,7 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(x, _) => { Expr::Array(x, _) => {
let mut arr = let mut arr = Array::with_capacity(x.len());
Array::with_capacity(crate::stdlib::cmp::max(TYPICAL_ARRAY_SIZE, x.len()));
for item in x.as_ref() { for item in x.as_ref() {
arr.push( arr.push(
self.eval_expr(scope, mods, state, lib, this_ptr, item, level)? self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?
@ -1695,8 +1686,7 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, _) => { Expr::Map(x, _) => {
let mut map = let mut map = Map::new();
Map::with_capacity(crate::stdlib::cmp::max(TYPICAL_MAP_SIZE, x.len()));
for (Ident { name: key, .. }, expr) in x.as_ref() { for (Ident { name: key, .. }, expr) in x.as_ref() {
map.insert( map.insert(
key.clone(), key.clone(),

View File

@ -880,7 +880,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn register_static_module(&mut self, name: &str, module: Shared<Module>) -> &mut Self { pub fn register_static_module(&mut self, name: &str, module: Shared<Module>) -> &mut Self {
fn register_static_module_raw( fn register_static_module_raw(
root: &mut crate::stdlib::collections::HashMap<crate::ImmutableString, Shared<Module>>, root: &mut crate::stdlib::collections::BTreeMap<crate::ImmutableString, Shared<Module>>,
name: &str, name: &str,
module: Shared<Module>, module: Shared<Module>,
) { ) {
@ -1012,14 +1012,14 @@ impl Engine {
ast::{ASTNode, Expr, Stmt}, ast::{ASTNode, Expr, Stmt},
fn_native::shared_take_or_clone, fn_native::shared_take_or_clone,
module::resolvers::StaticModuleResolver, module::resolvers::StaticModuleResolver,
stdlib::collections::HashSet, stdlib::collections::BTreeSet,
ImmutableString, ImmutableString,
}; };
fn collect_imports( fn collect_imports(
ast: &AST, ast: &AST,
resolver: &StaticModuleResolver, resolver: &StaticModuleResolver,
imports: &mut HashSet<ImmutableString>, imports: &mut BTreeSet<ImmutableString>,
) { ) {
ast.walk(&mut |path| match path.last().unwrap() { ast.walk(&mut |path| match path.last().unwrap() {
// Collect all `import` statements with a string constant path // Collect all `import` statements with a string constant path
@ -1035,7 +1035,7 @@ impl Engine {
let mut resolver = StaticModuleResolver::new(); let mut resolver = StaticModuleResolver::new();
let mut ast = self.compile_scripts_with_scope(scope, &[script])?; let mut ast = self.compile_scripts_with_scope(scope, &[script])?;
let mut imports = HashSet::<ImmutableString>::new(); let mut imports = Default::default();
collect_imports(&ast, &mut resolver, &mut imports); collect_imports(&ast, &mut resolver, &mut imports);

View File

@ -164,7 +164,7 @@ pub type Array = stdlib::vec::Vec<Dynamic>;
/// ///
/// Not available under `no_object`. /// Not available under `no_object`.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub type Map = stdlib::collections::HashMap<ImmutableString, Dynamic>; pub type Map = stdlib::collections::BTreeMap<ImmutableString, Dynamic>;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub use module::ModuleResolver; pub use module::ModuleResolver;

View File

@ -7,7 +7,7 @@ use crate::fn_register::RegisterNativeFunction;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
boxed::Box, boxed::Box,
collections::HashMap, collections::BTreeMap,
fmt, format, fmt, format,
iter::empty, iter::empty,
num::NonZeroUsize, num::NonZeroUsize,
@ -16,7 +16,6 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
use crate::token::Token; use crate::token::Token;
use crate::utils::StraightHasherBuilder;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ImmutableString, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ImmutableString,
NativeCallContext, Position, Shared, StaticVec, NativeCallContext, Position, Shared, StaticVec,
@ -130,20 +129,20 @@ pub struct Module {
/// ID identifying the module. /// ID identifying the module.
id: Option<ImmutableString>, id: Option<ImmutableString>,
/// Sub-modules. /// Sub-modules.
modules: HashMap<ImmutableString, Shared<Module>>, modules: BTreeMap<ImmutableString, Shared<Module>>,
/// [`Module`] variables. /// [`Module`] variables.
variables: HashMap<ImmutableString, Dynamic>, variables: BTreeMap<ImmutableString, Dynamic>,
/// Flattened collection of all [`Module`] variables, including those in sub-modules. /// Flattened collection of all [`Module`] variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>, all_variables: BTreeMap<u64, Dynamic>,
/// External Rust functions. /// External Rust functions.
functions: HashMap<u64, Box<FuncInfo>, StraightHasherBuilder>, functions: BTreeMap<u64, Box<FuncInfo>>,
/// Flattened collection of all external Rust functions, native or scripted. /// Flattened collection of all external Rust functions, native or scripted.
/// including those in sub-modules. /// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>, all_functions: BTreeMap<u64, CallableFunction>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: BTreeMap<TypeId, IteratorFn>,
/// Flattened collection of iterator functions, including those in sub-modules. /// Flattened collection of iterator functions, including those in sub-modules.
all_type_iterators: HashMap<TypeId, IteratorFn>, all_type_iterators: BTreeMap<TypeId, IteratorFn>,
/// Is the [`Module`] indexed? /// Is the [`Module`] indexed?
indexed: bool, indexed: bool,
/// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace?
@ -158,8 +157,8 @@ impl Default for Module {
modules: Default::default(), modules: Default::default(),
variables: Default::default(), variables: Default::default(),
all_variables: Default::default(), all_variables: Default::default(),
functions: HashMap::with_capacity_and_hasher(64, StraightHasherBuilder), functions: Default::default(),
all_functions: HashMap::with_capacity_and_hasher(256, StraightHasherBuilder), all_functions: Default::default(),
type_iterators: Default::default(), type_iterators: Default::default(),
all_type_iterators: Default::default(), all_type_iterators: Default::default(),
indexed: false, indexed: false,
@ -270,25 +269,6 @@ impl Module {
Default::default() Default::default()
} }
/// Create a new [`Module`] with a specified capacity for native Rust functions.
///
/// # Example
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ```
#[inline(always)]
pub fn new_with_capacity(capacity: usize) -> Self {
Self {
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
..Default::default()
}
}
/// Get the ID of the [`Module`], if any. /// Get the ID of the [`Module`], if any.
/// ///
/// # Example /// # Example
@ -520,7 +500,7 @@ impl Module {
.map(|f| f.func.get_fn_def()) .map(|f| f.func.get_fn_def())
} }
/// Get a mutable reference to the underlying [`HashMap`] of sub-modules. /// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules.
/// ///
/// # WARNING /// # WARNING
/// ///
@ -528,7 +508,7 @@ impl Module {
/// Thus the [`Module`] is automatically set to be non-indexed. /// Thus the [`Module`] is automatically set to be non-indexed.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub(crate) fn sub_modules_mut(&mut self) -> &mut HashMap<ImmutableString, Shared<Module>> { pub(crate) fn sub_modules_mut(&mut self) -> &mut BTreeMap<ImmutableString, Shared<Module>> {
// We must assume that the user has changed the sub-modules // We must assume that the user has changed the sub-modules
// (otherwise why take a mutable reference?) // (otherwise why take a mutable reference?)
self.all_functions.clear(); self.all_functions.clear();
@ -1235,13 +1215,16 @@ impl Module {
&mut self, &mut self,
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
self.functions.retain(|_, f| { self.functions = crate::stdlib::mem::take(&mut self.functions)
if f.func.is_script() { .into_iter()
filter(f.namespace, f.access, f.name.as_str(), f.params) .filter(|(_, f)| {
} else { if f.func.is_script() {
false filter(f.namespace, f.access, f.name.as_str(), f.params)
} } else {
}); false
}
})
.collect();
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
@ -1460,9 +1443,9 @@ impl Module {
fn index_module<'a>( fn index_module<'a>(
module: &'a Module, module: &'a Module,
path: &mut Vec<&'a str>, path: &mut Vec<&'a str>,
variables: &mut HashMap<u64, Dynamic, StraightHasherBuilder>, variables: &mut BTreeMap<u64, Dynamic>,
functions: &mut HashMap<u64, CallableFunction, StraightHasherBuilder>, functions: &mut BTreeMap<u64, CallableFunction>,
type_iterators: &mut HashMap<TypeId, IteratorFn>, type_iterators: &mut BTreeMap<TypeId, IteratorFn>,
) -> bool { ) -> bool {
let mut contains_indexed_global_functions = false; let mut contains_indexed_global_functions = false;
@ -1518,9 +1501,9 @@ impl Module {
if !self.indexed { if !self.indexed {
let mut path = Vec::with_capacity(4); let mut path = Vec::with_capacity(4);
let mut variables = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut variables = Default::default();
let mut functions = HashMap::with_capacity_and_hasher(256, StraightHasherBuilder); let mut functions = Default::default();
let mut type_iterators = HashMap::with_capacity(16); let mut type_iterators = Default::default();
path.push("root"); path.push("root");

View File

@ -1,6 +1,6 @@
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
collections::HashMap, collections::BTreeMap,
io::Error as IoError, io::Error as IoError,
path::{Path, PathBuf}, path::{Path, PathBuf},
string::String, string::String,
@ -44,9 +44,9 @@ pub struct FileModuleResolver {
cache_enabled: bool, cache_enabled: bool,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>, cache: crate::stdlib::cell::RefCell<BTreeMap<PathBuf, Shared<Module>>>,
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
cache: crate::stdlib::sync::RwLock<HashMap<PathBuf, Shared<Module>>>, cache: crate::stdlib::sync::RwLock<BTreeMap<PathBuf, Shared<Module>>>,
} }
impl Default for FileModuleResolver { impl Default for FileModuleResolver {

View File

@ -1,4 +1,4 @@
use crate::stdlib::{boxed::Box, collections::HashMap, ops::AddAssign, string::String}; use crate::stdlib::{boxed::Box, collections::BTreeMap, ops::AddAssign, string::String};
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// A static [module][Module] resolution service that serves [modules][Module] added into it. /// A static [module][Module] resolution service that serves [modules][Module] added into it.
@ -19,7 +19,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// engine.set_module_resolver(resolver); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct StaticModuleResolver(HashMap<String, Shared<Module>>); pub struct StaticModuleResolver(BTreeMap<String, Shared<Module>>);
impl StaticModuleResolver { impl StaticModuleResolver {
/// Create a new [`StaticModuleResolver`]. /// Create a new [`StaticModuleResolver`].

View File

@ -1,9 +1,9 @@
#![cfg(not(feature = "no_index"))] #![cfg(not(feature = "no_index"))]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::engine::{OP_EQUALS, TYPICAL_ARRAY_SIZE}; use crate::engine::OP_EQUALS;
use crate::plugin::*; use crate::plugin::*;
use crate::stdlib::{any::TypeId, boxed::Box, cmp::max, cmp::Ordering, mem, string::ToString}; use crate::stdlib::{any::TypeId, boxed::Box, cmp::Ordering, mem, string::ToString};
use crate::{def_package, Array, Dynamic, EvalAltResult, FnPtr, NativeCallContext, Position, INT}; use crate::{def_package, Array, Dynamic, EvalAltResult, FnPtr, NativeCallContext, Position, INT};
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
@ -170,7 +170,7 @@ mod array_functions {
array: &mut Array, array: &mut Array,
mapper: FnPtr, mapper: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
let mut ar = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, array.len())); let mut ar = Array::with_capacity(array.len());
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
ar.push( ar.push(
@ -203,7 +203,7 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
let mut ar = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, array.len())); let mut ar = Array::new();
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
if filter if filter
@ -565,7 +565,7 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
let mut drained = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, array.len())); let mut drained = Array::with_capacity(array.len());
let mut i = array.len(); let mut i = array.len();
@ -625,7 +625,7 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
let mut drained = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, array.len())); let mut drained = Array::new();
let mut i = array.len(); let mut i = array.len();

View File

@ -34,34 +34,34 @@ mod fn_ptr_functions {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
use crate::{ast::ScriptFnDef, stdlib::collections::HashMap, Array, Map}; use crate::{ast::ScriptFnDef, stdlib::collections::BTreeSet, Array, Map};
// Create a metadata record for a function. // Create a metadata record for a function.
fn make_metadata( fn make_metadata(
dict: &HashMap<&str, ImmutableString>, dict: &BTreeSet<ImmutableString>,
namespace: Option<ImmutableString>, namespace: Option<ImmutableString>,
f: &ScriptFnDef, f: &ScriptFnDef,
) -> Map { ) -> Map {
let mut map = Map::with_capacity(6); let mut map = Map::new();
if let Some(ns) = namespace { if let Some(ns) = namespace {
map.insert(dict["namespace"].clone(), ns.into()); map.insert(dict.get("namespace").unwrap().clone(), ns.into());
} }
map.insert(dict["name"].clone(), f.name.clone().into()); map.insert(dict.get("name").unwrap().clone(), f.name.clone().into());
map.insert( map.insert(
dict["access"].clone(), dict.get("access").unwrap().clone(),
match f.access { match f.access {
FnAccess::Public => dict["public"].clone(), FnAccess::Public => dict.get("public").unwrap().clone(),
FnAccess::Private => dict["private"].clone(), FnAccess::Private => dict.get("private").unwrap().clone(),
} }
.into(), .into(),
); );
map.insert( map.insert(
dict["is_anonymous"].clone(), dict.get("is_anonymous").unwrap().clone(),
f.name.starts_with(crate::engine::FN_ANONYMOUS).into(), f.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
); );
map.insert( map.insert(
dict["params"].clone(), dict.get("params").unwrap().clone(),
f.params f.params
.iter() .iter()
.cloned() .cloned()
@ -74,8 +74,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
} }
// Intern strings // Intern strings
let mut dict = HashMap::<&str, ImmutableString>::with_capacity(8); let dict: BTreeSet<ImmutableString> = [
[
"namespace", "namespace",
"name", "name",
"access", "access",
@ -85,9 +84,8 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
"params", "params",
] ]
.iter() .iter()
.for_each(|&s| { .map(|&s| s.into())
dict.insert(s, s.into()); .collect();
});
let mut list: Array = Default::default(); let mut list: Array = Default::default();
@ -100,7 +98,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
// Recursively scan modules for script-defined functions. // Recursively scan modules for script-defined functions.
fn scan_module( fn scan_module(
list: &mut Array, list: &mut Array,
dict: &HashMap<&str, ImmutableString>, dict: &BTreeSet<ImmutableString>,
namespace: ImmutableString, namespace: ImmutableString,
module: &Module, module: &Module,
) { ) {

View File

@ -89,7 +89,7 @@ macro_rules! def_package {
impl $package { impl $package {
pub fn new() -> Self { pub fn new() -> Self {
let mut module = $root::Module::new_with_capacity(1024); let mut module = $root::Module::new();
<Self as $root::packages::Package>::init(&mut module); <Self as $root::packages::Package>::init(&mut module);
module.build_index(); module.build_index();
Self(module.into()) Self(module.into())

View File

@ -12,7 +12,7 @@ use crate::optimize::OptimizationLevel;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::HashMap, collections::BTreeMap,
format, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty, iter::empty,
@ -23,7 +23,7 @@ use crate::stdlib::{
}; };
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream};
use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::utils::get_hasher;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position, calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position,
Scope, Shared, StaticVec, AST, Scope, Shared, StaticVec, AST,
@ -37,7 +37,7 @@ use crate::FnAccess;
type PERR = ParseErrorType; type PERR = ParseErrorType;
type FunctionsLib = HashMap<u64, Shared<ScriptFnDef>, StraightHasherBuilder>; type FunctionsLib = BTreeMap<u64, Shared<ScriptFnDef>>;
/// A type that encapsulates the current state of the parser. /// A type that encapsulates the current state of the parser.
#[derive(Debug)] #[derive(Debug)]
@ -45,14 +45,14 @@ struct ParseState<'e> {
/// Reference to the scripting [`Engine`]. /// Reference to the scripting [`Engine`].
engine: &'e Engine, engine: &'e Engine,
/// Interned strings. /// Interned strings.
strings: HashMap<String, ImmutableString>, interned_strings: BTreeMap<String, ImmutableString>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(ImmutableString, AccessMode)>, stack: Vec<(ImmutableString, AccessMode)>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
entry_stack_len: usize, entry_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: HashMap<ImmutableString, Position>, external_vars: BTreeMap<ImmutableString, Position>,
/// An indicator that disables variable capturing into externals one single time /// An indicator that disables variable capturing into externals one single time
/// up until the nearest consumed Identifier token. /// up until the nearest consumed Identifier token.
/// If set to false the next call to `access_var` will not capture the variable. /// If set to false the next call to `access_var` will not capture the variable.
@ -89,10 +89,10 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
max_function_expr_depth, max_function_expr_depth,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), external_vars: Default::default(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
strings: HashMap::with_capacity(64), interned_strings: Default::default(),
stack: Vec::with_capacity(16), stack: Vec::with_capacity(16),
entry_stack_len: 0, entry_stack_len: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -130,8 +130,8 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
if self.allow_capture { if self.allow_capture {
if index.is_none() && !self.externals.contains_key(name) { if index.is_none() && !self.external_vars.contains_key(name) {
self.externals.insert(name.into(), _pos); self.external_vars.insert(name.into(), _pos);
} }
} else { } else {
self.allow_capture = true self.allow_capture = true
@ -171,12 +171,13 @@ impl<'e> ParseState<'e> {
text: impl AsRef<str> + Into<ImmutableString>, text: impl AsRef<str> + Into<ImmutableString>,
) -> ImmutableString { ) -> ImmutableString {
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
if !self.strings.contains_key(text.as_ref()) { if !self.interned_strings.contains_key(text.as_ref()) {
let value = text.into(); let value = text.into();
self.strings.insert(value.clone().into(), value.clone()); self.interned_strings
.insert(value.clone().into(), value.clone());
value value
} else { } else {
self.strings.get(text.as_ref()).unwrap().clone() self.interned_strings.get(text.as_ref()).unwrap().clone()
} }
} }
} }
@ -813,7 +814,7 @@ fn parse_switch(
} }
} }
let mut table = HashMap::<u64, StmtBlock>::new(); let mut table = BTreeMap::<u64, StmtBlock>::new();
let mut def_stmt = None; let mut def_stmt = None;
loop { loop {
@ -902,13 +903,10 @@ fn parse_switch(
} }
} }
let mut final_table = HashMap::with_capacity_and_hasher(table.len(), StraightHasherBuilder);
final_table.extend(table.into_iter());
Ok(Stmt::Switch( Ok(Stmt::Switch(
item, item,
Box::new(( Box::new((
final_table.into(), table,
def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()), def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()),
)), )),
settings.pos, settings.pos,
@ -1003,7 +1001,7 @@ fn parse_primary(
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
new_state.externals.iter().for_each(|(closure, pos)| { new_state.external_vars.iter().for_each(|(closure, pos)| {
state.access_var(closure, *pos); state.access_var(closure, *pos);
}); });
@ -2739,7 +2737,7 @@ fn parse_fn(
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let externals = state let externals = state
.externals .external_vars
.iter() .iter()
.map(|(name, _)| name) .map(|(name, _)| name)
.filter(|name| !params.contains(name)) .filter(|name| !params.contains(name))
@ -2860,7 +2858,7 @@ fn parse_anon_fn(
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
{ {
state state
.externals .external_vars
.iter() .iter()
.map(|(name, &pos)| Ident { .map(|(name, &pos)| Ident {
name: name.clone(), name: name.clone(),
@ -2966,7 +2964,7 @@ impl Engine {
input: &mut TokenStream, input: &mut TokenStream,
) -> Result<(Vec<Stmt>, Vec<Shared<ScriptFnDef>>), ParseError> { ) -> Result<(Vec<Stmt>, Vec<Shared<ScriptFnDef>>), ParseError> {
let mut statements = Vec::with_capacity(16); let mut statements = Vec::with_capacity(16);
let mut functions = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut functions = BTreeMap::new();
let mut state = ParseState::new( let mut state = ParseState::new(
self, self,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]

View File

@ -390,7 +390,7 @@ impl Serializer for &mut DynamicSerializer {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
return Ok(StructVariantSerializer { return Ok(StructVariantSerializer {
variant: _variant, variant: _variant,
map: Map::with_capacity(_len), map: Default::default(),
}); });
#[cfg(feature = "no_object")] #[cfg(feature = "no_object")]
return EvalAltResult::ErrorMismatchDataType( return EvalAltResult::ErrorMismatchDataType(
@ -691,7 +691,7 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn make_variant(variant: &'static str, value: Dynamic) -> RhaiResult { fn make_variant(variant: &'static str, value: Dynamic) -> RhaiResult {
let mut map = Map::with_capacity(1); let mut map = Map::new();
map.insert(variant.into(), value); map.insert(variant.into(), value);
Ok(map.into()) Ok(map.into())
} }

View File

@ -19,7 +19,8 @@ mod inner {
pub use core_error as error; pub use core_error as error;
pub mod collections { pub mod collections {
pub use hashbrown::{hash_map, hash_set, HashMap, HashSet}; pub use alloc::collections::btree_map::BTreeMap;
pub use alloc::collections::btree_set::BTreeSet;
} }
} }

View File

@ -6,15 +6,13 @@ use crate::stdlib::{
borrow::Borrow, borrow::Borrow,
boxed::Box, boxed::Box,
cmp::Ordering, cmp::Ordering,
collections::HashMap,
fmt, fmt,
fmt::{Debug, Display}, fmt::{Debug, Display},
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::FromIterator, iter::FromIterator,
ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}, ops::{Add, AddAssign, Deref, Sub, SubAssign},
str::FromStr, str::FromStr,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
use crate::Shared; use crate::Shared;
@ -106,62 +104,6 @@ pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 {
a ^ b a ^ b
} }
/// _(INTERNALS)_ A type that wraps a [`HashMap`] and implements [`Hash`].
/// Exported under the `internals` feature only.
#[derive(Clone, Default)]
pub struct HashableHashMap<K, T, H: BuildHasher>(HashMap<K, T, H>);
impl<K, T, H: BuildHasher> From<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
#[inline(always)]
fn from(value: HashMap<K, T, H>) -> Self {
Self(value)
}
}
impl<K, T, H: BuildHasher> AsRef<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
#[inline(always)]
fn as_ref(&self) -> &HashMap<K, T, H> {
&self.0
}
}
impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
#[inline(always)]
fn as_mut(&mut self) -> &mut HashMap<K, T, H> {
&mut self.0
}
}
impl<K, T, H: BuildHasher> Deref for HashableHashMap<K, T, H> {
type Target = HashMap<K, T, H>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, T, H: BuildHasher> DerefMut for HashableHashMap<K, T, H> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<K: Debug, T: Debug, H: BuildHasher> Debug for HashableHashMap<K, T, H> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<K: Hash + Ord, T: Hash, H: BuildHasher> Hash for HashableHashMap<K, T, H> {
#[inline(always)]
fn hash<B: Hasher>(&self, state: &mut B) {
let mut keys: Vec<_> = self.0.keys().collect();
keys.sort();
keys.into_iter().for_each(|key| {
key.hash(state);
self.0.get(&key).unwrap().hash(state);
});
}
}
/// The system immutable string type. /// The system immutable string type.
/// ///
/// An [`ImmutableString`] wraps an [`Rc`][std::rc::Rc]`<`[`String`]`>` /// An [`ImmutableString`] wraps an [`Rc`][std::rc::Rc]`<`[`String`]`>`