From 8d1310c0f30141246ecd21acaaa375d994801da9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 26 Sep 2022 18:14:45 +0800 Subject: [PATCH] Make Scope serializable. --- CHANGELOG.md | 4 ++ src/serde/de.rs | 86 +++++++++++++++++++--------------------- src/serde/deserialize.rs | 81 +++++++++++++++++++++++++++++++------ src/serde/serialize.rs | 34 +++++++++++++++- src/types/scope.rs | 32 ++++++++++++++- tests/serde.rs | 33 ++++++++++++++- 6 files changed, 208 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a057eebe..d95bc3d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,12 @@ Version 1.11.0 ============== Enhancements +------------ * The look-ahead symbol for custom syntax now renders a string literal in quotes (instead of the generic term `string`). This facilitates more accurate parsing by separating strings and identifiers. +* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. +* `Scope` is now serializable and deserializable via `serde`. +* `Scope` now contains a const generic parameter that allows specifying how many entries to be kept inline. Version 1.10.1 diff --git a/src/serde/de.rs b/src/serde/de.rs index dbf43605..8f4cd237 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -9,9 +9,7 @@ use std::prelude::v1::*; use std::{any::type_name, fmt}; /// Deserializer for [`Dynamic`][crate::Dynamic]. -pub struct DynamicDeserializer<'de> { - value: &'de Dynamic, -} +pub struct DynamicDeserializer<'de>(&'de Dynamic); impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { type Deserializer = DynamicDeserializer<'de>; @@ -19,7 +17,7 @@ impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { #[inline(always)] #[must_use] fn into_deserializer(self) -> Self::Deserializer { - DynamicDeserializer { value: self } + DynamicDeserializer(self) } } @@ -31,7 +29,7 @@ impl<'de> DynamicDeserializer<'de> { #[inline(always)] #[must_use] pub const fn new(value: &'de Dynamic) -> Self { - Self { value } + Self(value) } /// Shortcut for a type conversion error. #[cold] @@ -43,12 +41,10 @@ impl<'de> DynamicDeserializer<'de> { #[cold] #[inline(never)] fn type_error_str(&self, error: &str) -> RhaiResultOf { - Err(ERR::ErrorMismatchOutputType( - error.into(), - self.value.type_name().into(), - Position::NONE, + Err( + ERR::ErrorMismatchOutputType(error.into(), self.0.type_name().into(), Position::NONE) + .into(), ) - .into()) } #[inline(always)] fn deserialize_int>(self, v: crate::INT, visitor: V) -> RhaiResultOf { @@ -128,7 +124,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { type Error = RhaiError; fn deserialize_any>(self, visitor: V) -> RhaiResultOf { - match self.value.0 { + match self.0 .0 { Union::Unit(..) => self.deserialize_unit(visitor), Union::Bool(..) => self.deserialize_bool(visitor), Union::Str(..) => self.deserialize_str(visitor), @@ -182,110 +178,110 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_bool>(self, visitor: V) -> RhaiResultOf { - visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?) + visitor.visit_bool(self.0.as_bool().or_else(|_| self.type_error())?) } fn deserialize_i8>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)) } } fn deserialize_i16>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)) } } fn deserialize_i32>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(feature = "only_i32") { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)) } } fn deserialize_i64>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)) } } fn deserialize_i128>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i128(x)) } } fn deserialize_u8>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)) } } fn deserialize_u16>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)) } } fn deserialize_u32>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)) } } fn deserialize_u64>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x)) } } fn deserialize_u128>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u128(x)) } @@ -294,7 +290,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_f32>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); @@ -304,7 +300,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { use rust_decimal::prelude::ToPrimitive; return self - .value + .0 .downcast_ref::() .and_then(|&x| x.to_f32()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v)); @@ -318,7 +314,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_f64>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); @@ -328,7 +324,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { use rust_decimal::prelude::ToPrimitive; return self - .value + .0 .downcast_ref::() .and_then(|&x| x.to_f64()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v)); @@ -340,13 +336,13 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_char>(self, visitor: V) -> RhaiResultOf { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_char(x)) } fn deserialize_str>(self, visitor: V) -> RhaiResultOf { - self.value.downcast_ref::().map_or_else( + self.0.downcast_ref::().map_or_else( || self.type_error(), |x| visitor.visit_borrowed_str(x.as_str()), ) @@ -359,7 +355,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_bytes>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |x| _visitor.visit_bytes(x)); @@ -372,7 +368,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_option>(self, visitor: V) -> RhaiResultOf { - if self.value.is::<()>() { + if self.0.is::<()>() { visitor.visit_none() } else { visitor.visit_some(self) @@ -380,7 +376,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_unit>(self, visitor: V) -> RhaiResultOf { - self.value + self.0 .downcast_ref::<()>() .map_or_else(|| self.type_error(), |_| visitor.visit_unit()) } @@ -403,7 +399,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_seq>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |arr| _visitor.visit_seq(IterateDynamicArray::new(arr.iter())), ); @@ -427,7 +423,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_map>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { _visitor.visit_map(IterateMap::new( @@ -456,11 +452,11 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { _variants: &'static [&'static str], visitor: V, ) -> RhaiResultOf { - if let Some(s) = self.value.read_lock::() { + if let Some(s) = self.0.read_lock::() { visitor.visit_enum(s.as_str().into_deserializer()) } else { #[cfg(not(feature = "no_object"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { let mut iter = map.iter(); diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index a41f8882..e6c5ae3a 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -1,14 +1,17 @@ //! Implementations of [`serde::Deserialize`]. -use crate::{Dynamic, ImmutableString, INT}; -use serde::de::{Deserialize, Deserializer, Error, Visitor}; +use crate::{Dynamic, Identifier, ImmutableString, Scope, INT}; +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, +}; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; struct DynamicVisitor; -impl<'d> Visitor<'d> for DynamicVisitor { +impl<'de> Visitor<'de> for DynamicVisitor { type Value = Dynamic; #[cold] @@ -145,12 +148,12 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[inline(always)] - fn visit_newtype_struct>(self, de: D) -> Result { + fn visit_newtype_struct>(self, de: D) -> Result { Deserialize::deserialize(de) } #[cfg(not(feature = "no_index"))] - fn visit_seq>(self, mut seq: A) -> Result { + fn visit_seq>(self, mut seq: A) -> Result { let mut arr = crate::Array::new(); while let Some(v) = seq.next_element()? { @@ -161,7 +164,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(not(feature = "no_object"))] - fn visit_map>(self, mut map: M) -> Result { + fn visit_map>(self, mut map: M) -> Result { let mut m = crate::Map::new(); while let Some((k, v)) = map.next_entry()? { @@ -172,17 +175,69 @@ impl<'d> Visitor<'d> for DynamicVisitor { } } -impl<'d> Deserialize<'d> for Dynamic { +impl<'de> Deserialize<'de> for Dynamic { #[inline(always)] - fn deserialize>(de: D) -> Result { - de.deserialize_any(DynamicVisitor) + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(DynamicVisitor) } } -impl<'d> Deserialize<'d> for ImmutableString { - #[inline] - fn deserialize>(de: D) -> Result { - let s: String = Deserialize::deserialize(de)?; +impl<'de> Deserialize<'de> for ImmutableString { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + let s: String = Deserialize::deserialize(deserializer)?; Ok(s.into()) } } + +impl<'de> Deserialize<'de> for Scope<'de> { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + #[derive(Debug, Clone, Hash, Deserialize)] + struct ScopeEntry { + pub name: Identifier, + pub value: Dynamic, + #[serde(default)] + pub is_constant: bool, + } + + struct VecVisitor; + + impl<'de> Visitor<'de> for VecVisitor { + type Value = Scope<'static>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + #[inline] + fn visit_seq(self, mut access: A) -> Result + where + A: SeqAccess<'de>, + { + let mut scope = if let Some(size) = access.size_hint() { + Scope::with_capacity(size) + } else { + Scope::new() + }; + + while let Some(ScopeEntry { + name, + value, + is_constant, + }) = access.next_element()? + { + if is_constant { + scope.push_constant_dynamic(name, value); + } else { + scope.push_dynamic(name, value); + } + } + + Ok(scope) + } + } + + deserializer.deserialize_seq(VecVisitor) + } +} diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 4aaa797e..818b0e9e 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -1,8 +1,8 @@ //! Implementations of [`serde::Serialize`]. use crate::types::dynamic::Union; -use crate::{Dynamic, ImmutableString}; -use serde::ser::{Serialize, Serializer}; +use crate::{Dynamic, ImmutableString, Scope}; +use serde::{ser::SerializeSeq, Serialize, Serializer}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -88,3 +88,33 @@ impl Serialize for ImmutableString { ser.serialize_str(self.as_str()) } } + +impl Serialize for Scope<'_> { + #[inline(always)] + fn serialize(&self, ser: S) -> Result { + #[derive(Debug, Clone, Hash, Serialize)] + struct ScopeEntry<'a> { + pub name: &'a str, + pub value: &'a Dynamic, + #[serde(default, skip_serializing_if = "is_false")] + pub is_constant: bool, + } + + fn is_false(value: &bool) -> bool { + !value + } + + let mut ser = ser.serialize_seq(Some(self.len()))?; + + for (name, is_constant, value) in self.iter_raw() { + let entry = ScopeEntry { + name, + value, + is_constant, + }; + ser.serialize_element(&entry)?; + } + + ser.end() + } +} diff --git a/src/types/scope.rs b/src/types/scope.rs index 46dd1d1f..4cc5a91c 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -22,6 +22,14 @@ const SCOPE_ENTRIES_INLINED: usize = 8; /// Currently the lifetime parameter is not used, but it is not guaranteed to remain unused for /// future versions. Until then, `'static` can be used. /// +/// # Constant Generic Parameter +/// +/// There is a constant generic parameter that indicates how many entries to keep inline. +/// As long as the number of entries does not exceed this limit, no allocations occur. +/// The default is 8. +/// +/// A larger value makes [`Scope`] larger, but reduces the chance of allocations. +/// /// # Thread Safety /// /// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it @@ -61,7 +69,7 @@ const SCOPE_ENTRIES_INLINED: usize = 8; // // [`Dynamic`] is reasonably small so packing it tightly improves cache performance. #[derive(Debug, Hash, Default)] -pub struct Scope<'a> { +pub struct Scope<'a, const N: usize = SCOPE_ENTRIES_INLINED> { /// Current value of the entry. values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, /// Name of the entry. @@ -170,6 +178,28 @@ impl Scope<'_> { dummy: PhantomData, } } + /// Create a new [`Scope`] with a particular capacity. + /// + /// # Example + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::with_capacity(10); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); + /// ``` + #[inline(always)] + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + values: SmallVec::with_capacity(capacity), + names: SmallVec::with_capacity(capacity), + aliases: SmallVec::with_capacity(capacity), + dummy: PhantomData, + } + } /// Empty the [`Scope`]. /// /// # Example diff --git a/tests/serde.rs b/tests/serde.rs index 45c16b72..4a08bb81 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -2,7 +2,7 @@ use rhai::{ serde::{from_dynamic, to_dynamic}, - Dynamic, Engine, EvalAltResult, ImmutableString, INT, + Dynamic, Engine, EvalAltResult, ImmutableString, Scope, INT, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -823,3 +823,34 @@ fn test_serde_json_borrowed_string() { println!("value: {value:?}"); let _: Dynamic = serde_json::from_value(value).unwrap(); } + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_scope() { + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + struct TestStruct { + foo: Option, + } + + let mut scope = Scope::new(); + scope.push("x", 42 as INT); + scope.push_constant("y", true); + scope.push("z", TestStruct { foo: None }); + + let json = serde_json::to_string(&scope).unwrap(); + + assert_eq!( + json, + r#"[{"name":"x","value":42},{"name":"y","value":true,"is_constant":true},{"name":"z","value":"serde::test_serde_scope::TestStruct"}]"# + ); + + scope = serde_json::from_str(&json).unwrap(); + + assert_eq!(scope.len(), 3); + assert_eq!(scope.get_value::("x").unwrap(), 42); + assert_eq!(scope.get_value::("y").unwrap(), true); + assert_eq!( + scope.get_value::("z").unwrap(), + "serde::test_serde_scope::TestStruct" + ); +}