1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 23:44:40 +00:00

optimize compound tag allocations

This commit is contained in:
mat 2024-05-12 00:49:17 -05:00
commit 1ab27e531e
3 changed files with 73 additions and 31 deletions

View file

@ -1,4 +1,4 @@
use std::io::Cursor; use std::{cell::UnsafeCell, io::Cursor};
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
@ -10,28 +10,34 @@ use crate::{
Error, Mutf8Str, Error, Mutf8Str,
}; };
use super::{list::NbtList, NbtTag}; use super::{list::NbtList, tag_alloc::TagAllocator, NbtTag};
/// A list of named tags. The order of the tags is preserved. /// A list of named tags. The order of the tags is preserved.
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]
pub struct NbtCompound<'a> { pub struct NbtCompound<'a> {
values: Vec<(&'a Mutf8Str, NbtTag<'a>)>, values: &'a [(&'a Mutf8Str, NbtTag<'a>)],
} }
impl<'a> NbtCompound<'a> { impl<'a, 'b> NbtCompound<'a> {
pub fn from_values(values: Vec<(&'a Mutf8Str, NbtTag<'a>)>) -> Self { pub fn read(
Self { values } data: &mut Cursor<&'a [u8]>,
alloc: &UnsafeCell<TagAllocator<'a>>,
) -> Result<Self, Error> {
Self::read_with_depth(data, alloc, 0)
} }
pub fn read(data: &mut Cursor<&'a [u8]>) -> Result<Self, Error> { pub fn read_with_depth(
Self::read_with_depth(data, 0) data: &mut Cursor<&'a [u8]>,
} alloc: &UnsafeCell<TagAllocator<'a>>,
depth: usize,
pub fn read_with_depth(data: &mut Cursor<&'a [u8]>, depth: usize) -> Result<Self, Error> { ) -> Result<Self, Error> {
if depth > MAX_DEPTH { if depth > MAX_DEPTH {
return Err(Error::MaxDepthExceeded); return Err(Error::MaxDepthExceeded);
} }
let mut values = Vec::with_capacity(4);
let alloc_mut = unsafe { alloc.get().as_mut().unwrap() };
let mut tags = alloc_mut.start_compound(depth);
loop { loop {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID { if tag_type == END_ID {
@ -39,13 +45,19 @@ impl<'a> NbtCompound<'a> {
} }
let tag_name = read_string(data)?; let tag_name = read_string(data)?;
values.push((tag_name, NbtTag::read_with_type(data, tag_type, depth)?)); tags.push(
tag_name,
NbtTag::read_with_type(data, alloc, tag_type, depth)?,
);
} }
let alloc_mut = unsafe { alloc.get().as_mut().unwrap() };
let values = alloc_mut.finish_compound(tags);
Ok(Self { values }) Ok(Self { values })
} }
pub fn write(&self, data: &mut Vec<u8>) { pub fn write(&self, data: &mut Vec<u8>) {
for (name, tag) in &self.values { for (name, tag) in self.values {
// reserve 4 bytes extra so we can avoid reallocating for small tags // reserve 4 bytes extra so we can avoid reallocating for small tags
data.reserve(1 + 2 + name.len() + 4); data.reserve(1 + 2 + name.len() + 4);
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and // SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
@ -109,7 +121,7 @@ impl<'a> NbtCompound<'a> {
pub fn get(&self, name: &str) -> Option<&NbtTag<'a>> { pub fn get(&self, name: &str) -> Option<&NbtTag<'a>> {
let name = Mutf8Str::from_str(name); let name = Mutf8Str::from_str(name);
let name = name.as_ref(); let name = name.as_ref();
for (key, value) in &self.values { for (key, value) in self.values {
if key == &name { if key == &name {
return Some(value); return Some(value);
} }
@ -121,7 +133,7 @@ impl<'a> NbtCompound<'a> {
pub fn contains(&self, name: &str) -> bool { pub fn contains(&self, name: &str) -> bool {
let name = Mutf8Str::from_str(name); let name = Mutf8Str::from_str(name);
let name = name.as_ref(); let name = name.as_ref();
for (key, _) in &self.values { for (key, _) in self.values {
if key == &name { if key == &name {
return true; return true;
} }

View file

@ -1,4 +1,4 @@
use std::io::Cursor; use std::{cell::UnsafeCell, io::Cursor};
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
@ -13,7 +13,7 @@ use crate::{
Error, Mutf8Str, Error, Mutf8Str,
}; };
use super::{read_u32, NbtCompound, MAX_DEPTH}; use super::{read_u32, tag_alloc::TagAllocator, NbtCompound, MAX_DEPTH};
/// A list of NBT tags of a single type. /// A list of NBT tags of a single type.
#[repr(u8)] #[repr(u8)]
@ -35,7 +35,11 @@ pub enum NbtList<'a> {
LongArray(Vec<RawList<'a, i64>>) = LONG_ARRAY_ID, LongArray(Vec<RawList<'a, i64>>) = LONG_ARRAY_ID,
} }
impl<'a> NbtList<'a> { impl<'a> NbtList<'a> {
pub fn read(data: &mut Cursor<&'a [u8]>, depth: usize) -> Result<Self, Error> { pub fn read(
data: &mut Cursor<&'a [u8]>,
alloc: &UnsafeCell<TagAllocator<'a>>,
depth: usize,
) -> Result<Self, Error> {
if depth > MAX_DEPTH { if depth > MAX_DEPTH {
return Err(Error::MaxDepthExceeded); return Err(Error::MaxDepthExceeded);
} }
@ -74,7 +78,7 @@ impl<'a> NbtList<'a> {
// arbitrary number to prevent big allocations // arbitrary number to prevent big allocations
let mut lists = Vec::with_capacity(length.min(128) as usize); let mut lists = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length { for _ in 0..length {
lists.push(NbtList::read(data, depth + 1)?) lists.push(NbtList::read(data, alloc, depth + 1)?)
} }
lists lists
}), }),
@ -83,7 +87,7 @@ impl<'a> NbtList<'a> {
// arbitrary number to prevent big allocations // arbitrary number to prevent big allocations
let mut compounds = Vec::with_capacity(length.min(128) as usize); let mut compounds = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length { for _ in 0..length {
compounds.push(NbtCompound::read_with_depth(data, depth + 1)?) compounds.push(NbtCompound::read_with_depth(data, alloc, depth + 1)?)
} }
compounds compounds
}), }),

View file

@ -2,8 +2,9 @@
mod compound; mod compound;
mod list; mod list;
mod tag_alloc;
use std::{io::Cursor, ops::Deref}; use std::{cell::UnsafeCell, io::Cursor, ops::Deref};
use byteorder::{ReadBytesExt, BE}; use byteorder::{ReadBytesExt, BE};
@ -17,13 +18,15 @@ use crate::{
Error, Mutf8Str, Error, Mutf8Str,
}; };
use self::tag_alloc::TagAllocator;
pub use self::{compound::NbtCompound, list::NbtList}; pub use self::{compound::NbtCompound, list::NbtList};
/// A complete NBT container. This contains a name and a compound tag. /// A complete NBT container. This contains a name and a compound tag.
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct BaseNbt<'a> { pub struct BaseNbt<'a> {
name: &'a Mutf8Str, name: &'a Mutf8Str,
tag: NbtCompound<'a>, tag: NbtCompound<'a>,
tag_alloc: TagAllocator<'a>,
} }
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default)]
@ -43,10 +46,16 @@ impl<'a> Nbt<'a> {
if root_type != COMPOUND_ID { if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type)); return Err(Error::InvalidRootType(root_type));
} }
let name = read_string(data)?; let tag_alloc = UnsafeCell::new(TagAllocator::new());
let tag = NbtCompound::read_with_depth(data, 0)?;
Ok(Nbt::Some(BaseNbt { name, tag })) let name = read_string(data)?;
let tag = NbtCompound::read_with_depth(data, &tag_alloc, 0)?;
Ok(Nbt::Some(BaseNbt {
name,
tag,
tag_alloc: tag_alloc.into_inner(),
}))
} }
pub fn write(&self, data: &mut Vec<u8>) { pub fn write(&self, data: &mut Vec<u8>) {
@ -83,6 +92,15 @@ impl<'a> BaseNbt<'a> {
self.name self.name
} }
} }
impl PartialEq for BaseNbt<'_> {
fn eq(&self, other: &Self) -> bool {
// we don't need to compare the tag allocator since comparing `tag` will
// still compare the values of the tags
self.name == other.name && self.tag == other.tag
}
}
impl<'a> Deref for BaseNbt<'a> { impl<'a> Deref for BaseNbt<'a> {
type Target = NbtCompound<'a>; type Target = NbtCompound<'a>;
@ -130,6 +148,7 @@ impl<'a> NbtTag<'a> {
fn read_with_type( fn read_with_type(
data: &mut Cursor<&'a [u8]>, data: &mut Cursor<&'a [u8]>,
alloc: &UnsafeCell<TagAllocator<'a>>,
tag_type: u8, tag_type: u8,
depth: usize, depth: usize,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
@ -154,9 +173,10 @@ impl<'a> NbtTag<'a> {
)), )),
BYTE_ARRAY_ID => Ok(NbtTag::ByteArray(read_with_u32_length(data, 1)?)), BYTE_ARRAY_ID => Ok(NbtTag::ByteArray(read_with_u32_length(data, 1)?)),
STRING_ID => Ok(NbtTag::String(read_string(data)?)), STRING_ID => Ok(NbtTag::String(read_string(data)?)),
LIST_ID => Ok(NbtTag::List(NbtList::read(data, depth + 1)?)), LIST_ID => Ok(NbtTag::List(NbtList::read(data, alloc, depth + 1)?)),
COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::read_with_depth( COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::read_with_depth(
data, data,
alloc,
depth + 1, depth + 1,
)?)), )?)),
INT_ARRAY_ID => Ok(NbtTag::IntArray(read_int_array(data)?)), INT_ARRAY_ID => Ok(NbtTag::IntArray(read_int_array(data)?)),
@ -165,17 +185,23 @@ impl<'a> NbtTag<'a> {
} }
} }
pub fn read(data: &mut Cursor<&'a [u8]>) -> Result<Self, Error> { pub fn read(
data: &mut Cursor<&'a [u8]>,
alloc: &UnsafeCell<TagAllocator<'a>>,
) -> Result<Self, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
Self::read_with_type(data, tag_type, 0) Self::read_with_type(data, alloc, tag_type, 0)
} }
pub fn read_optional(data: &mut Cursor<&'a [u8]>) -> Result<Option<Self>, Error> { pub fn read_optional(
data: &mut Cursor<&'a [u8]>,
alloc: &UnsafeCell<TagAllocator<'a>>,
) -> Result<Option<Self>, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID { if tag_type == END_ID {
return Ok(None); return Ok(None);
} }
Ok(Some(Self::read_with_type(data, tag_type, 0)?)) Ok(Some(Self::read_with_type(data, alloc, tag_type, 0)?))
} }
pub fn byte(&self) -> Option<i8> { pub fn byte(&self) -> Option<i8> {