From b59715db9dd542ac882a1251d83382a566e995fd Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 19:34:49 +0800 Subject: [PATCH 01/30] feat!: proper heterogeneous lists --- src/lib.rs | 1 + src/macros.rs | 4 +- src/nbt.rs | 1 + src/nbt/compound.rs | 9 +++- src/nbt/list.rs | 101 +++++++++++++++++++++++++++++++++++++++++ src/nbt/tag.rs | 47 +++++++++++++++---- src/serde/nbt_types.rs | 21 ++++++--- tests/list.rs | 31 +++++++++++++ tests/macros.rs | 6 +-- 9 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 src/nbt/list.rs create mode 100644 tests/list.rs diff --git a/src/lib.rs b/src/lib.rs index 76bac0e..1d3f660 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod nbt; pub mod serde; pub use crab_nbt::nbt::compound::NbtCompound; +pub use crab_nbt::nbt::list::NbtList; pub use crab_nbt::nbt::tag::NbtTag; pub use crab_nbt::nbt::Nbt; diff --git a/src/macros.rs b/src/macros.rs index d419c05..5dd492d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -84,10 +84,10 @@ macro_rules! nbt_inner { $crate::NbtTag::ByteArray(::bytes::Bytes::from_iter([$($lit),*])) }; ([$($lit:literal),* $(,)?]) => { - $crate::NbtTag::List(::std::vec![$($lit.into()),*]) + $crate::NbtTag::List($crate::NbtList::from_iter(::std::vec![$($lit.into()),*])) }; ([$($t:tt),* $(,)?]) => { - $crate::NbtTag::List(::std::vec![$(nbt_inner!($t).into()),*]) + $crate::NbtTag::List($crate::NbtList::from_iter(::std::vec![$(nbt_inner!($t).into()),*])) }; } diff --git a/src/nbt.rs b/src/nbt.rs index a4281ae..11021bc 100644 --- a/src/nbt.rs +++ b/src/nbt.rs @@ -8,6 +8,7 @@ use std::io::{Cursor, Write}; use std::ops::Deref; pub mod compound; +pub mod list; pub mod tag; pub mod utils; diff --git a/src/nbt/compound.rs b/src/nbt/compound.rs index bc8cfea..da90898 100644 --- a/src/nbt/compound.rs +++ b/src/nbt/compound.rs @@ -1,3 +1,4 @@ +use crate::nbt::list::NbtList; use crate::nbt::utils::{escape_name, join_formatted}; use crate::{error::Error, Nbt}; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -20,6 +21,12 @@ impl NbtCompound { } } + pub fn wrap(child: impl Into) -> Self { + Self { + child_tags: vec![(String::new(), child.into())], + } + } + pub fn deserialize_content(bytes: &mut impl Buf) -> Result { let mut compound = NbtCompound::new(); @@ -111,7 +118,7 @@ impl NbtCompound { self.get(name).and_then(|tag| tag.extract_string()) } - pub fn get_list(&self, name: &str) -> Option<&Vec> { + pub fn get_list(&self, name: &str) -> Option<&NbtList> { self.get(name).and_then(|tag| tag.extract_list()) } diff --git a/src/nbt/list.rs b/src/nbt/list.rs new file mode 100644 index 0000000..79f6067 --- /dev/null +++ b/src/nbt/list.rs @@ -0,0 +1,101 @@ +use crate::{NbtCompound, NbtTag}; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub enum NbtList { + Homogeneous((u8, Vec)), + Heterogeneous(Vec), +} + +impl Default for NbtList { + fn default() -> Self { + Self::new() + } +} + +impl NbtList { + pub fn new() -> NbtList { + Self::Homogeneous((NbtTag::End.get_type_id(), Vec::new())) + } + + pub fn with_capacity(initial_capacity: usize) -> NbtList { + Self::Homogeneous(( + NbtTag::End.get_type_id(), + Vec::with_capacity(initial_capacity), + )) + } + + pub fn push(mut self, element: impl Into) -> Self { + let element: NbtTag = element.into(); + match self { + NbtList::Homogeneous((mut ty, ref mut v)) => { + let type_id = element.get_type_id(); + if ty == type_id || ty == NbtTag::End.get_type_id() { + ty = type_id; + v.push(element); + NbtList::Homogeneous((ty, std::mem::take(v))) + } else { + let new = self.into_heterogeneous(); + new.push(element) + } + } + NbtList::Heterogeneous(mut v) => { + v.push(NbtCompound { + child_tags: vec![("".to_string(), element)], + }); + NbtList::Heterogeneous(v) + } + } + } + + pub fn into_heterogeneous(self) -> Self { + match self { + NbtList::Homogeneous((_, a)) => { + let mut b: Vec = Vec::with_capacity(a.capacity()); + for e in a { + b.push(NbtCompound { + child_tags: vec![("".to_string(), e)], + }) + } + NbtList::Heterogeneous(b) + } + NbtList::Heterogeneous(_) => self, + } + } + + pub fn len(&self) -> usize { + match self { + NbtList::Homogeneous((_, v)) => v.len(), + NbtList::Heterogeneous(v) => v.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn as_homogeneous(&self) -> Option<&Vec> { + match self { + NbtList::Homogeneous((_, v)) => Some(v), + NbtList::Heterogeneous(_) => None, + } + } + + pub fn as_heterogeneous(&self) -> Option<&Vec> { + match self { + NbtList::Homogeneous(_) => None, + NbtList::Heterogeneous(v) => Some(v), + } + } +} + +impl FromIterator for NbtList { + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter(); + let mut list = NbtList::with_capacity(iter.size_hint().0); + for element in iter { + list = list.push(element); + } + + list + } +} diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 40d6a68..516861c 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -6,6 +6,8 @@ use derive_more::From; use std::fmt::{self, Display, Formatter}; use std::io::Cursor; +use crate::nbt::list::NbtList; + /// Enum representing the different types of NBT tags. /// Each variant corresponds to a different type of data that can be stored in an NBT tag. #[repr(u8)] @@ -20,7 +22,7 @@ pub enum NbtTag { Double(f64) = DOUBLE_ID, ByteArray(Bytes) = BYTE_ARRAY_ID, String(String) = STRING_ID, - List(Vec) = LIST_ID, + List(NbtList) = LIST_ID, Compound(NbtCompound) = COMPOUND_ID, IntArray(Vec) = INT_ARRAY_ID, LongArray(Vec) = LONG_ARRAY_ID, @@ -60,10 +62,26 @@ impl NbtTag { bytes.put_slice(&java_string); } NbtTag::List(list) => { - bytes.put_u8(list.first().unwrap_or(&NbtTag::End).get_type_id()); + match list { + NbtList::Homogeneous((ty, _)) => { + bytes.put_u8(*ty); + } + NbtList::Heterogeneous(_) => { + bytes.put_u8(COMPOUND_ID); + } + } bytes.put_i32(list.len() as i32); - for nbt_tag in list { - bytes.put(nbt_tag.serialize_data()) + match list { + NbtList::Homogeneous((_, tags)) => { + for nbt_tag in tags { + bytes.put(nbt_tag.serialize_data()) + } + } + NbtList::Heterogeneous(compounds) => { + for compound in compounds { + bytes.put(compound.serialize_content()) + } + } } } NbtTag::Compound(compound) => { @@ -130,11 +148,10 @@ impl NbtTag { LIST_ID => { let tag_type_id = bytes.get_u8(); let len = bytes.get_i32(); - let mut list = Vec::with_capacity(len as usize); + let mut list = NbtList::with_capacity(len as usize); for _ in 0..len { let tag = NbtTag::deserialize_data(bytes, tag_type_id)?; - assert_eq!(tag.get_type_id(), tag_type_id); - list.push(tag); + list = list.push(tag); } Ok(NbtTag::List(list)) } @@ -228,7 +245,7 @@ impl NbtTag { } } - pub fn extract_list(&self) -> Option<&Vec> { + pub fn extract_list(&self) -> Option<&NbtList> { match self { NbtTag::List(list) => Some(list), _ => None, @@ -288,7 +305,7 @@ impl Display for NbtTag { Self::Double(x) => write!(f, "{x:?}d"), Self::ByteArray(arr) => write_listlike(f, "B; ", "B", arr.iter().map(|b| *b as i8)), Self::String(s) => write!(f, "{}", escape_string_value(s)), - Self::List(list) => write_listlike(f, "", "", list), + Self::List(list) => write_nbt_list(f, "", "", list), Self::Compound(compound) => write!(f, "{compound}"), Self::IntArray(arr) => write_listlike(f, "I; ", "", arr), Self::LongArray(arr) => write_listlike(f, "L; ", "L", arr), @@ -296,6 +313,18 @@ impl Display for NbtTag { } } +fn write_nbt_list( + f: &mut Formatter<'_>, + prefix: &'static str, + affix: &'static str, + list: &NbtList, +) -> fmt::Result { + match list { + NbtList::Homogeneous((_, tags)) => write_listlike(f, prefix, affix, tags), + NbtList::Heterogeneous(compounds) => write_listlike(f, prefix, affix, compounds), + } +} + fn write_listlike>( f: &mut Formatter<'_>, prefix: &'static str, diff --git a/src/serde/nbt_types.rs b/src/serde/nbt_types.rs index 4565461..5d40129 100644 --- a/src/serde/nbt_types.rs +++ b/src/serde/nbt_types.rs @@ -1,4 +1,4 @@ -use crate::{NbtCompound, NbtTag}; +use crate::{NbtCompound, NbtList, NbtTag}; use serde::de::value::MapAccessDeserializer; use serde::{Deserialize, Serialize}; @@ -27,8 +27,17 @@ impl Serialize for NbtTag { NbtTag::List(list_items) => { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(Some(list_items.len()))?; - for item in list_items.iter() { - seq.serialize_element(item)?; + match list_items { + NbtList::Homogeneous((_, tags)) => { + for tag in tags { + seq.serialize_element(tag)?; + } + } + NbtList::Heterogeneous(compounds) => { + for compound in compounds { + seq.serialize_element(compound)?; + } + } } seq.end() } @@ -106,9 +115,9 @@ impl<'de> Deserialize<'de> for NbtTag { where A: serde::de::SeqAccess<'de>, { - let mut items = Vec::new(); - while let Some(tag) = seq.next_element()? { - items.push(tag); + let mut items = NbtList::new(); + while let Some(tag) = seq.next_element::()? { + items = items.push(tag); } Ok(NbtTag::List(items)) } diff --git a/tests/list.rs b/tests/list.rs new file mode 100644 index 0000000..eb3bc7a --- /dev/null +++ b/tests/list.rs @@ -0,0 +1,31 @@ +use crab_nbt::{NbtCompound, NbtList}; + +#[test] +fn list_types() { + let homogeneous = NbtList::new().push(1).push(2).push(3); + + assert_eq!( + homogeneous, + NbtList::Homogeneous((3, vec![1.into(), 2.into(), 3.into()])) + ); + + let heterogeneous = homogeneous + .clone() + .push("four") + .push(vec![5]) + .push(NbtCompound::from_iter([("six".into(), 7.into())])); + + assert_eq!( + heterogeneous, + NbtList::Heterogeneous(vec![ + NbtCompound::wrap(1), + NbtCompound::wrap(2), + NbtCompound::wrap(3), + NbtCompound::wrap("four"), + NbtCompound::wrap(vec![5]), + NbtCompound::wrap(NbtCompound { + child_tags: vec![("six".into(), 7.into())] + }) + ]) + ); +} diff --git a/tests/macros.rs b/tests/macros.rs index d99a504..883acb4 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,5 +1,5 @@ use bytes::Bytes; -use crab_nbt::{nbt, Nbt, NbtCompound, NbtTag}; +use crab_nbt::{nbt, Nbt, NbtCompound, NbtList, NbtTag}; #[test] fn nbt_macro_handles_empty_content() { @@ -29,7 +29,7 @@ fn nbt_macro_handles_array_content() { let array = nbt.get_list("array").unwrap(); - assert_eq!(array, &vec![1.into(), 2.into(), 3.into()]); + assert_eq!(array, &NbtList::from_iter([1.into(), 2.into(), 3.into()])); } #[test] @@ -55,7 +55,7 @@ fn nbt_macro_complex_object() { ("int_array".to_owned(), NbtTag::IntArray(vec![1])), ( "list".to_owned(), - NbtTag::List(vec!["a".into(), "b".into(), "c".into()]), + NbtTag::List(NbtList::from_iter(["a".into(), "b".into(), "c".into()])), ), ( "nbt_inner".to_owned(), From ef5d869b62b79d7f3372a187c93cac0a4872ab4e Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 19:38:27 +0800 Subject: [PATCH 02/30] implement IntoIterator --- src/nbt/list.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 79f6067..cfb9668 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -99,3 +99,16 @@ impl FromIterator for NbtList { list } } + +impl IntoIterator for NbtList { + type Item = NbtTag; + + type IntoIter = Box>; + + fn into_iter(self) -> Self::IntoIter { + match self { + NbtList::Homogeneous((_, v)) => Box::new(v.into_iter()), + NbtList::Heterogeneous(v) => Box::new(v.into_iter().map(NbtTag::Compound)), + } + } +} From 5030d1d84ed99da1145bf06067f5f0bbbe6a81eb Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 19:42:06 +0800 Subject: [PATCH 03/30] add element_type_id --- src/nbt/list.rs | 7 +++++++ src/nbt/tag.rs | 9 +-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index cfb9668..f192885 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -86,6 +86,13 @@ impl NbtList { NbtList::Heterogeneous(v) => Some(v), } } + + pub fn element_type_id(&self) -> u8 { + match self { + NbtList::Homogeneous((ty, _)) => *ty, + NbtList::Heterogeneous(_) => crate::nbt::utils::COMPOUND_ID, + } + } } impl FromIterator for NbtList { diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 516861c..4d297e6 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -62,14 +62,7 @@ impl NbtTag { bytes.put_slice(&java_string); } NbtTag::List(list) => { - match list { - NbtList::Homogeneous((ty, _)) => { - bytes.put_u8(*ty); - } - NbtList::Heterogeneous(_) => { - bytes.put_u8(COMPOUND_ID); - } - } + bytes.put_u8(list.element_type_id()); bytes.put_i32(list.len() as i32); match list { NbtList::Homogeneous((_, tags)) => { From 847f50381cfcd9349495822313e7c47eebbb179d Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 19:42:50 +0800 Subject: [PATCH 04/30] fix tests --- tests/list.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/list.rs b/tests/list.rs index eb3bc7a..7b5b7ee 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -13,7 +13,9 @@ fn list_types() { .clone() .push("four") .push(vec![5]) - .push(NbtCompound::from_iter([("six".into(), 7.into())])); + .push(NbtCompound { + child_tags: vec![("six".into(), 7.into())], + }); assert_eq!( heterogeneous, From 8a8578094d68abba6f80dff99521b139b3818b99 Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 19:47:19 +0800 Subject: [PATCH 05/30] parity --- src/nbt/list.rs | 10 +++++++--- tests/list.rs | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index f192885..e6f2290 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -39,9 +39,13 @@ impl NbtList { } } NbtList::Heterogeneous(mut v) => { - v.push(NbtCompound { - child_tags: vec![("".to_string(), element)], - }); + if let NbtTag::Compound(compound) = element { + v.push(compound); + } else { + v.push(NbtCompound { + child_tags: vec![("".to_string(), element)], + }); + } NbtList::Heterogeneous(v) } } diff --git a/tests/list.rs b/tests/list.rs index 7b5b7ee..c659e00 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -25,9 +25,9 @@ fn list_types() { NbtCompound::wrap(3), NbtCompound::wrap("four"), NbtCompound::wrap(vec![5]), - NbtCompound::wrap(NbtCompound { + NbtCompound { child_tags: vec![("six".into(), 7.into())] - }) + } ]) ); } From ae720115a7091191df520b376a46024acac3f402 Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 20:25:52 +0800 Subject: [PATCH 06/30] nicer IntoIterator --- src/nbt/list.rs | 73 ++++++++++++++++++++++++++++++------------ src/nbt/tag.rs | 4 +-- src/serde/nbt_types.rs | 2 +- tests/list.rs | 29 ++++++++++++++++- 4 files changed, 84 insertions(+), 24 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index e6f2290..a5ffc1b 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -2,7 +2,7 @@ use crate::{NbtCompound, NbtTag}; #[derive(Clone, Debug, PartialEq, PartialOrd)] pub enum NbtList { - Homogeneous((u8, Vec)), + Homogeneous(Vec), Heterogeneous(Vec), } @@ -14,25 +14,23 @@ impl Default for NbtList { impl NbtList { pub fn new() -> NbtList { - Self::Homogeneous((NbtTag::End.get_type_id(), Vec::new())) + Self::Homogeneous(Vec::new()) } pub fn with_capacity(initial_capacity: usize) -> NbtList { - Self::Homogeneous(( - NbtTag::End.get_type_id(), - Vec::with_capacity(initial_capacity), - )) + Self::Homogeneous(Vec::with_capacity(initial_capacity)) } pub fn push(mut self, element: impl Into) -> Self { let element: NbtTag = element.into(); + let current_type_id = self.element_type_id(); + match self { - NbtList::Homogeneous((mut ty, ref mut v)) => { - let type_id = element.get_type_id(); - if ty == type_id || ty == NbtTag::End.get_type_id() { - ty = type_id; + NbtList::Homogeneous(ref mut v) => { + let new_type_id = element.get_type_id(); + if current_type_id == new_type_id || current_type_id == NbtTag::End.get_type_id() { v.push(element); - NbtList::Homogeneous((ty, std::mem::take(v))) + NbtList::Homogeneous(std::mem::take(v)) } else { let new = self.into_heterogeneous(); new.push(element) @@ -53,7 +51,7 @@ impl NbtList { pub fn into_heterogeneous(self) -> Self { match self { - NbtList::Homogeneous((_, a)) => { + NbtList::Homogeneous(a) => { let mut b: Vec = Vec::with_capacity(a.capacity()); for e in a { b.push(NbtCompound { @@ -68,7 +66,7 @@ impl NbtList { pub fn len(&self) -> usize { match self { - NbtList::Homogeneous((_, v)) => v.len(), + NbtList::Homogeneous(v) => v.len(), NbtList::Heterogeneous(v) => v.len(), } } @@ -79,7 +77,7 @@ impl NbtList { pub fn as_homogeneous(&self) -> Option<&Vec> { match self { - NbtList::Homogeneous((_, v)) => Some(v), + NbtList::Homogeneous(v) => Some(v), NbtList::Heterogeneous(_) => None, } } @@ -93,7 +91,13 @@ impl NbtList { pub fn element_type_id(&self) -> u8 { match self { - NbtList::Homogeneous((ty, _)) => *ty, + NbtList::Homogeneous(v) => { + if v.is_empty() { + crate::nbt::utils::END_ID + } else { + v[0].get_type_id() + } + } NbtList::Heterogeneous(_) => crate::nbt::utils::COMPOUND_ID, } } @@ -111,15 +115,44 @@ impl FromIterator for NbtList { } } +pub struct IntoIter { + list: NbtList, + idx: usize, +} + +impl Iterator for IntoIter { + type Item = NbtTag; + + fn next(&mut self) -> Option { + if self.list.len() > self.idx { + let element = match self.list { + NbtList::Homogeneous(ref mut v) => std::mem::replace(&mut v[self.idx], NbtTag::End), + NbtList::Heterogeneous(ref mut v) => { + let mut compound = std::mem::replace(&mut v[self.idx], NbtCompound::new()); + if compound.child_tags.len() == 1 { + let child = &mut compound.child_tags[0]; + if child.0.is_empty() { + self.idx += 1; + return Some(std::mem::replace(&mut child.1, NbtTag::End)); + } + } + compound.into() + } + }; + self.idx += 1; + Some(element) + } else { + None + } + } +} + impl IntoIterator for NbtList { type Item = NbtTag; - type IntoIter = Box>; + type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { - match self { - NbtList::Homogeneous((_, v)) => Box::new(v.into_iter()), - NbtList::Heterogeneous(v) => Box::new(v.into_iter().map(NbtTag::Compound)), - } + IntoIter { list: self, idx: 0 } } } diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 4d297e6..af699ba 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -65,7 +65,7 @@ impl NbtTag { bytes.put_u8(list.element_type_id()); bytes.put_i32(list.len() as i32); match list { - NbtList::Homogeneous((_, tags)) => { + NbtList::Homogeneous(tags) => { for nbt_tag in tags { bytes.put(nbt_tag.serialize_data()) } @@ -313,7 +313,7 @@ fn write_nbt_list( list: &NbtList, ) -> fmt::Result { match list { - NbtList::Homogeneous((_, tags)) => write_listlike(f, prefix, affix, tags), + NbtList::Homogeneous(tags) => write_listlike(f, prefix, affix, tags), NbtList::Heterogeneous(compounds) => write_listlike(f, prefix, affix, compounds), } } diff --git a/src/serde/nbt_types.rs b/src/serde/nbt_types.rs index 5d40129..afac72f 100644 --- a/src/serde/nbt_types.rs +++ b/src/serde/nbt_types.rs @@ -28,7 +28,7 @@ impl Serialize for NbtTag { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(Some(list_items.len()))?; match list_items { - NbtList::Homogeneous((_, tags)) => { + NbtList::Homogeneous(tags) => { for tag in tags { seq.serialize_element(tag)?; } diff --git a/tests/list.rs b/tests/list.rs index c659e00..46b28b5 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -2,13 +2,23 @@ use crab_nbt::{NbtCompound, NbtList}; #[test] fn list_types() { + let empty = NbtList::new(); + let mut iter = empty.clone().into_iter(); + assert_eq!(iter.next(), None); + let homogeneous = NbtList::new().push(1).push(2).push(3); assert_eq!( homogeneous, - NbtList::Homogeneous((3, vec![1.into(), 2.into(), 3.into()])) + NbtList::Homogeneous(vec![1.into(), 2.into(), 3.into()]) ); + let mut iter = homogeneous.clone().into_iter(); + assert_eq!(iter.next(), Some(1.into())); + assert_eq!(iter.next(), Some(2.into())); + assert_eq!(iter.next(), Some(3.into())); + assert_eq!(iter.next(), None); + let heterogeneous = homogeneous .clone() .push("four") @@ -30,4 +40,21 @@ fn list_types() { } ]) ); + + let mut iter = heterogeneous.clone().into_iter(); + assert_eq!(iter.next(), Some(1.into())); + assert_eq!(iter.next(), Some(2.into())); + assert_eq!(iter.next(), Some(3.into())); + assert_eq!(iter.next(), Some("four".into())); + assert_eq!(iter.next(), Some(vec![5].into())); + assert_eq!( + iter.next(), + Some( + NbtCompound { + child_tags: vec![("six".into(), 7.into())] + } + .into() + ) + ); + assert_eq!(iter.next(), None); } From e07f402259722f0750da31c8029742a983f58179 Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 20:52:59 +0800 Subject: [PATCH 07/30] refactor --- src/nbt/list.rs | 97 +++++++++++++++++++++++++++++------------- src/nbt/tag.rs | 27 ++---------- src/serde/nbt_types.rs | 13 +----- tests/list.rs | 43 +++++-------------- 4 files changed, 84 insertions(+), 96 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index a5ffc1b..aa9583e 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -3,7 +3,7 @@ use crate::{NbtCompound, NbtTag}; #[derive(Clone, Debug, PartialEq, PartialOrd)] pub enum NbtList { Homogeneous(Vec), - Heterogeneous(Vec), + Heterogeneous(Vec), } impl Default for NbtList { @@ -37,12 +37,11 @@ impl NbtList { } } NbtList::Heterogeneous(mut v) => { - if let NbtTag::Compound(compound) = element { - v.push(compound); - } else { - v.push(NbtCompound { - child_tags: vec![("".to_string(), element)], - }); + match element { + NbtTag::Compound(_) => { + v.push(element); + } + _ => v.push(NbtCompound::wrap(element).into()), } NbtList::Heterogeneous(v) } @@ -51,14 +50,13 @@ impl NbtList { pub fn into_heterogeneous(self) -> Self { match self { - NbtList::Homogeneous(a) => { - let mut b: Vec = Vec::with_capacity(a.capacity()); - for e in a { - b.push(NbtCompound { - child_tags: vec![("".to_string(), e)], - }) + NbtList::Homogeneous(mut v) => { + for e in &mut v { + if !matches!(e, NbtTag::Compound(_)) { + *e = NbtCompound::wrap(std::mem::replace(e, NbtTag::End)).into(); + } } - NbtList::Heterogeneous(b) + NbtList::Heterogeneous(v) } NbtList::Heterogeneous(_) => self, } @@ -75,20 +73,6 @@ impl NbtList { self.len() == 0 } - pub fn as_homogeneous(&self) -> Option<&Vec> { - match self { - NbtList::Homogeneous(v) => Some(v), - NbtList::Heterogeneous(_) => None, - } - } - - pub fn as_heterogeneous(&self) -> Option<&Vec> { - match self { - NbtList::Homogeneous(_) => None, - NbtList::Heterogeneous(v) => Some(v), - } - } - pub fn element_type_id(&self) -> u8 { match self { NbtList::Homogeneous(v) => { @@ -101,6 +85,10 @@ impl NbtList { NbtList::Heterogeneous(_) => crate::nbt::utils::COMPOUND_ID, } } + + pub fn iter<'a>(&'a self) -> Iter<'a> { + self.into_iter() + } } impl FromIterator for NbtList { @@ -115,11 +103,47 @@ impl FromIterator for NbtList { } } +pub struct Iter<'a> { + list: &'a NbtList, + idx: usize, +} + pub struct IntoIter { list: NbtList, idx: usize, } +impl<'a> Iterator for Iter<'a> { + type Item = &'a NbtTag; + + fn next(&mut self) -> Option { + if self.list.len() > self.idx { + let element = match &self.list { + NbtList::Homogeneous(v) => &v[self.idx], + NbtList::Heterogeneous(v) => { + let NbtTag::Compound(compound) = &v[self.idx] else { + unreachable!() + }; + + if compound.child_tags.len() == 1 { + let child = &compound.child_tags[0]; + if child.0.is_empty() { + self.idx += 1; + return Some(&child.1); + } + } + + &v[self.idx] + } + }; + self.idx += 1; + Some(&element) + } else { + None + } + } +} + impl Iterator for IntoIter { type Item = NbtTag; @@ -128,7 +152,12 @@ impl Iterator for IntoIter { let element = match self.list { NbtList::Homogeneous(ref mut v) => std::mem::replace(&mut v[self.idx], NbtTag::End), NbtList::Heterogeneous(ref mut v) => { - let mut compound = std::mem::replace(&mut v[self.idx], NbtCompound::new()); + let NbtTag::Compound(mut compound) = + std::mem::replace(&mut v[self.idx], NbtTag::End) + else { + unreachable!() + }; + if compound.child_tags.len() == 1 { let child = &mut compound.child_tags[0]; if child.0.is_empty() { @@ -156,3 +185,13 @@ impl IntoIterator for NbtList { IntoIter { list: self, idx: 0 } } } + +impl<'a> IntoIterator for &'a NbtList { + type Item = &'a NbtTag; + + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + Iter { list: self, idx: 0 } + } +} diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index af699ba..275c9dc 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -64,17 +64,8 @@ impl NbtTag { NbtTag::List(list) => { bytes.put_u8(list.element_type_id()); bytes.put_i32(list.len() as i32); - match list { - NbtList::Homogeneous(tags) => { - for nbt_tag in tags { - bytes.put(nbt_tag.serialize_data()) - } - } - NbtList::Heterogeneous(compounds) => { - for compound in compounds { - bytes.put(compound.serialize_content()) - } - } + for tag in list { + bytes.put(tag.serialize_data()) } } NbtTag::Compound(compound) => { @@ -298,7 +289,7 @@ impl Display for NbtTag { Self::Double(x) => write!(f, "{x:?}d"), Self::ByteArray(arr) => write_listlike(f, "B; ", "B", arr.iter().map(|b| *b as i8)), Self::String(s) => write!(f, "{}", escape_string_value(s)), - Self::List(list) => write_nbt_list(f, "", "", list), + Self::List(list) => write_listlike(f, "", "", list), Self::Compound(compound) => write!(f, "{compound}"), Self::IntArray(arr) => write_listlike(f, "I; ", "", arr), Self::LongArray(arr) => write_listlike(f, "L; ", "L", arr), @@ -306,18 +297,6 @@ impl Display for NbtTag { } } -fn write_nbt_list( - f: &mut Formatter<'_>, - prefix: &'static str, - affix: &'static str, - list: &NbtList, -) -> fmt::Result { - match list { - NbtList::Homogeneous(tags) => write_listlike(f, prefix, affix, tags), - NbtList::Heterogeneous(compounds) => write_listlike(f, prefix, affix, compounds), - } -} - fn write_listlike>( f: &mut Formatter<'_>, prefix: &'static str, diff --git a/src/serde/nbt_types.rs b/src/serde/nbt_types.rs index afac72f..07fdbb2 100644 --- a/src/serde/nbt_types.rs +++ b/src/serde/nbt_types.rs @@ -27,17 +27,8 @@ impl Serialize for NbtTag { NbtTag::List(list_items) => { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(Some(list_items.len()))?; - match list_items { - NbtList::Homogeneous(tags) => { - for tag in tags { - seq.serialize_element(tag)?; - } - } - NbtList::Heterogeneous(compounds) => { - for compound in compounds { - seq.serialize_element(compound)?; - } - } + for tag in list_items { + seq.serialize_element(tag)?; } seq.end() } diff --git a/tests/list.rs b/tests/list.rs index 46b28b5..cd5cf6c 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -7,16 +7,10 @@ fn list_types() { assert_eq!(iter.next(), None); let homogeneous = NbtList::new().push(1).push(2).push(3); - - assert_eq!( - homogeneous, - NbtList::Homogeneous(vec![1.into(), 2.into(), 3.into()]) - ); - - let mut iter = homogeneous.clone().into_iter(); - assert_eq!(iter.next(), Some(1.into())); - assert_eq!(iter.next(), Some(2.into())); - assert_eq!(iter.next(), Some(3.into())); + let mut iter = homogeneous.iter(); + assert_eq!(iter.next(), Some(&1.into())); + assert_eq!(iter.next(), Some(&2.into())); + assert_eq!(iter.next(), Some(&3.into())); assert_eq!(iter.next(), None); let heterogeneous = homogeneous @@ -26,31 +20,16 @@ fn list_types() { .push(NbtCompound { child_tags: vec![("six".into(), 7.into())], }); - - assert_eq!( - heterogeneous, - NbtList::Heterogeneous(vec![ - NbtCompound::wrap(1), - NbtCompound::wrap(2), - NbtCompound::wrap(3), - NbtCompound::wrap("four"), - NbtCompound::wrap(vec![5]), - NbtCompound { - child_tags: vec![("six".into(), 7.into())] - } - ]) - ); - - let mut iter = heterogeneous.clone().into_iter(); - assert_eq!(iter.next(), Some(1.into())); - assert_eq!(iter.next(), Some(2.into())); - assert_eq!(iter.next(), Some(3.into())); - assert_eq!(iter.next(), Some("four".into())); - assert_eq!(iter.next(), Some(vec![5].into())); + let mut iter = heterogeneous.iter(); + assert_eq!(iter.next(), Some(&1.into())); + assert_eq!(iter.next(), Some(&2.into())); + assert_eq!(iter.next(), Some(&3.into())); + assert_eq!(iter.next(), Some(&"four".into())); + assert_eq!(iter.next(), Some(&vec![5].into())); assert_eq!( iter.next(), Some( - NbtCompound { + &NbtCompound { child_tags: vec![("six".into(), 7.into())] } .into() From 7a5f1f1ac3eb7e622b54d26c1c0d3d0a92f31046 Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 20:54:37 +0800 Subject: [PATCH 08/30] clippy --- src/nbt/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index aa9583e..ebd6c8f 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -137,7 +137,7 @@ impl<'a> Iterator for Iter<'a> { } }; self.idx += 1; - Some(&element) + Some(element) } else { None } From 52985c865a5f402590fc01bda50bd71880c9f2c7 Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 21:27:29 +0800 Subject: [PATCH 09/30] add get and get_mut via polonius --- Cargo.toml | 1 + src/nbt/list.rs | 72 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a105e1a..b89086b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ simd_cesu8 = "1.0.1" derive_more = { version = "2.0.1", features = ["into", "from"] } thiserror = "2.0.11" serde = { version = "1.0.218", optional = true, features = ["derive"] } +polonius-the-crab = "0.5.0" [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/src/nbt/list.rs b/src/nbt/list.rs index ebd6c8f..ff8f186 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -21,6 +21,51 @@ impl NbtList { Self::Homogeneous(Vec::with_capacity(initial_capacity)) } + pub fn get(&self, index: usize) -> Option<&NbtTag> { + match self { + NbtList::Homogeneous(v) => v.get(index), + NbtList::Heterogeneous(v) => { + let NbtTag::Compound(compound) = &v.get(index)? else { + unreachable!() + }; + + if compound.child_tags.len() == 1 { + let child = &compound.child_tags[0]; + if child.0.is_empty() { + return Some(&child.1); + } + } + + Some(&v[index]) + } + } + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut NbtTag> { + match self { + NbtList::Homogeneous(v) => v.get_mut(index), + NbtList::Heterogeneous(ref mut v) => { + use polonius_the_crab::prelude::*; + + let mut tag = v.get_mut(index)?; + polonius!(|tag| -> Option<&'polonius mut NbtTag> { + let NbtTag::Compound(compound) = tag else { + unreachable!() + }; + + if compound.child_tags.len() == 1 { + let child = &mut compound.child_tags[0]; + if child.0.is_empty() { + polonius_return!(Some(&mut child.1)) + } + } + }); + + Some(tag) + } + } + } + pub fn push(mut self, element: impl Into) -> Self { let element: NbtTag = element.into(); let current_type_id = self.element_type_id(); @@ -108,6 +153,8 @@ pub struct Iter<'a> { idx: usize, } +// TODO: IterMut + pub struct IntoIter { list: NbtList, idx: usize, @@ -117,30 +164,11 @@ impl<'a> Iterator for Iter<'a> { type Item = &'a NbtTag; fn next(&mut self) -> Option { - if self.list.len() > self.idx { - let element = match &self.list { - NbtList::Homogeneous(v) => &v[self.idx], - NbtList::Heterogeneous(v) => { - let NbtTag::Compound(compound) = &v[self.idx] else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &compound.child_tags[0]; - if child.0.is_empty() { - self.idx += 1; - return Some(&child.1); - } - } - - &v[self.idx] - } - }; + let element = self.list.get(self.idx); + if element.is_some() { self.idx += 1; - Some(element) - } else { - None } + element } } From 2453590098657b5c8c49276b9691f05dd871c3de Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 21:50:04 +0800 Subject: [PATCH 10/30] ergonomics --- src/nbt/list.rs | 200 ++++++++++++++++++----------------------- src/nbt/tag.rs | 2 +- src/serde/nbt_types.rs | 2 +- 3 files changed, 88 insertions(+), 116 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index ff8f186..ad2430d 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -1,9 +1,9 @@ use crate::{NbtCompound, NbtTag}; #[derive(Clone, Debug, PartialEq, PartialOrd)] -pub enum NbtList { - Homogeneous(Vec), - Heterogeneous(Vec), +pub struct NbtList { + inner: Vec, + homogeneous: bool, } impl Default for NbtList { @@ -14,104 +14,103 @@ impl Default for NbtList { impl NbtList { pub fn new() -> NbtList { - Self::Homogeneous(Vec::new()) + Self { + inner: Vec::new(), + homogeneous: true, + } } pub fn with_capacity(initial_capacity: usize) -> NbtList { - Self::Homogeneous(Vec::with_capacity(initial_capacity)) + Self { + inner: Vec::with_capacity(initial_capacity), + homogeneous: true, + } + } + + pub fn is_homogeneous(&self) -> bool { + self.homogeneous + } + + pub fn is_heterogeneous(&self) -> bool { + !self.homogeneous } pub fn get(&self, index: usize) -> Option<&NbtTag> { - match self { - NbtList::Homogeneous(v) => v.get(index), - NbtList::Heterogeneous(v) => { - let NbtTag::Compound(compound) = &v.get(index)? else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &compound.child_tags[0]; - if child.0.is_empty() { - return Some(&child.1); - } - } + if self.homogeneous { + return self.inner.get(index); + } + + let NbtTag::Compound(compound) = &self.inner.get(index)? else { + unreachable!() + }; - Some(&v[index]) + if compound.child_tags.len() == 1 { + let child = &compound.child_tags[0]; + if child.0.is_empty() { + return Some(&child.1); } } + + Some(&self.inner[index]) } pub fn get_mut(&mut self, index: usize) -> Option<&mut NbtTag> { - match self { - NbtList::Homogeneous(v) => v.get_mut(index), - NbtList::Heterogeneous(ref mut v) => { - use polonius_the_crab::prelude::*; - - let mut tag = v.get_mut(index)?; - polonius!(|tag| -> Option<&'polonius mut NbtTag> { - let NbtTag::Compound(compound) = tag else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &mut compound.child_tags[0]; - if child.0.is_empty() { - polonius_return!(Some(&mut child.1)) - } - } - }); - - Some(tag) - } + use polonius_the_crab::prelude::*; + + if self.homogeneous { + return self.inner.get_mut(index); } + + let mut tag = self.inner.get_mut(index)?; + polonius!(|tag| -> Option<&'polonius mut NbtTag> { + let NbtTag::Compound(compound) = tag else { + unreachable!() + }; + + if compound.child_tags.len() == 1 { + let child = &mut compound.child_tags[0]; + if child.0.is_empty() { + polonius_return!(Some(&mut child.1)) + } + } + }); + + Some(tag) } - pub fn push(mut self, element: impl Into) -> Self { + pub fn push(&mut self, element: impl Into) { let element: NbtTag = element.into(); let current_type_id = self.element_type_id(); - match self { - NbtList::Homogeneous(ref mut v) => { - let new_type_id = element.get_type_id(); - if current_type_id == new_type_id || current_type_id == NbtTag::End.get_type_id() { - v.push(element); - NbtList::Homogeneous(std::mem::take(v)) - } else { - let new = self.into_heterogeneous(); - new.push(element) - } - } - NbtList::Heterogeneous(mut v) => { - match element { - NbtTag::Compound(_) => { - v.push(element); - } - _ => v.push(NbtCompound::wrap(element).into()), - } - NbtList::Heterogeneous(v) + if self.homogeneous { + let new_type_id = element.get_type_id(); + if current_type_id != new_type_id && current_type_id != NbtTag::End.get_type_id() { + self.make_heterogeneous(); } + self.inner.push(element); + return; + } + + match element { + NbtTag::Compound(_) => self.inner.push(element), + _ => self.inner.push(NbtCompound::wrap(element).into()), } } - pub fn into_heterogeneous(self) -> Self { - match self { - NbtList::Homogeneous(mut v) => { - for e in &mut v { - if !matches!(e, NbtTag::Compound(_)) { - *e = NbtCompound::wrap(std::mem::replace(e, NbtTag::End)).into(); - } - } - NbtList::Heterogeneous(v) + pub fn make_heterogeneous(&mut self) { + if self.is_heterogeneous() { + return; + } + + for e in &mut self.inner { + if !matches!(e, NbtTag::Compound(_)) { + *e = NbtCompound::wrap(std::mem::replace(e, NbtTag::End)).into(); } - NbtList::Heterogeneous(_) => self, } } pub fn len(&self) -> usize { - match self { - NbtList::Homogeneous(v) => v.len(), - NbtList::Heterogeneous(v) => v.len(), - } + self.inner.len() } pub fn is_empty(&self) -> bool { @@ -119,15 +118,12 @@ impl NbtList { } pub fn element_type_id(&self) -> u8 { - match self { - NbtList::Homogeneous(v) => { - if v.is_empty() { - crate::nbt::utils::END_ID - } else { - v[0].get_type_id() - } - } - NbtList::Heterogeneous(_) => crate::nbt::utils::COMPOUND_ID, + if self.is_heterogeneous() { + crate::nbt::utils::COMPOUND_ID + } else if self.inner.is_empty() { + crate::nbt::utils::END_ID + } else { + self.inner[0].get_type_id() } } @@ -141,7 +137,7 @@ impl FromIterator for NbtList { let iter = iter.into_iter(); let mut list = NbtList::with_capacity(iter.size_hint().0); for element in iter { - list = list.push(element); + list.push(element); } list @@ -164,11 +160,9 @@ impl<'a> Iterator for Iter<'a> { type Item = &'a NbtTag; fn next(&mut self) -> Option { - let element = self.list.get(self.idx); - if element.is_some() { - self.idx += 1; - } - element + let element = self.list.get(self.idx)?; + self.idx += 1; + Some(element) } } @@ -176,31 +170,9 @@ impl Iterator for IntoIter { type Item = NbtTag; fn next(&mut self) -> Option { - if self.list.len() > self.idx { - let element = match self.list { - NbtList::Homogeneous(ref mut v) => std::mem::replace(&mut v[self.idx], NbtTag::End), - NbtList::Heterogeneous(ref mut v) => { - let NbtTag::Compound(mut compound) = - std::mem::replace(&mut v[self.idx], NbtTag::End) - else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &mut compound.child_tags[0]; - if child.0.is_empty() { - self.idx += 1; - return Some(std::mem::replace(&mut child.1, NbtTag::End)); - } - } - compound.into() - } - }; - self.idx += 1; - Some(element) - } else { - None - } + let tag = self.list.get_mut(self.idx)?; + self.idx += 1; + Some(std::mem::replace(tag, NbtTag::End)) } } diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 275c9dc..2e6b776 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -135,7 +135,7 @@ impl NbtTag { let mut list = NbtList::with_capacity(len as usize); for _ in 0..len { let tag = NbtTag::deserialize_data(bytes, tag_type_id)?; - list = list.push(tag); + list.push(tag); } Ok(NbtTag::List(list)) } diff --git a/src/serde/nbt_types.rs b/src/serde/nbt_types.rs index 07fdbb2..ec176be 100644 --- a/src/serde/nbt_types.rs +++ b/src/serde/nbt_types.rs @@ -108,7 +108,7 @@ impl<'de> Deserialize<'de> for NbtTag { { let mut items = NbtList::new(); while let Some(tag) = seq.next_element::()? { - items = items.push(tag); + items.push(tag); } Ok(NbtTag::List(items)) } From 76fabc3aeb1fe9ca8161b1755cafc5563856b8df Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 22:13:00 +0800 Subject: [PATCH 11/30] fix up --- src/nbt/list.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++-- tests/list.rs | 32 +++++++++++++++-------------- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index ad2430d..70a771a 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -84,10 +84,10 @@ impl NbtList { if self.homogeneous { let new_type_id = element.get_type_id(); + self.inner.push(element); if current_type_id != new_type_id && current_type_id != NbtTag::End.get_type_id() { self.make_heterogeneous(); } - self.inner.push(element); return; } @@ -102,6 +102,7 @@ impl NbtList { return; } + self.homogeneous = false; for e in &mut self.inner { if !matches!(e, NbtTag::Compound(_)) { *e = NbtCompound::wrap(std::mem::replace(e, NbtTag::End)).into(); @@ -130,6 +131,10 @@ impl NbtList { pub fn iter<'a>(&'a self) -> Iter<'a> { self.into_iter() } + + pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a> { + self.into_iter() + } } impl FromIterator for NbtList { @@ -149,7 +154,10 @@ pub struct Iter<'a> { idx: usize, } -// TODO: IterMut +pub struct IterMut<'a> { + iter: std::slice::IterMut<'a, NbtTag>, + homogeneous: bool, +} pub struct IntoIter { list: NbtList, @@ -166,6 +174,35 @@ impl<'a> Iterator for Iter<'a> { } } +impl<'a> Iterator for IterMut<'a> { + type Item = &'a mut NbtTag; + + fn next(&mut self) -> Option { + use polonius_the_crab::prelude::*; + + let mut element = self.iter.next()?; + + if self.homogeneous { + return Some(element); + } + + polonius!(|element| -> Option<&'polonius mut NbtTag> { + let NbtTag::Compound(compound) = element else { + unreachable!() + }; + + if compound.child_tags.len() == 1 { + let child = &mut compound.child_tags[0]; + if child.0.is_empty() { + polonius_return!(Some(&mut child.1)) + } + } + }); + + Some(element) + } +} + impl Iterator for IntoIter { type Item = NbtTag; @@ -195,3 +232,16 @@ impl<'a> IntoIterator for &'a NbtList { Iter { list: self, idx: 0 } } } + +impl<'a> IntoIterator for &'a mut NbtList { + type Item = &'a mut NbtTag; + + type IntoIter = IterMut<'a>; + + fn into_iter(self) -> Self::IntoIter { + IterMut { + iter: self.inner.iter_mut(), + homogeneous: self.homogeneous, + } + } +} diff --git a/tests/list.rs b/tests/list.rs index cd5cf6c..4a5cc19 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -6,30 +6,32 @@ fn list_types() { let mut iter = empty.clone().into_iter(); assert_eq!(iter.next(), None); - let homogeneous = NbtList::new().push(1).push(2).push(3); + let mut homogeneous = NbtList::new(); + homogeneous.push(1); + homogeneous.push(2); + homogeneous.push(3); let mut iter = homogeneous.iter(); assert_eq!(iter.next(), Some(&1.into())); assert_eq!(iter.next(), Some(&2.into())); assert_eq!(iter.next(), Some(&3.into())); assert_eq!(iter.next(), None); - let heterogeneous = homogeneous - .clone() - .push("four") - .push(vec![5]) - .push(NbtCompound { - child_tags: vec![("six".into(), 7.into())], - }); - let mut iter = heterogeneous.iter(); - assert_eq!(iter.next(), Some(&1.into())); - assert_eq!(iter.next(), Some(&2.into())); - assert_eq!(iter.next(), Some(&3.into())); - assert_eq!(iter.next(), Some(&"four".into())); - assert_eq!(iter.next(), Some(&vec![5].into())); + let mut heterogeneous = homogeneous.clone(); + heterogeneous.push("four"); + heterogeneous.push(vec![5]); + heterogeneous.push(NbtCompound { + child_tags: vec![("six".into(), 7.into())], + }); + let mut iter = heterogeneous.iter_mut(); + assert_eq!(iter.next(), Some(&mut 1.into())); + assert_eq!(iter.next(), Some(&mut 2.into())); + assert_eq!(iter.next(), Some(&mut 3.into())); + assert_eq!(iter.next(), Some(&mut "four".into())); + assert_eq!(iter.next(), Some(&mut vec![5].into())); assert_eq!( iter.next(), Some( - &NbtCompound { + &mut NbtCompound { child_tags: vec![("six".into(), 7.into())] } .into() From 79c365c74c5c70a5aab069b5f925a0d1294be0e8 Mon Sep 17 00:00:00 2001 From: Qther Date: Mon, 6 Jul 2026 22:13:58 +0800 Subject: [PATCH 12/30] add tests for homogeneousity --- tests/list.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/list.rs b/tests/list.rs index 4a5cc19..4f7277e 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -10,6 +10,7 @@ fn list_types() { homogeneous.push(1); homogeneous.push(2); homogeneous.push(3); + assert!(homogeneous.is_homogeneous()); let mut iter = homogeneous.iter(); assert_eq!(iter.next(), Some(&1.into())); assert_eq!(iter.next(), Some(&2.into())); @@ -22,6 +23,7 @@ fn list_types() { heterogeneous.push(NbtCompound { child_tags: vec![("six".into(), 7.into())], }); + assert!(heterogeneous.is_heterogeneous()); let mut iter = heterogeneous.iter_mut(); assert_eq!(iter.next(), Some(&mut 1.into())); assert_eq!(iter.next(), Some(&mut 2.into())); From 0a2f93f9f69990079c76a42da803b309d17f15ad Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 13:14:15 +0800 Subject: [PATCH 13/30] add convenience methods --- src/nbt/list.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 70a771a..5d59dc4 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -1,3 +1,5 @@ +use bytes::{BufMut, Bytes, BytesMut}; + use crate::{NbtCompound, NbtTag}; #[derive(Clone, Debug, PartialEq, PartialOrd)] @@ -110,6 +112,54 @@ impl NbtList { } } + pub fn into_native(self) -> Result { + if self.is_heterogeneous() { + return Err(self); + } + + match self.element_type_id() { + crate::nbt::utils::BYTE_ID => { + let mut v = BytesMut::with_capacity(self.len()); + for e in self.into_iter() { + match e { + NbtTag::Byte(e) => v.put_i8(e), + _ => unreachable!(), + } + } + Ok(NbtTag::ByteArray(v.into())) + } + crate::nbt::utils::INT_ID => { + let mut v = Vec::with_capacity(self.len()); + for e in self.into_iter() { + match e { + NbtTag::Int(e) => v.push(e), + _ => unreachable!(), + } + } + Ok(NbtTag::IntArray(v)) + } + crate::nbt::utils::LONG_ID => { + let mut v = Vec::with_capacity(self.len()); + for e in self.into_iter() { + match e { + NbtTag::Long(e) => v.push(e), + _ => unreachable!(), + } + } + Ok(NbtTag::LongArray(v)) + } + _ => Err(self), + } + } + + pub fn into_inner(self) -> Vec { + self.inner + } + + pub fn contains(&self, element: &NbtTag) -> bool { + self.inner.contains(element) + } + pub fn len(&self) -> usize { self.inner.len() } @@ -245,3 +295,9 @@ impl<'a> IntoIterator for &'a mut NbtList { } } } + +impl AsRef<[NbtTag]> for NbtList { + fn as_ref(&self) -> &[NbtTag] { + &self.inner + } +} From dd78e09a69249b7aad01dc7feb6042763db3e538 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 13:44:22 +0800 Subject: [PATCH 14/30] more convenience functions --- src/nbt/list.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 5d59dc4..aace7b9 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -1,4 +1,7 @@ -use bytes::{BufMut, Bytes, BytesMut}; +use std::ops::{Index, IndexMut}; + +use bytes::{BufMut, BytesMut}; +use thiserror::Error; use crate::{NbtCompound, NbtTag}; @@ -14,6 +17,14 @@ impl Default for NbtList { } } +#[derive(Error, Debug)] +pub enum MakeHomogeneousError { + #[error("Not homogeneous")] + NotHomogeneous, + #[error("Non-Compound element in heterogeneous List")] + NonCompoundElement, +} + impl NbtList { pub fn new() -> NbtList { Self { @@ -99,6 +110,10 @@ impl NbtList { } } + pub fn remove(&mut self, index: usize) -> NbtTag { + self.inner.remove(index) + } + pub fn make_heterogeneous(&mut self) { if self.is_heterogeneous() { return; @@ -112,6 +127,40 @@ impl NbtList { } } + pub fn make_homogeneous(&mut self) -> Result<(), MakeHomogeneousError> { + if self.homogeneous { + return Ok(()); + } + + if self.is_empty() { + self.homogeneous = true; + return Ok(()); + } + + let first_element_id = self.get(0).unwrap().get_type_id(); + let mut new = Vec::with_capacity(self.len()); + for e in self.into_iter() { + if e.get_type_id() != first_element_id { + return Err(MakeHomogeneousError::NotHomogeneous); + } + + let NbtTag::Compound(ref mut compound) = e else { + return Err(MakeHomogeneousError::NonCompoundElement); + }; + + if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { + new.push(compound.child_tags.remove(0).1); + } else { + new.push(NbtTag::Compound(std::mem::replace( + compound, + NbtCompound::new(), + ))); + } + } + + Ok(()) + } + pub fn into_native(self) -> Result { if self.is_heterogeneous() { return Err(self); @@ -301,3 +350,17 @@ impl AsRef<[NbtTag]> for NbtList { &self.inner } } + +impl Index for NbtList { + type Output = NbtTag; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl IndexMut for NbtList { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} From fc1ac434179fa2d886798110a699b97288504554 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 13:45:26 +0800 Subject: [PATCH 15/30] add extend --- src/nbt/list.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index aace7b9..f2283f3 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -234,6 +234,10 @@ impl NbtList { pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a> { self.into_iter() } + + pub fn extend(&mut self, iter: impl IntoIterator) { + iter.into_iter().for_each(|e| self.push(e)); + } } impl FromIterator for NbtList { From 0cacc6c05d77aee8637adb7cc40fea97807d4f2b Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 13:49:02 +0800 Subject: [PATCH 16/30] add reserve and insert --- src/nbt/list.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index f2283f3..b513d10 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -40,6 +40,14 @@ impl NbtList { } } + pub fn reserve(&mut self, additional_capacity: usize) { + self.inner.reserve(additional_capacity) + } + + pub fn reserve_exact(&mut self, additional_capacity: usize) { + self.inner.reserve_exact(additional_capacity) + } + pub fn is_homogeneous(&self) -> bool { self.homogeneous } @@ -110,6 +118,25 @@ impl NbtList { } } + pub fn insert(&mut self, index: usize, element: impl Into) { + let element: NbtTag = element.into(); + let current_type_id = self.element_type_id(); + + if self.homogeneous { + let new_type_id = element.get_type_id(); + self.inner.insert(index, element); + if current_type_id != new_type_id && current_type_id != NbtTag::End.get_type_id() { + self.make_heterogeneous(); + } + return; + } + + match element { + NbtTag::Compound(_) => self.inner.insert(index, element), + _ => self.inner.insert(index, NbtCompound::wrap(element).into()), + } + } + pub fn remove(&mut self, index: usize) -> NbtTag { self.inner.remove(index) } @@ -236,6 +263,8 @@ impl NbtList { } pub fn extend(&mut self, iter: impl IntoIterator) { + let iter = iter.into_iter(); + self.reserve(iter.size_hint().0); iter.into_iter().for_each(|e| self.push(e)); } } From 60848dbae5bab68b9941c1073ba1c9e40acfbac9 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 13:56:15 +0800 Subject: [PATCH 17/30] fix extend --- src/nbt/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index b513d10..2c4bf7d 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -262,7 +262,7 @@ impl NbtList { self.into_iter() } - pub fn extend(&mut self, iter: impl IntoIterator) { + pub fn extend(&mut self, iter: impl IntoIterator) { let iter = iter.into_iter(); self.reserve(iter.size_hint().0); iter.into_iter().for_each(|e| self.push(e)); From 46066603fa251be2be4de171ea92807ab879ec2a Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 14:05:08 +0800 Subject: [PATCH 18/30] even more convenience functions --- src/nbt/list.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 2c4bf7d..1de925a 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -1,4 +1,4 @@ -use std::ops::{Index, IndexMut}; +use std::ops::{Index, IndexMut, RangeBounds}; use bytes::{BufMut, BytesMut}; use thiserror::Error; @@ -262,10 +262,22 @@ impl NbtList { self.into_iter() } - pub fn extend(&mut self, iter: impl IntoIterator) { + pub fn extend(&mut self, iter: T) + where + T: IntoIterator, + I: Into, + { let iter = iter.into_iter(); self.reserve(iter.size_hint().0); - iter.into_iter().for_each(|e| self.push(e)); + iter.into_iter().for_each(|e| self.push(e.into())); + } + + pub fn retain(&mut self, condition: impl FnMut(&NbtTag) -> bool) { + self.inner.retain(condition) + } + + pub fn drain(&mut self, range: impl RangeBounds) -> std::vec::Drain<'_, NbtTag> { + self.inner.drain(range) } } From 324ef792b7ee1a07e295aeb10ad853e508b5c970 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 16:42:08 +0800 Subject: [PATCH 19/30] fix make_homogeneous --- src/nbt/list.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 1de925a..2135950 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -184,6 +184,7 @@ impl NbtList { ))); } } + self.homogeneous = true; Ok(()) } From 6f601fe9d851e9b8e0997d80cead2b7d81cf96cc Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 18:29:06 +0800 Subject: [PATCH 20/30] fix deserialization --- src/nbt/list.rs | 4 ++++ src/nbt/tag.rs | 14 +++++++++++++- tests/list.rs | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 2135950..c7af3d2 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -233,6 +233,10 @@ impl NbtList { self.inner } + pub fn as_inner(&self) -> &Vec { + &self.inner + } + pub fn contains(&self, element: &NbtTag) -> bool { self.inner.contains(element) } diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 2e6b776..ccf6583 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -64,7 +64,7 @@ impl NbtTag { NbtTag::List(list) => { bytes.put_u8(list.element_type_id()); bytes.put_i32(list.len() as i32); - for tag in list { + for tag in list.as_inner() { bytes.put(tag.serialize_data()) } } @@ -133,10 +133,22 @@ impl NbtTag { let tag_type_id = bytes.get_u8(); let len = bytes.get_i32(); let mut list = NbtList::with_capacity(len as usize); + let mut all_compounds = true; + let mut any_singletons = false; for _ in 0..len { let tag = NbtTag::deserialize_data(bytes, tag_type_id)?; + if let NbtTag::Compound(compound) = &tag { + if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { + any_singletons = true; + } + } else { + all_compounds = false; + } list.push(tag); } + if all_compounds && any_singletons { + list.make_heterogeneous(); + } Ok(NbtTag::List(list)) } COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::deserialize_content(bytes)?)), diff --git a/tests/list.rs b/tests/list.rs index 4f7277e..b9b468d 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -1,4 +1,4 @@ -use crab_nbt::{NbtCompound, NbtList}; +use crab_nbt::{Nbt, NbtCompound, NbtList, NbtTag}; #[test] fn list_types() { @@ -17,6 +17,12 @@ fn list_types() { assert_eq!(iter.next(), Some(&3.into())); assert_eq!(iter.next(), None); + let mut serialized = NbtTag::List(homogeneous).serialize(); + let NbtTag::List(homogeneous) = NbtTag::deserialize(&mut serialized).unwrap() else { + panic!("homogeneous list did not survive round trip"); + }; + assert!(homogeneous.is_homogeneous()); + let mut heterogeneous = homogeneous.clone(); heterogeneous.push("four"); heterogeneous.push(vec![5]); @@ -40,4 +46,11 @@ fn list_types() { ) ); assert_eq!(iter.next(), None); + + let mut serialized = NbtTag::List(heterogeneous).serialize(); + eprintln!("serialized: {serialized:?}"); + let NbtTag::List(heterogeneous) = NbtTag::deserialize(&mut serialized).unwrap() else { + panic!("heterogeneous list did not survive round trip"); + }; + assert!(heterogeneous.is_heterogeneous()); } From 64cbe6be3533ed23f87ddebae8df668d297d3f4f Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 18:33:30 +0800 Subject: [PATCH 21/30] add unsafe methods to set homogeneousity for deserialization --- src/nbt/list.rs | 12 ++++++++++++ src/nbt/tag.rs | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index c7af3d2..5e889c2 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -229,6 +229,18 @@ impl NbtList { } } + /// # SAFETY + /// Caller to ensure that the data in the list is homogeneous + pub unsafe fn force_homogeneous(&mut self) { + self.homogeneous = true; + } + + /// # SAFETY + /// Caller to ensure that the data in the list is heterogeneous + pub unsafe fn force_heterogeneous(&mut self) { + self.homogeneous = false; + } + pub fn into_inner(self) -> Vec { self.inner } diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index ccf6583..2a70c45 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -147,7 +147,9 @@ impl NbtTag { list.push(tag); } if all_compounds && any_singletons { - list.make_heterogeneous(); + // # SAFETY + // Data in the list was just checked to be heterogeneous + unsafe { list.force_heterogeneous() }; } Ok(NbtTag::List(list)) } From 6dd358729644ed5144333c8d1ce9d2a438809438 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 18:34:57 +0800 Subject: [PATCH 22/30] cleanup unused import --- tests/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/list.rs b/tests/list.rs index b9b468d..6027d90 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -1,4 +1,4 @@ -use crab_nbt::{Nbt, NbtCompound, NbtList, NbtTag}; +use crab_nbt::{NbtCompound, NbtList, NbtTag}; #[test] fn list_types() { From b235b3f8001b5400b566e1615ff4f0bb4519bd79 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 20:48:01 +0800 Subject: [PATCH 23/30] cleanup --- src/macros.rs | 4 ++-- src/nbt/list.rs | 41 ++++++++++++++++++++++------------------- src/nbt/tag.rs | 23 ++++------------------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 5dd492d..a06c587 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -84,10 +84,10 @@ macro_rules! nbt_inner { $crate::NbtTag::ByteArray(::bytes::Bytes::from_iter([$($lit),*])) }; ([$($lit:literal),* $(,)?]) => { - $crate::NbtTag::List($crate::NbtList::from_iter(::std::vec![$($lit.into()),*])) + $crate::NbtTag::List($crate::NbtList::from(::std::vec![$($lit.into()),*])) }; ([$($t:tt),* $(,)?]) => { - $crate::NbtTag::List($crate::NbtList::from_iter(::std::vec![$(nbt_inner!($t).into()),*])) + $crate::NbtTag::List($crate::NbtList::from(::std::vec![$(nbt_inner!($t).into()),*])) }; } diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 5e889c2..27d0db3 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -229,18 +229,6 @@ impl NbtList { } } - /// # SAFETY - /// Caller to ensure that the data in the list is homogeneous - pub unsafe fn force_homogeneous(&mut self) { - self.homogeneous = true; - } - - /// # SAFETY - /// Caller to ensure that the data in the list is heterogeneous - pub unsafe fn force_heterogeneous(&mut self) { - self.homogeneous = false; - } - pub fn into_inner(self) -> Vec { self.inner } @@ -298,15 +286,30 @@ impl NbtList { } } -impl FromIterator for NbtList { - fn from_iter>(iter: T) -> Self { - let iter = iter.into_iter(); - let mut list = NbtList::with_capacity(iter.size_hint().0); - for element in iter { - list.push(element); +impl From> for NbtList { + fn from(inner: Vec) -> Self { + let mut all_compounds = true; + let mut any_singletons = false; + for e in &inner { + if let NbtTag::Compound(compound) = &e { + if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { + any_singletons = true; + } + } else { + all_compounds = false; + } } - list + Self { + inner, + homogeneous: !(all_compounds && any_singletons), + } + } +} + +impl FromIterator for NbtList { + fn from_iter>(iter: T) -> Self { + Vec::from_iter(iter).into() } } diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 2a70c45..2df1034 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -132,25 +132,10 @@ impl NbtTag { LIST_ID => { let tag_type_id = bytes.get_u8(); let len = bytes.get_i32(); - let mut list = NbtList::with_capacity(len as usize); - let mut all_compounds = true; - let mut any_singletons = false; - for _ in 0..len { - let tag = NbtTag::deserialize_data(bytes, tag_type_id)?; - if let NbtTag::Compound(compound) = &tag { - if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { - any_singletons = true; - } - } else { - all_compounds = false; - } - list.push(tag); - } - if all_compounds && any_singletons { - // # SAFETY - // Data in the list was just checked to be heterogeneous - unsafe { list.force_heterogeneous() }; - } + let list = std::iter::repeat_n((), len as usize) + .map(|_| NbtTag::deserialize_data(bytes, tag_type_id)) + .collect::, _>>()? + .into(); Ok(NbtTag::List(list)) } COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::deserialize_content(bytes)?)), From e14f91d4bef0130d284834f49a78ac5538ccbed5 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 20:51:25 +0800 Subject: [PATCH 24/30] fix From --- src/nbt/list.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 27d0db3..54355f8 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -300,10 +300,16 @@ impl From> for NbtList { } } - Self { + let mut list = Self { inner, - homogeneous: !(all_compounds && any_singletons), + homogeneous: true, + }; + + if all_compounds && any_singletons { + list.make_heterogeneous(); } + + list } } From 92e99a82dc917973076d9f8a20a52b5054b03ac5 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 20:57:20 +0800 Subject: [PATCH 25/30] more fixes --- src/nbt/list.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 54355f8..4f58532 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -288,15 +288,26 @@ impl NbtList { impl From> for NbtList { fn from(inner: Vec) -> Self { - let mut all_compounds = true; + if inner.is_empty() { + return Self { + inner, + homogeneous: true, + }; + } + + let first_id = inner[0].get_type_id(); + let mut all_equal_ids = true; let mut any_singletons = false; + for e in &inner { + if e.get_type_id() != first_id { + all_equal_ids = false; + } + if let NbtTag::Compound(compound) = &e { if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { any_singletons = true; } - } else { - all_compounds = false; } } @@ -305,7 +316,7 @@ impl From> for NbtList { homogeneous: true, }; - if all_compounds && any_singletons { + if !all_equal_ids || (first_id == super::utils::COMPOUND_ID && any_singletons) { list.make_heterogeneous(); } From e6c1c694d25d8e88c3bf66d65392bd500ab13798 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 21:02:37 +0800 Subject: [PATCH 26/30] remove eprintln --- tests/list.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/list.rs b/tests/list.rs index 6027d90..e93513b 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -48,7 +48,6 @@ fn list_types() { assert_eq!(iter.next(), None); let mut serialized = NbtTag::List(heterogeneous).serialize(); - eprintln!("serialized: {serialized:?}"); let NbtTag::List(heterogeneous) = NbtTag::deserialize(&mut serialized).unwrap() else { panic!("heterogeneous list did not survive round trip"); }; From e1ad51a6dd6d77715ccbccf7a595d5798b552857 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 21:11:26 +0800 Subject: [PATCH 27/30] move extend impl to trait --- src/nbt/list.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index 4f58532..af3221b 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -267,16 +267,6 @@ impl NbtList { self.into_iter() } - pub fn extend(&mut self, iter: T) - where - T: IntoIterator, - I: Into, - { - let iter = iter.into_iter(); - self.reserve(iter.size_hint().0); - iter.into_iter().for_each(|e| self.push(e.into())); - } - pub fn retain(&mut self, condition: impl FnMut(&NbtTag) -> bool) { self.inner.retain(condition) } @@ -286,6 +276,14 @@ impl NbtList { } } +impl Extend for NbtList { + fn extend>(&mut self, iter: T) { + let iter = iter.into_iter(); + self.reserve(iter.size_hint().0); + iter.into_iter().for_each(|e| self.push(e)); + } +} + impl From> for NbtList { fn from(inner: Vec) -> Self { if inner.is_empty() { From ca26f350c0704a93000de487f8043fcf6efb4074 Mon Sep 17 00:00:00 2001 From: Qther Date: Tue, 7 Jul 2026 22:02:42 +0800 Subject: [PATCH 28/30] remove ways to index mut and add replace --- src/nbt/list.rs | 60 ++++++++++++++++++++++++++++--------------------- tests/list.rs | 14 ++++++------ 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/nbt/list.rs b/src/nbt/list.rs index af3221b..80afcfd 100644 --- a/src/nbt/list.rs +++ b/src/nbt/list.rs @@ -1,4 +1,4 @@ -use std::ops::{Index, IndexMut, RangeBounds}; +use std::ops::{Index, RangeBounds}; use bytes::{BufMut, BytesMut}; use thiserror::Error; @@ -75,7 +75,21 @@ impl NbtList { Some(&self.inner[index]) } - pub fn get_mut(&mut self, index: usize) -> Option<&mut NbtTag> { + #[must_use] + pub fn replace(&mut self, index: usize, mut element: NbtTag) -> Option { + let was_homogeneous = self.homogeneous; + let existing = self.get_mut(index)?; + let existing_type_id = existing.get_type_id(); + + std::mem::swap(existing, &mut element); + if was_homogeneous && existing_type_id != element.get_type_id() { + self.make_heterogeneous(); + } + + Some(element) + } + + fn get_mut(&mut self, index: usize) -> Option<&mut NbtTag> { use polonius_the_crab::prelude::*; if self.homogeneous { @@ -166,12 +180,12 @@ impl NbtList { let first_element_id = self.get(0).unwrap().get_type_id(); let mut new = Vec::with_capacity(self.len()); - for e in self.into_iter() { - if e.get_type_id() != first_element_id { + for idx in 0..self.len() { + if self[idx].get_type_id() != first_element_id { return Err(MakeHomogeneousError::NotHomogeneous); } - let NbtTag::Compound(ref mut compound) = e else { + let NbtTag::Compound(ref mut compound) = self.get_mut(idx).unwrap() else { return Err(MakeHomogeneousError::NonCompoundElement); }; @@ -263,9 +277,9 @@ impl NbtList { self.into_iter() } - pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a> { - self.into_iter() - } + // pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a> { + // self.into_iter() + // } pub fn retain(&mut self, condition: impl FnMut(&NbtTag) -> bool) { self.inner.retain(condition) @@ -412,18 +426,18 @@ impl<'a> IntoIterator for &'a NbtList { } } -impl<'a> IntoIterator for &'a mut NbtList { - type Item = &'a mut NbtTag; - - type IntoIter = IterMut<'a>; - - fn into_iter(self) -> Self::IntoIter { - IterMut { - iter: self.inner.iter_mut(), - homogeneous: self.homogeneous, - } - } -} +// impl<'a> IntoIterator for &'a mut NbtList { +// type Item = &'a mut NbtTag; +// +// type IntoIter = IterMut<'a>; +// +// fn into_iter(self) -> Self::IntoIter { +// IterMut { +// iter: self.inner.iter_mut(), +// homogeneous: self.homogeneous, +// } +// } +// } impl AsRef<[NbtTag]> for NbtList { fn as_ref(&self) -> &[NbtTag] { @@ -438,9 +452,3 @@ impl Index for NbtList { self.get(index).unwrap() } } - -impl IndexMut for NbtList { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.get_mut(index).unwrap() - } -} diff --git a/tests/list.rs b/tests/list.rs index e93513b..ce913d8 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -30,16 +30,16 @@ fn list_types() { child_tags: vec![("six".into(), 7.into())], }); assert!(heterogeneous.is_heterogeneous()); - let mut iter = heterogeneous.iter_mut(); - assert_eq!(iter.next(), Some(&mut 1.into())); - assert_eq!(iter.next(), Some(&mut 2.into())); - assert_eq!(iter.next(), Some(&mut 3.into())); - assert_eq!(iter.next(), Some(&mut "four".into())); - assert_eq!(iter.next(), Some(&mut vec![5].into())); + let mut iter = heterogeneous.iter(); + assert_eq!(iter.next(), Some(&1.into())); + assert_eq!(iter.next(), Some(&2.into())); + assert_eq!(iter.next(), Some(&3.into())); + assert_eq!(iter.next(), Some(&"four".into())); + assert_eq!(iter.next(), Some(&vec![5].into())); assert_eq!( iter.next(), Some( - &mut NbtCompound { + &NbtCompound { child_tags: vec![("six".into(), 7.into())] } .into() From d696832c6bfcf6d9fc4929befe197ac32ce7be4c Mon Sep 17 00:00:00 2001 From: Qther Date: Thu, 9 Jul 2026 16:28:01 +0800 Subject: [PATCH 29/30] more transparent list --- src/error.rs | 2 + src/lib.rs | 1 - src/macros.rs | 4 +- src/nbt.rs | 1 - src/nbt/compound.rs | 7 +- src/nbt/list.rs | 454 ----------------------------------------- src/nbt/tag.rs | 77 +++++-- src/serde/nbt_types.rs | 10 +- tests/list.rs | 34 +-- tests/macros.rs | 6 +- 10 files changed, 99 insertions(+), 497 deletions(-) delete mode 100644 src/nbt/list.rs diff --git a/src/error.rs b/src/error.rs index af3b631..ea6d65e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,6 +19,8 @@ pub enum Error { SerdeError(String), #[error("NBT doesn't support this type {0}")] UnsupportedType(String), + #[error("List contains element of different type")] + ListDifferentType, #[error(transparent)] Io(#[from] io::Error), } diff --git a/src/lib.rs b/src/lib.rs index 1d3f660..76bac0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ mod nbt; pub mod serde; pub use crab_nbt::nbt::compound::NbtCompound; -pub use crab_nbt::nbt::list::NbtList; pub use crab_nbt::nbt::tag::NbtTag; pub use crab_nbt::nbt::Nbt; diff --git a/src/macros.rs b/src/macros.rs index a06c587..d419c05 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -84,10 +84,10 @@ macro_rules! nbt_inner { $crate::NbtTag::ByteArray(::bytes::Bytes::from_iter([$($lit),*])) }; ([$($lit:literal),* $(,)?]) => { - $crate::NbtTag::List($crate::NbtList::from(::std::vec![$($lit.into()),*])) + $crate::NbtTag::List(::std::vec![$($lit.into()),*]) }; ([$($t:tt),* $(,)?]) => { - $crate::NbtTag::List($crate::NbtList::from(::std::vec![$(nbt_inner!($t).into()),*])) + $crate::NbtTag::List(::std::vec![$(nbt_inner!($t).into()),*]) }; } diff --git a/src/nbt.rs b/src/nbt.rs index 11021bc..a4281ae 100644 --- a/src/nbt.rs +++ b/src/nbt.rs @@ -8,7 +8,6 @@ use std::io::{Cursor, Write}; use std::ops::Deref; pub mod compound; -pub mod list; pub mod tag; pub mod utils; diff --git a/src/nbt/compound.rs b/src/nbt/compound.rs index da90898..7d5d1c8 100644 --- a/src/nbt/compound.rs +++ b/src/nbt/compound.rs @@ -1,4 +1,3 @@ -use crate::nbt::list::NbtList; use crate::nbt::utils::{escape_name, join_formatted}; use crate::{error::Error, Nbt}; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -27,6 +26,10 @@ impl NbtCompound { } } + pub fn is_wrapper(&self) -> bool { + self.child_tags.len() == 1 && self.child_tags[0].0.is_empty() + } + pub fn deserialize_content(bytes: &mut impl Buf) -> Result { let mut compound = NbtCompound::new(); @@ -118,7 +121,7 @@ impl NbtCompound { self.get(name).and_then(|tag| tag.extract_string()) } - pub fn get_list(&self, name: &str) -> Option<&NbtList> { + pub fn get_list(&self, name: &str) -> Option<&Vec> { self.get(name).and_then(|tag| tag.extract_list()) } diff --git a/src/nbt/list.rs b/src/nbt/list.rs deleted file mode 100644 index 80afcfd..0000000 --- a/src/nbt/list.rs +++ /dev/null @@ -1,454 +0,0 @@ -use std::ops::{Index, RangeBounds}; - -use bytes::{BufMut, BytesMut}; -use thiserror::Error; - -use crate::{NbtCompound, NbtTag}; - -#[derive(Clone, Debug, PartialEq, PartialOrd)] -pub struct NbtList { - inner: Vec, - homogeneous: bool, -} - -impl Default for NbtList { - fn default() -> Self { - Self::new() - } -} - -#[derive(Error, Debug)] -pub enum MakeHomogeneousError { - #[error("Not homogeneous")] - NotHomogeneous, - #[error("Non-Compound element in heterogeneous List")] - NonCompoundElement, -} - -impl NbtList { - pub fn new() -> NbtList { - Self { - inner: Vec::new(), - homogeneous: true, - } - } - - pub fn with_capacity(initial_capacity: usize) -> NbtList { - Self { - inner: Vec::with_capacity(initial_capacity), - homogeneous: true, - } - } - - pub fn reserve(&mut self, additional_capacity: usize) { - self.inner.reserve(additional_capacity) - } - - pub fn reserve_exact(&mut self, additional_capacity: usize) { - self.inner.reserve_exact(additional_capacity) - } - - pub fn is_homogeneous(&self) -> bool { - self.homogeneous - } - - pub fn is_heterogeneous(&self) -> bool { - !self.homogeneous - } - - pub fn get(&self, index: usize) -> Option<&NbtTag> { - if self.homogeneous { - return self.inner.get(index); - } - - let NbtTag::Compound(compound) = &self.inner.get(index)? else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &compound.child_tags[0]; - if child.0.is_empty() { - return Some(&child.1); - } - } - - Some(&self.inner[index]) - } - - #[must_use] - pub fn replace(&mut self, index: usize, mut element: NbtTag) -> Option { - let was_homogeneous = self.homogeneous; - let existing = self.get_mut(index)?; - let existing_type_id = existing.get_type_id(); - - std::mem::swap(existing, &mut element); - if was_homogeneous && existing_type_id != element.get_type_id() { - self.make_heterogeneous(); - } - - Some(element) - } - - fn get_mut(&mut self, index: usize) -> Option<&mut NbtTag> { - use polonius_the_crab::prelude::*; - - if self.homogeneous { - return self.inner.get_mut(index); - } - - let mut tag = self.inner.get_mut(index)?; - polonius!(|tag| -> Option<&'polonius mut NbtTag> { - let NbtTag::Compound(compound) = tag else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &mut compound.child_tags[0]; - if child.0.is_empty() { - polonius_return!(Some(&mut child.1)) - } - } - }); - - Some(tag) - } - - pub fn push(&mut self, element: impl Into) { - let element: NbtTag = element.into(); - let current_type_id = self.element_type_id(); - - if self.homogeneous { - let new_type_id = element.get_type_id(); - self.inner.push(element); - if current_type_id != new_type_id && current_type_id != NbtTag::End.get_type_id() { - self.make_heterogeneous(); - } - return; - } - - match element { - NbtTag::Compound(_) => self.inner.push(element), - _ => self.inner.push(NbtCompound::wrap(element).into()), - } - } - - pub fn insert(&mut self, index: usize, element: impl Into) { - let element: NbtTag = element.into(); - let current_type_id = self.element_type_id(); - - if self.homogeneous { - let new_type_id = element.get_type_id(); - self.inner.insert(index, element); - if current_type_id != new_type_id && current_type_id != NbtTag::End.get_type_id() { - self.make_heterogeneous(); - } - return; - } - - match element { - NbtTag::Compound(_) => self.inner.insert(index, element), - _ => self.inner.insert(index, NbtCompound::wrap(element).into()), - } - } - - pub fn remove(&mut self, index: usize) -> NbtTag { - self.inner.remove(index) - } - - pub fn make_heterogeneous(&mut self) { - if self.is_heterogeneous() { - return; - } - - self.homogeneous = false; - for e in &mut self.inner { - if !matches!(e, NbtTag::Compound(_)) { - *e = NbtCompound::wrap(std::mem::replace(e, NbtTag::End)).into(); - } - } - } - - pub fn make_homogeneous(&mut self) -> Result<(), MakeHomogeneousError> { - if self.homogeneous { - return Ok(()); - } - - if self.is_empty() { - self.homogeneous = true; - return Ok(()); - } - - let first_element_id = self.get(0).unwrap().get_type_id(); - let mut new = Vec::with_capacity(self.len()); - for idx in 0..self.len() { - if self[idx].get_type_id() != first_element_id { - return Err(MakeHomogeneousError::NotHomogeneous); - } - - let NbtTag::Compound(ref mut compound) = self.get_mut(idx).unwrap() else { - return Err(MakeHomogeneousError::NonCompoundElement); - }; - - if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { - new.push(compound.child_tags.remove(0).1); - } else { - new.push(NbtTag::Compound(std::mem::replace( - compound, - NbtCompound::new(), - ))); - } - } - self.homogeneous = true; - - Ok(()) - } - - pub fn into_native(self) -> Result { - if self.is_heterogeneous() { - return Err(self); - } - - match self.element_type_id() { - crate::nbt::utils::BYTE_ID => { - let mut v = BytesMut::with_capacity(self.len()); - for e in self.into_iter() { - match e { - NbtTag::Byte(e) => v.put_i8(e), - _ => unreachable!(), - } - } - Ok(NbtTag::ByteArray(v.into())) - } - crate::nbt::utils::INT_ID => { - let mut v = Vec::with_capacity(self.len()); - for e in self.into_iter() { - match e { - NbtTag::Int(e) => v.push(e), - _ => unreachable!(), - } - } - Ok(NbtTag::IntArray(v)) - } - crate::nbt::utils::LONG_ID => { - let mut v = Vec::with_capacity(self.len()); - for e in self.into_iter() { - match e { - NbtTag::Long(e) => v.push(e), - _ => unreachable!(), - } - } - Ok(NbtTag::LongArray(v)) - } - _ => Err(self), - } - } - - pub fn into_inner(self) -> Vec { - self.inner - } - - pub fn as_inner(&self) -> &Vec { - &self.inner - } - - pub fn contains(&self, element: &NbtTag) -> bool { - self.inner.contains(element) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub fn element_type_id(&self) -> u8 { - if self.is_heterogeneous() { - crate::nbt::utils::COMPOUND_ID - } else if self.inner.is_empty() { - crate::nbt::utils::END_ID - } else { - self.inner[0].get_type_id() - } - } - - pub fn iter<'a>(&'a self) -> Iter<'a> { - self.into_iter() - } - - // pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a> { - // self.into_iter() - // } - - pub fn retain(&mut self, condition: impl FnMut(&NbtTag) -> bool) { - self.inner.retain(condition) - } - - pub fn drain(&mut self, range: impl RangeBounds) -> std::vec::Drain<'_, NbtTag> { - self.inner.drain(range) - } -} - -impl Extend for NbtList { - fn extend>(&mut self, iter: T) { - let iter = iter.into_iter(); - self.reserve(iter.size_hint().0); - iter.into_iter().for_each(|e| self.push(e)); - } -} - -impl From> for NbtList { - fn from(inner: Vec) -> Self { - if inner.is_empty() { - return Self { - inner, - homogeneous: true, - }; - } - - let first_id = inner[0].get_type_id(); - let mut all_equal_ids = true; - let mut any_singletons = false; - - for e in &inner { - if e.get_type_id() != first_id { - all_equal_ids = false; - } - - if let NbtTag::Compound(compound) = &e { - if compound.child_tags.len() == 1 && compound.child_tags[0].0.is_empty() { - any_singletons = true; - } - } - } - - let mut list = Self { - inner, - homogeneous: true, - }; - - if !all_equal_ids || (first_id == super::utils::COMPOUND_ID && any_singletons) { - list.make_heterogeneous(); - } - - list - } -} - -impl FromIterator for NbtList { - fn from_iter>(iter: T) -> Self { - Vec::from_iter(iter).into() - } -} - -pub struct Iter<'a> { - list: &'a NbtList, - idx: usize, -} - -pub struct IterMut<'a> { - iter: std::slice::IterMut<'a, NbtTag>, - homogeneous: bool, -} - -pub struct IntoIter { - list: NbtList, - idx: usize, -} - -impl<'a> Iterator for Iter<'a> { - type Item = &'a NbtTag; - - fn next(&mut self) -> Option { - let element = self.list.get(self.idx)?; - self.idx += 1; - Some(element) - } -} - -impl<'a> Iterator for IterMut<'a> { - type Item = &'a mut NbtTag; - - fn next(&mut self) -> Option { - use polonius_the_crab::prelude::*; - - let mut element = self.iter.next()?; - - if self.homogeneous { - return Some(element); - } - - polonius!(|element| -> Option<&'polonius mut NbtTag> { - let NbtTag::Compound(compound) = element else { - unreachable!() - }; - - if compound.child_tags.len() == 1 { - let child = &mut compound.child_tags[0]; - if child.0.is_empty() { - polonius_return!(Some(&mut child.1)) - } - } - }); - - Some(element) - } -} - -impl Iterator for IntoIter { - type Item = NbtTag; - - fn next(&mut self) -> Option { - let tag = self.list.get_mut(self.idx)?; - self.idx += 1; - Some(std::mem::replace(tag, NbtTag::End)) - } -} - -impl IntoIterator for NbtList { - type Item = NbtTag; - - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - IntoIter { list: self, idx: 0 } - } -} - -impl<'a> IntoIterator for &'a NbtList { - type Item = &'a NbtTag; - - type IntoIter = Iter<'a>; - - fn into_iter(self) -> Self::IntoIter { - Iter { list: self, idx: 0 } - } -} - -// impl<'a> IntoIterator for &'a mut NbtList { -// type Item = &'a mut NbtTag; -// -// type IntoIter = IterMut<'a>; -// -// fn into_iter(self) -> Self::IntoIter { -// IterMut { -// iter: self.inner.iter_mut(), -// homogeneous: self.homogeneous, -// } -// } -// } - -impl AsRef<[NbtTag]> for NbtList { - fn as_ref(&self) -> &[NbtTag] { - &self.inner - } -} - -impl Index for NbtList { - type Output = NbtTag; - - fn index(&self, index: usize) -> &Self::Output { - self.get(index).unwrap() - } -} diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 2df1034..7619596 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -6,8 +6,6 @@ use derive_more::From; use std::fmt::{self, Display, Formatter}; use std::io::Cursor; -use crate::nbt::list::NbtList; - /// Enum representing the different types of NBT tags. /// Each variant corresponds to a different type of data that can be stored in an NBT tag. #[repr(u8)] @@ -22,7 +20,7 @@ pub enum NbtTag { Double(f64) = DOUBLE_ID, ByteArray(Bytes) = BYTE_ARRAY_ID, String(String) = STRING_ID, - List(NbtList) = LIST_ID, + List(Vec) = LIST_ID, Compound(NbtCompound) = COMPOUND_ID, IntArray(Vec) = INT_ARRAY_ID, LongArray(Vec) = LONG_ARRAY_ID, @@ -62,10 +60,38 @@ impl NbtTag { bytes.put_slice(&java_string); } NbtTag::List(list) => { - bytes.put_u8(list.element_type_id()); - bytes.put_i32(list.len() as i32); - for tag in list.as_inner() { - bytes.put(tag.serialize_data()) + let ty = list + .first() + .map(|e| e.get_type_id()) + .unwrap_or(NbtTag::End.get_type_id()); + + let mut homogeneous = true; + for element in list { + if element.get_type_id() != ty { + homogeneous = false; + } + } + + if homogeneous { + bytes.put_u8(ty); + bytes.put_i32(list.len() as i32); + for tag in list { + bytes.put(tag.serialize_data()) + } + } else { + bytes.put_u8(COMPOUND_ID); + bytes.put_i32(list.len() as i32); + for tag in list { + match tag { + NbtTag::Compound(_) => bytes.put(tag.serialize_data()), + other => { + bytes.put_u8(other.get_type_id()); + bytes.put(NbtTag::String(String::new()).serialize_data()); + bytes.put(other.serialize_data()); + bytes.put_u8(END_ID); + } + } + } } } NbtTag::Compound(compound) => { @@ -132,10 +158,37 @@ impl NbtTag { LIST_ID => { let tag_type_id = bytes.get_u8(); let len = bytes.get_i32(); - let list = std::iter::repeat_n((), len as usize) - .map(|_| NbtTag::deserialize_data(bytes, tag_type_id)) - .collect::, _>>()? - .into(); + let mut list = Vec::with_capacity(len as usize); + let mut all_compounds = len != 0; + let mut any_singletons = false; + for _ in 0..len { + let tag = NbtTag::deserialize_data(bytes, tag_type_id)?; + if tag.get_type_id() != tag_type_id { + return Err(Error::ListDifferentType); + } + + if let NbtTag::Compound(compound) = &tag { + if !any_singletons && compound.is_wrapper() { + any_singletons = true; + } + } else { + all_compounds = false; + } + + list.push(tag); + } + + if all_compounds && any_singletons { + for tag in &mut list { + match tag { + NbtTag::Compound(ref mut compound) if compound.is_wrapper() => { + *tag = compound.child_tags.remove(0).1; + } + _ => (), + } + } + } + Ok(NbtTag::List(list)) } COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::deserialize_content(bytes)?)), @@ -228,7 +281,7 @@ impl NbtTag { } } - pub fn extract_list(&self) -> Option<&NbtList> { + pub fn extract_list(&self) -> Option<&Vec> { match self { NbtTag::List(list) => Some(list), _ => None, diff --git a/src/serde/nbt_types.rs b/src/serde/nbt_types.rs index ec176be..4565461 100644 --- a/src/serde/nbt_types.rs +++ b/src/serde/nbt_types.rs @@ -1,4 +1,4 @@ -use crate::{NbtCompound, NbtList, NbtTag}; +use crate::{NbtCompound, NbtTag}; use serde::de::value::MapAccessDeserializer; use serde::{Deserialize, Serialize}; @@ -27,8 +27,8 @@ impl Serialize for NbtTag { NbtTag::List(list_items) => { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(Some(list_items.len()))?; - for tag in list_items { - seq.serialize_element(tag)?; + for item in list_items.iter() { + seq.serialize_element(item)?; } seq.end() } @@ -106,8 +106,8 @@ impl<'de> Deserialize<'de> for NbtTag { where A: serde::de::SeqAccess<'de>, { - let mut items = NbtList::new(); - while let Some(tag) = seq.next_element::()? { + let mut items = Vec::new(); + while let Some(tag) = seq.next_element()? { items.push(tag); } Ok(NbtTag::List(items)) diff --git a/tests/list.rs b/tests/list.rs index ce913d8..c207800 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -1,16 +1,16 @@ -use crab_nbt::{NbtCompound, NbtList, NbtTag}; +use crab_nbt::{NbtCompound, NbtTag}; +#[allow(clippy::perf)] #[test] fn list_types() { - let empty = NbtList::new(); + let empty: Vec = Vec::new(); let mut iter = empty.clone().into_iter(); assert_eq!(iter.next(), None); - let mut homogeneous = NbtList::new(); - homogeneous.push(1); - homogeneous.push(2); - homogeneous.push(3); - assert!(homogeneous.is_homogeneous()); + let mut homogeneous = Vec::new(); + homogeneous.push(1.into()); + homogeneous.push(2.into()); + homogeneous.push(3.into()); let mut iter = homogeneous.iter(); assert_eq!(iter.next(), Some(&1.into())); assert_eq!(iter.next(), Some(&2.into())); @@ -18,18 +18,19 @@ fn list_types() { assert_eq!(iter.next(), None); let mut serialized = NbtTag::List(homogeneous).serialize(); - let NbtTag::List(homogeneous) = NbtTag::deserialize(&mut serialized).unwrap() else { + let Ok(NbtTag::List(homogeneous)) = NbtTag::deserialize(&mut serialized) else { panic!("homogeneous list did not survive round trip"); }; - assert!(homogeneous.is_homogeneous()); let mut heterogeneous = homogeneous.clone(); - heterogeneous.push("four"); - heterogeneous.push(vec![5]); - heterogeneous.push(NbtCompound { - child_tags: vec![("six".into(), 7.into())], - }); - assert!(heterogeneous.is_heterogeneous()); + heterogeneous.push("four".into()); + heterogeneous.push(vec![5].into()); + heterogeneous.push( + NbtCompound { + child_tags: vec![("six".into(), 7.into())], + } + .into(), + ); let mut iter = heterogeneous.iter(); assert_eq!(iter.next(), Some(&1.into())); assert_eq!(iter.next(), Some(&2.into())); @@ -48,8 +49,7 @@ fn list_types() { assert_eq!(iter.next(), None); let mut serialized = NbtTag::List(heterogeneous).serialize(); - let NbtTag::List(heterogeneous) = NbtTag::deserialize(&mut serialized).unwrap() else { + let Ok(NbtTag::List(_heterogeneous)) = NbtTag::deserialize(&mut serialized) else { panic!("heterogeneous list did not survive round trip"); }; - assert!(heterogeneous.is_heterogeneous()); } diff --git a/tests/macros.rs b/tests/macros.rs index 883acb4..d99a504 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,5 +1,5 @@ use bytes::Bytes; -use crab_nbt::{nbt, Nbt, NbtCompound, NbtList, NbtTag}; +use crab_nbt::{nbt, Nbt, NbtCompound, NbtTag}; #[test] fn nbt_macro_handles_empty_content() { @@ -29,7 +29,7 @@ fn nbt_macro_handles_array_content() { let array = nbt.get_list("array").unwrap(); - assert_eq!(array, &NbtList::from_iter([1.into(), 2.into(), 3.into()])); + assert_eq!(array, &vec![1.into(), 2.into(), 3.into()]); } #[test] @@ -55,7 +55,7 @@ fn nbt_macro_complex_object() { ("int_array".to_owned(), NbtTag::IntArray(vec![1])), ( "list".to_owned(), - NbtTag::List(NbtList::from_iter(["a".into(), "b".into(), "c".into()])), + NbtTag::List(vec!["a".into(), "b".into(), "c".into()]), ), ( "nbt_inner".to_owned(), From afa4233ddc9d575bf195651a49d00178313581d3 Mon Sep 17 00:00:00 2001 From: Qther Date: Thu, 9 Jul 2026 20:01:23 +0800 Subject: [PATCH 30/30] add a comment and more directly serialize value as a singleton Compound --- src/nbt/tag.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nbt/tag.rs b/src/nbt/tag.rs index 7619596..bd7ab8e 100644 --- a/src/nbt/tag.rs +++ b/src/nbt/tag.rs @@ -84,10 +84,11 @@ impl NbtTag { for tag in list { match tag { NbtTag::Compound(_) => bytes.put(tag.serialize_data()), - other => { - bytes.put_u8(other.get_type_id()); - bytes.put(NbtTag::String(String::new()).serialize_data()); - bytes.put(other.serialize_data()); + value => { + // Manually serialize value as a Compound, to avoid a Clone + bytes.put_u8(value.get_type_id()); + bytes.put_u16(0); // Empty string key + bytes.put(value.serialize_data()); bytes.put_u8(END_ID); } }