From 1257a0d102a8030f98b0636c013ece91e9e05a75 Mon Sep 17 00:00:00 2001 From: Micah Date: Fri, 10 Nov 2023 09:51:12 -0800 Subject: [PATCH 1/4] Allow a database to be specified in both rbx_xml and rbx_binary (#375) Also, use `'dom` and `'db` lifetimes consistently --- rbx_binary/CHANGELOG.md | 3 ++ rbx_binary/src/core.rs | 22 ++++++------- rbx_binary/src/deserializer/mod.rs | 24 +++++++++++--- rbx_binary/src/deserializer/state.rs | 16 +++++----- rbx_binary/src/serializer/mod.rs | 31 ++++++++++++++---- rbx_binary/src/serializer/state.rs | 47 ++++++++++++++++------------ rbx_xml/CHANGELOG.md | 3 ++ rbx_xml/src/core.rs | 30 +++++++++--------- rbx_xml/src/deserializer.rs | 41 +++++++++++++++++------- rbx_xml/src/lib.rs | 10 ++++-- rbx_xml/src/serializer.rs | 44 +++++++++++++++++--------- 11 files changed, 178 insertions(+), 93 deletions(-) diff --git a/rbx_binary/CHANGELOG.md b/rbx_binary/CHANGELOG.md index cfe41e98..5cfbf8cb 100644 --- a/rbx_binary/CHANGELOG.md +++ b/rbx_binary/CHANGELOG.md @@ -1,6 +1,9 @@ # rbx_binary Changelog ## Unreleased +* Add the ability to specify a `ReflectionDatabase` to use for serializing and deserializing. This takes the form of `Deserializer::reflection_database` and `Serializer::reflection_database`. ([#375]) + +[#375]: https://github.com/rojo-rbx/rbx-dom/pull/375 ## 0.7.3 (2023-10-23) * Fixed missing fallback default for `SecurityCapabilities` ([#371]). diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index e39db059..8ea8b836 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -336,18 +336,18 @@ pub fn untransform_i64(value: i64) -> i64 { ((value as u64) >> 1) as i64 ^ -(value & 1) } -pub struct PropertyDescriptors<'a> { - pub canonical: &'a PropertyDescriptor<'a>, - pub serialized: Option<&'a PropertyDescriptor<'a>>, +pub struct PropertyDescriptors<'db> { + pub canonical: &'db PropertyDescriptor<'db>, + pub serialized: Option<&'db PropertyDescriptor<'db>>, } /// Find both the canonical and serialized property descriptors for a given /// class and property name pair. These might be the same descriptor! -pub fn find_property_descriptors<'a>( - database: &'a ReflectionDatabase<'a>, +pub fn find_property_descriptors<'db>( + database: &'db ReflectionDatabase<'db>, class_name: &str, property_name: &str, -) -> Option> { +) -> Option> { let mut class_descriptor = database.classes.get(class_name)?; // We need to find the canonical property descriptor associated with @@ -437,11 +437,11 @@ pub fn find_property_descriptors<'a>( /// Given the canonical property descriptor for a logical property along with /// its serialization, returns the serialized form of the logical property if /// this property is serializable. -fn find_serialized_from_canonical<'a>( - class: &'a ClassDescriptor<'a>, - canonical: &'a PropertyDescriptor<'a>, - serialization: &'a PropertySerialization<'a>, -) -> Option<&'a PropertyDescriptor<'a>> { +fn find_serialized_from_canonical<'db>( + class: &'db ClassDescriptor<'db>, + canonical: &'db PropertyDescriptor<'db>, + serialization: &'db PropertySerialization<'db>, +) -> Option<&'db PropertyDescriptor<'db>> { match serialization { // This property serializes as-is. This is the happiest path: both the // canonical and serialized descriptors are the same! diff --git a/rbx_binary/src/deserializer/mod.rs b/rbx_binary/src/deserializer/mod.rs index d8a77f9c..4ff1d4d8 100644 --- a/rbx_binary/src/deserializer/mod.rs +++ b/rbx_binary/src/deserializer/mod.rs @@ -38,18 +38,32 @@ pub use self::error::Error; /// /// # Ok::<(), Box>(()) /// ``` -pub struct Deserializer<'a> { - database: Option<&'a ReflectionDatabase<'a>>, +/// +/// ## Configuration +/// +/// A custom [`ReflectionDatabase`][ReflectionDatabase] can be specified via +/// [`reflection_database`][reflection_database]. +/// +/// [ReflectionDatabase]: rbx_reflection::ReflectionDatabase +/// [reflection_database]: Deserializer#method.reflection_database +pub struct Deserializer<'db> { + database: &'db ReflectionDatabase<'db>, } -impl<'a> Deserializer<'a> { +impl<'db> Deserializer<'db> { /// Create a new `Deserializer` with the default settings. pub fn new() -> Self { Self { - database: Some(rbx_reflection_database::get()), + database: rbx_reflection_database::get(), } } + /// Sets what reflection database for the deserializer to use. + #[inline] + pub fn reflection_database(self, database: &'db ReflectionDatabase<'db>) -> Self { + Self { database } + } + /// Deserialize a Roblox binary model or place from the given stream using /// this deserializer. pub fn deserialize(&self, reader: R) -> Result { @@ -81,7 +95,7 @@ impl<'a> Deserializer<'a> { } } -impl<'a> Default for Deserializer<'a> { +impl<'db> Default for Deserializer<'db> { fn default() -> Self { Self::new() } diff --git a/rbx_binary/src/deserializer/state.rs b/rbx_binary/src/deserializer/state.rs index 5caf0be2..c2876606 100644 --- a/rbx_binary/src/deserializer/state.rs +++ b/rbx_binary/src/deserializer/state.rs @@ -24,9 +24,9 @@ use crate::{ use super::{error::InnerError, header::FileHeader, Deserializer}; -pub(super) struct DeserializerState<'a, R> { +pub(super) struct DeserializerState<'db, R> { /// The user-provided configuration that we should use. - deserializer: &'a Deserializer<'a>, + deserializer: &'db Deserializer<'db>, /// The input data encoded as a binary model. input: R, @@ -90,10 +90,10 @@ struct Instance { /// contains a migration for some properties Roblox has replaced with /// others (like Font, which has been superceded by FontFace). #[derive(Debug)] -struct CanonicalProperty<'a> { - name: &'a str, +struct CanonicalProperty<'db> { + name: &'db str, ty: VariantType, - migration: Option<&'a PropertySerialization<'a>>, + migration: Option<&'db PropertySerialization<'db>>, } fn find_canonical_property<'de>( @@ -210,9 +210,9 @@ fn add_property(instance: &mut Instance, canonical_property: &CanonicalProperty, } } -impl<'a, R: Read> DeserializerState<'a, R> { +impl<'db, R: Read> DeserializerState<'db, R> { pub(super) fn new( - deserializer: &'a Deserializer<'a>, + deserializer: &'db Deserializer<'db>, mut input: R, ) -> Result { let tree = WeakDom::new(InstanceBuilder::new("DataModel")); @@ -382,7 +382,7 @@ impl<'a, R: Read> DeserializerState<'a, R> { } let property = if let Some(property) = find_canonical_property( - self.deserializer.database.unwrap(), + self.deserializer.database, binary_type, &type_info.type_name, &prop_name, diff --git a/rbx_binary/src/serializer/mod.rs b/rbx_binary/src/serializer/mod.rs index b5d4937c..7a2ea0e3 100644 --- a/rbx_binary/src/serializer/mod.rs +++ b/rbx_binary/src/serializer/mod.rs @@ -4,6 +4,7 @@ mod state; use std::io::Write; use rbx_dom_weak::{types::Ref, WeakDom}; +use rbx_reflection::ReflectionDatabase; use self::state::SerializerState; @@ -27,16 +28,34 @@ pub use self::error::Error; /// /// # Ok::<(), Box>(()) /// ``` +/// +/// ## Configuration +/// +/// A custom [`ReflectionDatabase`][ReflectionDatabase] can be specified via +/// [`reflection_database`][reflection_database]. +/// +/// [ReflectionDatabase]: rbx_reflection::ReflectionDatabase +/// [reflection_database]: Serializer#method.reflection_database +// // future settings: -// * reflection_database: Option = default // * recursive: bool = true #[non_exhaustive] -pub struct Serializer {} +pub struct Serializer<'db> { + database: &'db ReflectionDatabase<'db>, +} -impl Serializer { +impl<'db> Serializer<'db> { /// Create a new `Serializer` with the default settings. pub fn new() -> Self { - Serializer {} + Serializer { + database: rbx_reflection_database::get(), + } + } + + /// Sets what reflection database for the serializer to use. + #[inline] + pub fn reflection_database(self, database: &'db ReflectionDatabase<'db>) -> Self { + Self { database } } /// Serialize a Roblox binary model or place into the given stream using @@ -44,7 +63,7 @@ impl Serializer { pub fn serialize(&self, writer: W, dom: &WeakDom, refs: &[Ref]) -> Result<(), Error> { profiling::scope!("rbx_binary::seserialize"); - let mut serializer = SerializerState::new(dom, writer); + let mut serializer = SerializerState::new(self, dom, writer); serializer.add_instances(refs)?; serializer.generate_referents(); @@ -60,7 +79,7 @@ impl Serializer { } } -impl Default for Serializer { +impl<'db> Default for Serializer<'db> { fn default() -> Self { Self::new() } diff --git a/rbx_binary/src/serializer/state.rs b/rbx_binary/src/serializer/state.rs index aa6c4bfe..5a70dd63 100644 --- a/rbx_binary/src/serializer/state.rs +++ b/rbx_binary/src/serializer/state.rs @@ -19,6 +19,7 @@ use rbx_dom_weak::{ use rbx_reflection::{ ClassDescriptor, ClassTag, DataType, PropertyKind, PropertyMigration, PropertySerialization, + ReflectionDatabase, }; use crate::{ @@ -27,6 +28,7 @@ use crate::{ find_property_descriptors, RbxWriteExt, FILE_MAGIC_HEADER, FILE_SIGNATURE, FILE_VERSION, }, types::Type, + Serializer, }; use super::error::InnerError; @@ -36,7 +38,9 @@ static FILE_FOOTER: &[u8] = b""; /// Represents all of the state during a single serialization session. A new /// `BinarySerializer` object should be created every time we want to serialize /// a binary model file. -pub(super) struct SerializerState<'dom, W> { +pub(super) struct SerializerState<'dom, 'db, W> { + serializer: &'db Serializer<'db>, + /// The dom containing all of the instances that we're serializing. dom: &'dom WeakDom, @@ -53,7 +57,7 @@ pub(super) struct SerializerState<'dom, W> { /// All of the types of instance discovered by our serializer that we'll be /// writing into the output. - type_infos: TypeInfos<'dom>, + type_infos: TypeInfos<'dom, 'db>, /// All of the SharedStrings in the DOM, in the order they'll be written // in. @@ -67,7 +71,7 @@ pub(super) struct SerializerState<'dom, W> { /// An instance class that our serializer knows about. We should have one struct /// per unique ClassName. #[derive(Debug)] -struct TypeInfo<'dom> { +struct TypeInfo<'dom, 'db> { /// The ID that this serializer will use to refer to this type of instance. type_id: u32, @@ -85,16 +89,16 @@ struct TypeInfo<'dom> { /// /// Stored in a sorted map to try to ensure that we write out properties in /// a deterministic order. - properties: BTreeMap, PropInfo>, + properties: BTreeMap, PropInfo<'db>>, /// A reference to the type's class descriptor from rbx_reflection, if this /// is a known class. - class_descriptor: Option<&'static ClassDescriptor<'static>>, + class_descriptor: Option<&'db ClassDescriptor<'db>>, /// A set containing the properties that we have seen so far in the file and /// processed. This helps us avoid traversing the reflection database /// multiple times if there are many copies of the same kind of instance. - properties_visited: HashSet<(Cow<'static, str>, VariantType)>, + properties_visited: HashSet<(Cow<'db, str>, VariantType)>, } /// A property on a specific class that our serializer knows about. @@ -104,7 +108,7 @@ struct TypeInfo<'dom> { /// `BasePart.size` are present in the same document, they should share a /// `PropInfo` as they are the same logical property. #[derive(Debug)] -struct PropInfo { +struct PropInfo<'db> { /// The binary format type ID that will be use to serialize this property. /// This type is related to the type of the serialized form of the logical /// property, but is not 1:1. @@ -117,7 +121,7 @@ struct PropInfo { /// The serialized name for this property. This is the name that is actually /// written as part of the PROP chunk and may not line up with the canonical /// name for the property. - serialized_name: Cow<'static, str>, + serialized_name: Cow<'db, str>, /// A set containing the names of all aliases discovered while preparing to /// serialize this property. Ideally, this set will remain empty (and not @@ -136,34 +140,36 @@ struct PropInfo { /// /// Default values are first populated from the reflection database, if /// present, followed by an educated guess based on the type of the value. - default_value: Cow<'static, Variant>, + default_value: Cow<'db, Variant>, /// If a logical property has a migration associated with it (i.e. BrickColor -> /// Color, Font -> FontFace), this field contains Some(PropertyMigration). Otherwise, /// it is None. - migration: Option<&'static PropertyMigration>, + migration: Option<&'db PropertyMigration>, } /// Contains all of the `TypeInfo` objects known to the serializer so far. This /// struct was broken out to help encapsulate the behavior here and to ease /// self-borrowing issues from BinarySerializer getting too large. #[derive(Debug)] -struct TypeInfos<'dom> { +struct TypeInfos<'dom, 'db> { + database: &'db ReflectionDatabase<'db>, /// A map containing one entry for each unique ClassName discovered in the /// DOM. /// /// These are stored sorted so that we naturally iterate over them in order /// and improve our chances of being deterministic. - values: BTreeMap>, + values: BTreeMap>, /// The next type ID that should be assigned if a type is discovered and /// added to the serializer. next_type_id: u32, } -impl<'dom> TypeInfos<'dom> { - fn new() -> Self { +impl<'dom, 'db> TypeInfos<'dom, 'db> { + fn new(database: &'db ReflectionDatabase<'db>) -> Self { Self { + database, values: BTreeMap::new(), next_type_id: 0, } @@ -171,12 +177,12 @@ impl<'dom> TypeInfos<'dom> { /// Finds the type info from the given ClassName if it exists, or creates /// one and returns a reference to it if not. - fn get_or_create(&mut self, class: &str) -> &mut TypeInfo<'dom> { + fn get_or_create(&mut self, class: &str) -> &mut TypeInfo<'dom, 'db> { if !self.values.contains_key(class) { let type_id = self.next_type_id; self.next_type_id += 1; - let class_descriptor = rbx_reflection_database::get().classes.get(class); + let class_descriptor = self.database.classes.get(class); let is_service = if let Some(descriptor) = &class_descriptor { descriptor.tags.contains(&ClassTag::Service) @@ -224,14 +230,15 @@ impl<'dom> TypeInfos<'dom> { } } -impl<'dom, W: Write> SerializerState<'dom, W> { - pub fn new(dom: &'dom WeakDom, output: W) -> Self { +impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { + pub fn new(serializer: &'db Serializer<'db>, dom: &'dom WeakDom, output: W) -> Self { SerializerState { + serializer, dom, output, relevant_instances: Vec::new(), id_to_referent: HashMap::new(), - type_infos: TypeInfos::new(), + type_infos: TypeInfos::new(serializer.database), shared_strings: Vec::new(), shared_string_ids: HashMap::new(), } @@ -311,7 +318,7 @@ impl<'dom, W: Write> SerializerState<'dom, W> { let serialized_ty; let mut migration = None; - let database = rbx_reflection_database::get(); + let database = self.serializer.database; match find_property_descriptors(database, &instance.class, prop_name) { Some(descriptors) => { // For any properties that do not serialize, we can skip diff --git a/rbx_xml/CHANGELOG.md b/rbx_xml/CHANGELOG.md index 95b59fbc..3688e7d6 100644 --- a/rbx_xml/CHANGELOG.md +++ b/rbx_xml/CHANGELOG.md @@ -1,6 +1,9 @@ # rbx_xml Changelog ## Unreleased +* Add the ability to specify a `ReflectionDatabase` to use for serializing and deserializing. This takes the form of `DecodeOptions::reflection_database` and `EncodeOptions::reflection_database`. ([#375]) + +[#375]: https://github.com/rojo-rbx/rbx-dom/pull/375 ## 0.13.2 (2023-10-03) * Added support for `SecurityCapabilities` values. ([#359]) diff --git a/rbx_xml/src/core.rs b/rbx_xml/src/core.rs index 8ad64760..ad783815 100644 --- a/rbx_xml/src/core.rs +++ b/rbx_xml/src/core.rs @@ -1,6 +1,6 @@ use std::io::{Read, Write}; -use rbx_reflection::{PropertyDescriptor, PropertyKind, PropertySerialization}; +use rbx_reflection::{PropertyDescriptor, PropertyKind, PropertySerialization, ReflectionDatabase}; use crate::{ deserializer_core::XmlEventReader, @@ -39,30 +39,32 @@ pub trait XmlType: Sized { } } -pub fn find_canonical_property_descriptor( +pub fn find_canonical_property_descriptor<'db>( class_name: &str, property_name: &str, -) -> Option<&'static PropertyDescriptor<'static>> { - find_property_descriptors(class_name, property_name).map(|(canonical, _serialized)| canonical) + database: &'db ReflectionDatabase<'db>, +) -> Option<&'db PropertyDescriptor<'db>> { + find_property_descriptors(class_name, property_name, database) + .map(|(canonical, _serialized)| canonical) } -pub fn find_serialized_property_descriptor( +pub fn find_serialized_property_descriptor<'db>( class_name: &str, property_name: &str, -) -> Option<&'static PropertyDescriptor<'static>> { - find_property_descriptors(class_name, property_name).map(|(_canonical, serialized)| serialized) + database: &'db ReflectionDatabase<'db>, +) -> Option<&'db PropertyDescriptor<'db>> { + find_property_descriptors(class_name, property_name, database) + .map(|(_canonical, serialized)| serialized) } /// Find both the canonical and serialized property descriptors for a given /// class and property name pair. These might be the same descriptor! -fn find_property_descriptors( +fn find_property_descriptors<'db>( class_name: &str, property_name: &str, -) -> Option<( - &'static PropertyDescriptor<'static>, - &'static PropertyDescriptor<'static>, -)> { - let class_descriptor = rbx_reflection_database::get().classes.get(class_name)?; + database: &'db ReflectionDatabase<'db>, +) -> Option<(&'db PropertyDescriptor<'db>, &'db PropertyDescriptor<'db>)> { + let class_descriptor = database.classes.get(class_name)?; let mut current_class_descriptor = class_descriptor; @@ -133,7 +135,7 @@ fn find_property_descriptors( // If a property descriptor isn't found in our class, check // our superclass. - current_class_descriptor = rbx_reflection_database::get() + current_class_descriptor = database .classes .get(superclass_name) .expect("Superclass in reflection database didn't exist"); diff --git a/rbx_xml/src/deserializer.rs b/rbx_xml/src/deserializer.rs index 9cc06627..744003a8 100644 --- a/rbx_xml/src/deserializer.rs +++ b/rbx_xml/src/deserializer.rs @@ -8,7 +8,7 @@ use rbx_dom_weak::{ types::{Ref, SharedString, Variant, VariantType}, InstanceBuilder, WeakDom, }; -use rbx_reflection::{DataType, PropertyKind, PropertySerialization}; +use rbx_reflection::{DataType, PropertyKind, PropertySerialization, ReflectionDatabase}; use crate::{ conversion::ConvertVariant, @@ -69,16 +69,18 @@ pub enum DecodePropertyBehavior { /// Options available for deserializing an XML-format model or place. #[derive(Debug, Clone)] -pub struct DecodeOptions { +pub struct DecodeOptions<'db> { property_behavior: DecodePropertyBehavior, + database: &'db ReflectionDatabase<'db>, } -impl DecodeOptions { +impl<'db> DecodeOptions<'db> { /// Constructs a `DecodeOptions` with all values set to their defaults. #[inline] pub fn new() -> Self { DecodeOptions { property_behavior: DecodePropertyBehavior::IgnoreUnknown, + database: rbx_reflection_database::get(), } } @@ -86,7 +88,17 @@ impl DecodeOptions { /// ones. #[inline] pub fn property_behavior(self, property_behavior: DecodePropertyBehavior) -> Self { - DecodeOptions { property_behavior } + DecodeOptions { + property_behavior, + ..self + } + } + + /// Determines what reflection database rbx_xml will use to deserialize + /// properties. + #[inline] + pub fn reflection_database(self, database: &'db ReflectionDatabase<'db>) -> Self { + DecodeOptions { database, ..self } } /// A utility function to determine whether or not we should reference the @@ -96,16 +108,17 @@ impl DecodeOptions { } } -impl Default for DecodeOptions { - fn default() -> DecodeOptions { +impl<'db> Default for DecodeOptions<'db> { + fn default() -> DecodeOptions<'db> { DecodeOptions::new() } } /// The state needed to deserialize an XML model into an `WeakDom`. -pub struct ParseState<'a> { - tree: &'a mut WeakDom, - options: DecodeOptions, +pub struct ParseState<'dom, 'db> { + tree: &'dom mut WeakDom, + + options: DecodeOptions<'db>, /// Metadata deserialized from 'Meta' fields in the file. /// Known fields are: @@ -150,8 +163,8 @@ struct SharedStringRewrite { shared_string_hash: String, } -impl<'a> ParseState<'a> { - fn new(tree: &mut WeakDom, options: DecodeOptions) -> ParseState { +impl<'dom, 'db> ParseState<'dom, 'db> { + fn new(tree: &'dom mut WeakDom, options: DecodeOptions<'db>) -> ParseState<'dom, 'db> { ParseState { tree, options, @@ -559,7 +572,11 @@ fn deserialize_properties( ); let maybe_descriptor = if state.options.use_reflection() { - find_canonical_property_descriptor(&class_name, &xml_property_name) + find_canonical_property_descriptor( + &class_name, + &xml_property_name, + state.options.database, + ) } else { None }; diff --git a/rbx_xml/src/lib.rs b/rbx_xml/src/lib.rs index 76ad1e00..e0bf7048 100644 --- a/rbx_xml/src/lib.rs +++ b/rbx_xml/src/lib.rs @@ -89,9 +89,13 @@ //! ``` //! //! ## Configuration -//! rbx_xml exposes no useful configuration yet, but there are methods that -//! accept [`DecodeOptions`][DecodeOptions] and -//! [`EncodeOptions`][EncodeOptions] that will be useful when it does. +//! rbx_xml exposes a few configuration options at the moment in the form of +//! [`DecodeOptions`][DecodeOptions] and [`EncodeOptions`][EncodeOptions]. +//! For information on the configuration, see the documentation for those +//! structs. +//! +//! The non-default reader and writer functions accept these as their `options` +//! argument. //! //! [DecodeOptions]: struct.DecodeOptions.html //! [EncodeOptions]: struct.EncodeOptions.html diff --git a/rbx_xml/src/serializer.rs b/rbx_xml/src/serializer.rs index 21a2884d..9c7a77e3 100644 --- a/rbx_xml/src/serializer.rs +++ b/rbx_xml/src/serializer.rs @@ -8,7 +8,7 @@ use rbx_dom_weak::{ types::{Ref, SharedString, SharedStringHash, Variant, VariantType}, WeakDom, }; -use rbx_reflection::{DataType, PropertyKind, PropertySerialization}; +use rbx_reflection::{DataType, PropertyKind, PropertySerialization, ReflectionDatabase}; use crate::{ conversion::ConvertVariant, @@ -74,16 +74,18 @@ pub enum EncodePropertyBehavior { /// Options available for serializing an XML-format model or place. #[derive(Debug, Clone)] -pub struct EncodeOptions { +pub struct EncodeOptions<'db> { property_behavior: EncodePropertyBehavior, + database: &'db ReflectionDatabase<'db>, } -impl EncodeOptions { +impl<'db> EncodeOptions<'db> { /// Constructs a `EncodeOptions` with all values set to their defaults. #[inline] pub fn new() -> Self { EncodeOptions { property_behavior: EncodePropertyBehavior::IgnoreUnknown, + database: rbx_reflection_database::get(), } } @@ -91,7 +93,17 @@ impl EncodeOptions { /// ones. #[inline] pub fn property_behavior(self, property_behavior: EncodePropertyBehavior) -> Self { - EncodeOptions { property_behavior } + EncodeOptions { + property_behavior, + ..self + } + } + + /// Determines what reflection database rbx_xml will use to serialize + /// properties. + #[inline] + pub fn reflection_database(self, database: &'db ReflectionDatabase<'db>) -> Self { + EncodeOptions { database, ..self } } pub(crate) fn use_reflection(&self) -> bool { @@ -99,14 +111,14 @@ impl EncodeOptions { } } -impl Default for EncodeOptions { - fn default() -> EncodeOptions { +impl<'db> Default for EncodeOptions<'db> { + fn default() -> EncodeOptions<'db> { EncodeOptions::new() } } -pub struct EmitState { - options: EncodeOptions, +pub struct EmitState<'db> { + options: EncodeOptions<'db>, /// A map of IDs written so far to the generated referent that they use. /// This map is used to correctly emit Ref properties. @@ -120,8 +132,8 @@ pub struct EmitState { shared_strings_to_emit: BTreeMap, } -impl EmitState { - pub fn new(options: EncodeOptions) -> EmitState { +impl<'db> EmitState<'db> { + pub fn new(options: EncodeOptions<'db>) -> EmitState<'db> { EmitState { options, referent_map: HashMap::new(), @@ -151,12 +163,12 @@ impl EmitState { /// /// `property_buffer` is a Vec that can be reused between calls to /// serialize_instance to make sorting properties more efficient. -fn serialize_instance<'a, W: Write>( +fn serialize_instance<'dom, W: Write>( writer: &mut XmlEventWriter, state: &mut EmitState, - tree: &'a WeakDom, + tree: &'dom WeakDom, id: Ref, - property_buffer: &mut Vec<(&'a String, &'a Variant)>, + property_buffer: &mut Vec<(&'dom String, &'dom Variant)>, ) -> Result<(), NewEncodeError> { let instance = tree.get_by_ref(id).unwrap(); let mapped_id = state.map_id(id); @@ -183,7 +195,11 @@ fn serialize_instance<'a, W: Write>( for (property_name, value) in property_buffer.drain(..) { let maybe_serialized_descriptor = if state.options.use_reflection() { - find_serialized_property_descriptor(&instance.class, property_name) + find_serialized_property_descriptor( + &instance.class, + property_name, + state.options.database, + ) } else { None }; From e9732e427b8f0903b6ec9f5d02aa3f1f9e884e94 Mon Sep 17 00:00:00 2001 From: Micah Date: Tue, 21 Nov 2023 13:02:17 -0800 Subject: [PATCH 2/4] Add `len` and `is_empty` method to attributes (#377) --- rbx_types/CHANGELOG.md | 2 ++ rbx_types/src/attributes/mod.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/rbx_types/CHANGELOG.md b/rbx_types/CHANGELOG.md index cf0d0df5..76994f2f 100644 --- a/rbx_types/CHANGELOG.md +++ b/rbx_types/CHANGELOG.md @@ -1,6 +1,7 @@ # rbx_types Changelog ## Unreleased Changes +* Add `len` and `is_empty` methods to `Attributes` struct. ([#377]) ## 1.7.0 (2023-10-03) * Implemented `FromStr` for `TerrainMaterials`. ([#354]) @@ -12,6 +13,7 @@ [#355]: https://github.com/rojo-rbx/rbx-dom/pull/355 [#358]: https://github.com/rojo-rbx/rbx-dom/pull/358 [#363]: https://github.com/rojo-rbx/rbx-dom/pull/363 +[#377]: https://github.com/rojo-rbx/rbx-dom/pull/377 ## 1.6.0 (2023-08-09) * Added support for `UniqueId` values. ([#271]) diff --git a/rbx_types/src/attributes/mod.rs b/rbx_types/src/attributes/mod.rs index 6d068874..a76e6565 100644 --- a/rbx_types/src/attributes/mod.rs +++ b/rbx_types/src/attributes/mod.rs @@ -78,6 +78,18 @@ impl Attributes { pub fn iter(&self) -> impl Iterator { self.data.iter() } + + /// Returns the number of attributes. + #[inline] + pub fn len(&self) -> usize { + self.data.len() + } + + /// Returns true if the struct contains no attributes. + #[inline] + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } } impl IntoIterator for Attributes { From 04f185586be0471feba93ed51dad32d3c2a50f36 Mon Sep 17 00:00:00 2001 From: Kenneth Loeffler Date: Wed, 13 Dec 2023 12:34:04 -0800 Subject: [PATCH 3/4] Use in-progress db for rbx_reflector default place decode (#378) Closes #345 Because of #375, it's possible to continue using `DecodePropertyBehavior::IgnoreUnknown` and simply swap to the in-progress database via `DecodeOptions::reflection_database` --- rbx_reflector/src/defaults.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rbx_reflector/src/defaults.rs b/rbx_reflector/src/defaults.rs index 06568cfe..f6297602 100644 --- a/rbx_reflector/src/defaults.rs +++ b/rbx_reflector/src/defaults.rs @@ -18,7 +18,8 @@ pub fn apply_defaults( let file = BufReader::new(File::open(defaults_place).context("Could not find defaults place")?); let decode_options = rbx_xml::DecodeOptions::new() - .property_behavior(rbx_xml::DecodePropertyBehavior::IgnoreUnknown); + .property_behavior(rbx_xml::DecodePropertyBehavior::IgnoreUnknown) + .reflection_database(database); let tree = rbx_xml::from_reader(file, decode_options).context("Could not decode defaults place")?; From 989869763999c84801076680244e14cc6a398dbf Mon Sep 17 00:00:00 2001 From: Dervex <78505208+DervexHero@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:27:55 +0100 Subject: [PATCH 4/4] Add `Default` implementation for `WeakDom` (#379) Title basically explains everything. Aside from the fact that it is good practise, `Default` is required e.g. when using `#[serde(skip)]` attribute. --- rbx_dom_weak/CHANGELOG.md | 1 + rbx_dom_weak/src/dom.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/rbx_dom_weak/CHANGELOG.md b/rbx_dom_weak/CHANGELOG.md index 5f0f0cb8..a1b589d2 100644 --- a/rbx_dom_weak/CHANGELOG.md +++ b/rbx_dom_weak/CHANGELOG.md @@ -1,6 +1,7 @@ # rbx_dom_weak Changelog ## Unreleased Changes +* Implemented `Default` for `WeakDom`, useful when using Serde or creating an empty `WeakDom` ## 2.6.0 (2023-10-03) * Added `WeakDom::clone_multiple_into_external` that allows cloning multiple subtrees all at once into a given `WeakDom`, useful for preserving `Ref` properties that point across cloned subtrees ([#364]) diff --git a/rbx_dom_weak/src/dom.rs b/rbx_dom_weak/src/dom.rs index 479508af..99c5b9dc 100644 --- a/rbx_dom_weak/src/dom.rs +++ b/rbx_dom_weak/src/dom.rs @@ -346,6 +346,16 @@ impl WeakDom { } } +impl Default for WeakDom { + fn default() -> WeakDom { + WeakDom { + instances: HashMap::new(), + root_ref: Ref::none(), + unique_ids: HashSet::new(), + } + } +} + #[derive(Debug, Default)] struct CloneContext { queue: VecDeque<(Ref, Ref)>,