From 80a6fd897bd297334b77afa858516da0d19393e4 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 24 May 2025 12:29:15 -0700 Subject: [PATCH 01/12] use IntoIterator for interleaved byte writers --- rbx_binary/src/core.rs | 28 ++++++++---- rbx_binary/src/serializer/state.rs | 68 +++++++++++++++--------------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index 94f53146a..dae8fed63 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -260,16 +260,22 @@ pub trait RbxWriteExt: Write { /// bytes. Transformation is applied to the values as they're written. fn write_interleaved_i32_array(&mut self, values: I) -> io::Result<()> where - I: Iterator, + I: IntoIterator, { - let values: Vec<_> = values.map(|v| transform_i32(v).to_be_bytes()).collect(); + let values: Vec<_> = values + .into_iter() + .map(|v| transform_i32(v).to_be_bytes()) + .collect(); self.write_interleaved_bytes(&values) } /// Writes all items from `values` into the buffer as a blob of interleaved /// bytes. - fn write_interleaved_u32_array(&mut self, values: &[u32]) -> io::Result<()> { - let values: Vec<_> = values.iter().map(|v| v.to_be_bytes()).collect(); + fn write_interleaved_u32_array(&mut self, values: I) -> io::Result<()> + where + I: IntoIterator, + { + let values: Vec<_> = values.into_iter().map(|v| v.to_be_bytes()).collect(); self.write_interleaved_bytes(&values) } @@ -277,9 +283,10 @@ pub trait RbxWriteExt: Write { /// bytes. Rotation is applied to the values as they're written. fn write_interleaved_f32_array(&mut self, values: I) -> io::Result<()> where - I: Iterator, + I: IntoIterator, { let values: Vec<_> = values + .into_iter() .map(|v| v.to_bits().rotate_left(1).to_be_bytes()) .collect(); self.write_interleaved_bytes(&values) @@ -290,10 +297,10 @@ pub trait RbxWriteExt: Write { /// values are written. fn write_referent_array(&mut self, values: I) -> io::Result<()> where - I: Iterator, + I: IntoIterator, { let mut last_value = 0; - let delta_encoded = values.map(|value| { + let delta_encoded = values.into_iter().map(|value| { let encoded = value - last_value; last_value = value; encoded @@ -306,9 +313,12 @@ pub trait RbxWriteExt: Write { /// bytes. Transformation is applied to the values as they're written. fn write_interleaved_i64_array(&mut self, values: I) -> io::Result<()> where - I: Iterator, + I: IntoIterator, { - let values: Vec<_> = values.map(|v| transform_i64(v).to_be_bytes()).collect(); + let values: Vec<_> = values + .into_iter() + .map(|v| transform_i64(v).to_be_bytes()) + .collect(); self.write_interleaved_bytes(&values) } } diff --git a/rbx_binary/src/serializer/state.rs b/rbx_binary/src/serializer/state.rs index 21eaf77b3..41e92515c 100644 --- a/rbx_binary/src/serializer/state.rs +++ b/rbx_binary/src/serializer/state.rs @@ -754,7 +754,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_i32_array(buf.into_iter())?; + chunk.write_interleaved_i32_array(buf)?; } Type::Float32 => { let mut buf = Vec::with_capacity(values.len()); @@ -767,7 +767,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(buf.into_iter())?; + chunk.write_interleaved_f32_array(buf)?; } Type::Float64 => { for (i, rbx_value) in values { @@ -795,8 +795,8 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(scale.into_iter())?; - chunk.write_interleaved_i32_array(offset.into_iter())?; + chunk.write_interleaved_f32_array(scale)?; + chunk.write_interleaved_i32_array(offset)?; } Type::UDim2 => { let mut scale_x = Vec::with_capacity(values.len()); @@ -815,10 +815,10 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(scale_x.into_iter())?; - chunk.write_interleaved_f32_array(scale_y.into_iter())?; - chunk.write_interleaved_i32_array(offset_x.into_iter())?; - chunk.write_interleaved_i32_array(offset_y.into_iter())?; + chunk.write_interleaved_f32_array(scale_x)?; + chunk.write_interleaved_f32_array(scale_y)?; + chunk.write_interleaved_i32_array(offset_x)?; + chunk.write_interleaved_i32_array(offset_y)?; } Type::Font => { for (i, rbx_value) in values { @@ -879,7 +879,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_u32_array(&numbers)?; + chunk.write_interleaved_u32_array(numbers)?; } Type::Color3 => { let mut r = Vec::with_capacity(values.len()); @@ -896,9 +896,9 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(r.into_iter())?; - chunk.write_interleaved_f32_array(g.into_iter())?; - chunk.write_interleaved_f32_array(b.into_iter())?; + chunk.write_interleaved_f32_array(r)?; + chunk.write_interleaved_f32_array(g)?; + chunk.write_interleaved_f32_array(b)?; } Type::Vector2 => { let mut x = Vec::with_capacity(values.len()); @@ -913,8 +913,8 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(x.into_iter())?; - chunk.write_interleaved_f32_array(y.into_iter())?; + chunk.write_interleaved_f32_array(x)?; + chunk.write_interleaved_f32_array(y)?; } Type::Vector3 => { let mut x = Vec::with_capacity(values.len()); @@ -931,9 +931,9 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(x.into_iter())?; - chunk.write_interleaved_f32_array(y.into_iter())?; - chunk.write_interleaved_f32_array(z.into_iter())?; + chunk.write_interleaved_f32_array(x)?; + chunk.write_interleaved_f32_array(y)?; + chunk.write_interleaved_f32_array(z)?; } Type::CFrame => { let mut rotations = Vec::with_capacity(values.len()); @@ -972,9 +972,9 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(x.into_iter())?; - chunk.write_interleaved_f32_array(y.into_iter())?; - chunk.write_interleaved_f32_array(z.into_iter())?; + chunk.write_interleaved_f32_array(x)?; + chunk.write_interleaved_f32_array(y)?; + chunk.write_interleaved_f32_array(z)?; } Type::Enum => { let mut buf = Vec::with_capacity(values.len()); @@ -987,7 +987,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_u32_array(&buf)?; + chunk.write_interleaved_u32_array(buf)?; } Type::Ref => { let mut buf = Vec::with_capacity(values.len()); @@ -1004,7 +1004,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_referent_array(buf.into_iter())?; + chunk.write_referent_array(buf)?; } Type::Vector3int16 => { for (i, rbx_value) in values { @@ -1078,10 +1078,10 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(x_min.into_iter())?; - chunk.write_interleaved_f32_array(y_min.into_iter())?; - chunk.write_interleaved_f32_array(x_max.into_iter())?; - chunk.write_interleaved_f32_array(y_max.into_iter())?; + chunk.write_interleaved_f32_array(x_min)?; + chunk.write_interleaved_f32_array(y_min)?; + chunk.write_interleaved_f32_array(x_max)?; + chunk.write_interleaved_f32_array(y_max)?; } Type::PhysicalProperties => { for (i, rbx_value) in values { @@ -1153,7 +1153,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_i64_array(buf.into_iter())?; + chunk.write_interleaved_i64_array(buf)?; } Type::SharedString => { let mut entries = Vec::with_capacity(values.len()); @@ -1183,7 +1183,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_u32_array(&entries)?; + chunk.write_interleaved_u32_array(entries)?; } Type::OptionalCFrame => { let mut rotations = Vec::with_capacity(values.len()); @@ -1234,9 +1234,9 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_f32_array(x.into_iter())?; - chunk.write_interleaved_f32_array(y.into_iter())?; - chunk.write_interleaved_f32_array(z.into_iter())?; + chunk.write_interleaved_f32_array(x)?; + chunk.write_interleaved_f32_array(y)?; + chunk.write_interleaved_f32_array(z)?; chunk.write_u8(Type::Bool as u8)?; chunk.write_all(bools.as_slice())?; @@ -1271,7 +1271,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_i64_array(capabilities.into_iter())?; + chunk.write_interleaved_i64_array(capabilities)?; } Type::Content => { let mut source_types = Vec::with_capacity(values.len()); @@ -1299,14 +1299,14 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { return type_mismatch(i, &rbx_value, "Content"); } } - chunk.write_interleaved_i32_array(source_types.into_iter())?; + chunk.write_interleaved_i32_array(source_types)?; chunk.write_le_u32(uris.len() as u32)?; for uri in uris { chunk.write_string(&uri)?; } chunk.write_le_u32(objects.len() as u32)?; - chunk.write_referent_array(objects.into_iter())?; + chunk.write_referent_array(objects)?; // If we ever need to support the external referents, // we will need to add it here. From 3455b21d271a087c741bb7093f1b4c9753b11eac Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 23 May 2025 17:58:37 -0700 Subject: [PATCH 02/12] rethink interleaved bytes using iterators --- rbx_binary/src/chunk.rs | 23 ++++ rbx_binary/src/core.rs | 239 ++++++++++++++++++++-------------------- 2 files changed, 145 insertions(+), 117 deletions(-) diff --git a/rbx_binary/src/chunk.rs b/rbx_binary/src/chunk.rs index 1d4598c2b..e91ca8e04 100644 --- a/rbx_binary/src/chunk.rs +++ b/rbx_binary/src/chunk.rs @@ -78,6 +78,29 @@ impl ChunkBuilder { } } + /// Reserve bytes and return a slice of possibly uninitialized memory. + /// + /// SAFETY: All bytes in the mutable slice must be overwritten. + // + // Alternatively, the memory can be zeroed with safe code: + // + // let current_len = self.buffer.len(); + // self.buffer.extend(core::iter::repeat_n(0, len)); + // &mut self.buffer[current_len..current_len + len] + #[must_use] + pub unsafe fn reserve_bytes_mut(&mut self, len: usize) -> &mut [u8] { + let current_len = self.buffer.len(); + // Reserve space + self.buffer.reserve(len); + + unsafe { + // Update the length + self.buffer.set_len(current_len + len); + // Take a slice of uninitialized memory + core::slice::from_raw_parts_mut(self.buffer.as_mut_ptr().add(current_len), len) + } + } + /// Consume the chunk and write it to the given writer. pub fn dump(self, mut writer: W) -> io::Result<()> { writer.write_all(self.chunk_name)?; diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index dae8fed63..c51892b39 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -1,17 +1,41 @@ -use std::{ - io::{self, Read, Write}, - mem, -}; +use std::io::{self, Read, Write}; use rbx_dom_weak::Ustr; use rbx_reflection::{ ClassDescriptor, PropertyDescriptor, PropertyKind, PropertySerialization, ReflectionDatabase, }; +use crate::chunk::ChunkBuilder; + pub static FILE_MAGIC_HEADER: &[u8] = b" { + buffer: Vec, + index: usize, + len: usize, +} +impl ReadInterleavedBufferIter { + fn new(len: usize) -> Self { + let index = 0; + let buffer = vec![0; len * N]; + Self { buffer, index, len } + } +} +impl Iterator for ReadInterleavedBufferIter { + type Item = [u8; N]; + fn next(&mut self) -> Option { + if self.index < self.len { + let output = core::array::from_fn(|i| self.buffer[self.index + self.len * i]); + self.index += 1; + Some(output) + } else { + None + } + } +} + pub trait RbxReadExt: Read { fn read_le_u32(&mut self) -> io::Result { let mut buffer = [0; 4]; @@ -99,89 +123,66 @@ pub trait RbxReadExt: Read { Ok(self.read_u8()? != 0) } - /// Fills `output` with blocks of `N` bytes from the buffer, - /// deinterleaving them in the process. - /// - /// This function allocates `N * output.len()` bytes before reading. - fn read_interleaved_bytes(&mut self, output: &mut [[u8; N]]) -> io::Result<()> { - let len = output.len(); - let mut buffer = vec![0; len * N]; - self.read_exact(&mut buffer)?; - - for (i, array) in output.iter_mut().enumerate() { - for (j, byte) in array.iter_mut().enumerate() { - *byte = buffer[i + len * j]; - } - } - - Ok(()) - } - - /// Fills `output` with big-endian `i32` values read from the buffer. - /// These values are untransformed while being read. - fn read_interleaved_i32_array(&mut self, output: &mut [i32]) -> io::Result<()> { - let mut read = vec![[0; mem::size_of::()]; output.len()]; - self.read_interleaved_bytes(&mut read)?; - - for (chunk, out) in read.into_iter().zip(output) { - *out = untransform_i32(i32::from_be_bytes(chunk)); - } - - Ok(()) - } - - /// Fills `output` with big-endian `u32` values read from the buffer. - fn read_interleaved_u32_array(&mut self, output: &mut [u32]) -> io::Result<()> { - let mut read = vec![[0; mem::size_of::()]; output.len()]; - self.read_interleaved_bytes(&mut read)?; - - for (chunk, out) in read.into_iter().zip(output) { - *out = u32::from_be_bytes(chunk); - } - - Ok(()) - } - - /// Fills `output` with big-endian `f32` values read from the buffer. - /// These values are properly unrotated while being read. - fn read_interleaved_f32_array(&mut self, output: &mut [f32]) -> io::Result<()> { - let mut read = vec![[0; mem::size_of::()]; output.len()]; - self.read_interleaved_bytes(&mut read)?; - - for (chunk, out) in read.into_iter().zip(output) { - *out = f32::from_bits(u32::from_be_bytes(chunk).rotate_right(1)); - } - - Ok(()) - } - - /// Fills `output` with big-endian `i32` values read from the buffer. - /// The values are properly untransformed and accumulated so as to properly - /// read arrays of referent values. - fn read_referent_array(&mut self, output: &mut [i32]) -> io::Result<()> { - self.read_interleaved_i32_array(output)?; - + /// Create an iterator that reads chunks of N interleaved bytes. + /// This function allocates `N * len` bytes before reading. + fn read_interleaved_bytes( + &mut self, + len: usize, + ) -> io::Result> { + let mut it = ReadInterleavedBufferIter::new(len); + self.read_exact(&mut it.buffer)?; + Ok(it) + } + + /// Creates an iterator of `len` big-endian i32 values. + /// The bytes are read into a buffer immediately, + /// and the values are transformed during iteration. + fn read_interleaved_i32_array(&mut self, len: usize) -> io::Result> { + Ok(self + .read_interleaved_bytes(len)? + .map(|out| untransform_i32(i32::from_be_bytes(out)))) + } + + /// Creates an iterator of `len` big-endian u32 values. + /// The bytes are read into a buffer immediately, + /// and the values are transformed during iteration. + fn read_interleaved_u32_array(&mut self, len: usize) -> io::Result> { + Ok(self + .read_interleaved_bytes(len)? + .map(u32::from_be_bytes)) + } + + /// Creates an iterator of `len` big-endian f32 values. + /// The bytes are read into a buffer immediately, + /// and the values are properly unrotated during iteration. + fn read_interleaved_f32_array(&mut self, len: usize) -> io::Result> { + Ok(self + .read_interleaved_bytes(len)? + .map(|out| f32::from_bits(u32::from_be_bytes(out).rotate_right(1)))) + } + + /// Creates an iterator of `len` big-endian i32 values. + /// The bytes are read into a buffer immediately, + /// and the values are properly untransformed and accumulated + /// so as to properly read arrays of referent values. + fn read_referent_array(&mut self, len: usize) -> io::Result> { let mut last = 0; - - for referent in output.iter_mut() { - *referent += last; - last = *referent; - } - - Ok(()) - } - - /// Fills `output` with big-endian `64` values read from the buffer. - /// These values are untransformed while being read. - fn read_interleaved_i64_array(&mut self, output: &mut [i64]) -> io::Result<()> { - let mut read = vec![[0; mem::size_of::()]; output.len()]; - self.read_interleaved_bytes(&mut read)?; - - for (chunk, out) in read.into_iter().zip(output) { - *out = untransform_i64(i64::from_be_bytes(chunk)); - } - - Ok(()) + Ok(self + .read_interleaved_i32_array(len)? + .map(move |mut referent| { + referent += last; + last = referent; + referent + })) + } + + /// Creates an iterator of `len` big-endian i64 values. + /// The bytes are read into a buffer immediately, + /// and the values are transformed during iteration. + fn read_interleaved_i64_array(&mut self, len: usize) -> io::Result> { + Ok(self + .read_interleaved_bytes(len)? + .map(|out| untransform_i64(i64::from_be_bytes(out)))) } } @@ -238,66 +239,73 @@ pub trait RbxWriteExt: Write { fn write_bool(&mut self, value: bool) -> io::Result<()> { self.write_u8(value as u8) } +} +impl ChunkBuilder { /// Takes `values` and writes it as a blob of data with each value /// interleaved by `N` bytes. - /// - /// This function allocates `N * values.len()` bytes before writing. - fn write_interleaved_bytes(&mut self, values: &[[u8; N]]) -> io::Result<()> { - let len = values.len(); - let mut blob = vec![0; len * N]; - for (i, bytes) in values.iter().enumerate() { - for (j, byte) in bytes.iter().enumerate() { - blob[i + len * j] = *byte; + pub fn write_interleaved_bytes(&mut self, values: I) -> io::Result<()> + where + I: IntoIterator, + ::IntoIter: ExactSizeIterator, + { + let values = values.into_iter(); + let values_len = values.len(); + let bytes_len = values_len * N; + + // SAFETY: I promise that I am about to write `bytes_len` bytes into the buffer. + let buffer = unsafe { self.reserve_bytes_mut(bytes_len) }; + + for (i, bytes) in values.enumerate() { + for (b, byte) in IntoIterator::into_iter(bytes).enumerate() { + buffer[i + b * values_len] = byte; } } - self.write_all(&blob)?; Ok(()) } /// Writes all items from `values` into the buffer as a blob of interleaved /// bytes. Transformation is applied to the values as they're written. - fn write_interleaved_i32_array(&mut self, values: I) -> io::Result<()> + pub fn write_interleaved_i32_array(&mut self, values: I) -> io::Result<()> where I: IntoIterator, + ::IntoIter: ExactSizeIterator, { - let values: Vec<_> = values - .into_iter() - .map(|v| transform_i32(v).to_be_bytes()) - .collect(); - self.write_interleaved_bytes(&values) + self.write_interleaved_bytes(values.into_iter().map(|v| transform_i32(v).to_be_bytes())) } /// Writes all items from `values` into the buffer as a blob of interleaved /// bytes. - fn write_interleaved_u32_array(&mut self, values: I) -> io::Result<()> + pub fn write_interleaved_u32_array(&mut self, values: I) -> io::Result<()> where I: IntoIterator, + ::IntoIter: ExactSizeIterator, { - let values: Vec<_> = values.into_iter().map(|v| v.to_be_bytes()).collect(); - self.write_interleaved_bytes(&values) + self.write_interleaved_bytes(values.into_iter().map(|v| v.to_be_bytes())) } /// Writes all items from `values` into the buffer as a blob of interleaved /// bytes. Rotation is applied to the values as they're written. - fn write_interleaved_f32_array(&mut self, values: I) -> io::Result<()> + pub fn write_interleaved_f32_array(&mut self, values: I) -> io::Result<()> where I: IntoIterator, + ::IntoIter: ExactSizeIterator, { - let values: Vec<_> = values - .into_iter() - .map(|v| v.to_bits().rotate_left(1).to_be_bytes()) - .collect(); - self.write_interleaved_bytes(&values) + self.write_interleaved_bytes( + values + .into_iter() + .map(|v| v.to_bits().rotate_left(1).to_be_bytes()), + ) } /// Writes all items from `values` into the buffer as a blob of interleaved /// bytes. The appropriate transformation and de-accumulation is done as /// values are written. - fn write_referent_array(&mut self, values: I) -> io::Result<()> + pub fn write_referent_array(&mut self, values: I) -> io::Result<()> where I: IntoIterator, + ::IntoIter: ExactSizeIterator, { let mut last_value = 0; let delta_encoded = values.into_iter().map(|value| { @@ -311,15 +319,12 @@ pub trait RbxWriteExt: Write { /// Writes all items from `values` into the buffer as a blob of interleaved /// bytes. Transformation is applied to the values as they're written. - fn write_interleaved_i64_array(&mut self, values: I) -> io::Result<()> + pub fn write_interleaved_i64_array(&mut self, values: I) -> io::Result<()> where I: IntoIterator, + ::IntoIter: ExactSizeIterator, { - let values: Vec<_> = values - .into_iter() - .map(|v| transform_i64(v).to_be_bytes()) - .collect(); - self.write_interleaved_bytes(&values) + self.write_interleaved_bytes(values.into_iter().map(|v| transform_i64(v).to_be_bytes())) } } From 58c8b5f2654c818836e69bcdc924a62cb916947c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 31 May 2025 21:29:17 -0700 Subject: [PATCH 03/12] omit many copies and intermediate allocations --- rbx_binary/src/deserializer/state.rs | 191 ++++++++--------------- rbx_binary/src/serializer/state.rs | 2 +- rbx_binary/src/tests/core_read_write.rs | 22 ++- rbx_binary/src/text_deserializer.rs | 194 +++++++++--------------- 4 files changed, 153 insertions(+), 256 deletions(-) diff --git a/rbx_binary/src/deserializer/state.rs b/rbx_binary/src/deserializer/state.rs index d09d4ab66..4de5b1a0e 100644 --- a/rbx_binary/src/deserializer/state.rs +++ b/rbx_binary/src/deserializer/state.rs @@ -281,8 +281,9 @@ impl<'db, R: Read> DeserializerState<'db, R> { "INST chunk (type ID {type_id}, type name {type_name}, format {object_format}, {number_instances} instances)", ); - let mut referents = vec![0; number_instances as usize]; - chunk.read_referent_array(&mut referents)?; + let referents = chunk + .read_referent_array(number_instances as usize)? + .collect(); let prop_capacity = self .deserializer @@ -542,10 +543,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Int32 => match canonical_type { VariantType::Int32 => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_i32_array(&mut values)?; + let values = chunk.read_interleaved_i32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); add_property(instance, &property, value.into()); } @@ -554,10 +554,9 @@ rbx-dom may require changes to fully support this property. Please open an issue // Basically, we convert Int32 to Int64 when we expect a Int64 but read a Int32 // See: #301 VariantType::Int64 => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_i32_array(&mut values)?; + let values = chunk.read_interleaved_i32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); let value_converted = i64::from(value); add_property(instance, &property, value_converted.into()); @@ -574,10 +573,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Float32 => match canonical_type { VariantType::Float32 => { - let mut values = vec![0.0; type_info.referents.len()]; - chunk.read_interleaved_f32_array(&mut values)?; + let values = chunk.read_interleaved_f32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); add_property(instance, &property, value.into()); } @@ -603,10 +601,9 @@ rbx-dom may require changes to fully support this property. Please open an issue // Basically, we convert Float32 to Float64 when we expect a Float64 but read a Float32 // See: #301 VariantType::Float32 => { - let mut values = vec![0.0; type_info.referents.len()]; - chunk.read_interleaved_f32_array(&mut values)?; + let values = chunk.read_interleaved_f32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); let converted_value = f64::from(value); add_property(instance, &property, converted_value.into()); @@ -623,14 +620,10 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::UDim => match canonical_type { VariantType::UDim => { - let mut scales = vec![0.0; type_info.referents.len()]; - let mut offsets = vec![0; type_info.referents.len()]; - - chunk.read_interleaved_f32_array(&mut scales)?; - chunk.read_interleaved_i32_array(&mut offsets)?; + let scales = chunk.read_interleaved_f32_array(type_info.referents.len())?; + let offsets = chunk.read_interleaved_i32_array(type_info.referents.len())?; let values = scales - .into_iter() .zip(offsets) .map(|(scale, offset)| UDim::new(scale, offset)); @@ -651,23 +644,16 @@ rbx-dom may require changes to fully support this property. Please open an issue Type::UDim2 => match canonical_type { VariantType::UDim2 => { let prop_count = type_info.referents.len(); - let mut scale_x = vec![0.0; prop_count]; - let mut scale_y = vec![0.0; prop_count]; - let mut offset_x = vec![0; prop_count]; - let mut offset_y = vec![0; prop_count]; - - chunk.read_interleaved_f32_array(&mut scale_x)?; - chunk.read_interleaved_f32_array(&mut scale_y)?; - chunk.read_interleaved_i32_array(&mut offset_x)?; - chunk.read_interleaved_i32_array(&mut offset_y)?; + let scale_x = chunk.read_interleaved_f32_array(prop_count)?; + let scale_y = chunk.read_interleaved_f32_array(prop_count)?; + let offset_x = chunk.read_interleaved_i32_array(prop_count)?; + let offset_y = chunk.read_interleaved_i32_array(prop_count)?; let x = scale_x - .into_iter() .zip(offset_x) .map(|(scale, offset)| UDim::new(scale, offset)); let y = scale_y - .into_iter() .zip(offset_y) .map(|(scale, offset)| UDim::new(scale, offset)); @@ -772,10 +758,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::BrickColor => match canonical_type { VariantType::BrickColor => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_u32_array(&mut values)?; + let values = chunk.read_interleaved_u32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); let color = value .try_into() @@ -802,19 +787,11 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Color3 => match canonical_type { VariantType::Color3 => { - let mut r = vec![0.0; type_info.referents.len()]; - let mut g = vec![0.0; type_info.referents.len()]; - let mut b = vec![0.0; type_info.referents.len()]; + let r = chunk.read_interleaved_f32_array(type_info.referents.len())?; + let g = chunk.read_interleaved_f32_array(type_info.referents.len())?; + let b = chunk.read_interleaved_f32_array(type_info.referents.len())?; - chunk.read_interleaved_f32_array(&mut r)?; - chunk.read_interleaved_f32_array(&mut g)?; - chunk.read_interleaved_f32_array(&mut b)?; - - let colors = r - .into_iter() - .zip(g) - .zip(b) - .map(|((r, g), b)| Color3::new(r, g, b)); + let colors = r.zip(g).zip(b).map(|((r, g), b)| Color3::new(r, g, b)); for (color, referent) in colors.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); @@ -832,13 +809,10 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Vector2 => match canonical_type { VariantType::Vector2 => { - let mut x = vec![0.0; type_info.referents.len()]; - let mut y = vec![0.0; type_info.referents.len()]; - - chunk.read_interleaved_f32_array(&mut x)?; - chunk.read_interleaved_f32_array(&mut y)?; + let x = chunk.read_interleaved_f32_array(type_info.referents.len())?; + let y = chunk.read_interleaved_f32_array(type_info.referents.len())?; - let values = x.into_iter().zip(y).map(|(x, y)| Vector2::new(x, y)); + let values = x.zip(y).map(|(x, y)| Vector2::new(x, y)); for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); @@ -856,19 +830,11 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Vector3 => match canonical_type { VariantType::Vector3 => { - let mut x = vec![0.0; type_info.referents.len()]; - let mut y = vec![0.0; type_info.referents.len()]; - let mut z = vec![0.0; type_info.referents.len()]; - - chunk.read_interleaved_f32_array(&mut x)?; - chunk.read_interleaved_f32_array(&mut y)?; - chunk.read_interleaved_f32_array(&mut z)?; + let x = chunk.read_interleaved_f32_array(type_info.referents.len())?; + let y = chunk.read_interleaved_f32_array(type_info.referents.len())?; + let z = chunk.read_interleaved_f32_array(type_info.referents.len())?; - let values = x - .into_iter() - .zip(y) - .zip(z) - .map(|((x, y), z)| Vector3::new(x, y, z)); + let values = x.zip(y).zip(z).map(|((x, y), z)| Vector3::new(x, y, z)); for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); @@ -920,16 +886,11 @@ rbx-dom may require changes to fully support this property. Please open an issue } } - let mut x = vec![0.0; referents.len()]; - let mut y = vec![0.0; referents.len()]; - let mut z = vec![0.0; referents.len()]; - - chunk.read_interleaved_f32_array(&mut x)?; - chunk.read_interleaved_f32_array(&mut y)?; - chunk.read_interleaved_f32_array(&mut z)?; + let x = chunk.read_interleaved_f32_array(referents.len())?; + let y = chunk.read_interleaved_f32_array(referents.len())?; + let z = chunk.read_interleaved_f32_array(referents.len())?; let values = x - .into_iter() .zip(y) .zip(z) .map(|((x, y), z)| Vector3::new(x, y, z)) @@ -952,10 +913,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Enum => match canonical_type { VariantType::Enum => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_u32_array(&mut values)?; + let values = chunk.read_interleaved_u32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); add_property(instance, &property, Enum::from_u32(value).into()); } @@ -971,10 +931,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Ref => match canonical_type { VariantType::Ref => { - let mut refs = vec![0; type_info.referents.len()]; - chunk.read_referent_array(&mut refs)?; + let refs = chunk.read_referent_array(type_info.referents.len())?; - for (value, referent) in refs.into_iter().zip(&type_info.referents) { + for (value, referent) in refs.zip(&type_info.referents) { let rbx_value = if let Some(instance) = self.instances_by_ref.get(&value) { instance.builder.referent() } else { @@ -1140,17 +1099,12 @@ rbx-dom may require changes to fully support this property. Please open an issue Type::Rect => match canonical_type { VariantType::Rect => { let len = type_info.referents.len(); - let mut x_min = vec![0.0; len]; - let mut y_min = vec![0.0; len]; - let mut x_max = vec![0.0; len]; - let mut y_max = vec![0.0; len]; + let x_min = chunk.read_interleaved_f32_array(len)?; + let y_min = chunk.read_interleaved_f32_array(len)?; + let x_max = chunk.read_interleaved_f32_array(len)?; + let y_max = chunk.read_interleaved_f32_array(len)?; - chunk.read_interleaved_f32_array(&mut x_min)?; - chunk.read_interleaved_f32_array(&mut y_min)?; - chunk.read_interleaved_f32_array(&mut x_max)?; - chunk.read_interleaved_f32_array(&mut y_max)?; - - let values = x_min.into_iter().zip(y_min).zip(x_max).zip(y_max).map( + let values = x_min.zip(y_min).zip(x_max).zip(y_max).map( |(((x_min, y_min), x_max), y_max)| { Rect::new(Vector2::new(x_min, y_min), Vector2::new(x_max, y_max)) }, @@ -1245,10 +1199,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Int64 => match canonical_type { VariantType::Int64 => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_i64_array(&mut values)?; + let values = chunk.read_interleaved_i64_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); add_property(instance, &property, value.into()); } @@ -1264,10 +1217,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::SharedString => match canonical_type { VariantType::SharedString => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_u32_array(&mut values)?; + let values = chunk.read_interleaved_u32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let shared_string = self.shared_strings.get(value as usize).ok_or_else(|| { InnerError::InvalidPropData { @@ -1362,13 +1314,9 @@ rbx-dom may require changes to fully support this property. Please open an issue } } - let mut x = vec![0.0; referents.len()]; - let mut y = vec![0.0; referents.len()]; - let mut z = vec![0.0; referents.len()]; - - chunk.read_interleaved_f32_array(&mut x)?; - chunk.read_interleaved_f32_array(&mut y)?; - chunk.read_interleaved_f32_array(&mut z)?; + let x = chunk.read_interleaved_f32_array(referents.len())?; + let y = chunk.read_interleaved_f32_array(referents.len())?; + let z = chunk.read_interleaved_f32_array(referents.len())?; // Roblox writes a type marker for Bool here that we don't // need to use. We explicitly check for this right now just @@ -1383,7 +1331,6 @@ rbx-dom may require changes to fully support this property. Please open an issue } let values = x - .into_iter() .zip(y) .zip(z) .map(|((x, y), z)| Vector3::new(x, y, z)) @@ -1413,12 +1360,14 @@ rbx-dom may require changes to fully support this property. Please open an issue Type::UniqueId => match canonical_type { VariantType::UniqueId => { let n = type_info.referents.len(); - let mut values = vec![[0; 16]; n]; - chunk.read_interleaved_bytes::<16>(&mut values)?; - - for (i, referent) in type_info.referents.iter().enumerate() { - let mut value = values[i].as_slice(); - let instance = self.instances_by_ref.get_mut(referent).unwrap(); + let values = chunk.read_interleaved_bytes::<16>(n)?; + + for (i, value) in values.enumerate() { + let mut value = value.as_slice(); + let instance = self + .instances_by_ref + .get_mut(&type_info.referents[i]) + .unwrap(); add_property( instance, &property, @@ -1442,14 +1391,9 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::SecurityCapabilities => match canonical_type { VariantType::SecurityCapabilities => { - let mut values = vec![0; type_info.referents.len()]; + let values = chunk.read_interleaved_i64_array(type_info.referents.len())?; - chunk.read_interleaved_i64_array(values.as_mut_slice())?; - - let values: Vec = values - .into_iter() - .map(|value| SecurityCapabilities::from_bits(value as u64)) - .collect(); + let values = values.map(|value| SecurityCapabilities::from_bits(value as u64)); for (referent, value) in type_info.referents.iter().zip(values) { let instance = self.instances_by_ref.get_mut(referent).unwrap(); @@ -1467,8 +1411,8 @@ rbx-dom may require changes to fully support this property. Please open an issue }, Type::Content => match canonical_type { VariantType::Content => { - let mut source_types = vec![0; type_info.referents.len()]; - chunk.read_interleaved_i32_array(&mut source_types)?; + let source_types = + chunk.read_interleaved_i32_array(type_info.referents.len())?; let uri_count = chunk.read_le_u32()? as usize; let mut uris = VecDeque::with_capacity(uri_count); @@ -1477,8 +1421,8 @@ rbx-dom may require changes to fully support this property. Please open an issue } let object_count = chunk.read_le_u32()? as usize; - let mut objects: VecDeque = vec![0; object_count].into(); - chunk.read_referent_array(objects.make_contiguous())?; + let mut objects: VecDeque = + chunk.read_referent_array(object_count)?.collect(); let external_count = chunk.read_le_u32().unwrap() as usize; // We are advised by Roblox to just ignore this, as it's @@ -1536,13 +1480,10 @@ rbx-dom may require changes to fully support this property. Please open an issue log::trace!("PRNT chunk ({number_objects} instances)"); - let mut subjects = vec![0; number_objects as usize]; - let mut parents = vec![0; number_objects as usize]; - - chunk.read_referent_array(&mut subjects)?; - chunk.read_referent_array(&mut parents)?; + let subjects = chunk.read_referent_array(number_objects as usize)?; + let parents = chunk.read_referent_array(number_objects as usize)?; - for (id, parent_ref) in subjects.iter().copied().zip(parents.iter().copied()) { + for (id, parent_ref) in subjects.zip(parents) { if parent_ref == -1 { self.root_instance_refs.push(id); } else { diff --git a/rbx_binary/src/serializer/state.rs b/rbx_binary/src/serializer/state.rs index 41e92515c..51a533e6b 100644 --- a/rbx_binary/src/serializer/state.rs +++ b/rbx_binary/src/serializer/state.rs @@ -1258,7 +1258,7 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { } } - chunk.write_interleaved_bytes::<16>(&blobs)?; + chunk.write_interleaved_bytes::<16, _>(blobs)?; } Type::SecurityCapabilities => { let mut capabilities = Vec::with_capacity(values.len()); diff --git a/rbx_binary/src/tests/core_read_write.rs b/rbx_binary/src/tests/core_read_write.rs index b17edb956..e38e18be7 100644 --- a/rbx_binary/src/tests/core_read_write.rs +++ b/rbx_binary/src/tests/core_read_write.rs @@ -1,4 +1,4 @@ -use crate::core::{RbxReadExt, RbxWriteExt}; +use crate::{chunk::ChunkBuilder, core::RbxReadExt, CompressionType}; #[test] fn read_interleaved_bytes() { @@ -28,8 +28,10 @@ fn read_interleaved_bytes() { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], ]; - let mut result = vec![[0; 16]; expected.len()]; - input.read_interleaved_bytes::<16>(&mut result).unwrap(); + let result: Vec<_> = input + .read_interleaved_bytes::<16>(expected.len()) + .unwrap() + .collect(); assert_eq!(result, expected) } @@ -62,8 +64,16 @@ fn write_interleaved_bytes() { 15, 15, 15, ]; - let mut result = Vec::new(); - result.write_interleaved_bytes::<16>(input).unwrap(); + let mut chunk = ChunkBuilder::new(b"ASDF", CompressionType::None); + chunk + .write_interleaved_bytes::<16, _>(input.into_iter().copied()) + .unwrap(); - assert_eq!(result, expected) + let mut dump = Vec::new(); + chunk.dump(&mut dump).unwrap(); + + // the first 16 bytes are the chunk header + let result = &dump[16..]; + + assert_eq!(result, expected); } diff --git a/rbx_binary/src/text_deserializer.rs b/rbx_binary/src/text_deserializer.rs index 212b38de3..96a61028a 100644 --- a/rbx_binary/src/text_deserializer.rs +++ b/rbx_binary/src/text_deserializer.rs @@ -122,8 +122,10 @@ fn decode_inst_chunk( count_by_type_id.insert(type_id, num_instances as usize); - let mut referents = vec![0; num_instances as usize]; - reader.read_referent_array(&mut referents).unwrap(); + let referents = reader + .read_referent_array(num_instances as usize) + .unwrap() + .collect(); let mut remaining = Vec::new(); reader.read_to_end(&mut remaining).unwrap(); @@ -175,17 +177,10 @@ fn decode_prnt_chunk(mut reader: R) -> DecodedChunk { let version = reader.read_u8().unwrap(); let num_referents = reader.read_le_u32().unwrap(); - let mut subjects = vec![0; num_referents as usize]; - let mut parents = vec![0; num_referents as usize]; - - reader.read_referent_array(&mut subjects).unwrap(); - reader.read_referent_array(&mut parents).unwrap(); + let subjects = reader.read_referent_array(num_referents as usize).unwrap(); + let parents = reader.read_referent_array(num_referents as usize).unwrap(); - let links = subjects - .iter() - .copied() - .zip(parents.iter().copied()) - .collect(); + let links = subjects.zip(parents).collect(); let mut remaining = Vec::new(); reader.read_to_end(&mut remaining).unwrap(); @@ -255,16 +250,18 @@ impl DecodedValues { Some(DecodedValues::Bool(values)) } Type::Int32 => { - let mut values = vec![0; prop_count]; - - reader.read_interleaved_i32_array(&mut values).unwrap(); + let values = reader + .read_interleaved_i32_array(prop_count) + .unwrap() + .collect(); Some(DecodedValues::Int32(values)) } Type::Float32 => { - let mut values = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut values).unwrap(); + let values = reader + .read_interleaved_f32_array(prop_count) + .unwrap() + .collect(); Some(DecodedValues::Float32(values)) } @@ -278,14 +275,10 @@ impl DecodedValues { Some(DecodedValues::Float64(values)) } Type::UDim => { - let mut scale = vec![0.0; prop_count]; - let mut offset = vec![0; prop_count]; - - reader.read_interleaved_f32_array(&mut scale).unwrap(); - reader.read_interleaved_i32_array(&mut offset).unwrap(); + let scale = reader.read_interleaved_f32_array(prop_count).unwrap(); + let offset = reader.read_interleaved_i32_array(prop_count).unwrap(); let values = scale - .into_iter() .zip(offset) .map(|(scale, offset)| UDim::new(scale, offset)) .collect(); @@ -293,22 +286,15 @@ impl DecodedValues { Some(DecodedValues::UDim(values)) } Type::UDim2 => { - let mut scale_x = vec![0.0; prop_count]; - let mut scale_y = vec![0.0; prop_count]; - let mut offset_x = vec![0; prop_count]; - let mut offset_y = vec![0; prop_count]; - - reader.read_interleaved_f32_array(&mut scale_x).unwrap(); - reader.read_interleaved_f32_array(&mut scale_y).unwrap(); - reader.read_interleaved_i32_array(&mut offset_x).unwrap(); - reader.read_interleaved_i32_array(&mut offset_y).unwrap(); + let scale_x = reader.read_interleaved_f32_array(prop_count).unwrap(); + let scale_y = reader.read_interleaved_f32_array(prop_count).unwrap(); + let offset_x = reader.read_interleaved_i32_array(prop_count).unwrap(); + let offset_y = reader.read_interleaved_i32_array(prop_count).unwrap(); let x_values = scale_x - .into_iter() .zip(offset_x) .map(|(scale, offset)| UDim::new(scale, offset)); let y_values = scale_y - .into_iter() .zip(offset_y) .map(|(scale, offset)| UDim::new(scale, offset)); @@ -383,12 +369,10 @@ impl DecodedValues { Some(DecodedValues::Axes(values)) } Type::BrickColor => { - let mut values = vec![0; prop_count]; - reader.read_interleaved_u32_array(&mut values).unwrap(); + let values = reader.read_interleaved_u32_array(prop_count).unwrap(); let values = values - .into_iter() - .map(|value| BrickColor::from_number(value.try_into().unwrap()).unwrap()) + .map(|value| BrickColor::from_number(value as u16).unwrap()) .collect(); Some(DecodedValues::BrickColor(values)) @@ -421,16 +405,11 @@ impl DecodedValues { } } - let mut x = vec![0.0; prop_count]; - let mut y = vec![0.0; prop_count]; - let mut z = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut x).unwrap(); - reader.read_interleaved_f32_array(&mut y).unwrap(); - reader.read_interleaved_f32_array(&mut z).unwrap(); + let x = reader.read_interleaved_f32_array(prop_count).unwrap(); + let y = reader.read_interleaved_f32_array(prop_count).unwrap(); + let z = reader.read_interleaved_f32_array(prop_count).unwrap(); let values = x - .into_iter() .zip(y) .zip(z) .zip(rotations) @@ -440,30 +419,23 @@ impl DecodedValues { Some(DecodedValues::CFrame(values)) } Type::Enum => { - let mut ints = vec![0; prop_count]; - reader.read_interleaved_u32_array(&mut ints).unwrap(); + let ints = reader.read_interleaved_u32_array(prop_count).unwrap(); - let values = ints.into_iter().map(Enum::from_u32).collect(); + let values = ints.map(Enum::from_u32).collect(); Some(DecodedValues::Enum(values)) } Type::Ref => { - let mut refs = vec![0; prop_count]; - reader.read_referent_array(&mut refs).unwrap(); + let refs = reader.read_referent_array(prop_count).unwrap().collect(); Some(DecodedValues::Ref(refs)) } Type::Color3 => { - let mut r = vec![0.0; prop_count]; - let mut g = vec![0.0; prop_count]; - let mut b = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut r).unwrap(); - reader.read_interleaved_f32_array(&mut g).unwrap(); - reader.read_interleaved_f32_array(&mut b).unwrap(); + let r = reader.read_interleaved_f32_array(prop_count).unwrap(); + let g = reader.read_interleaved_f32_array(prop_count).unwrap(); + let b = reader.read_interleaved_f32_array(prop_count).unwrap(); let values = r - .into_iter() .zip(g) .zip(b) .map(|((r, g), b)| Color3::new(r, g, b)) @@ -472,31 +444,19 @@ impl DecodedValues { Some(DecodedValues::Color3(values)) } Type::Vector2 => { - let mut x = vec![0.0; prop_count]; - let mut y = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut x).unwrap(); - reader.read_interleaved_f32_array(&mut y).unwrap(); + let x = reader.read_interleaved_f32_array(prop_count).unwrap(); + let y = reader.read_interleaved_f32_array(prop_count).unwrap(); - let values = x - .into_iter() - .zip(y) - .map(|(x, y)| Vector2::new(x, y)) - .collect(); + let values = x.zip(y).map(|(x, y)| Vector2::new(x, y)).collect(); Some(DecodedValues::Vector2(values)) } Type::Vector3 => { - let mut x = vec![0.0; prop_count]; - let mut y = vec![0.0; prop_count]; - let mut z = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut x).unwrap(); - reader.read_interleaved_f32_array(&mut y).unwrap(); - reader.read_interleaved_f32_array(&mut z).unwrap(); + let x = reader.read_interleaved_f32_array(prop_count).unwrap(); + let y = reader.read_interleaved_f32_array(prop_count).unwrap(); + let z = reader.read_interleaved_f32_array(prop_count).unwrap(); let values = x - .into_iter() .zip(y) .zip(z) .map(|((x, y), z)| Vector3::new(x, y, z)) @@ -576,18 +536,12 @@ impl DecodedValues { Some(DecodedValues::NumberSequence(values)) } Type::Rect => { - let mut x_min = vec![0.0; prop_count]; - let mut y_min = vec![0.0; prop_count]; - let mut x_max = vec![0.0; prop_count]; - let mut y_max = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut x_min).unwrap(); - reader.read_interleaved_f32_array(&mut y_min).unwrap(); - reader.read_interleaved_f32_array(&mut x_max).unwrap(); - reader.read_interleaved_f32_array(&mut y_max).unwrap(); + let x_min = reader.read_interleaved_f32_array(prop_count).unwrap(); + let y_min = reader.read_interleaved_f32_array(prop_count).unwrap(); + let x_max = reader.read_interleaved_f32_array(prop_count).unwrap(); + let y_max = reader.read_interleaved_f32_array(prop_count).unwrap(); let values = x_min - .into_iter() .zip(y_min) .zip(x_max) .zip(y_max) @@ -649,16 +603,18 @@ impl DecodedValues { Some(DecodedValues::Color3uint8(values)) } Type::Int64 => { - let mut values = vec![0; prop_count]; - - reader.read_interleaved_i64_array(&mut values).unwrap(); + let values = reader + .read_interleaved_i64_array(prop_count) + .unwrap() + .collect(); Some(DecodedValues::Int64(values)) } Type::SharedString => { - let mut values = vec![0; prop_count]; - - reader.read_interleaved_u32_array(&mut values).unwrap(); + let values = reader + .read_interleaved_u32_array(prop_count) + .unwrap() + .collect(); Some(DecodedValues::SharedString(values)) } @@ -692,18 +648,13 @@ impl DecodedValues { } } - let mut x = vec![0.0; prop_count]; - let mut y = vec![0.0; prop_count]; - let mut z = vec![0.0; prop_count]; - - reader.read_interleaved_f32_array(&mut x).unwrap(); - reader.read_interleaved_f32_array(&mut y).unwrap(); - reader.read_interleaved_f32_array(&mut z).unwrap(); + let x = reader.read_interleaved_f32_array(prop_count).unwrap(); + let y = reader.read_interleaved_f32_array(prop_count).unwrap(); + let z = reader.read_interleaved_f32_array(prop_count).unwrap(); reader.read_u8().unwrap(); let values = x - .into_iter() .zip(y) .zip(z) .zip(rotations) @@ -719,26 +670,26 @@ impl DecodedValues { Some(DecodedValues::OptionalCFrame(values)) } Type::UniqueId => { - let mut values = Vec::with_capacity(prop_count); - let mut blobs = vec![[0; 16]; prop_count]; - reader.read_interleaved_bytes::<16>(&mut blobs).unwrap(); - - for mut value in blobs.iter().map(|v| v.as_slice()) { - let index = value.read_be_u32().unwrap(); - let time = value.read_be_u32().unwrap(); - let random = value.read_be_i64().unwrap().rotate_right(1); - values.push(UniqueId::new(index, time, random)); - } + let values = reader + .read_interleaved_bytes::<16>(prop_count) + .unwrap() + .map(|v| { + let mut bytes = v.as_slice(); + + let index = bytes.read_be_u32().unwrap(); + let time = bytes.read_be_u32().unwrap(); + let random = bytes.read_be_i64().unwrap().rotate_right(1); + + UniqueId::new(index, time, random) + }) + .collect(); Some(DecodedValues::UniqueId(values)) } Type::SecurityCapabilities => { - let mut values = vec![0; prop_count]; - - reader.read_interleaved_i64_array(&mut values).unwrap(); + let values = reader.read_interleaved_i64_array(prop_count).unwrap(); let values = values - .into_iter() .map(|value| SecurityCapabilities::from_bits(value as u64)) .collect(); @@ -747,10 +698,7 @@ impl DecodedValues { Type::Content => { let mut values = vec![SerializedContentType::None; prop_count]; - let mut source_types = vec![0; prop_count]; - reader - .read_interleaved_i32_array(&mut source_types) - .unwrap(); + let source_types = reader.read_interleaved_i32_array(prop_count).unwrap(); let uri_count = reader.read_le_u32().unwrap() as usize; let mut uris = VecDeque::with_capacity(uri_count); @@ -759,10 +707,8 @@ impl DecodedValues { } let object_count = reader.read_le_u32().unwrap() as usize; - let mut objects: VecDeque = vec![0; object_count].into(); - reader - .read_referent_array(objects.make_contiguous()) - .unwrap(); + let mut objects: VecDeque = + reader.read_referent_array(object_count).unwrap().collect(); let external_count = reader.read_le_u32().unwrap() as usize; let mut external_objects = vec![0; external_count * 4]; From b718679cb2bf74ff52f4797ba7b399e71624bd04 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 31 May 2025 21:21:47 -0700 Subject: [PATCH 04/12] explicitly uncapture implicitly captured lifetime The &mut self reference must be dropped at the end of the function scope, but it is implicitly captured by the impl. --- rbx_binary/src/core.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index c51892b39..3b4e08e33 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -137,7 +137,10 @@ pub trait RbxReadExt: Read { /// Creates an iterator of `len` big-endian i32 values. /// The bytes are read into a buffer immediately, /// and the values are transformed during iteration. - fn read_interleaved_i32_array(&mut self, len: usize) -> io::Result> { + fn read_interleaved_i32_array( + &mut self, + len: usize, + ) -> io::Result + use> { Ok(self .read_interleaved_bytes(len)? .map(|out| untransform_i32(i32::from_be_bytes(out)))) @@ -146,16 +149,20 @@ pub trait RbxReadExt: Read { /// Creates an iterator of `len` big-endian u32 values. /// The bytes are read into a buffer immediately, /// and the values are transformed during iteration. - fn read_interleaved_u32_array(&mut self, len: usize) -> io::Result> { - Ok(self - .read_interleaved_bytes(len)? - .map(u32::from_be_bytes)) + fn read_interleaved_u32_array( + &mut self, + len: usize, + ) -> io::Result + use> { + Ok(self.read_interleaved_bytes(len)?.map(u32::from_be_bytes)) } /// Creates an iterator of `len` big-endian f32 values. /// The bytes are read into a buffer immediately, /// and the values are properly unrotated during iteration. - fn read_interleaved_f32_array(&mut self, len: usize) -> io::Result> { + fn read_interleaved_f32_array( + &mut self, + len: usize, + ) -> io::Result + use> { Ok(self .read_interleaved_bytes(len)? .map(|out| f32::from_bits(u32::from_be_bytes(out).rotate_right(1)))) @@ -165,7 +172,10 @@ pub trait RbxReadExt: Read { /// The bytes are read into a buffer immediately, /// and the values are properly untransformed and accumulated /// so as to properly read arrays of referent values. - fn read_referent_array(&mut self, len: usize) -> io::Result> { + fn read_referent_array( + &mut self, + len: usize, + ) -> io::Result + use> { let mut last = 0; Ok(self .read_interleaved_i32_array(len)? @@ -179,7 +189,10 @@ pub trait RbxReadExt: Read { /// Creates an iterator of `len` big-endian i64 values. /// The bytes are read into a buffer immediately, /// and the values are transformed during iteration. - fn read_interleaved_i64_array(&mut self, len: usize) -> io::Result> { + fn read_interleaved_i64_array( + &mut self, + len: usize, + ) -> io::Result + use> { Ok(self .read_interleaved_bytes(len)? .map(|out| untransform_i64(i64::from_be_bytes(out)))) From 22bfd55f90be2f918fb0033340166bf550570776 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 1 Jun 2025 10:18:54 -0700 Subject: [PATCH 05/12] unsafe fn v2 clippy likes --- rbx_binary/src/chunk.rs | 29 ++++++++++++++++++++--------- rbx_binary/src/core.rs | 16 +++++++++------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/rbx_binary/src/chunk.rs b/rbx_binary/src/chunk.rs index e91ca8e04..9621a0eff 100644 --- a/rbx_binary/src/chunk.rs +++ b/rbx_binary/src/chunk.rs @@ -78,27 +78,38 @@ impl ChunkBuilder { } } - /// Reserve bytes and return a slice of possibly uninitialized memory. + /// Reserve bytes and use a closure to initialize possibly uninitialized memory. /// - /// SAFETY: All bytes in the mutable slice must be overwritten. + /// SAFETY: + /// - All bytes in the mutable slice must be overwritten. + /// - The closure cannot panic. // // Alternatively, the memory can be zeroed with safe code: // // let current_len = self.buffer.len(); // self.buffer.extend(core::iter::repeat_n(0, len)); - // &mut self.buffer[current_len..current_len + len] - #[must_use] - pub unsafe fn reserve_bytes_mut(&mut self, len: usize) -> &mut [u8] { + // initialize_bytes(&mut self.buffer[current_len..current_len + len]); + pub unsafe fn initialize_bytes_with( + &mut self, + len: usize, + initialize_bytes: impl FnOnce(&mut [u8]), + ) { let current_len = self.buffer.len(); // Reserve space self.buffer.reserve(len); + // Take a slice of uninitialized memory + let bytes = unsafe { + core::slice::from_raw_parts_mut(self.buffer.as_mut_ptr().add(current_len), len) + }; + + // Pls no panic c: + initialize_bytes(bytes); + + // Update the length unsafe { - // Update the length self.buffer.set_len(current_len + len); - // Take a slice of uninitialized memory - core::slice::from_raw_parts_mut(self.buffer.as_mut_ptr().add(current_len), len) - } + }; } /// Consume the chunk and write it to the given writer. diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index 3b4e08e33..ef2fe0a25 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -266,14 +266,16 @@ impl ChunkBuilder { let values_len = values.len(); let bytes_len = values_len * N; - // SAFETY: I promise that I am about to write `bytes_len` bytes into the buffer. - let buffer = unsafe { self.reserve_bytes_mut(bytes_len) }; - - for (i, bytes) in values.enumerate() { - for (b, byte) in IntoIterator::into_iter(bytes).enumerate() { - buffer[i + b * values_len] = byte; + let initialize_bytes = |buffer: &mut [u8]| { + for (i, bytes) in values.enumerate() { + for (b, byte) in IntoIterator::into_iter(bytes).enumerate() { + buffer[i + b * values_len] = byte; + } } - } + }; + + // SAFETY: I promise `initialize_bytes` writes `bytes_len` bytes into the buffer. + unsafe { self.initialize_bytes_with(bytes_len, initialize_bytes) }; Ok(()) } From f1ad901aad9b74fad1e7071fdee76e302795a22e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 1 Jun 2025 10:45:03 -0700 Subject: [PATCH 06/12] no bounds check in `initialize_bytes` --- rbx_binary/src/core.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index ef2fe0a25..653979f28 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -269,7 +269,9 @@ impl ChunkBuilder { let initialize_bytes = |buffer: &mut [u8]| { for (i, bytes) in values.enumerate() { for (b, byte) in IntoIterator::into_iter(bytes).enumerate() { - buffer[i + b * values_len] = byte; + // SAFETY: The index is within bounds, but + // potentially too complicated for the compiler to verify. + *unsafe { buffer.get_unchecked_mut(i + b * values_len) } = byte; } } }; From bdb0298069e37ca37cc8ba93c57981502684c8a8 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 1 Jun 2025 10:50:55 -0700 Subject: [PATCH 07/12] just use safe version --- rbx_binary/src/chunk.rs | 35 ++++------------------------------- rbx_binary/src/core.rs | 7 ++----- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/rbx_binary/src/chunk.rs b/rbx_binary/src/chunk.rs index 9621a0eff..db93d3192 100644 --- a/rbx_binary/src/chunk.rs +++ b/rbx_binary/src/chunk.rs @@ -78,38 +78,11 @@ impl ChunkBuilder { } } - /// Reserve bytes and use a closure to initialize possibly uninitialized memory. - /// - /// SAFETY: - /// - All bytes in the mutable slice must be overwritten. - /// - The closure cannot panic. - // - // Alternatively, the memory can be zeroed with safe code: - // - // let current_len = self.buffer.len(); - // self.buffer.extend(core::iter::repeat_n(0, len)); - // initialize_bytes(&mut self.buffer[current_len..current_len + len]); - pub unsafe fn initialize_bytes_with( - &mut self, - len: usize, - initialize_bytes: impl FnOnce(&mut [u8]), - ) { + /// Reserve bytes and use a closure to initialize them. + pub fn initialize_bytes_with(&mut self, len: usize, initialize_bytes: impl FnOnce(&mut [u8])) { let current_len = self.buffer.len(); - // Reserve space - self.buffer.reserve(len); - - // Take a slice of uninitialized memory - let bytes = unsafe { - core::slice::from_raw_parts_mut(self.buffer.as_mut_ptr().add(current_len), len) - }; - - // Pls no panic c: - initialize_bytes(bytes); - - // Update the length - unsafe { - self.buffer.set_len(current_len + len); - }; + self.buffer.extend(core::iter::repeat_n(0, len)); + initialize_bytes(&mut self.buffer[current_len..current_len + len]); } /// Consume the chunk and write it to the given writer. diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index 653979f28..b6213dc23 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -269,15 +269,12 @@ impl ChunkBuilder { let initialize_bytes = |buffer: &mut [u8]| { for (i, bytes) in values.enumerate() { for (b, byte) in IntoIterator::into_iter(bytes).enumerate() { - // SAFETY: The index is within bounds, but - // potentially too complicated for the compiler to verify. - *unsafe { buffer.get_unchecked_mut(i + b * values_len) } = byte; + buffer[i + b * values_len] = byte; } } }; - // SAFETY: I promise `initialize_bytes` writes `bytes_len` bytes into the buffer. - unsafe { self.initialize_bytes_with(bytes_len, initialize_bytes) }; + self.initialize_bytes_with(bytes_len, initialize_bytes); Ok(()) } From 75892c0cb88f2fdfd487c31e5d44d4bbc01f1511 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 10 Jun 2025 15:31:38 -0700 Subject: [PATCH 08/12] slice to end of buffer is always correct --- rbx_binary/src/chunk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbx_binary/src/chunk.rs b/rbx_binary/src/chunk.rs index db93d3192..0c0fc837c 100644 --- a/rbx_binary/src/chunk.rs +++ b/rbx_binary/src/chunk.rs @@ -82,7 +82,7 @@ impl ChunkBuilder { pub fn initialize_bytes_with(&mut self, len: usize, initialize_bytes: impl FnOnce(&mut [u8])) { let current_len = self.buffer.len(); self.buffer.extend(core::iter::repeat_n(0, len)); - initialize_bytes(&mut self.buffer[current_len..current_len + len]); + initialize_bytes(&mut self.buffer[current_len..]); } /// Consume the chunk and write it to the given writer. From d17a733b11db0eba76c06ad06afa1a851a66fa8d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 10 Jun 2025 15:37:44 -0700 Subject: [PATCH 09/12] appease clippy --- rbx_binary/src/tests/core_read_write.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rbx_binary/src/tests/core_read_write.rs b/rbx_binary/src/tests/core_read_write.rs index e38e18be7..486411cab 100644 --- a/rbx_binary/src/tests/core_read_write.rs +++ b/rbx_binary/src/tests/core_read_write.rs @@ -38,7 +38,7 @@ fn read_interleaved_bytes() { #[test] fn write_interleaved_bytes() { - let input: &[[u8; 16]] = &[ + let input: [[u8; 16]; 3] = [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], @@ -65,9 +65,7 @@ fn write_interleaved_bytes() { ]; let mut chunk = ChunkBuilder::new(b"ASDF", CompressionType::None); - chunk - .write_interleaved_bytes::<16, _>(input.into_iter().copied()) - .unwrap(); + chunk.write_interleaved_bytes(input).unwrap(); let mut dump = Vec::new(); chunk.dump(&mut dump).unwrap(); From 65cc3833c9d8b239bd29dad17ba628e6f6acbe15 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 17 Jun 2025 03:47:21 -0700 Subject: [PATCH 10/12] use fallible conversion in text_deserializer --- rbx_binary/src/text_deserializer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbx_binary/src/text_deserializer.rs b/rbx_binary/src/text_deserializer.rs index 96a61028a..51aabcfa0 100644 --- a/rbx_binary/src/text_deserializer.rs +++ b/rbx_binary/src/text_deserializer.rs @@ -372,7 +372,7 @@ impl DecodedValues { let values = reader.read_interleaved_u32_array(prop_count).unwrap(); let values = values - .map(|value| BrickColor::from_number(value as u16).unwrap()) + .map(|value| BrickColor::from_number(value.try_into().unwrap()).unwrap()) .collect(); Some(DecodedValues::BrickColor(values)) From c01eb3c15b0d3e72ff92e5e72d9d29b17ba901bc Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Wed, 5 Nov 2025 19:55:11 -0800 Subject: [PATCH 11/12] fix NetAssetRef --- rbx_binary/src/deserializer/state.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rbx_binary/src/deserializer/state.rs b/rbx_binary/src/deserializer/state.rs index 4de5b1a0e..f3dbb3e65 100644 --- a/rbx_binary/src/deserializer/state.rs +++ b/rbx_binary/src/deserializer/state.rs @@ -1236,10 +1236,9 @@ rbx-dom may require changes to fully support this property. Please open an issue } } VariantType::NetAssetRef => { - let mut values = vec![0; type_info.referents.len()]; - chunk.read_interleaved_u32_array(&mut values)?; + let values = chunk.read_interleaved_u32_array(type_info.referents.len())?; - for (value, referent) in values.into_iter().zip(&type_info.referents) { + for (value, referent) in values.zip(&type_info.referents) { let net_asset = NetAssetRef::from( self.shared_strings .get(value as usize) From c26f10a228dcfef38f1334021caa0646f86fff0a Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Wed, 5 Nov 2025 19:58:07 -0800 Subject: [PATCH 12/12] add newlines --- rbx_binary/src/core.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index b6213dc23..eafbc277a 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -16,6 +16,7 @@ pub struct ReadInterleavedBufferIter { index: usize, len: usize, } + impl ReadInterleavedBufferIter { fn new(len: usize) -> Self { let index = 0; @@ -23,6 +24,7 @@ impl ReadInterleavedBufferIter { Self { buffer, index, len } } } + impl Iterator for ReadInterleavedBufferIter { type Item = [u8; N]; fn next(&mut self) -> Option {