Skip to content

Commit

Permalink
Merge pull request #1 from rojo-rbx/master
Browse files Browse the repository at this point in the history
merge upstream changes
  • Loading branch information
blackshibe committed Jan 3, 2024
2 parents 5cbbb8d + 9898697 commit c7a87bc
Show file tree
Hide file tree
Showing 16 changed files with 205 additions and 94 deletions.
3 changes: 3 additions & 0 deletions rbx_binary/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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]).
Expand Down
22 changes: 11 additions & 11 deletions rbx_binary/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PropertyDescriptors<'a>> {
) -> Option<PropertyDescriptors<'db>> {
let mut class_descriptor = database.classes.get(class_name)?;

// We need to find the canonical property descriptor associated with
Expand Down Expand Up @@ -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!
Expand Down
24 changes: 19 additions & 5 deletions rbx_binary/src/deserializer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,32 @@ pub use self::error::Error;
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
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<R: Read>(&self, reader: R) -> Result<WeakDom, Error> {
Expand Down Expand Up @@ -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()
}
Expand Down
16 changes: 8 additions & 8 deletions rbx_binary/src/deserializer/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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<Self, InnerError> {
let tree = WeakDom::new(InstanceBuilder::new("DataModel"));
Expand Down Expand Up @@ -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,
Expand Down
31 changes: 25 additions & 6 deletions rbx_binary/src/serializer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,24 +28,42 @@ pub use self::error::Error;
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// ## 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<ReflectionDatabase> = 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
/// this serializer.
pub fn serialize<W: Write>(&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();
Expand All @@ -60,7 +79,7 @@ impl Serializer {
}
}

impl Default for Serializer {
impl<'db> Default for Serializer<'db> {
fn default() -> Self {
Self::new()
}
Expand Down
47 changes: 27 additions & 20 deletions rbx_binary/src/serializer/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use rbx_dom_weak::{

use rbx_reflection::{
ClassDescriptor, ClassTag, DataType, PropertyKind, PropertyMigration, PropertySerialization,
ReflectionDatabase,
};

use crate::{
Expand All @@ -27,6 +28,7 @@ use crate::{
find_property_descriptors, RbxWriteExt, FILE_MAGIC_HEADER, FILE_SIGNATURE, FILE_VERSION,
},
types::Type,
Serializer,
};

use super::error::InnerError;
Expand All @@ -36,7 +38,9 @@ static FILE_FOOTER: &[u8] = b"</roblox>";
/// 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,

Expand All @@ -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.
Expand All @@ -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,

Expand All @@ -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<Cow<'static, str>, PropInfo>,
properties: BTreeMap<Cow<'db, str>, 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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -136,47 +140,49 @@ 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<String, TypeInfo<'dom>>,
values: BTreeMap<String, TypeInfo<'dom, 'db>>,

/// 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,
}
}

/// 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)
Expand Down Expand Up @@ -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(),
}
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions rbx_dom_weak/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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])
Expand Down
Loading

0 comments on commit c7a87bc

Please sign in to comment.