1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 15:36:03 +00:00

OptionalNbt

This commit is contained in:
mat 2023-09-21 17:48:42 -05:00
parent 837b5990e7
commit 76347de9eb
11 changed files with 665 additions and 571 deletions

View file

@ -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| {

View file

@ -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));
})

View file

@ -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| {

View file

@ -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));
}

View file

@ -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
View 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))
}
}

View file

@ -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);

View file

@ -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
View 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
}
}

View file

@ -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)]

View file

@ -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);