mirror of
https://github.com/azalea-rs/simdnbt.git
synced 2025-08-02 15:36:03 +00:00
OptionalNbt
This commit is contained in:
parent
837b5990e7
commit
76347de9eb
11 changed files with 665 additions and 571 deletions
|
@ -27,7 +27,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
|||
// group.bench_function("simdnbt_borrow_parse", |b| {
|
||||
// b.iter(|| {
|
||||
// let input = black_box(input);
|
||||
// let nbt = simdnbt::borrow::Nbt::new(&mut Cursor::new(input))
|
||||
// let nbt = simdnbt::borrow::OptionalNbt::new(&mut Cursor::new(input))
|
||||
// .unwrap()
|
||||
// .unwrap();
|
||||
// // let _ = black_box(nbt.list("").unwrap().ints());
|
||||
|
@ -35,7 +35,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
|||
// })
|
||||
// });
|
||||
|
||||
let nbt = simdnbt::borrow::Nbt::new(&mut Cursor::new(input))
|
||||
let nbt = simdnbt::borrow::OptionalNbt::read(&mut Cursor::new(input))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
group.bench_function("simdnbt_borrow_write", |b| {
|
||||
|
@ -49,14 +49,14 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
|||
// group.bench_function("simdnbt_owned_parse", |b| {
|
||||
// b.iter(|| {
|
||||
// let input = black_box(input);
|
||||
// let nbt = simdnbt::owned::Nbt::new(&mut Cursor::new(input))
|
||||
// let nbt = simdnbt::owned::OptionalNbt::new(&mut Cursor::new(input))
|
||||
// .unwrap()
|
||||
// .unwrap();
|
||||
// // let _ = black_box(nbt.list("").unwrap().ints());
|
||||
// black_box(nbt);
|
||||
// })
|
||||
// });
|
||||
let nbt = simdnbt::owned::Nbt::new(&mut Cursor::new(input))
|
||||
let nbt = simdnbt::owned::OptionalNbt::read(&mut Cursor::new(input))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
group.bench_function("simdnbt_owned_write", |b| {
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
|||
graphite_items_from_nbt(graphite_binary::nbt::decode::read(&mut &input[..]).unwrap())
|
||||
.unwrap();
|
||||
let simdnbt_nbt = simdnbt_items_from_nbt(
|
||||
simdnbt::borrow::Nbt::new(&mut Cursor::new(input))
|
||||
simdnbt::borrow::OptionalNbt::read(&mut Cursor::new(input))
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
)
|
||||
|
@ -63,7 +63,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
|||
group.bench_function("simdnbt_parse", |b| {
|
||||
b.iter(|| {
|
||||
let input = black_box(input);
|
||||
let nbt = black_box(simdnbt::borrow::Nbt::new(&mut Cursor::new(input)));
|
||||
let nbt = black_box(simdnbt::borrow::OptionalNbt::read(&mut Cursor::new(input)));
|
||||
let nbt = nbt.unwrap().unwrap();
|
||||
black_box(simdnbt_items_from_nbt(nbt));
|
||||
})
|
||||
|
|
|
@ -27,12 +27,12 @@ fn bench_file(filename: &str, c: &mut Criterion) {
|
|||
|
||||
group.bench_function("Decode", |b| {
|
||||
b.iter(|| {
|
||||
black_box(simdnbt::borrow::Nbt::new(&mut decoded_src_stream).unwrap());
|
||||
black_box(simdnbt::borrow::OptionalNbt::read(&mut decoded_src_stream).unwrap());
|
||||
decoded_src_stream.set_position(0);
|
||||
})
|
||||
});
|
||||
|
||||
let nbt = simdnbt::borrow::Nbt::new(&mut decoded_src_stream)
|
||||
let nbt = simdnbt::borrow::OptionalNbt::read(&mut decoded_src_stream)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
group.bench_function("Get", |b| {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashMap, hint::black_box, io::Cursor};
|
||||
|
||||
use simdnbt::borrow::Nbt;
|
||||
use simdnbt::borrow::{Nbt, OptionalNbt};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Item {
|
||||
|
@ -103,7 +103,7 @@ fn main() {
|
|||
let input = black_box(include_bytes!("../tests/realworld.nbt"));
|
||||
|
||||
for _ in 0..10000 {
|
||||
let nbt = Nbt::new(&mut Cursor::new(input));
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(input));
|
||||
let nbt = black_box(nbt.unwrap().unwrap());
|
||||
black_box(simdnbt_items_from_nbt(nbt));
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
}
|
||||
let input = input.as_slice();
|
||||
|
||||
let nbt = simdnbt::owned::Nbt::new(&mut Cursor::new(input))
|
||||
let nbt = simdnbt::owned::OptionalNbt::read(&mut Cursor::new(input))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
|
|
217
src/borrow/compound.rs
Normal file
217
src/borrow/compound.rs
Normal file
|
@ -0,0 +1,217 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use byteorder::{ReadBytesExt, BE};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
read_int_array, read_long_array, read_string, read_with_u32_length, unchecked_extend,
|
||||
unchecked_push, unchecked_write_string, write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID,
|
||||
DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID,
|
||||
MAX_DEPTH, SHORT_ID, STRING_ID,
|
||||
},
|
||||
Mutf8Str, ReadError,
|
||||
};
|
||||
|
||||
use super::{list::ListTag, Tag};
|
||||
|
||||
/// A list of named tags. The order of the tags is preserved.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct CompoundTag<'a> {
|
||||
values: Vec<(&'a Mutf8Str, Tag<'a>)>,
|
||||
}
|
||||
|
||||
impl<'a> CompoundTag<'a> {
|
||||
pub fn new(data: &mut Cursor<&'a [u8]>, depth: usize) -> Result<Self, ReadError> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(ReadError::MaxDepthExceeded);
|
||||
}
|
||||
let mut values = Vec::with_capacity(4);
|
||||
loop {
|
||||
let tag_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if tag_type == END_ID {
|
||||
break;
|
||||
}
|
||||
let tag_name = read_string(data)?;
|
||||
|
||||
match tag_type {
|
||||
BYTE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Byte(data.read_i8().map_err(|_| ReadError::UnexpectedEof)?),
|
||||
)),
|
||||
SHORT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Short(
|
||||
data.read_i16::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
INT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Int(
|
||||
data.read_i32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
LONG_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Long(
|
||||
data.read_i64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
FLOAT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Float(
|
||||
data.read_f32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
DOUBLE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Double(
|
||||
data.read_f64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
BYTE_ARRAY_ID => {
|
||||
values.push((tag_name, Tag::ByteArray(read_with_u32_length(data, 1)?)))
|
||||
}
|
||||
STRING_ID => values.push((tag_name, Tag::String(read_string(data)?))),
|
||||
LIST_ID => values.push((tag_name, Tag::List(ListTag::new(data, depth + 1)?))),
|
||||
COMPOUND_ID => {
|
||||
values.push((tag_name, Tag::Compound(CompoundTag::new(data, depth + 1)?)))
|
||||
}
|
||||
INT_ARRAY_ID => values.push((tag_name, Tag::IntArray(read_int_array(data)?))),
|
||||
LONG_ARRAY_ID => values.push((tag_name, Tag::LongArray(read_long_array(data)?))),
|
||||
_ => return Err(ReadError::UnknownTagId(tag_type)),
|
||||
}
|
||||
}
|
||||
Ok(Self { values })
|
||||
}
|
||||
|
||||
pub fn write(&self, data: &mut Vec<u8>) {
|
||||
for (name, tag) in &self.values {
|
||||
// reserve 4 bytes extra so we can avoid reallocating for small tags
|
||||
data.reserve(1 + 2 + name.len() + 4);
|
||||
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
|
||||
// 4 bytes of tag data.
|
||||
unsafe {
|
||||
unchecked_push(data, tag.id());
|
||||
unchecked_write_string(data, name);
|
||||
}
|
||||
match tag {
|
||||
Tag::Byte(byte) => unsafe {
|
||||
unchecked_push(data, *byte as u8);
|
||||
},
|
||||
Tag::Short(short) => unsafe {
|
||||
unchecked_extend(data, &short.to_be_bytes());
|
||||
},
|
||||
Tag::Int(int) => unsafe {
|
||||
unchecked_extend(data, &int.to_be_bytes());
|
||||
},
|
||||
Tag::Long(long) => {
|
||||
data.extend_from_slice(&long.to_be_bytes());
|
||||
}
|
||||
Tag::Float(float) => unsafe {
|
||||
unchecked_extend(data, &float.to_be_bytes());
|
||||
},
|
||||
Tag::Double(double) => {
|
||||
data.extend_from_slice(&double.to_be_bytes());
|
||||
}
|
||||
Tag::ByteArray(byte_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &byte_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(byte_array);
|
||||
}
|
||||
Tag::String(string) => {
|
||||
write_string(data, string);
|
||||
}
|
||||
Tag::List(list) => {
|
||||
list.write(data);
|
||||
}
|
||||
Tag::Compound(compound) => {
|
||||
compound.write(data);
|
||||
}
|
||||
Tag::IntArray(int_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &int_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&int_array.as_big_endian());
|
||||
}
|
||||
Tag::LongArray(long_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &long_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&long_array.as_big_endian());
|
||||
}
|
||||
}
|
||||
}
|
||||
data.push(END_ID);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, name: &str) -> Option<&Tag<'a>> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, value) in &self.values {
|
||||
if key == &name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns whether there is a tag with the given name.
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, _) in &self.values {
|
||||
if key == &name {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn byte(&self, name: &str) -> Option<i8> {
|
||||
self.get(name).and_then(|tag| tag.byte())
|
||||
}
|
||||
pub fn short(&self, name: &str) -> Option<i16> {
|
||||
self.get(name).and_then(|tag| tag.short())
|
||||
}
|
||||
pub fn int(&self, name: &str) -> Option<i32> {
|
||||
self.get(name).and_then(|tag| tag.int())
|
||||
}
|
||||
pub fn long(&self, name: &str) -> Option<i64> {
|
||||
self.get(name).and_then(|tag| tag.long())
|
||||
}
|
||||
pub fn float(&self, name: &str) -> Option<f32> {
|
||||
self.get(name).and_then(|tag| tag.float())
|
||||
}
|
||||
pub fn double(&self, name: &str) -> Option<f64> {
|
||||
self.get(name).and_then(|tag| tag.double())
|
||||
}
|
||||
pub fn byte_array(&self, name: &str) -> Option<&[u8]> {
|
||||
self.get(name).and_then(|tag| tag.byte_array())
|
||||
}
|
||||
pub fn string(&self, name: &str) -> Option<&Mutf8Str> {
|
||||
self.get(name).and_then(|tag| tag.string())
|
||||
}
|
||||
pub fn list(&self, name: &str) -> Option<&ListTag<'a>> {
|
||||
self.get(name).and_then(|tag| tag.list())
|
||||
}
|
||||
pub fn compound(&self, name: &str) -> Option<&CompoundTag<'a>> {
|
||||
self.get(name).and_then(|tag| tag.compound())
|
||||
}
|
||||
pub fn int_array(&self, name: &str) -> Option<Vec<i32>> {
|
||||
self.get(name).and_then(|tag| tag.int_array())
|
||||
}
|
||||
pub fn long_array(&self, name: &str) -> Option<Vec<i64>> {
|
||||
self.get(name).and_then(|tag| tag.long_array())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &Tag<'a>)> {
|
||||
self.values.iter().map(|(k, v)| (*k, v))
|
||||
}
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
//! The borrowed variant of NBT. This is useful if you're only reading data and you can keep a reference to the original buffer.
|
||||
|
||||
pub mod compound;
|
||||
pub mod list;
|
||||
|
||||
use std::{io::Cursor, ops::Deref};
|
||||
|
||||
use byteorder::{ReadBytesExt, BE};
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
read_int_array, read_long_array, read_string, read_u32, read_with_u32_length,
|
||||
unchecked_extend, unchecked_push, unchecked_write_string, write_string, BYTE_ARRAY_ID,
|
||||
BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID,
|
||||
LONG_ARRAY_ID, LONG_ID, MAX_DEPTH, SHORT_ID, STRING_ID,
|
||||
read_string, read_u32, write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID,
|
||||
END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_DEPTH,
|
||||
SHORT_ID, STRING_ID,
|
||||
},
|
||||
raw_list::RawList,
|
||||
Mutf8Str, ReadError,
|
||||
};
|
||||
|
||||
use self::list::ListTag;
|
||||
use self::{compound::CompoundTag, list::ListTag};
|
||||
|
||||
/// A complete NBT container. This contains a name and a compound tag.
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -26,10 +26,44 @@ pub struct Nbt<'a> {
|
|||
tag: CompoundTag<'a>,
|
||||
}
|
||||
|
||||
/// A list of named tags. The order of the tags is preserved.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct CompoundTag<'a> {
|
||||
values: Vec<(&'a Mutf8Str, Tag<'a>)>,
|
||||
pub enum OptionalNbt<'a> {
|
||||
Some(Nbt<'a>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> OptionalNbt<'a> {
|
||||
/// Reads NBT from the given data. Returns `Ok(None)` if there is no data.
|
||||
pub fn read(data: &mut Cursor<&'a [u8]>) -> Result<OptionalNbt<'a>, ReadError> {
|
||||
let root_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if root_type == END_ID {
|
||||
return Ok(OptionalNbt::None);
|
||||
}
|
||||
if root_type != COMPOUND_ID {
|
||||
return Err(ReadError::InvalidRootType(root_type));
|
||||
}
|
||||
let name = read_string(data)?;
|
||||
let tag = CompoundTag::new(data, 0)?;
|
||||
|
||||
Ok(OptionalNbt::Some(Nbt { name, tag }))
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> Nbt<'a> {
|
||||
match self {
|
||||
OptionalNbt::Some(nbt) => nbt,
|
||||
OptionalNbt::None => panic!("called `OptionalNbt::unwrap()` on a `None` value"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_some(&self) -> bool {
|
||||
match self {
|
||||
OptionalNbt::Some(_) => true,
|
||||
OptionalNbt::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
!self.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Nbt<'a> {
|
||||
|
@ -47,21 +81,6 @@ impl<'a> Deref for Nbt<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Nbt<'a> {
|
||||
/// Reads NBT from the given data. Returns `Ok(None)` if there is no data.
|
||||
pub fn new(data: &mut Cursor<&'a [u8]>) -> Result<Option<Nbt<'a>>, ReadError> {
|
||||
let root_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if root_type == END_ID {
|
||||
return Ok(None);
|
||||
}
|
||||
if root_type != COMPOUND_ID {
|
||||
return Err(ReadError::InvalidRootType(root_type));
|
||||
}
|
||||
let name = read_string(data)?;
|
||||
let tag = CompoundTag::new(data, 0)?;
|
||||
|
||||
Ok(Some(Nbt { name, tag }))
|
||||
}
|
||||
|
||||
pub fn write(&self, data: &mut Vec<u8>) {
|
||||
data.push(COMPOUND_ID);
|
||||
write_string(data, self.name);
|
||||
|
@ -70,202 +89,6 @@ impl<'a> Nbt<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> CompoundTag<'a> {
|
||||
fn new(data: &mut Cursor<&'a [u8]>, depth: usize) -> Result<Self, ReadError> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(ReadError::MaxDepthExceeded);
|
||||
}
|
||||
let mut values = Vec::with_capacity(4);
|
||||
loop {
|
||||
let tag_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if tag_type == END_ID {
|
||||
break;
|
||||
}
|
||||
let tag_name = read_string(data)?;
|
||||
|
||||
match tag_type {
|
||||
BYTE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Byte(data.read_i8().map_err(|_| ReadError::UnexpectedEof)?),
|
||||
)),
|
||||
SHORT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Short(
|
||||
data.read_i16::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
INT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Int(
|
||||
data.read_i32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
LONG_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Long(
|
||||
data.read_i64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
FLOAT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Float(
|
||||
data.read_f32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
DOUBLE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Double(
|
||||
data.read_f64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
BYTE_ARRAY_ID => {
|
||||
values.push((tag_name, Tag::ByteArray(read_with_u32_length(data, 1)?)))
|
||||
}
|
||||
STRING_ID => values.push((tag_name, Tag::String(read_string(data)?))),
|
||||
LIST_ID => values.push((tag_name, Tag::List(ListTag::new(data, depth + 1)?))),
|
||||
COMPOUND_ID => {
|
||||
values.push((tag_name, Tag::Compound(CompoundTag::new(data, depth + 1)?)))
|
||||
}
|
||||
INT_ARRAY_ID => values.push((tag_name, Tag::IntArray(read_int_array(data)?))),
|
||||
LONG_ARRAY_ID => values.push((tag_name, Tag::LongArray(read_long_array(data)?))),
|
||||
_ => return Err(ReadError::UnknownTagId(tag_type)),
|
||||
}
|
||||
}
|
||||
Ok(Self { values })
|
||||
}
|
||||
|
||||
pub fn write(&self, data: &mut Vec<u8>) {
|
||||
for (name, tag) in &self.values {
|
||||
// reserve 4 bytes extra so we can avoid reallocating for small tags
|
||||
data.reserve(1 + 2 + name.len() + 4);
|
||||
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
|
||||
// 4 bytes of tag data.
|
||||
unsafe {
|
||||
unchecked_push(data, tag.id());
|
||||
unchecked_write_string(data, name);
|
||||
}
|
||||
match tag {
|
||||
Tag::Byte(byte) => unsafe {
|
||||
unchecked_push(data, *byte as u8);
|
||||
},
|
||||
Tag::Short(short) => unsafe {
|
||||
unchecked_extend(data, &short.to_be_bytes());
|
||||
},
|
||||
Tag::Int(int) => unsafe {
|
||||
unchecked_extend(data, &int.to_be_bytes());
|
||||
},
|
||||
Tag::Long(long) => {
|
||||
data.extend_from_slice(&long.to_be_bytes());
|
||||
}
|
||||
Tag::Float(float) => unsafe {
|
||||
unchecked_extend(data, &float.to_be_bytes());
|
||||
},
|
||||
Tag::Double(double) => {
|
||||
data.extend_from_slice(&double.to_be_bytes());
|
||||
}
|
||||
Tag::ByteArray(byte_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &byte_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(byte_array);
|
||||
}
|
||||
Tag::String(string) => {
|
||||
write_string(data, string);
|
||||
}
|
||||
Tag::List(list) => {
|
||||
list.write(data);
|
||||
}
|
||||
Tag::Compound(compound) => {
|
||||
compound.write(data);
|
||||
}
|
||||
Tag::IntArray(int_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &int_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&int_array.as_big_endian());
|
||||
}
|
||||
Tag::LongArray(long_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &long_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&long_array.as_big_endian());
|
||||
}
|
||||
}
|
||||
}
|
||||
data.push(END_ID);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, name: &str) -> Option<&Tag<'a>> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, value) in &self.values {
|
||||
if key == &name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns whether there is a tag with the given name.
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, _) in &self.values {
|
||||
if key == &name {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn byte(&self, name: &str) -> Option<i8> {
|
||||
self.get(name).and_then(|tag| tag.byte())
|
||||
}
|
||||
pub fn short(&self, name: &str) -> Option<i16> {
|
||||
self.get(name).and_then(|tag| tag.short())
|
||||
}
|
||||
pub fn int(&self, name: &str) -> Option<i32> {
|
||||
self.get(name).and_then(|tag| tag.int())
|
||||
}
|
||||
pub fn long(&self, name: &str) -> Option<i64> {
|
||||
self.get(name).and_then(|tag| tag.long())
|
||||
}
|
||||
pub fn float(&self, name: &str) -> Option<f32> {
|
||||
self.get(name).and_then(|tag| tag.float())
|
||||
}
|
||||
pub fn double(&self, name: &str) -> Option<f64> {
|
||||
self.get(name).and_then(|tag| tag.double())
|
||||
}
|
||||
pub fn byte_array(&self, name: &str) -> Option<&[u8]> {
|
||||
self.get(name).and_then(|tag| tag.byte_array())
|
||||
}
|
||||
pub fn string(&self, name: &str) -> Option<&Mutf8Str> {
|
||||
self.get(name).and_then(|tag| tag.string())
|
||||
}
|
||||
pub fn list(&self, name: &str) -> Option<&ListTag<'a>> {
|
||||
self.get(name).and_then(|tag| tag.list())
|
||||
}
|
||||
pub fn compound(&self, name: &str) -> Option<&CompoundTag<'a>> {
|
||||
self.get(name).and_then(|tag| tag.compound())
|
||||
}
|
||||
pub fn int_array(&self, name: &str) -> Option<Vec<i32>> {
|
||||
self.get(name).and_then(|tag| tag.int_array())
|
||||
}
|
||||
pub fn long_array(&self, name: &str) -> Option<Vec<i64>> {
|
||||
self.get(name).and_then(|tag| tag.long_array())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &Tag<'a>)> {
|
||||
self.values.iter().map(|(k, v)| (*k, v))
|
||||
}
|
||||
}
|
||||
|
||||
/// A single NBT tag.
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -372,7 +195,7 @@ impl<'a> Tag<'a> {
|
|||
mod tests {
|
||||
use std::io::Read;
|
||||
|
||||
use byteorder::WriteBytesExt;
|
||||
use byteorder::{WriteBytesExt, BE};
|
||||
use flate2::read::GzDecoder;
|
||||
|
||||
use crate::common::{INT_ID, LIST_ID, LONG_ID};
|
||||
|
@ -381,7 +204,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
let nbt = Nbt::new(&mut Cursor::new(include_bytes!(
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(include_bytes!(
|
||||
"../../tests/hello_world.nbt"
|
||||
)))
|
||||
.unwrap()
|
||||
|
@ -401,7 +224,9 @@ mod tests {
|
|||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
let nbt = Nbt::new(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&decoded_src))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(nbt.int("PersistentId"), Some(1946940766));
|
||||
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
|
||||
|
@ -414,7 +239,9 @@ mod tests {
|
|||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
let nbt = Nbt::new(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&decoded_src))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
|
||||
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
|
||||
|
@ -427,11 +254,13 @@ mod tests {
|
|||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
let nbt = Nbt::new(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&decoded_src))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let mut out = Vec::new();
|
||||
nbt.write(&mut out);
|
||||
let nbt = Nbt::new(&mut Cursor::new(&out)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&out)).unwrap().unwrap();
|
||||
|
||||
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
|
||||
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
|
||||
|
@ -439,7 +268,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn inttest_1023() {
|
||||
let nbt = Nbt::new(&mut Cursor::new(include_bytes!(
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(include_bytes!(
|
||||
"../../tests/inttest1023.nbt"
|
||||
)))
|
||||
.unwrap()
|
||||
|
@ -467,7 +296,7 @@ mod tests {
|
|||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
let nbt = Nbt::new(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let ints = nbt.list("").unwrap().ints().unwrap();
|
||||
for (i, &item) in ints.iter().enumerate() {
|
||||
assert_eq!(i as i32, item);
|
||||
|
@ -489,7 +318,7 @@ mod tests {
|
|||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
let nbt = Nbt::new(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let ints = nbt.list("").unwrap().ints().unwrap();
|
||||
for (i, &item) in ints.iter().enumerate() {
|
||||
assert_eq!(i as i32, item);
|
||||
|
@ -511,7 +340,7 @@ mod tests {
|
|||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
let nbt = Nbt::new(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let ints = nbt.list("").unwrap().longs().unwrap();
|
||||
for (i, &item) in ints.iter().enumerate() {
|
||||
assert_eq!(i as i64, item);
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
//! afaik, this is currently the fastest nbt decoder in existence.
|
||||
//!
|
||||
//! ```
|
||||
//! use simdnbt::borrow::Nbt;
|
||||
//! use simdnbt::borrow::{Nbt, OptionalNbt};
|
||||
//! use std::io::Cursor;
|
||||
//!
|
||||
//! let nbt = Nbt::new(&mut Cursor::new(include_bytes!("../tests/hello_world.nbt"))).unwrap().unwrap();
|
||||
//! let nbt = OptionalNbt::read(&mut Cursor::new(include_bytes!("../tests/hello_world.nbt"))).unwrap().unwrap();
|
||||
//! assert_eq!(nbt.name().to_str(), "hello world");
|
||||
//! assert_eq!(nbt.string("name").unwrap().to_str(), "Bananrama");
|
||||
//! ```
|
||||
|
|
311
src/owned/compound.rs
Normal file
311
src/owned/compound.rs
Normal file
|
@ -0,0 +1,311 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use byteorder::{ReadBytesExt, BE};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
read_int_array, read_long_array, read_string, read_with_u32_length,
|
||||
slice_into_u8_big_endian, unchecked_extend, unchecked_push, unchecked_write_string,
|
||||
write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID,
|
||||
INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_DEPTH, SHORT_ID, STRING_ID,
|
||||
},
|
||||
mutf8::Mutf8String,
|
||||
Mutf8Str, ReadError,
|
||||
};
|
||||
|
||||
use super::{list::ListTag, Tag};
|
||||
|
||||
/// A list of named tags. The order of the tags is preserved.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct CompoundTag {
|
||||
values: Vec<(Mutf8String, Tag)>,
|
||||
}
|
||||
|
||||
impl CompoundTag {
|
||||
pub fn new(data: &mut Cursor<&[u8]>, depth: usize) -> Result<Self, ReadError> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(ReadError::MaxDepthExceeded);
|
||||
}
|
||||
let mut values = Vec::with_capacity(8);
|
||||
loop {
|
||||
let tag_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if tag_type == END_ID {
|
||||
break;
|
||||
}
|
||||
let tag_name = read_string(data)?.to_owned();
|
||||
|
||||
match tag_type {
|
||||
BYTE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Byte(data.read_i8().map_err(|_| ReadError::UnexpectedEof)?),
|
||||
)),
|
||||
SHORT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Short(
|
||||
data.read_i16::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
INT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Int(
|
||||
data.read_i32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
LONG_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Long(
|
||||
data.read_i64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
FLOAT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Float(
|
||||
data.read_f32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
DOUBLE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Double(
|
||||
data.read_f64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
BYTE_ARRAY_ID => values.push((
|
||||
tag_name,
|
||||
Tag::ByteArray(read_with_u32_length(data, 1)?.to_owned()),
|
||||
)),
|
||||
STRING_ID => values.push((tag_name, Tag::String(read_string(data)?.to_owned()))),
|
||||
LIST_ID => values.push((tag_name, Tag::List(ListTag::new(data, depth + 1)?))),
|
||||
COMPOUND_ID => {
|
||||
values.push((tag_name, Tag::Compound(CompoundTag::new(data, depth + 1)?)))
|
||||
}
|
||||
INT_ARRAY_ID => {
|
||||
values.push((tag_name, Tag::IntArray(read_int_array(data)?.to_vec())))
|
||||
}
|
||||
LONG_ARRAY_ID => {
|
||||
values.push((tag_name, Tag::LongArray(read_long_array(data)?.to_vec())))
|
||||
}
|
||||
_ => return Err(ReadError::UnknownTagId(tag_type)),
|
||||
}
|
||||
}
|
||||
Ok(Self { values })
|
||||
}
|
||||
|
||||
pub fn write(&self, data: &mut Vec<u8>) {
|
||||
for (name, tag) in &self.values {
|
||||
// reserve 4 bytes extra so we can avoid reallocating for small tags
|
||||
data.reserve(1 + 2 + name.len() + 4);
|
||||
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
|
||||
// 4 bytes of tag data.
|
||||
unsafe {
|
||||
unchecked_push(data, tag.id());
|
||||
unchecked_write_string(data, name);
|
||||
}
|
||||
match tag {
|
||||
Tag::Byte(byte) => unsafe {
|
||||
unchecked_push(data, *byte as u8);
|
||||
},
|
||||
Tag::Short(short) => unsafe {
|
||||
unchecked_extend(data, &short.to_be_bytes());
|
||||
},
|
||||
Tag::Int(int) => unsafe {
|
||||
unchecked_extend(data, &int.to_be_bytes());
|
||||
},
|
||||
Tag::Long(long) => {
|
||||
data.extend_from_slice(&long.to_be_bytes());
|
||||
}
|
||||
Tag::Float(float) => unsafe {
|
||||
unchecked_extend(data, &float.to_be_bytes());
|
||||
},
|
||||
Tag::Double(double) => {
|
||||
data.extend_from_slice(&double.to_be_bytes());
|
||||
}
|
||||
Tag::ByteArray(byte_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &byte_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(byte_array);
|
||||
}
|
||||
Tag::String(string) => {
|
||||
write_string(data, string);
|
||||
}
|
||||
Tag::List(list) => {
|
||||
list.write(data);
|
||||
}
|
||||
Tag::Compound(compound) => {
|
||||
compound.write(data);
|
||||
}
|
||||
Tag::IntArray(int_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &int_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&slice_into_u8_big_endian(int_array));
|
||||
}
|
||||
Tag::LongArray(long_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &long_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&slice_into_u8_big_endian(long_array));
|
||||
}
|
||||
}
|
||||
}
|
||||
data.push(END_ID);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, name: &str) -> Option<&Tag> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, value) in &self.values {
|
||||
if key.as_str() == name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Tag> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, value) in &mut self.values {
|
||||
if key.as_str() == name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns whether there is a tag with the given name.
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, _) in &self.values {
|
||||
if key.as_str() == name {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn byte(&self, name: &str) -> Option<i8> {
|
||||
self.get(name).and_then(|tag| tag.byte())
|
||||
}
|
||||
pub fn byte_mut(&mut self, name: &str) -> Option<&mut i8> {
|
||||
self.get_mut(name).and_then(|tag| tag.byte_mut())
|
||||
}
|
||||
pub fn short(&self, name: &str) -> Option<i16> {
|
||||
self.get(name).and_then(|tag| tag.short())
|
||||
}
|
||||
pub fn short_mut(&mut self, name: &str) -> Option<&mut i16> {
|
||||
self.get_mut(name).and_then(|tag| tag.short_mut())
|
||||
}
|
||||
pub fn int(&self, name: &str) -> Option<i32> {
|
||||
self.get(name).and_then(|tag| tag.int())
|
||||
}
|
||||
pub fn int_mut(&mut self, name: &str) -> Option<&mut i32> {
|
||||
self.get_mut(name).and_then(|tag| tag.int_mut())
|
||||
}
|
||||
pub fn long(&self, name: &str) -> Option<i64> {
|
||||
self.get(name).and_then(|tag| tag.long())
|
||||
}
|
||||
pub fn long_mut(&mut self, name: &str) -> Option<&mut i64> {
|
||||
self.get_mut(name).and_then(|tag| tag.long_mut())
|
||||
}
|
||||
pub fn float(&self, name: &str) -> Option<f32> {
|
||||
self.get(name).and_then(|tag| tag.float())
|
||||
}
|
||||
pub fn float_mut(&mut self, name: &str) -> Option<&mut f32> {
|
||||
self.get_mut(name).and_then(|tag| tag.float_mut())
|
||||
}
|
||||
pub fn double(&self, name: &str) -> Option<f64> {
|
||||
self.get(name).and_then(|tag| tag.double())
|
||||
}
|
||||
pub fn double_mut(&mut self, name: &str) -> Option<&mut f64> {
|
||||
self.get_mut(name).and_then(|tag| tag.double_mut())
|
||||
}
|
||||
pub fn byte_array(&self, name: &str) -> Option<&[u8]> {
|
||||
self.get(name).and_then(|tag| tag.byte_array())
|
||||
}
|
||||
pub fn byte_array_mut(&mut self, name: &str) -> Option<&mut Vec<u8>> {
|
||||
self.get_mut(name).and_then(|tag| tag.byte_array_mut())
|
||||
}
|
||||
pub fn string(&self, name: &str) -> Option<&Mutf8Str> {
|
||||
self.get(name).and_then(|tag| tag.string())
|
||||
}
|
||||
pub fn string_mut(&mut self, name: &str) -> Option<&mut Mutf8String> {
|
||||
self.get_mut(name).and_then(|tag| tag.string_mut())
|
||||
}
|
||||
pub fn list(&self, name: &str) -> Option<&ListTag> {
|
||||
self.get(name).and_then(|tag| tag.list())
|
||||
}
|
||||
pub fn list_mut(&mut self, name: &str) -> Option<&mut ListTag> {
|
||||
self.get_mut(name).and_then(|tag| tag.list_mut())
|
||||
}
|
||||
pub fn compound(&self, name: &str) -> Option<&CompoundTag> {
|
||||
self.get(name).and_then(|tag| tag.compound())
|
||||
}
|
||||
pub fn compound_mut(&mut self, name: &str) -> Option<&mut CompoundTag> {
|
||||
self.get_mut(name).and_then(|tag| tag.compound_mut())
|
||||
}
|
||||
pub fn int_array(&self, name: &str) -> Option<&[i32]> {
|
||||
self.get(name).and_then(|tag| tag.int_array())
|
||||
}
|
||||
pub fn int_array_mut(&mut self, name: &str) -> Option<&mut Vec<i32>> {
|
||||
self.get_mut(name).and_then(|tag| tag.int_array_mut())
|
||||
}
|
||||
pub fn long_array(&self, name: &str) -> Option<&[i64]> {
|
||||
self.get(name).and_then(|tag| tag.long_array())
|
||||
}
|
||||
pub fn long_array_mut(&mut self, name: &str) -> Option<&mut Vec<i64>> {
|
||||
self.get_mut(name).and_then(|tag| tag.long_array_mut())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &Tag)> {
|
||||
self.values.iter().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Mutf8Str, &mut Tag)> {
|
||||
self.values.iter_mut().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
pub fn keys(&self) -> impl Iterator<Item = &Mutf8Str> {
|
||||
self.values.iter().map(|(k, _)| k.as_str())
|
||||
}
|
||||
pub fn keys_mut(&mut self) -> impl Iterator<Item = &mut Mutf8String> {
|
||||
self.values.iter_mut().map(|(k, _)| k)
|
||||
}
|
||||
pub fn values(&self) -> impl Iterator<Item = &Tag> {
|
||||
self.values.iter().map(|(_, v)| v)
|
||||
}
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Tag> {
|
||||
self.values.iter_mut().map(|(_, v)| v)
|
||||
}
|
||||
pub fn into_iter(self) -> impl Iterator<Item = (Mutf8String, Tag)> {
|
||||
self.values.into_iter()
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.values.clear();
|
||||
}
|
||||
pub fn insert(&mut self, name: Mutf8String, tag: Tag) {
|
||||
self.values.push((name, tag));
|
||||
}
|
||||
pub fn remove(&mut self, name: &str) -> Option<Tag> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for i in 0..self.values.len() {
|
||||
if self.values[i].0.as_str() == name {
|
||||
return Some(self.values.remove(i).1);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ use crate::{
|
|||
ReadError,
|
||||
};
|
||||
|
||||
use super::{read_u32, CompoundTag, MAX_DEPTH};
|
||||
use super::{compound::CompoundTag, read_u32, MAX_DEPTH};
|
||||
|
||||
/// A list of NBT tags of a single type.
|
||||
#[repr(u8)]
|
||||
|
|
389
src/owned/mod.rs
389
src/owned/mod.rs
|
@ -1,23 +1,23 @@
|
|||
//! The owned variant of NBT. This is useful if you're writing data from scratch or if you can't keep a reference to the original data.
|
||||
|
||||
pub mod compound;
|
||||
pub mod list;
|
||||
|
||||
use std::{io::Cursor, ops::Deref};
|
||||
|
||||
use byteorder::{ReadBytesExt, BE};
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
read_int_array, read_long_array, read_string, read_u32, read_with_u32_length,
|
||||
slice_into_u8_big_endian, unchecked_extend, unchecked_push, unchecked_write_string,
|
||||
write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID,
|
||||
INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_DEPTH, SHORT_ID, STRING_ID,
|
||||
read_string, read_u32, write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID,
|
||||
END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_DEPTH,
|
||||
SHORT_ID, STRING_ID,
|
||||
},
|
||||
mutf8::Mutf8String,
|
||||
Mutf8Str, ReadError,
|
||||
};
|
||||
|
||||
use self::list::ListTag;
|
||||
use self::{compound::CompoundTag, list::ListTag};
|
||||
|
||||
/// A complete NBT container. This contains a name and a compound tag.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -25,6 +25,47 @@ pub struct Nbt {
|
|||
name: Mutf8String,
|
||||
tag: CompoundTag,
|
||||
}
|
||||
|
||||
pub enum OptionalNbt {
|
||||
Some(Nbt),
|
||||
None,
|
||||
}
|
||||
|
||||
impl OptionalNbt {
|
||||
/// Reads NBT from the given data. Returns `Ok(None)` if there is no data.
|
||||
pub fn read(data: &mut Cursor<&[u8]>) -> Result<OptionalNbt, ReadError> {
|
||||
let root_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if root_type == END_ID {
|
||||
return Ok(OptionalNbt::None);
|
||||
}
|
||||
if root_type != COMPOUND_ID {
|
||||
return Err(ReadError::InvalidRootType(root_type));
|
||||
}
|
||||
let name = read_string(data)?.to_owned();
|
||||
let tag = CompoundTag::new(data, 0)?;
|
||||
|
||||
Ok(OptionalNbt::Some(Nbt { name, tag }))
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> Nbt {
|
||||
match self {
|
||||
OptionalNbt::Some(nbt) => nbt,
|
||||
OptionalNbt::None => panic!("called `OptionalNbt::unwrap()` on a `None` value"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_some(&self) -> bool {
|
||||
match self {
|
||||
OptionalNbt::Some(_) => true,
|
||||
OptionalNbt::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
!self.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Nbt {
|
||||
/// Get the name of the NBT compound. This is often an empty string.
|
||||
pub fn name(&self) -> &Mutf8Str {
|
||||
|
@ -40,21 +81,6 @@ impl Deref for Nbt {
|
|||
}
|
||||
|
||||
impl Nbt {
|
||||
/// Reads NBT from the given data. Returns `Ok(None)` if there is no data.
|
||||
pub fn new(data: &mut Cursor<&[u8]>) -> Result<Option<Nbt>, ReadError> {
|
||||
let root_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if root_type == END_ID {
|
||||
return Ok(None);
|
||||
}
|
||||
if root_type != COMPOUND_ID {
|
||||
return Err(ReadError::InvalidRootType(root_type));
|
||||
}
|
||||
let name = read_string(data)?.to_owned();
|
||||
let tag = CompoundTag::new(data, 0)?;
|
||||
|
||||
Ok(Some(Nbt { name, tag }))
|
||||
}
|
||||
|
||||
/// Writes the NBT to the given buffer.
|
||||
pub fn write(&self, data: &mut Vec<u8>) {
|
||||
data.push(COMPOUND_ID);
|
||||
|
@ -63,301 +89,6 @@ impl Nbt {
|
|||
}
|
||||
}
|
||||
|
||||
/// A list of named tags. The order of the tags is preserved.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct CompoundTag {
|
||||
values: Vec<(Mutf8String, Tag)>,
|
||||
}
|
||||
|
||||
impl CompoundTag {
|
||||
fn new(data: &mut Cursor<&[u8]>, depth: usize) -> Result<Self, ReadError> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(ReadError::MaxDepthExceeded);
|
||||
}
|
||||
let mut values = Vec::with_capacity(8);
|
||||
loop {
|
||||
let tag_type = data.read_u8().map_err(|_| ReadError::UnexpectedEof)?;
|
||||
if tag_type == END_ID {
|
||||
break;
|
||||
}
|
||||
let tag_name = read_string(data)?.to_owned();
|
||||
|
||||
match tag_type {
|
||||
BYTE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Byte(data.read_i8().map_err(|_| ReadError::UnexpectedEof)?),
|
||||
)),
|
||||
SHORT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Short(
|
||||
data.read_i16::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
INT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Int(
|
||||
data.read_i32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
LONG_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Long(
|
||||
data.read_i64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
FLOAT_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Float(
|
||||
data.read_f32::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
DOUBLE_ID => values.push((
|
||||
tag_name,
|
||||
Tag::Double(
|
||||
data.read_f64::<BE>()
|
||||
.map_err(|_| ReadError::UnexpectedEof)?,
|
||||
),
|
||||
)),
|
||||
BYTE_ARRAY_ID => values.push((
|
||||
tag_name,
|
||||
Tag::ByteArray(read_with_u32_length(data, 1)?.to_owned()),
|
||||
)),
|
||||
STRING_ID => values.push((tag_name, Tag::String(read_string(data)?.to_owned()))),
|
||||
LIST_ID => values.push((tag_name, Tag::List(ListTag::new(data, depth + 1)?))),
|
||||
COMPOUND_ID => {
|
||||
values.push((tag_name, Tag::Compound(CompoundTag::new(data, depth + 1)?)))
|
||||
}
|
||||
INT_ARRAY_ID => {
|
||||
values.push((tag_name, Tag::IntArray(read_int_array(data)?.to_vec())))
|
||||
}
|
||||
LONG_ARRAY_ID => {
|
||||
values.push((tag_name, Tag::LongArray(read_long_array(data)?.to_vec())))
|
||||
}
|
||||
_ => return Err(ReadError::UnknownTagId(tag_type)),
|
||||
}
|
||||
}
|
||||
Ok(Self { values })
|
||||
}
|
||||
|
||||
pub fn write(&self, data: &mut Vec<u8>) {
|
||||
for (name, tag) in &self.values {
|
||||
// reserve 4 bytes extra so we can avoid reallocating for small tags
|
||||
data.reserve(1 + 2 + name.len() + 4);
|
||||
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
|
||||
// 4 bytes of tag data.
|
||||
unsafe {
|
||||
unchecked_push(data, tag.id());
|
||||
unchecked_write_string(data, name);
|
||||
}
|
||||
match tag {
|
||||
Tag::Byte(byte) => unsafe {
|
||||
unchecked_push(data, *byte as u8);
|
||||
},
|
||||
Tag::Short(short) => unsafe {
|
||||
unchecked_extend(data, &short.to_be_bytes());
|
||||
},
|
||||
Tag::Int(int) => unsafe {
|
||||
unchecked_extend(data, &int.to_be_bytes());
|
||||
},
|
||||
Tag::Long(long) => {
|
||||
data.extend_from_slice(&long.to_be_bytes());
|
||||
}
|
||||
Tag::Float(float) => unsafe {
|
||||
unchecked_extend(data, &float.to_be_bytes());
|
||||
},
|
||||
Tag::Double(double) => {
|
||||
data.extend_from_slice(&double.to_be_bytes());
|
||||
}
|
||||
Tag::ByteArray(byte_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &byte_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(byte_array);
|
||||
}
|
||||
Tag::String(string) => {
|
||||
write_string(data, string);
|
||||
}
|
||||
Tag::List(list) => {
|
||||
list.write(data);
|
||||
}
|
||||
Tag::Compound(compound) => {
|
||||
compound.write(data);
|
||||
}
|
||||
Tag::IntArray(int_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &int_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&slice_into_u8_big_endian(int_array));
|
||||
}
|
||||
Tag::LongArray(long_array) => {
|
||||
unsafe {
|
||||
unchecked_extend(data, &long_array.len().to_be_bytes());
|
||||
}
|
||||
data.extend_from_slice(&slice_into_u8_big_endian(long_array));
|
||||
}
|
||||
}
|
||||
}
|
||||
data.push(END_ID);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, name: &str) -> Option<&Tag> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, value) in &self.values {
|
||||
if key.as_str() == name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Tag> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, value) in &mut self.values {
|
||||
if key.as_str() == name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns whether there is a tag with the given name.
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for (key, _) in &self.values {
|
||||
if key.as_str() == name {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn byte(&self, name: &str) -> Option<i8> {
|
||||
self.get(name).and_then(|tag| tag.byte())
|
||||
}
|
||||
pub fn byte_mut(&mut self, name: &str) -> Option<&mut i8> {
|
||||
self.get_mut(name).and_then(|tag| tag.byte_mut())
|
||||
}
|
||||
pub fn short(&self, name: &str) -> Option<i16> {
|
||||
self.get(name).and_then(|tag| tag.short())
|
||||
}
|
||||
pub fn short_mut(&mut self, name: &str) -> Option<&mut i16> {
|
||||
self.get_mut(name).and_then(|tag| tag.short_mut())
|
||||
}
|
||||
pub fn int(&self, name: &str) -> Option<i32> {
|
||||
self.get(name).and_then(|tag| tag.int())
|
||||
}
|
||||
pub fn int_mut(&mut self, name: &str) -> Option<&mut i32> {
|
||||
self.get_mut(name).and_then(|tag| tag.int_mut())
|
||||
}
|
||||
pub fn long(&self, name: &str) -> Option<i64> {
|
||||
self.get(name).and_then(|tag| tag.long())
|
||||
}
|
||||
pub fn long_mut(&mut self, name: &str) -> Option<&mut i64> {
|
||||
self.get_mut(name).and_then(|tag| tag.long_mut())
|
||||
}
|
||||
pub fn float(&self, name: &str) -> Option<f32> {
|
||||
self.get(name).and_then(|tag| tag.float())
|
||||
}
|
||||
pub fn float_mut(&mut self, name: &str) -> Option<&mut f32> {
|
||||
self.get_mut(name).and_then(|tag| tag.float_mut())
|
||||
}
|
||||
pub fn double(&self, name: &str) -> Option<f64> {
|
||||
self.get(name).and_then(|tag| tag.double())
|
||||
}
|
||||
pub fn double_mut(&mut self, name: &str) -> Option<&mut f64> {
|
||||
self.get_mut(name).and_then(|tag| tag.double_mut())
|
||||
}
|
||||
pub fn byte_array(&self, name: &str) -> Option<&[u8]> {
|
||||
self.get(name).and_then(|tag| tag.byte_array())
|
||||
}
|
||||
pub fn byte_array_mut(&mut self, name: &str) -> Option<&mut Vec<u8>> {
|
||||
self.get_mut(name).and_then(|tag| tag.byte_array_mut())
|
||||
}
|
||||
pub fn string(&self, name: &str) -> Option<&Mutf8Str> {
|
||||
self.get(name).and_then(|tag| tag.string())
|
||||
}
|
||||
pub fn string_mut(&mut self, name: &str) -> Option<&mut Mutf8String> {
|
||||
self.get_mut(name).and_then(|tag| tag.string_mut())
|
||||
}
|
||||
pub fn list(&self, name: &str) -> Option<&ListTag> {
|
||||
self.get(name).and_then(|tag| tag.list())
|
||||
}
|
||||
pub fn list_mut(&mut self, name: &str) -> Option<&mut ListTag> {
|
||||
self.get_mut(name).and_then(|tag| tag.list_mut())
|
||||
}
|
||||
pub fn compound(&self, name: &str) -> Option<&CompoundTag> {
|
||||
self.get(name).and_then(|tag| tag.compound())
|
||||
}
|
||||
pub fn compound_mut(&mut self, name: &str) -> Option<&mut CompoundTag> {
|
||||
self.get_mut(name).and_then(|tag| tag.compound_mut())
|
||||
}
|
||||
pub fn int_array(&self, name: &str) -> Option<&[i32]> {
|
||||
self.get(name).and_then(|tag| tag.int_array())
|
||||
}
|
||||
pub fn int_array_mut(&mut self, name: &str) -> Option<&mut Vec<i32>> {
|
||||
self.get_mut(name).and_then(|tag| tag.int_array_mut())
|
||||
}
|
||||
pub fn long_array(&self, name: &str) -> Option<&[i64]> {
|
||||
self.get(name).and_then(|tag| tag.long_array())
|
||||
}
|
||||
pub fn long_array_mut(&mut self, name: &str) -> Option<&mut Vec<i64>> {
|
||||
self.get_mut(name).and_then(|tag| tag.long_array_mut())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &Tag)> {
|
||||
self.values.iter().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Mutf8Str, &mut Tag)> {
|
||||
self.values.iter_mut().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
pub fn keys(&self) -> impl Iterator<Item = &Mutf8Str> {
|
||||
self.values.iter().map(|(k, _)| k.as_str())
|
||||
}
|
||||
pub fn keys_mut(&mut self) -> impl Iterator<Item = &mut Mutf8String> {
|
||||
self.values.iter_mut().map(|(k, _)| k)
|
||||
}
|
||||
pub fn values(&self) -> impl Iterator<Item = &Tag> {
|
||||
self.values.iter().map(|(_, v)| v)
|
||||
}
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Tag> {
|
||||
self.values.iter_mut().map(|(_, v)| v)
|
||||
}
|
||||
pub fn into_iter(self) -> impl Iterator<Item = (Mutf8String, Tag)> {
|
||||
self.values.into_iter()
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.values.clear();
|
||||
}
|
||||
pub fn insert(&mut self, name: Mutf8String, tag: Tag) {
|
||||
self.values.push((name, tag));
|
||||
}
|
||||
pub fn remove(&mut self, name: &str) -> Option<Tag> {
|
||||
let name = Mutf8Str::from_str(name);
|
||||
let name = name.as_ref();
|
||||
for i in 0..self.values.len() {
|
||||
if self.values[i].0.as_str() == name {
|
||||
return Some(self.values.remove(i).1);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A single NBT tag.
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -536,7 +267,7 @@ impl Tag {
|
|||
mod tests {
|
||||
use std::io::Read;
|
||||
|
||||
use byteorder::WriteBytesExt;
|
||||
use byteorder::{WriteBytesExt, BE};
|
||||
use flate2::read::GzDecoder;
|
||||
|
||||
use crate::common::{INT_ID, LIST_ID, LONG_ID};
|
||||
|
@ -545,7 +276,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
let nbt = Nbt::new(&mut Cursor::new(include_bytes!(
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(include_bytes!(
|
||||
"../../tests/hello_world.nbt"
|
||||
)))
|
||||
.unwrap()
|
||||
|
@ -565,7 +296,9 @@ mod tests {
|
|||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
let nbt = Nbt::new(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&decoded_src))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(nbt.int("PersistentId"), Some(1946940766));
|
||||
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
|
||||
|
@ -578,7 +311,9 @@ mod tests {
|
|||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
let nbt = Nbt::new(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&decoded_src))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
|
||||
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
|
||||
|
@ -591,11 +326,13 @@ mod tests {
|
|||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
let nbt = Nbt::new(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&decoded_src))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let mut out = Vec::new();
|
||||
nbt.write(&mut out);
|
||||
let nbt = Nbt::new(&mut Cursor::new(&out)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&out)).unwrap().unwrap();
|
||||
|
||||
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
|
||||
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
|
||||
|
@ -603,7 +340,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn inttest_1023() {
|
||||
let nbt = Nbt::new(&mut Cursor::new(include_bytes!(
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(include_bytes!(
|
||||
"../../tests/inttest1023.nbt"
|
||||
)))
|
||||
.unwrap()
|
||||
|
@ -631,7 +368,7 @@ mod tests {
|
|||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
let nbt = Nbt::new(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let ints = nbt.list("").unwrap().ints().unwrap();
|
||||
for (i, &item) in ints.iter().enumerate() {
|
||||
assert_eq!(i as i32, item);
|
||||
|
@ -653,7 +390,7 @@ mod tests {
|
|||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
let nbt = Nbt::new(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let ints = nbt.list("").unwrap().ints().unwrap();
|
||||
for (i, &item) in ints.iter().enumerate() {
|
||||
assert_eq!(i as i32, item);
|
||||
|
@ -675,7 +412,7 @@ mod tests {
|
|||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
let nbt = Nbt::new(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let nbt = OptionalNbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
|
||||
let ints = nbt.list("").unwrap().longs().unwrap();
|
||||
for (i, &item) in ints.iter().enumerate() {
|
||||
assert_eq!(i as i64, item);
|
||||
|
|
Loading…
Add table
Reference in a new issue