diff --git a/Cargo.lock b/Cargo.lock index 2331c5ee..67364af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,7 @@ dependencies = [ "azalea-buf-macros", "byteorder", "serde_json", + "simdnbt", "thiserror", "tracing", "uuid", @@ -295,7 +296,6 @@ dependencies = [ "azalea-crypto", "azalea-entity", "azalea-inventory", - "azalea-nbt", "azalea-physics", "azalea-protocol", "azalea-registry", @@ -314,6 +314,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "simdnbt", "thiserror", "tokio", "tracing", @@ -326,13 +327,14 @@ version = "0.8.0" dependencies = [ "azalea-buf", "azalea-inventory", - "azalea-nbt", "azalea-registry", "bevy_ecs", "nohash-hasher", "num-traits", "serde", "serde_json", + "simdnbt", + "tracing", "uuid", ] @@ -362,7 +364,6 @@ dependencies = [ "azalea-chat", "azalea-core", "azalea-inventory", - "azalea-nbt", "azalea-registry", "azalea-world", "bevy_app", @@ -371,6 +372,7 @@ dependencies = [ "enum-as-inner", "nohash-hasher", "parking_lot", + "simdnbt", "thiserror", "tracing", "uuid", @@ -382,8 +384,8 @@ version = "0.8.0" dependencies = [ "azalea-buf", "azalea-inventory-macros", - "azalea-nbt", "azalea-registry", + "simdnbt", ] [[package]] @@ -404,24 +406,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "azalea-nbt" -version = "0.8.0" -dependencies = [ - "azalea-buf", - "byteorder", - "compact_str", - "criterion", - "enum-as-inner", - "fastnbt", - "flate2", - "graphite_binary", - "serde", - "thiserror", - "tracing", - "valence_nbt", -] - [[package]] name = "azalea-physics" version = "0.8.0" @@ -457,7 +441,6 @@ dependencies = [ "azalea-crypto", "azalea-entity", "azalea-inventory", - "azalea-nbt", "azalea-protocol-macros", "azalea-registry", "azalea-world", @@ -471,6 +454,7 @@ dependencies = [ "once_cell", "serde", "serde_json", + "simdnbt", "thiserror", "tokio", "tokio-util", @@ -496,7 +480,7 @@ dependencies = [ "azalea-buf", "azalea-registry-macros", "once_cell", - "serde", + "simdnbt", ] [[package]] @@ -517,7 +501,6 @@ dependencies = [ "azalea-client", "azalea-core", "azalea-inventory", - "azalea-nbt", "azalea-registry", "bevy_ecs", "criterion", @@ -528,6 +511,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", + "simdnbt", "thiserror", "tracing", "uuid", @@ -805,15 +789,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.0.83" @@ -823,12 +798,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfb8" version = "0.8.1" @@ -916,20 +885,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "serde", - "static_assertions", -] - [[package]] name = "concurrent-queue" version = "2.3.0" @@ -1202,18 +1157,6 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "fastnbt" -version = "2.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369bd70629bccfda7e344883c9ae3ab7f3b10a357bcf8b0f69caa7256bcf188" -dependencies = [ - "byteorder", - "cesu8", - "serde", - "serde_bytes", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1417,31 +1360,6 @@ dependencies = [ "serde", ] -[[package]] -name = "graphite_binary" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc8b44c673c50a2b3e6ec6652b8c8d26532254a3a182cc43b76d1b6e4cd1572" -dependencies = [ - "anyhow", - "bytes", - "cesu8", - "graphite_binary_macros", - "thiserror", -] - -[[package]] -name = "graphite_binary_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30667bf8d368a37fa37f4165d90ee84362e360d83d85924898c41cfe3d097521" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "h2" version = "0.3.21" @@ -2233,6 +2151,21 @@ dependencies = [ "winreg", ] +[[package]] +name = "residua-cesu8" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca29b145d9861719b5505602d881afc46705200144153ca9dbc0802be2938ea" + +[[package]] +name = "residua-mutf8" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2adba843a48e520e7dad6d1e9c367a4f818787eaccf4530c6b90dd1f035e630d" +dependencies = [ + "residua-cesu8", +] + [[package]] name = "ring" version = "0.16.20" @@ -2345,12 +2278,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "ryu" version = "1.0.15" @@ -2397,15 +2324,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.192" @@ -2490,6 +2408,30 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simdnbt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e3431666c99066308d860437da87ac1dfdf7225b80b551aa007cbd3a46a086" +dependencies = [ + "byteorder", + "flate2", + "residua-mutf8", + "simdnbt-derive", + "thiserror", +] + +[[package]] +name = "simdnbt-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840927b3f00258339cb4ccb4658a33f572409a1bf42e1b98a3f872a995894b4e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "simple_asn1" version = "0.5.4" @@ -2565,12 +2507,6 @@ dependencies = [ "der", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "subtle" version = "2.5.0" @@ -2959,12 +2895,6 @@ dependencies = [ "serde", ] -[[package]] -name = "valence_nbt" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3cddc3222ed5ead4fa446881b3deeeee0dba60b0088b2bf12fedbac7eda2312" - [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c9bbddbb..9ce1fc84 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "azalea-chat", "azalea-core", "azalea-auth", - "azalea-nbt", "azalea-brigadier", "azalea-crypto", "azalea-world", diff --git a/azalea-buf/Cargo.toml b/azalea-buf/Cargo.toml index 95f3b4ac..dc25632b 100644 --- a/azalea-buf/Cargo.toml +++ b/azalea-buf/Cargo.toml @@ -15,6 +15,7 @@ tracing = "0.1.40" serde_json = { version = "^1.0", optional = true } thiserror = "1.0.50" uuid = "^1.5.0" +simdnbt = { version = "0.2.1" } [features] serde_json = ["dep:serde_json"] diff --git a/azalea-buf/src/read.rs b/azalea-buf/src/read.rs index 78db7357..d5c4d0a8 100755 --- a/azalea-buf/src/read.rs +++ b/azalea-buf/src/read.rs @@ -50,6 +50,18 @@ pub enum BufReadError { #[backtrace] source: serde_json::Error, }, + #[error("{source}")] + Nbt { + #[from] + #[backtrace] + source: simdnbt::Error, + }, + #[error("{source}")] + DeserializeNbt { + #[from] + #[backtrace] + source: simdnbt::DeserializeError, + }, } fn read_bytes<'a>(buf: &'a mut Cursor<&[u8]>, length: usize) -> Result<&'a [u8], BufReadError> { @@ -340,3 +352,24 @@ impl McBufReadable for [T; N] { }) } } + +impl McBufReadable for simdnbt::owned::NbtTag { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result { + Ok(simdnbt::owned::NbtTag::read(buf)?) + } +} + +impl McBufReadable for simdnbt::owned::NbtCompound { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result { + match simdnbt::owned::NbtTag::read(buf)? { + simdnbt::owned::NbtTag::Compound(compound) => Ok(compound), + _ => Err(BufReadError::Custom("Expected compound tag".to_string())), + } + } +} + +impl McBufReadable for simdnbt::owned::Nbt { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result { + Ok(simdnbt::owned::Nbt::read_unnamed(buf)?) + } +} diff --git a/azalea-buf/src/write.rs b/azalea-buf/src/write.rs index f48bf2ec..03d40d79 100755 --- a/azalea-buf/src/write.rs +++ b/azalea-buf/src/write.rs @@ -257,3 +257,27 @@ impl McBufWritable for [T; N] { Ok(()) } } + +impl McBufWritable for simdnbt::owned::NbtTag { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + let mut data = Vec::new(); + self.write(&mut data); + data.write_into(buf) + } +} + +impl McBufWritable for simdnbt::owned::NbtCompound { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + let mut data = Vec::new(); + simdnbt::owned::NbtTag::Compound(self.clone()).write(&mut data); + data.write_into(buf) + } +} + +impl McBufWritable for simdnbt::owned::Nbt { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + let mut data = Vec::new(); + self.write_unnamed(&mut data); + data.write_into(buf) + } +} diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 71f117ed..4694fbd9 100644 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -14,7 +14,7 @@ anyhow = "1.0.75" async-trait = "0.1.74" azalea-auth = { path = "../azalea-auth", version = "0.8.0" } azalea-block = { path = "../azalea-block", version = "0.8.0" } -azalea-nbt = { path = "../azalea-nbt", version = "0.8.0" } +simdnbt = { version = "0.2.1" } azalea-chat = { path = "../azalea-chat", version = "0.8.0" } azalea-core = { path = "../azalea-core", version = "0.8.0" } azalea-crypto = { path = "../azalea-crypto", version = "0.8.0" } diff --git a/azalea-client/src/chunks.rs b/azalea-client/src/chunks.rs index 4d2641f5..e91e6b01 100644 --- a/azalea-client/src/chunks.rs +++ b/azalea-client/src/chunks.rs @@ -4,17 +4,18 @@ use std::{ io::Cursor, + ops::Deref, time::{Duration, Instant}, }; use azalea_core::position::ChunkPos; -use azalea_nbt::NbtCompound; use azalea_protocol::packets::game::{ clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket, serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket, }; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; +use simdnbt::owned::BaseNbt; use tracing::{error, trace}; use crate::{ @@ -99,10 +100,10 @@ fn handle_receive_chunk_events( } } - let heightmaps = event.packet.chunk_data.heightmaps.as_compound(); + let heightmaps_nbt = &event.packet.chunk_data.heightmaps; // necessary to make the unwrap_or work - let empty_nbt_compound = NbtCompound::default(); - let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); + let empty_nbt = BaseNbt::default(); + let heightmaps = heightmaps_nbt.unwrap_or(&empty_nbt).deref(); if let Err(e) = partial_instance.chunks.replace_with_packet_data( &pos, diff --git a/azalea-client/src/interact.rs b/azalea-client/src/interact.rs index 64cbd7be..bdb17827 100644 --- a/azalea-client/src/interact.rs +++ b/azalea-client/src/interact.rs @@ -11,7 +11,6 @@ use azalea_entity::{ clamp_look_direction, view_vector, Attributes, EyeHeight, LocalEntity, LookDirection, Position, }; use azalea_inventory::{ItemSlot, ItemSlotData}; -use azalea_nbt::NbtList; use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType}; use azalea_protocol::packets::game::{ serverbound_interact_packet::InteractionHand, @@ -29,6 +28,7 @@ use bevy_ecs::{ system::{Commands, Query, Res}, }; use derive_more::{Deref, DerefMut}; +use simdnbt::owned::NbtList; use tracing::warn; use crate::{ @@ -272,9 +272,8 @@ pub fn check_block_can_be_broken_by_item_in_adventure_mode( let Some(can_destroy) = item .nbt - .as_compound() - .and_then(|nbt| nbt.get("tag").and_then(|nbt| nbt.as_compound())) - .and_then(|nbt| nbt.get("CanDestroy").and_then(|nbt| nbt.as_list())) + .compound("tag") + .and_then(|nbt| nbt.list("CanDestroy")) else { // no CanDestroy tag return false; diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index a25485a2..957eaaf5 100644 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -11,7 +11,7 @@ version = "0.8.0" [dependencies] azalea-buf = { path = "../azalea-buf", version = "0.8.0" } azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } -azalea-nbt = { path = "../azalea-nbt", version = "0.8.0" } +simdnbt = { version = "0.2.1" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" } bevy_ecs = { version = "0.12.0", default-features = false, optional = true } nohash-hasher = "0.2.0" @@ -19,6 +19,7 @@ num-traits = "0.2.17" serde = { version = "^1.0", optional = true } uuid = "^1.5.0" serde_json = "^1.0.108" +tracing = "0.1.40" [features] bevy_ecs = ["dep:bevy_ecs"] diff --git a/azalea-core/src/registry_holder.rs b/azalea-core/src/registry_holder.rs index 7f811e23..6d58f77a 100644 --- a/azalea-core/src/registry_holder.rs +++ b/azalea-core/src/registry_holder.rs @@ -6,100 +6,103 @@ //! biomes. use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; -use azalea_nbt::Nbt; -use serde::{ - de::{self, DeserializeOwned}, - Deserialize, Deserializer, Serialize, Serializer, +use simdnbt::{ + owned::{NbtCompound, NbtTag}, + Deserialize, FromNbtTag, Serialize, ToNbtTag, }; use std::{collections::HashMap, io::Cursor}; +use tracing::error; use crate::resource_location::ResourceLocation; /// The base of the registry. /// /// This is the registry that is sent to the client upon login. -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone)] pub struct RegistryHolder { - pub map: HashMap, + pub map: HashMap, } impl RegistryHolder { - fn get(&self, name: &ResourceLocation) -> Option { - let nbt = self.map.get(name)?; - serde_json::from_value(serde_json::to_value(nbt).ok()?).ok() + fn get( + &self, + name: &ResourceLocation, + ) -> Option> { + self.map.get(name).map(|nbt| T::from_compound(nbt.clone())) } /// Get the dimension type registry, or `None` if it doesn't exist. You /// should do some type of error handling if this returns `None`. pub fn dimension_type(&self) -> Option> { - self.get(&ResourceLocation::new("minecraft:dimension_type")) - } -} - -impl TryFrom for RegistryHolder { - type Error = serde_json::Error; - - fn try_from(value: Nbt) -> Result { - Ok(RegistryHolder { - map: serde_json::from_value(serde_json::to_value(value)?)?, - }) - } -} - -impl TryInto for RegistryHolder { - type Error = serde_json::Error; - - fn try_into(self) -> Result { - serde_json::from_value(serde_json::to_value(self.map)?) + let name = ResourceLocation::new("minecraft:dimension_type"); + match self.get(&name) { + Some(Ok(registry)) => Some(registry), + Some(Err(err)) => { + error!( + "Error deserializing dimension type registry: {err:?}\n{:?}", + self.map.get(&name) + ); + None + } + None => None, + } } } impl McBufReadable for RegistryHolder { fn read_from(buf: &mut Cursor<&[u8]>) -> Result { - RegistryHolder::try_from(Nbt::read_from(buf)?) - .map_err(|e| BufReadError::Deserialization { source: e }) + let nbt_compound = NbtCompound::read_from(buf)?; + Ok(RegistryHolder { + map: simdnbt::Deserialize::from_compound(nbt_compound)?, + }) } } impl McBufWritable for RegistryHolder { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { - TryInto::::try_into(self.clone())?.write_into(buf) + let mut written = Vec::new(); + self.map.clone().to_compound().write_into(&mut written)?; + buf.write_all(&written) } } /// A collection of values for a certain type of registry data. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] -pub struct RegistryType { - #[serde(rename = "type")] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] +pub struct RegistryType +where + T: Serialize + Deserialize, +{ + #[simdnbt(rename = "type")] pub kind: ResourceLocation, pub value: Vec>, } /// A value for a certain type of registry data. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] -pub struct TypeValue { +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] +pub struct TypeValue +where + T: Serialize + Deserialize, +{ pub id: u32, pub name: ResourceLocation, pub element: T, } #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct TrimMaterialElement { pub asset_name: String, pub ingredient: ResourceLocation, pub item_model_index: f32, pub override_armor_materials: HashMap, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, } /// Data about a kind of chat message #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct ChatTypeElement { pub chat: ChatTypeData, pub narration: ChatTypeData, @@ -107,48 +110,29 @@ pub struct ChatTypeElement { /// Data about a chat message. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct ChatTypeData { pub translation_key: String, pub parameters: Vec, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub style: Option, } /// The style of a chat message. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct ChatTypeStyle { - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub color: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "Convert")] pub bold: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "Convert")] pub italic: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "Convert")] pub underlined: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "Convert")] pub strikethrough: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "Convert")] pub obfuscated: Option, } /// Dimension attributes. #[cfg(feature = "strict_registry")] #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] +#[simdnbt(deny_unknown_fields)] pub struct DimensionTypeElement { pub ambient_light: f32, #[serde(with = "Convert")] @@ -186,99 +170,124 @@ pub struct DimensionTypeElement { pub struct DimensionTypeElement { pub height: u32, pub min_y: i32, - #[serde(flatten)] - pub _extra: HashMap, + #[simdnbt(flatten)] + pub _extra: HashMap, } /// The light level at which monsters can spawn. /// /// This can be either a single minimum value, or a formula with a min and /// max. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[derive(Debug, Clone)] +// #[serde(untagged)] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub enum MonsterSpawnLightLevel { /// A simple minimum value. Simple(u32), /// A complex value with a type, minimum, and maximum. /// Vanilla minecraft only uses one type, "minecraft:uniform". Complex { - #[serde(rename = "type")] kind: ResourceLocation, value: MonsterSpawnLightLevelValues, }, } +impl FromNbtTag for MonsterSpawnLightLevel { + fn from_nbt_tag(tag: simdnbt::owned::NbtTag) -> Option { + if let Some(value) = tag.int() { + Some(Self::Simple(value as u32)) + } else if let Some(value) = tag.compound() { + let kind = ResourceLocation::from_nbt_tag(value.get("type")?.clone())?; + let value = MonsterSpawnLightLevelValues::from_nbt_tag(value.get("value")?.clone())?; + Some(Self::Complex { kind, value }) + } else { + None + } + } +} + +impl ToNbtTag for MonsterSpawnLightLevel { + fn to_nbt_tag(self) -> simdnbt::owned::NbtTag { + match self { + Self::Simple(value) => value.to_nbt_tag(), + Self::Complex { kind, value } => { + let mut compound = NbtCompound::new(); + compound.insert("type", kind.to_nbt_tag()); + compound.insert("value", value.to_nbt_tag()); + simdnbt::owned::NbtTag::Compound(compound) + } + } + } +} + /// The min and max light levels at which monsters can spawn. /// /// Values are inclusive. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct MonsterSpawnLightLevelValues { - #[serde(rename = "min_inclusive")] + #[simdnbt(rename = "min_inclusive")] pub min: u32, - #[serde(rename = "max_inclusive")] + #[simdnbt(rename = "max_inclusive")] pub max: u32, } /// Biome attributes. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct WorldTypeElement { - #[serde(with = "Convert")] pub has_precipitation: bool, pub temperature: f32, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub temperature_modifier: Option, pub downfall: f32, pub effects: BiomeEffects, } /// The precipitation of a biome. -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum BiomePrecipitation { - #[serde(rename = "none")] None, - #[serde(rename = "rain")] Rain, - #[serde(rename = "snow")] Snow, } +impl FromNbtTag for BiomePrecipitation { + fn from_nbt_tag(tag: NbtTag) -> Option { + match tag.string()?.to_str().as_ref() { + "none" => Some(Self::None), + "rain" => Some(Self::Rain), + "snow" => Some(Self::Snow), + _ => None, + } + } +} +impl ToNbtTag for BiomePrecipitation { + fn to_nbt_tag(self) -> NbtTag { + match self { + Self::None => NbtTag::String("none".into()), + Self::Rain => NbtTag::String("rain".into()), + Self::Snow => NbtTag::String("snow".into()), + } + } +} /// The effects of a biome. /// /// This includes the sky, fog, water, and grass color, /// as well as music and other sound effects. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct BiomeEffects { pub sky_color: u32, pub fog_color: u32, pub water_color: u32, pub water_fog_color: u32, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub foliage_color: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub grass_color: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub grass_color_modifier: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub music: Option, pub mood_sound: BiomeMoodSound, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub additions_sound: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub ambient_sound: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub particle: Option, } @@ -286,9 +295,8 @@ pub struct BiomeEffects { /// /// Some biomes have unique music that only play when inside them. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct BiomeMusic { - #[serde(with = "Convert")] pub replace_current_music: bool, pub max_delay: u32, pub min_delay: u32, @@ -296,7 +304,7 @@ pub struct BiomeMusic { } #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct BiomeMoodSound { pub tick_delay: u32, pub block_search_extent: u32, @@ -305,7 +313,7 @@ pub struct BiomeMoodSound { } #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct AdditionsSound { pub tick_chance: f32, pub sound: azalea_registry::SoundEvent, @@ -315,98 +323,25 @@ pub struct AdditionsSound { /// /// Some biomes have particles that spawn in the air. #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct BiomeParticle { pub probability: f32, pub options: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct TrimPatternElement { - #[serde(flatten)] + #[simdnbt(flatten)] pub pattern: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))] pub struct DamageTypeElement { pub message_id: String, pub scaling: String, pub exhaustion: f32, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub effects: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub death_message_type: Option, } - -// Using a trait because you can't implement methods for -// types you don't own, in this case Option and bool. -trait Convert: Sized { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer; - - fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>; -} - -// Convert between bool and u8 -impl Convert for bool { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_u8(if *self { 1 } else { 0 }) - } - - fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - convert::(u8::deserialize(deserializer)?) - } -} - -// Convert between Option and u8 -impl Convert for Option { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if let Some(value) = self { - Convert::serialize(value, serializer) - } else { - serializer.serialize_none() - } - } - - fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - if let Some(value) = Option::::deserialize(deserializer)? { - Ok(Some(convert::(value)?)) - } else { - Ok(None) - } - } -} - -// Deserializing logic here to deduplicate code -fn convert<'de, D>(value: u8) -> Result -where - D: Deserializer<'de>, -{ - match value { - 0 => Ok(false), - 1 => Ok(true), - other => Err(de::Error::invalid_value( - de::Unexpected::Unsigned(other as u64), - &"zero or one", - )), - } -} diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs index cc669841..e6a70247 100755 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -1,7 +1,11 @@ //! A resource, like minecraft:stone use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; -use std::io::{Cursor, Write}; +use simdnbt::{owned::NbtTag, FromNbtTag, ToNbtTag}; +use std::{ + io::{Cursor, Write}, + str::FromStr, +}; #[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; @@ -47,6 +51,13 @@ impl std::fmt::Debug for ResourceLocation { write!(f, "{}:{}", self.namespace, self.path) } } +impl FromStr for ResourceLocation { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + Ok(ResourceLocation::new(s)) + } +} impl McBufReadable for ResourceLocation { fn read_from(buf: &mut Cursor<&[u8]>) -> Result { @@ -88,6 +99,18 @@ impl<'de> Deserialize<'de> for ResourceLocation { } } +impl FromNbtTag for ResourceLocation { + fn from_nbt_tag(tag: NbtTag) -> Option { + tag.string().and_then(|s| s.to_str().parse().ok()) + } +} + +impl ToNbtTag for ResourceLocation { + fn to_nbt_tag(self) -> NbtTag { + NbtTag::String(self.to_string().into()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/azalea-core/src/slot.rs b/azalea-core/src/slot.rs deleted file mode 100755 index 22961437..00000000 --- a/azalea-core/src/slot.rs +++ /dev/null @@ -1,40 +0,0 @@ -// TODO: have an azalea-inventory or azalea-container crate and put this there - -use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; -use azalea_nbt::Nbt; -use std::io::{Cursor, Write}; - -#[derive(Debug, Clone, Default)] -pub enum Slot { - #[default] - Empty, - Present(SlotData), -} - -#[derive(Debug, Clone, McBuf)] -pub struct SlotData { - #[var] - pub id: u32, - pub count: u8, - pub nbt: Nbt, -} - -impl McBufReadable for Slot { - fn read_from(buf: &mut Cursor<&[u8]>) -> Result { - let slot = Option::::read_from(buf)?; - Ok(slot.map_or(Slot::Empty, Slot::Present)) - } -} - -impl McBufWritable for Slot { - fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - match self { - Slot::Empty => false.write_into(buf)?, - Slot::Present(i) => { - true.write_into(buf)?; - i.write_into(buf)?; - } - }; - Ok(()) - } -} diff --git a/azalea-entity/Cargo.toml b/azalea-entity/Cargo.toml index 4399ddf3..7cdf5343 100644 --- a/azalea-entity/Cargo.toml +++ b/azalea-entity/Cargo.toml @@ -14,7 +14,7 @@ azalea-buf = { version = "0.8.0", path = "../azalea-buf" } azalea-chat = { version = "0.8.0", path = "../azalea-chat" } azalea-core = { version = "0.8.0", path = "../azalea-core" } azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } -azalea-nbt = { version = "0.8.0", path = "../azalea-nbt" } +simdnbt = { version = "0.2.1" } azalea-registry = { version = "0.8.0", path = "../azalea-registry" } azalea-world = { version = "0.8.0", path = "../azalea-world" } bevy_app = "0.12.0" diff --git a/azalea-entity/src/data.rs b/azalea-entity/src/data.rs index 54487ef1..83779b21 100755 --- a/azalea-entity/src/data.rs +++ b/azalea-entity/src/data.rs @@ -81,7 +81,7 @@ pub enum EntityDataValue { BlockState(azalea_block::BlockState), /// If this is air, that means it's absent, OptionalBlockState(azalea_block::BlockState), - CompoundTag(azalea_nbt::Nbt), + CompoundTag(simdnbt::owned::NbtCompound), Particle(Particle), VillagerData(VillagerData), // 0 for absent; 1 + actual value otherwise. Used for entity IDs. diff --git a/azalea-entity/src/metadata.rs b/azalea-entity/src/metadata.rs index 39ba9527..006020d1 100644 --- a/azalea-entity/src/metadata.rs +++ b/azalea-entity/src/metadata.rs @@ -6054,9 +6054,9 @@ pub struct PlayerModeCustomisation(pub u8); #[derive(Component, Deref, DerefMut, Clone)] pub struct PlayerMainHand(pub u8); #[derive(Component, Deref, DerefMut, Clone)] -pub struct ShoulderLeft(pub azalea_nbt::Nbt); +pub struct ShoulderLeft(pub simdnbt::owned::NbtCompound); #[derive(Component, Deref, DerefMut, Clone)] -pub struct ShoulderRight(pub azalea_nbt::Nbt); +pub struct ShoulderRight(pub simdnbt::owned::NbtCompound); #[derive(Component)] pub struct Player; impl Player { @@ -6137,8 +6137,8 @@ impl Default for PlayerMetadataBundle { score: Score(0), player_mode_customisation: PlayerModeCustomisation(0), player_main_hand: PlayerMainHand(Default::default()), - shoulder_left: ShoulderLeft(azalea_nbt::Nbt::Compound(Default::default())), - shoulder_right: ShoulderRight(azalea_nbt::Nbt::Compound(Default::default())), + shoulder_left: ShoulderLeft(simdnbt::owned::NbtCompound::default()), + shoulder_right: ShoulderRight(simdnbt::owned::NbtCompound::default()), } } } diff --git a/azalea-inventory/Cargo.toml b/azalea-inventory/Cargo.toml index 64db6423..994dbb2b 100644 --- a/azalea-inventory/Cargo.toml +++ b/azalea-inventory/Cargo.toml @@ -11,5 +11,5 @@ version = "0.8.0" [dependencies] azalea-buf = { version = "0.8.0", path = "../azalea-buf" } azalea-inventory-macros = { version = "0.8.0", path = "./azalea-inventory-macros" } -azalea-nbt = { version = "0.8.0", path = "../azalea-nbt" } +simdnbt = { version = "0.2.1" } azalea-registry = { version = "0.8.0", path = "../azalea-registry" } diff --git a/azalea-inventory/src/slot.rs b/azalea-inventory/src/slot.rs index cef555d7..814b6c37 100644 --- a/azalea-inventory/src/slot.rs +++ b/azalea-inventory/src/slot.rs @@ -1,5 +1,5 @@ use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; -use azalea_nbt::Nbt; +use simdnbt::owned::Nbt; use std::io::{Cursor, Write}; /// Either an item in an inventory or nothing. diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml deleted file mode 100644 index 788d4a07..00000000 --- a/azalea-nbt/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -description = "A fast NBT serializer and deserializer." -edition = "2021" -license = "MIT" -name = "azalea-nbt" -version = "0.8.0" -repository = "https://github.com/azalea-rs/azalea/tree/main/azalea-nbt" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.8.0" } -byteorder = "^1.5.0" -compact_str = { version = "0.7.1", features = ["serde"] } -enum-as-inner = "0.6.0" -flate2 = "^1.0.28" -tracing = "0.1.40" -serde = { version = "^1.0", features = ["derive"], optional = true } -thiserror = "1.0.50" - -[dev-dependencies] -criterion = { version = "^0.5.1", features = ["html_reports"] } -graphite_binary = "0.1.0" -valence_nbt = "0.8.0" -fastnbt = "2.4.4" - -[features] -default = [] -serde = ["dep:serde"] - -[[bench]] -harness = false -name = "nbt" - -[[bench]] -harness = false -name = "compare" diff --git a/azalea-nbt/README.md b/azalea-nbt/README.md deleted file mode 100755 index e13bcf22..00000000 --- a/azalea-nbt/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Azalea NBT - -A fast NBT serializer and deserializer. - -- Gzip and Zlib compression -- All data is owned for ease-of-use -- Serde support with the `serde` feature. - -# Examples - -``` -use azalea_nbt::{Nbt, NbtCompound}; -use std::io::Cursor; - -let buf = include_bytes!("../tests/hello_world.nbt"); -let tag = Nbt::read(&mut Cursor::new(&buf[..])).unwrap(); -assert_eq!( - tag, - Nbt::Compound(NbtCompound::from_iter(vec![( - "hello world".into(), - Nbt::Compound(NbtCompound::from_iter(vec![( - "name".into(), - Nbt::String("Bananrama".into()), - )])) - )])) -); -``` - -# Benchmarks - -At the time of writing, Azalea NBT is the fastest NBT decoder (approximately twice as fast as Graphite NBT, the second fastest) and on-par with the fastest NBT encoders (sometimes the fastest, depending on the data). - -You can run the benchmarks to compare against other NBT libraries with `cargo bench --bench compare` and the normal benchmarks with `cargo bench --bench nbt`. - -Note: For best performance, use `RUSTFLAGS='-C target-cpu=native'`. diff --git a/azalea-nbt/benches/compare.rs b/azalea-nbt/benches/compare.rs deleted file mode 100755 index 863e0b9a..00000000 --- a/azalea-nbt/benches/compare.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::{ - fs::File, - io::{Cursor, Read}, -}; - -use azalea_buf::McBufReadable; -use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; -use flate2::read::GzDecoder; - -pub fn bench_read_file(filename: &str, c: &mut Criterion) { - let mut file = File::open(filename).unwrap(); - let mut contents = Vec::new(); - file.read_to_end(&mut contents).unwrap(); - let mut src = &contents[..]; - - // decode the original src so most of the time isn't spent on unzipping - let mut decoded_src_decoder = GzDecoder::new(&mut src); - let mut input = Vec::new(); - decoded_src_decoder.read_to_end(&mut input).unwrap(); - let input = input.as_slice(); - - let mut group = c.benchmark_group(filename); - group.throughput(Throughput::Bytes(input.len() as u64)); - - group.bench_function("azalea_parse", |b| { - b.iter(|| { - let input = black_box(input); - let nbt = azalea_nbt::Nbt::read(&mut Cursor::new(input)).unwrap(); - black_box(nbt); - }) - }); - - group.bench_function("graphite_parse", |b| { - b.iter(|| { - let input = black_box(input); - let nbt = graphite_binary::nbt::decode::read(&mut &input[..]).unwrap(); - black_box(nbt); - }) - }); - - // group.bench_function("valence_parse", |b| { - // b.iter(|| { - // let input = black_box(input); - // let nbt = valence_nbt::from_binary_slice(&mut &input[..]).unwrap(); - // black_box(nbt); - // }) - // }); - - // // writing - - let nbt = azalea_nbt::Nbt::read_from(&mut Cursor::new(input)).unwrap(); - group.bench_function("azalea_write", |b| { - b.iter(|| { - let nbt = black_box(&nbt); - let mut written = Vec::new(); - nbt.write(&mut written); - black_box(written); - }) - }); - - let nbt = graphite_binary::nbt::decode::read(&mut &input[..]).unwrap(); - group.bench_function("graphite_write", |b| { - b.iter(|| { - let nbt = black_box(&nbt); - let written = graphite_binary::nbt::encode::write(nbt); - black_box(written); - }) - }); - - // let nbt = valence_nbt::from_binary_slice(&mut &input[..]).unwrap(); - // group.bench_function("valence_write", |b| { - // b.iter(|| { - // let nbt = black_box(&nbt); - // let mut written = Vec::new(); - // valence_nbt::to_binary_writer(&mut written, &nbt.0, - // &nbt.1).unwrap(); black_box(written); - // }) - // }); -} - -fn bench(c: &mut Criterion) { - bench_read_file("tests/bigtest.nbt", c); - // bench_read_file("tests/simple_player.dat", c); - bench_read_file("tests/complex_player.dat", c); - // bench_read_file("tests/level.dat", c); - // bench_read_file("tests/stringtest.nbt", c); - // bench_read_file("tests/inttest.nbt", c); -} - -criterion_group!(benches, bench); -criterion_main!(benches); diff --git a/azalea-nbt/benches/nbt.rs b/azalea-nbt/benches/nbt.rs deleted file mode 100755 index 60f620b5..00000000 --- a/azalea-nbt/benches/nbt.rs +++ /dev/null @@ -1,75 +0,0 @@ -use azalea_nbt::Nbt; -use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; -use flate2::read::GzDecoder; -use std::{ - fs::File, - io::{Cursor, Read}, -}; - -fn bench_file(filename: &str, c: &mut Criterion) { - let mut file = File::open(filename).unwrap(); - let mut contents = Vec::new(); - file.read_to_end(&mut contents).unwrap(); - let mut src = &contents[..]; - - // decode the original src so most of the time isn't spent on unzipping - let mut decoded_src_decoder = GzDecoder::new(&mut src); - let mut decoded_src = Vec::new(); - if decoded_src_decoder.read_to_end(&mut decoded_src).is_err() { - // oh probably wasn't gzipped then - decoded_src = contents; - } - - let mut decoded_src_stream = Cursor::new(&decoded_src[..]); - - let nbt = Nbt::read(&mut decoded_src_stream).unwrap(); - decoded_src_stream.set_position(0); - - let mut group = c.benchmark_group(filename); - - group.throughput(Throughput::Bytes(decoded_src.len() as u64)); - - group.bench_function("Decode", |b| { - b.iter(|| { - black_box(Nbt::read(&mut decoded_src_stream).unwrap()); - decoded_src_stream.set_position(0); - }) - }); - - group.bench_function("Encode", |b| { - b.iter(|| { - nbt.write(&mut black_box(Vec::new())); - }) - }); - - // group.bench_function("Get", |b| { - // b.iter(|| { - // let level = nbt - // .as_compound() - // .unwrap() - // .get("Level") - // .unwrap() - // .as_compound() - // .unwrap(); - // for (k, _) in level.iter() { - // black_box(level.get(black_box(k))); - // } - // }) - // }); - group.finish(); -} - -fn bench(c: &mut Criterion) { - bench_file("tests/bigtest.nbt", c); - // bench_file("tests/simple_player.dat", c); - // bench_file("tests/complex_player.dat", c); - // bench_file("tests/level.dat", c); - // bench_file("tests/stringtest.nbt", c); - // bench_file("tests/inttest16.nbt", c); - - // bench_file("tests/inttest1023.nbt", c); - // bench_file("tests/inttest3.nbt", c); -} - -criterion_group!(benches, bench); -criterion_main!(benches); diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs deleted file mode 100755 index 23247b74..00000000 --- a/azalea-nbt/src/decode.rs +++ /dev/null @@ -1,314 +0,0 @@ -use crate::tag::*; -use crate::Error; -use azalea_buf::{BufReadError, McBufReadable}; -use byteorder::{ReadBytesExt, BE}; -use flate2::read::{GzDecoder, ZlibDecoder}; -use std::io::{BufRead, Cursor, Read}; -use tracing::warn; - -#[inline] -fn read_bytes<'a>(buf: &'a mut Cursor<&[u8]>, length: usize) -> Result<&'a [u8], Error> { - if length > (buf.get_ref().len() - buf.position() as usize) { - return Err(Error::UnexpectedEof); - } - let initial_position = buf.position() as usize; - buf.set_position(buf.position() + length as u64); - let data = &buf.get_ref()[initial_position..initial_position + length]; - Ok(data) -} - -#[inline] -fn read_string(stream: &mut Cursor<&[u8]>) -> Result { - let length = stream.read_u16::()? as usize; - - let buf = read_bytes(stream, length)?; - - Ok(if let Ok(string) = std::str::from_utf8(buf) { - string.into() - } else { - let lossy_string = String::from_utf8_lossy(buf).into_owned(); - warn!("Error decoding utf8 (bytes: {buf:?}, lossy: \"{lossy_string})\""); - lossy_string.into() - }) -} - -#[inline] -fn read_byte_array(stream: &mut Cursor<&[u8]>) -> Result { - let length = stream.read_u32::()? as usize; - let bytes = read_bytes(stream, length)?.to_vec(); - Ok(bytes) -} - -// https://stackoverflow.com/a/59707887 -fn vec_u8_into_i8(v: Vec) -> Vec { - // ideally we'd use Vec::into_raw_parts, but it's unstable, - // so we have to do it manually: - - // first, make sure v's destructor doesn't free the data - // it thinks it owns when it goes out of scope - let mut v = std::mem::ManuallyDrop::new(v); - - // then, pick apart the existing Vec - let p = v.as_mut_ptr(); - let len = v.len(); - let cap = v.capacity(); - - // finally, adopt the data into a new Vec - unsafe { Vec::from_raw_parts(p as *mut i8, len, cap) } -} - -#[inline] -fn read_list(stream: &mut Cursor<&[u8]>) -> Result { - let type_id = stream.read_u8()?; - let length = stream.read_u32::()?; - let list = match type_id { - END_ID => NbtList::Empty, - BYTE_ID => NbtList::Byte(vec_u8_into_i8( - read_bytes(stream, length as usize)?.to_vec(), - )), - SHORT_ID => NbtList::Short({ - if ((length * 2) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| stream.read_i16::()) - .collect::, _>>()? - }), - INT_ID => NbtList::Int({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| stream.read_i32::()) - .collect::, _>>()? - }), - LONG_ID => NbtList::Long({ - if ((length * 8) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| stream.read_i64::()) - .collect::, _>>()? - }), - FLOAT_ID => NbtList::Float({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| stream.read_f32::()) - .collect::, _>>()? - }), - DOUBLE_ID => NbtList::Double({ - if ((length * 8) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| stream.read_f64::()) - .collect::, _>>()? - }), - BYTE_ARRAY_ID => NbtList::ByteArray({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| read_byte_array(stream)) - .collect::, _>>()? - }), - STRING_ID => NbtList::String({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| read_string(stream)) - .collect::, _>>()? - }), - LIST_ID => NbtList::List({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| read_list(stream)) - .collect::, _>>()? - }), - COMPOUND_ID => NbtList::Compound({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| read_compound(stream)) - .collect::, _>>()? - }), - INT_ARRAY_ID => NbtList::IntArray({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| read_int_array(stream)) - .collect::, _>>()? - }), - LONG_ARRAY_ID => NbtList::LongArray({ - if ((length * 4) as usize) > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - (0..length) - .map(|_| read_long_array(stream)) - .collect::, _>>()? - }), - _ => return Err(Error::InvalidTagType(type_id)), - }; - Ok(list) -} - -#[inline] -fn read_compound(stream: &mut Cursor<&[u8]>) -> Result { - // we default to capacity 4 because it'll probably not be empty - let mut map = NbtCompound::with_capacity(4); - loop { - let tag_id = stream.read_u8().unwrap_or(0); - if tag_id == 0 { - break; - } - let name = read_string(stream)?; - let tag = Nbt::read_known(stream, tag_id)?; - map.insert_unsorted(name, tag); - } - map.sort(); - Ok(map) -} - -#[inline] -fn read_int_array(stream: &mut Cursor<&[u8]>) -> Result { - let length = stream.read_u32::()? as usize; - if length * 4 > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - let mut ints = NbtIntArray::with_capacity(length); - for _ in 0..length { - ints.push(stream.read_i32::()?); - } - Ok(ints) -} - -#[inline] -fn read_long_array(stream: &mut Cursor<&[u8]>) -> Result { - let length = stream.read_u32::()? as usize; - if length * 8 > (stream.get_ref().len() - stream.position() as usize) { - return Err(Error::UnexpectedEof); - } - let mut longs = NbtLongArray::with_capacity(length); - for _ in 0..length { - longs.push(stream.read_i64::()?); - } - Ok(longs) -} - -impl Nbt { - /// Read the NBT data when you already know the ID of the tag. You usually - /// want [`Nbt::read`] if you're reading an NBT file. - #[inline] - fn read_known(stream: &mut Cursor<&[u8]>, id: u8) -> Result { - Ok(match id { - // Signifies the end of a TAG_Compound. It is only ever used inside - // a TAG_Compound, and is not named despite being in a TAG_Compound - END_ID => Nbt::End, - // A single signed byte - BYTE_ID => Nbt::Byte(stream.read_i8()?), - // A single signed, big endian 16 bit integer - SHORT_ID => Nbt::Short(stream.read_i16::()?), - // A single signed, big endian 32 bit integer - INT_ID => Nbt::Int(stream.read_i32::()?), - // A single signed, big endian 64 bit integer - LONG_ID => Nbt::Long(stream.read_i64::()?), - // A single, big endian IEEE-754 single-precision floating point - // number (NaN possible) - FLOAT_ID => Nbt::Float(stream.read_f32::()?), - // A single, big endian IEEE-754 double-precision floating point - // number (NaN possible) - DOUBLE_ID => Nbt::Double(stream.read_f64::()?), - // A length-prefixed array of signed bytes. The prefix is a signed - // integer (thus 4 bytes) - BYTE_ARRAY_ID => Nbt::ByteArray(read_byte_array(stream)?), - // A length-prefixed modified UTF-8 string. The prefix is an - // unsigned short (thus 2 bytes) signifying the length of the - // string in bytes - STRING_ID => Nbt::String(read_string(stream)?), - // A list of nameless tags, all of the same type. The list is - // prefixed with the Type ID of the items it contains (thus 1 - // byte), and the length of the list as a signed integer (a further - // 4 bytes). If the length of the list is 0 or negative, the type - // may be 0 (TAG_End) but otherwise it must be any other type. (The - // notchian implementation uses TAG_End in that situation, but - // another reference implementation by Mojang uses 1 instead; - // parsers should accept any type if the length is <= 0). - LIST_ID => Nbt::List(read_list(stream)?), - // Effectively a list of a named tags. Order is not guaranteed. - COMPOUND_ID => Nbt::Compound(read_compound(stream)?), - // A length-prefixed array of signed integers. The prefix is a - // signed integer (thus 4 bytes) and indicates the number of 4 byte - // integers. - INT_ARRAY_ID => Nbt::IntArray(read_int_array(stream)?), - // A length-prefixed array of signed longs. The prefix is a signed - // integer (thus 4 bytes) and indicates the number of 8 byte longs. - LONG_ARRAY_ID => Nbt::LongArray(read_long_array(stream)?), - _ => return Err(Error::InvalidTagType(id)), - }) - } - - /// Read the NBT data. This will return a compound tag with a single item. - /// - /// Minecraft usually uses this function when reading from files. - /// [`Nbt::read_any_tag`] is used when reading from the network. - pub fn read(stream: &mut Cursor<&[u8]>) -> Result { - // default to compound tag - - // the parent compound only ever has one item - let tag_id = stream.read_u8().unwrap_or(0); - if tag_id == 0 { - return Ok(Nbt::End); - } - let name = read_string(stream)?; - let tag = Nbt::read_known(stream, tag_id)?; - let mut map = NbtCompound::with_capacity(1); - map.insert_unsorted(name, tag); - - Ok(Nbt::Compound(map)) - } - - /// Read the NBT data. There is no guarantee that the tag will be a compound - /// with a single item. - /// - /// The Minecraft protocol uses this function when reading from the network. - /// [`Nbt::read`] is usually used when reading from files. - pub fn read_any_tag(stream: &mut Cursor<&[u8]>) -> Result { - let tag_id = stream.read_u8().unwrap_or(0); - let tag = Nbt::read_known(stream, tag_id)?; - Ok(tag) - } - - /// Read the NBT data compressed wtih zlib. - pub fn read_zlib(stream: &mut impl BufRead) -> Result { - let mut gz = ZlibDecoder::new(stream); - let mut buf = Vec::new(); - gz.read_to_end(&mut buf)?; - Nbt::read(&mut Cursor::new(&buf)) - } - - /// Read the NBT data compressed wtih gzip. - pub fn read_gzip(stream: &mut Cursor>) -> Result { - let mut gz = GzDecoder::new(stream); - let mut buf = Vec::new(); - gz.read_to_end(&mut buf)?; - Nbt::read(&mut Cursor::new(&buf)) - } -} - -impl McBufReadable for Nbt { - fn read_from(buf: &mut Cursor<&[u8]>) -> Result { - Ok(Nbt::read_any_tag(buf)?) - } -} -impl From for BufReadError { - fn from(e: Error) -> Self { - BufReadError::Custom(e.to_string()) - } -} diff --git a/azalea-nbt/src/encode.rs b/azalea-nbt/src/encode.rs deleted file mode 100755 index 34c451d2..00000000 --- a/azalea-nbt/src/encode.rs +++ /dev/null @@ -1,296 +0,0 @@ -use crate::tag::*; -use azalea_buf::McBufWritable; -use byteorder::{WriteBytesExt, BE}; -use flate2::write::{GzEncoder, ZlibEncoder}; -// use packed_simd_2::{i32x16, i32x2, i32x4, i32x8, i64x2, i64x4, i64x8}; -use std::io::Write; - -#[inline] -fn write_string(writer: &mut impl Write, string: &NbtString) { - writer.write_u16::(string.len() as u16).unwrap(); - writer.write_all(string.as_bytes()).unwrap(); -} - -#[inline] -fn write_compound(writer: &mut impl Write, value: &NbtCompound, end_tag: bool) { - for (key, tag) in value.iter() { - writer.write_u8(tag.id()).unwrap(); - write_string(writer, key); - write_known(writer, tag); - } - if end_tag { - writer.write_u8(END_ID).unwrap(); - } -} - -fn write_known(writer: &mut impl Write, tag: &Nbt) { - match tag { - Nbt::End => {} - Nbt::Byte(value) => { - writer.write_i8(*value).unwrap(); - } - Nbt::Short(value) => { - writer.write_i16::(*value).unwrap(); - } - Nbt::Int(value) => { - writer.write_i32::(*value).unwrap(); - } - Nbt::Long(value) => { - writer.write_i64::(*value).unwrap(); - } - Nbt::Float(value) => { - writer.write_f32::(*value).unwrap(); - } - Nbt::Double(value) => { - writer.write_f64::(*value).unwrap(); - } - Nbt::ByteArray(value) => { - write_byte_array(writer, value); - } - Nbt::String(value) => { - write_string(writer, value); - } - Nbt::List(value) => { - write_list(writer, value); - } - Nbt::Compound(value) => { - write_compound(writer, value, true); - } - Nbt::IntArray(value) => { - write_int_array(writer, value); - } - Nbt::LongArray(value) => { - write_long_array(writer, value); - } - } -} - -#[inline] -fn write_list(writer: &mut impl Write, value: &NbtList) { - writer.write_u8(value.id()).unwrap(); - match value { - NbtList::Empty => writer.write_all(&[0; 4]).unwrap(), - NbtList::Byte(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - let l = l.as_slice(); - writer - // convert [i8] into [u8] - .write_all(unsafe { std::slice::from_raw_parts(l.as_ptr() as *const u8, l.len()) }) - .unwrap(); - } - NbtList::Short(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for &v in l { - writer.write_i16::(v).unwrap(); - } - } - NbtList::Int(l) => write_int_array(writer, l), - NbtList::Long(l) => write_long_array(writer, l), - NbtList::Float(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for &v in l { - writer.write_f32::(v).unwrap(); - } - } - NbtList::Double(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for &v in l { - writer.write_f64::(v).unwrap(); - } - } - NbtList::ByteArray(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for v in l { - write_byte_array(writer, v); - } - } - NbtList::String(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for v in l { - write_string(writer, v); - } - } - NbtList::List(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for v in l { - write_list(writer, v); - } - } - NbtList::Compound(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for v in l { - write_compound(writer, v, true); - } - } - NbtList::IntArray(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for v in l { - write_int_array(writer, v); - } - } - NbtList::LongArray(l) => { - writer.write_i32::(l.len() as i32).unwrap(); - for v in l { - write_long_array(writer, v); - } - } - } -} - -#[inline] -fn write_byte_array(writer: &mut impl Write, value: &[u8]) { - writer.write_u32::(value.len() as u32).unwrap(); - writer.write_all(value).unwrap(); -} - -#[inline] -fn write_int_array(writer: &mut impl Write, array: &[i32]) { - writer.write_i32::(array.len() as i32).unwrap(); - - for &item in array { - writer.write_i32::(item).unwrap(); - } - - // (disabled for now since i realized packed_simd to_be does not work as - // expected) // flip the bits to big endian with simd - // let mut position = 0; - // // x16 - // while array.len() - position >= 16 { - // let l = unsafe { - // i32x16::from_slice_unaligned_unchecked(&array[position..]) }; let - // l = l.to_be(); let l = unsafe { std::mem::transmute::(l) }; writer.write_all(&l).unwrap(); - // position += 16; - // } - // // x8 - // if array.len() - position >= 8 { - // let l = unsafe { - // i32x8::from_slice_unaligned_unchecked(&array[position..]) }; - // let l = l.to_be(); - // let l = unsafe { std::mem::transmute::(l) }; - // writer.write_all(&l).unwrap(); - // position += 8; - // } - // // x4 - // if array.len() - position >= 4 { - // let l = unsafe { - // i32x4::from_slice_unaligned_unchecked(&array[position..]) }; - // let l = l.to_be(); - // let l = unsafe { std::mem::transmute::(l) }; - // writer.write_all(&l).unwrap(); - // position += 4; - // } - // // x2 - // if array.len() - position >= 2 { - // let l = unsafe { - // i32x2::from_slice_unaligned_unchecked(&array[position..]) }; - // let l = l.to_be(); - // let l = l.swap_bytes(); - // let l = unsafe { std::mem::transmute::(l) }; - // writer.write_all(&l).unwrap(); - // position += 2; - // } - // // x1 ... just a normal write_i32 - // if array.len() - position >= 1 { - // writer.write_i32::(array[position]).unwrap(); - // } -} - -#[inline] -fn write_long_array(writer: &mut impl Write, l: &[i64]) { - writer.write_i32::(l.len() as i32).unwrap(); - - for &item in l { - writer.write_i64::(item).unwrap(); - } - - // (disabled for now since i realized packed_simd to_be does not work as - // expected) - - // // flip the bits to big endian with simd - // let mut position = 0; - // // x16 - // while l.len() - position >= 8 { - // let l = unsafe { - // i64x8::from_slice_unaligned_unchecked(&l[position..]) }; - // l.to_be(); - // let l = unsafe { std::mem::transmute::(l) }; - // writer.write_all(&l).unwrap(); - // position += 8; - // } - // // x4 - // if l.len() - position >= 4 { - // let l = unsafe { - // i64x4::from_slice_unaligned_unchecked(&l[position..]) }; - // l.to_be(); - // let l = unsafe { std::mem::transmute::(l) }; - // writer.write_all(&l).unwrap(); - // position += 4; - // } - // // x2 - // if l.len() - position >= 2 { - // let l = unsafe { - // i64x2::from_slice_unaligned_unchecked(&l[position..]) }; - // l.to_be(); - // let l = unsafe { std::mem::transmute::(l) }; - // writer.write_all(&l).unwrap(); - // position += 2; - // } - // // x1 ... just a normal write_i32 - // if l.len() - position >= 1 { - // writer.write_i64::(l[position]).unwrap(); - // } -} - -impl Nbt { - /// Write the compound tag as NBT data. - /// - /// # Panics - /// - /// Will panic if the tag is not a Compound or End tag. - pub fn write(&self, writer: &mut impl Write) { - match self { - Nbt::Compound(value) => { - write_compound(writer, value, false); - } - Nbt::End => { - END_ID.write_into(writer).unwrap(); - } - _ => panic!("Not a compound tag"), - } - } - - /// Write any tag as NBT data. This is used by Minecraft when writing to the - /// network, otherwise [`Nbt::write`] is usually used instead. - pub fn write_any(&self, writer: &mut impl Write) { - writer.write_u8(self.id()).unwrap(); - write_known(writer, self); - } - - /// Write the compound tag as NBT data compressed wtih zlib. - /// - /// # Errors - /// - /// Returns an `Err` if it's not a Compound or End tag. - pub fn write_zlib(&self, writer: &mut impl Write) { - let mut encoder = ZlibEncoder::new(writer, flate2::Compression::default()); - self.write(&mut encoder) - } - - /// Write the compound tag as NBT data compressed wtih gzip. - /// - /// # Errors - /// - /// Returns an `Err` if it's not a Compound or End tag. - pub fn write_gzip(&self, writer: &mut impl Write) { - let mut encoder = GzEncoder::new(writer, flate2::Compression::default()); - self.write(&mut encoder) - } -} - -impl McBufWritable for Nbt { - fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - self.write_any(buf); - Ok(()) - } -} diff --git a/azalea-nbt/src/error.rs b/azalea-nbt/src/error.rs deleted file mode 100755 index ace7fcd3..00000000 --- a/azalea-nbt/src/error.rs +++ /dev/null @@ -1,15 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error("Invalid tag type: {0}")] - InvalidTagType(u8), - #[error("Invalid tag")] - InvalidTag, - #[error("Write error: {0}")] - WriteError(#[from] std::io::Error), - #[error("Utf8 error: {0}")] - Utf8Error(#[from] std::str::Utf8Error), - #[error("Unexpected EOF")] - UnexpectedEof, -} diff --git a/azalea-nbt/src/lib.rs b/azalea-nbt/src/lib.rs deleted file mode 100755 index 1a636520..00000000 --- a/azalea-nbt/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![doc = include_str!("../README.md")] - -mod decode; -mod encode; -mod error; -mod tag; - -pub use error::Error; -pub use tag::*; - -#[cfg(test)] -mod tests { - use std::io::Cursor; - - use crate::tag::NbtCompound; - - use super::*; - use azalea_buf::{McBufReadable, McBufWritable}; - - #[test] - fn mcbuf_nbt() { - let mut buf = Vec::new(); - let tag = Nbt::Compound(NbtCompound::from_iter(vec![( - "hello world".into(), - Nbt::Compound(NbtCompound::from_iter(vec![( - "name".into(), - Nbt::String("Bananrama".into()), - )])), - )])); - tag.write_into(&mut buf).unwrap(); - - let mut buf = Cursor::new(&buf[..]); - - let result = Nbt::read_from(&mut buf).unwrap(); - assert_eq!( - result, - Nbt::Compound(NbtCompound::from_iter(vec![( - "hello world".into(), - Nbt::Compound(NbtCompound::from_iter(vec![( - "name".into(), - Nbt::String("Bananrama".into()), - )])), - )])) - ); - } -} diff --git a/azalea-nbt/src/tag.rs b/azalea-nbt/src/tag.rs deleted file mode 100755 index 224db2d3..00000000 --- a/azalea-nbt/src/tag.rs +++ /dev/null @@ -1,271 +0,0 @@ -use compact_str::CompactString; -use enum_as_inner::EnumAsInner; -#[cfg(feature = "serde")] -use serde::{ser::SerializeMap, Deserialize, Serialize}; - -pub type NbtByte = i8; -pub type NbtShort = i16; -pub type NbtInt = i32; -pub type NbtLong = i64; -pub type NbtFloat = f32; -pub type NbtDouble = f64; -pub type NbtByteArray = Vec; -pub type NbtString = CompactString; -pub type NbtIntArray = Vec; -pub type NbtLongArray = Vec; - -pub const END_ID: u8 = 0; -pub const BYTE_ID: u8 = 1; -pub const SHORT_ID: u8 = 2; -pub const INT_ID: u8 = 3; -pub const LONG_ID: u8 = 4; -pub const FLOAT_ID: u8 = 5; -pub const DOUBLE_ID: u8 = 6; -pub const BYTE_ARRAY_ID: u8 = 7; -pub const STRING_ID: u8 = 8; -pub const LIST_ID: u8 = 9; -pub const COMPOUND_ID: u8 = 10; -pub const INT_ARRAY_ID: u8 = 11; -pub const LONG_ARRAY_ID: u8 = 12; - -/// An NBT value. -#[derive(Clone, Debug, PartialEq, Default, EnumAsInner)] -#[repr(u8)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))] -pub enum Nbt { - #[default] - End = END_ID, - Byte(NbtByte) = BYTE_ID, - Short(NbtShort) = SHORT_ID, - Int(NbtInt) = INT_ID, - Long(NbtLong) = LONG_ID, - Float(NbtFloat) = FLOAT_ID, - Double(NbtDouble) = DOUBLE_ID, - ByteArray(NbtByteArray) = BYTE_ARRAY_ID, - String(NbtString) = STRING_ID, - List(NbtList) = LIST_ID, - Compound(NbtCompound) = COMPOUND_ID, - IntArray(NbtIntArray) = INT_ARRAY_ID, - LongArray(NbtLongArray) = LONG_ARRAY_ID, -} -impl Nbt { - /// Get the numerical ID of the tag type. - #[inline] - pub fn id(&self) -> u8 { - // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` - // `union` between `repr(C)` structs, each of which has the `u8` - // discriminant as its first field, so we can read the discriminant - // without offsetting the pointer. - unsafe { *<*const _>::from(self).cast::() } - } -} - -/// An NBT value. -#[derive(Clone, Debug, PartialEq)] -#[repr(u8)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))] -pub enum NbtList { - Empty = END_ID, - Byte(Vec) = BYTE_ID, - Short(Vec) = SHORT_ID, - Int(Vec) = INT_ID, - Long(Vec) = LONG_ID, - Float(Vec) = FLOAT_ID, - Double(Vec) = DOUBLE_ID, - ByteArray(Vec) = BYTE_ARRAY_ID, - String(Vec) = STRING_ID, - List(Vec) = LIST_ID, - Compound(Vec) = COMPOUND_ID, - IntArray(Vec) = INT_ARRAY_ID, - LongArray(Vec) = LONG_ARRAY_ID, -} - -impl NbtList { - /// Get the numerical ID of the tag type. - #[inline] - pub fn id(&self) -> u8 { - // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` - // `union` between `repr(C)` structs, each of which has the `u8` - // discriminant as its first field, so we can read the discriminant - // without offsetting the pointer. - unsafe { *<*const _>::from(self).cast::() } - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Byte(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Short(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Int(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Long(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Float(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Double(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::ByteArray(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::String(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::List(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::Compound(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::IntArray(v) - } -} -impl From> for NbtList { - fn from(v: Vec) -> Self { - Self::LongArray(v) - } -} - -// thanks to Moulberry/Graphite for the idea to use a vec and binary search -#[derive(Debug, Clone, Default, PartialEq)] -pub struct NbtCompound { - inner: Vec<(NbtString, Nbt)>, -} -impl NbtCompound { - #[inline] - pub fn with_capacity(capacity: usize) -> Self { - Self { - inner: Vec::with_capacity(capacity), - } - } - - #[inline] - fn binary_search(&self, key: &NbtString) -> Result { - self.inner.binary_search_by(|(k, _)| k.cmp(key)) - } - - /// Get a reference to the value corresponding to the key in this compound. - /// - /// If you previously used [`Self::insert_unsorted`] without [`Self::sort`], - /// this function may return incorrect results. - #[inline] - pub fn get(&self, key: &str) -> Option<&Nbt> { - if self.is_worth_sorting() { - let key = NbtString::from(key); - self.binary_search(&key).ok().map(|i| &self.inner[i].1) - } else { - for (k, v) in &self.inner { - if &key == k { - return Some(v); - } - } - None - } - } - - #[inline] - pub fn insert_unsorted(&mut self, key: NbtString, value: Nbt) { - self.inner.push((key, value)); - } - - /// Insert an item into the compound, returning the previous value if it - /// existed. - /// - /// If you're adding many items at once, it's more efficient to use - /// [`Self::insert_unsorted`] and then [`Self::sort`] after everything is - /// inserted. - #[inline] - pub fn insert(&mut self, key: NbtString, value: Nbt) { - self.inner.push((key, value)); - self.sort() - } - - #[inline] - pub fn sort(&mut self) { - if !self.is_worth_sorting() { - return; - } - self.inner.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - } - - #[inline] - pub fn iter(&self) -> std::slice::Iter<'_, (CompactString, Nbt)> { - self.inner.iter() - } - - #[inline] - fn is_worth_sorting(&self) -> bool { - // i don't actually know when binary search starts being better, but it's at - // least more than 12 - self.inner.len() >= 32 - } -} - -impl IntoIterator for NbtCompound { - type Item = (NbtString, Nbt); - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.inner.into_iter() - } -} - -#[cfg(feature = "serde")] -impl Serialize for NbtCompound { - fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(Some(self.inner.len()))?; - for (key, value) in &self.inner { - map.serialize_entry(key, value)?; - } - map.end() - } -} -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for NbtCompound { - fn deserialize>(deserializer: D) -> Result { - use std::collections::BTreeMap; - let map = as Deserialize>::deserialize(deserializer)?; - Ok(Self { - inner: map.into_iter().collect(), - }) - } -} - -impl FromIterator<(NbtString, Nbt)> for NbtCompound { - fn from_iter>(iter: T) -> Self { - let inner = iter.into_iter().collect::>(); - Self { inner } - } -} - -impl From> for NbtCompound { - fn from(inner: Vec<(NbtString, Nbt)>) -> Self { - Self { inner } - } -} diff --git a/azalea-nbt/tests/bigtest.nbt b/azalea-nbt/tests/bigtest.nbt deleted file mode 100755 index dc3769bc..00000000 Binary files a/azalea-nbt/tests/bigtest.nbt and /dev/null differ diff --git a/azalea-nbt/tests/complex_player.dat b/azalea-nbt/tests/complex_player.dat deleted file mode 100755 index d7f3fcbd..00000000 Binary files a/azalea-nbt/tests/complex_player.dat and /dev/null differ diff --git a/azalea-nbt/tests/hello_world.nbt b/azalea-nbt/tests/hello_world.nbt deleted file mode 100755 index f3f5e21a..00000000 Binary files a/azalea-nbt/tests/hello_world.nbt and /dev/null differ diff --git a/azalea-nbt/tests/inttest1023.nbt b/azalea-nbt/tests/inttest1023.nbt deleted file mode 100644 index 481dde19..00000000 Binary files a/azalea-nbt/tests/inttest1023.nbt and /dev/null differ diff --git a/azalea-nbt/tests/inttest16.nbt b/azalea-nbt/tests/inttest16.nbt deleted file mode 100755 index ad9255f2..00000000 Binary files a/azalea-nbt/tests/inttest16.nbt and /dev/null differ diff --git a/azalea-nbt/tests/inttest3.nbt b/azalea-nbt/tests/inttest3.nbt deleted file mode 100644 index 2890f577..00000000 Binary files a/azalea-nbt/tests/inttest3.nbt and /dev/null differ diff --git a/azalea-nbt/tests/level.dat b/azalea-nbt/tests/level.dat deleted file mode 100755 index e5d11b8c..00000000 Binary files a/azalea-nbt/tests/level.dat and /dev/null differ diff --git a/azalea-nbt/tests/simple_player.dat b/azalea-nbt/tests/simple_player.dat deleted file mode 100755 index e5091b41..00000000 Binary files a/azalea-nbt/tests/simple_player.dat and /dev/null differ diff --git a/azalea-nbt/tests/stringtest.nbt b/azalea-nbt/tests/stringtest.nbt deleted file mode 100755 index ac111433..00000000 Binary files a/azalea-nbt/tests/stringtest.nbt and /dev/null differ diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs deleted file mode 100755 index 5c82c3c7..00000000 --- a/azalea-nbt/tests/tests.rs +++ /dev/null @@ -1,140 +0,0 @@ -use azalea_nbt::{Nbt, NbtCompound, NbtList}; -use std::io::Cursor; - -#[test] -fn test_decode_hello_world() { - // read hello_world.nbt - let buf = include_bytes!("hello_world.nbt").to_vec(); - let tag = Nbt::read(&mut Cursor::new(&buf[..])).unwrap(); - assert_eq!( - tag, - Nbt::Compound(NbtCompound::from_iter(vec![( - "hello world".into(), - Nbt::Compound(NbtCompound::from_iter(vec![( - "name".into(), - Nbt::String("Bananrama".into()), - )])) - )])) - ); -} - -#[test] -fn test_roundtrip_hello_world() { - let original = include_bytes!("hello_world.nbt").to_vec(); - - let mut original_stream = Cursor::new(&original[..]); - let tag = Nbt::read(&mut original_stream).unwrap(); - - // write hello_world.nbt - let mut result = Vec::new(); - tag.write(&mut result); - - assert_eq!(result, original); -} - -#[test] -fn test_bigtest() { - // read bigtest.nbt - let original = include_bytes!("bigtest.nbt").to_vec(); - - let mut original_stream = Cursor::new(original); - let original_tag = Nbt::read_gzip(&mut original_stream).unwrap(); - - let mut result = Vec::new(); - original_tag.write(&mut result); - - let decoded_tag = Nbt::read(&mut Cursor::new(&result)).unwrap(); - - assert_eq!(decoded_tag, original_tag); -} - -#[test] -fn test_stringtest() { - let correct_tag = Nbt::Compound(NbtCompound::from_iter(vec![( - "😃".into(), - Nbt::List(NbtList::String(vec![ - "asdfkghasfjgihsdfogjsndfg".into(), - "jnabsfdgihsabguiqwrntgretqwejirhbiqw".into(), - "asd".into(), - "wqierjgt7wqy8u4rtbwreithwretiwerutbwenryq8uwervqwer9iuqwbrgyuqrbtwierotugqewrtqwropethert".into(), - "asdf".into(), - "alsdkjiqwoe".into(), - "lmqi9hyqd".into(), - "qwertyuiop".into(), - "asdfghjkl".into(), - "zxcvbnm".into(), - " ".into(), - "words words words words words words".into(), - "aaaaaaaaaaaaaaaaaaaa".into(), - "♥".into(), - "a\nb\n\n\nc\r\rd".into(), - "😁".into(), - ])) - )])); - let original = include_bytes!("stringtest.nbt").to_vec(); - - let mut original_stream = Cursor::new(original); - let original_tag = Nbt::read_gzip(&mut original_stream).unwrap(); - - assert_eq!(original_tag, correct_tag); -} - -#[test] -fn test_complex_player() { - let original = include_bytes!("complex_player.dat").to_vec(); - - let mut original_stream = Cursor::new(original); - let original_tag = Nbt::read_gzip(&mut original_stream).unwrap(); - - let mut result = Vec::new(); - original_tag.write(&mut result); - - let decoded_tag = Nbt::read(&mut Cursor::new(&result)).unwrap(); - - assert_eq!(decoded_tag, original_tag); -} - -#[test] -fn test_simple_player() { - let original = include_bytes!("simple_player.dat").to_vec(); - - let mut original_stream = Cursor::new(original); - let original_tag = Nbt::read_gzip(&mut original_stream).unwrap(); - - let mut result = Vec::new(); - original_tag.write(&mut result); - - let decoded_tag = Nbt::read(&mut Cursor::new(&result)).unwrap(); - - assert_eq!(decoded_tag, original_tag); -} - -// #[test] -// fn test_inttest() { -// let original = include_bytes!("inttest.nbt").to_vec(); - -// let mut original_stream = Cursor::new(original); -// let original_tag = Nbt::read_gzip(&mut original_stream).unwrap(); - -// let mut result = Vec::new(); -// original_tag.write(&mut result); - -// let decoded_tag = Nbt::read(&mut Cursor::new(&result)).unwrap(); - -// assert_eq!(decoded_tag, original_tag); -// } - -#[test] -fn test_inttest1023() { - let original = include_bytes!("inttest1023.nbt").to_vec(); - - let mut original_stream = Cursor::new(original.as_slice()); - let original_tag = Nbt::read(&mut original_stream).unwrap(); - - let mut result = Vec::new(); - original_tag.write(&mut result); - - let decoded_tag = Nbt::read(&mut Cursor::new(&result)).unwrap(); - - assert_eq!(decoded_tag, original_tag); -} diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 9a143ed5..81a3dde1 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -27,9 +27,7 @@ azalea-core = { path = "../azalea-core", optional = true, version = "^0.8.0", fe azalea-crypto = { path = "../azalea-crypto", version = "0.8.0" } azalea-entity = { version = "0.8.0", path = "../azalea-entity" } azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } -azalea-nbt = { path = "../azalea-nbt", version = "^0.8.0", features = [ - "serde", -] } +simdnbt = { version = "0.2.1" } azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.8.0" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" } azalea-world = { path = "../azalea-world", version = "0.8.0" } diff --git a/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs index f9c889f8..95e9c6c3 100755 --- a/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs @@ -1,10 +1,11 @@ use azalea_buf::McBuf; use azalea_core::position::BlockPos; use azalea_protocol_macros::ClientboundGamePacket; +use simdnbt::owned::NbtTag; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundBlockEntityDataPacket { pub pos: BlockPos, pub block_entity_type: azalea_registry::BlockEntityKind, - pub tag: azalea_nbt::Nbt, + pub tag: NbtTag, } diff --git a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs index c10fa737..f8927ca4 100755 --- a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs @@ -1,6 +1,6 @@ use azalea_buf::McBuf; -use azalea_nbt::Nbt; use azalea_protocol_macros::ClientboundGamePacket; +use simdnbt::owned::Nbt; use super::clientbound_light_update_packet::ClientboundLightUpdatePacketData; diff --git a/azalea-protocol/src/packets/game/clientbound_tag_query_packet.rs b/azalea-protocol/src/packets/game/clientbound_tag_query_packet.rs index db238f66..d1073cd5 100755 --- a/azalea-protocol/src/packets/game/clientbound_tag_query_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_tag_query_packet.rs @@ -1,9 +1,10 @@ use azalea_buf::McBuf; use azalea_protocol_macros::ClientboundGamePacket; +use simdnbt::owned::NbtTag; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundTagQueryPacket { #[var] pub transaction_id: u32, - pub tag: azalea_nbt::Nbt, + pub tag: NbtTag, } diff --git a/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs index c43d64bb..37ffd9ce 100755 --- a/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs @@ -1,5 +1,6 @@ use azalea_buf::McBuf; use azalea_protocol_macros::ClientboundGamePacket; +use simdnbt::owned::NbtTag; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundUpdateMobEffectPacket { @@ -10,5 +11,5 @@ pub struct ClientboundUpdateMobEffectPacket { #[var] pub effect_duration_ticks: u32, pub flags: u8, - pub factor_data: Option, + pub factor_data: Option, } diff --git a/azalea-registry/Cargo.toml b/azalea-registry/Cargo.toml index b32c5759..ffd59035 100644 --- a/azalea-registry/Cargo.toml +++ b/azalea-registry/Cargo.toml @@ -12,8 +12,8 @@ version = "0.8.0" azalea-buf = { path = "../azalea-buf", version = "0.8.0" } azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.8.0" } once_cell = "1.18.0" -serde = { version = "^1.0", optional = true } +simdnbt = { version = "0.2.1" } [features] -serde = ["dep:serde", "azalea-registry-macros/serde"] +serde = ["azalea-registry-macros/serde"] default = ["serde"] diff --git a/azalea-registry/azalea-registry-macros/src/lib.rs b/azalea-registry/azalea-registry-macros/src/lib.rs index 3bf798a5..f3289d96 100755 --- a/azalea-registry/azalea-registry-macros/src/lib.rs +++ b/azalea-registry/azalea-registry-macros/src/lib.rs @@ -75,7 +75,7 @@ pub fn registry(input: TokenStream) -> TokenStream { let attributes = input.attributes; generated.extend(quote! { #(#attributes)* - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, azalea_buf::McBuf)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, azalea_buf::McBuf, simdnbt::ToNbtTag, simdnbt::FromNbtTag)] #[repr(u32)] pub enum #name { #enum_items @@ -167,28 +167,5 @@ pub fn registry(input: TokenStream) -> TokenStream { } }); - #[cfg(feature = "serde")] - { - generated.extend(quote! { - impl serde::Serialize for #name { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } - } - impl<'de> serde::Deserialize<'de> for #name { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) - } - } - }); - } - generated.into() } diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 4e0b4efa..ef2d6517 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -15,7 +15,7 @@ azalea-core = { path = "../azalea-core", version = "^0.8.0", features = [ "bevy_ecs", ] } azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } -azalea-nbt = { path = "../azalea-nbt", version = "0.8.0" } +simdnbt = { version = "0.2.1" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" } bevy_ecs = "0.12.0" derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 7301fdd1..ac81fd09 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -5,9 +5,9 @@ use crate::palette::PalettedContainerKind; use azalea_block::BlockState; use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; -use azalea_nbt::NbtCompound; use nohash_hasher::IntMap; use parking_lot::RwLock; +use simdnbt::owned::NbtCompound; use std::collections::hash_map::Entry; use std::str::FromStr; use std::{ @@ -323,11 +323,11 @@ impl Chunk { let mut heightmaps = HashMap::new(); for (name, heightmap) in heightmaps_nbt.iter() { - let Ok(kind) = HeightmapKind::from_str(name) else { + let Ok(kind) = HeightmapKind::from_str(&name.to_str()) else { warn!("Unknown heightmap kind: {name}"); continue; }; - let Some(data) = heightmap.as_long_array() else { + let Some(data) = heightmap.long_array() else { warn!("Heightmap {name} is not a long array"); continue; }; diff --git a/codegen/lib/code/entity.py b/codegen/lib/code/entity.py index 7be6b01d..8fa11430 100644 --- a/codegen/lib/code/entity.py +++ b/codegen/lib/code/entity.py @@ -400,7 +400,7 @@ impl From for UpdateMetadataError { if default is None: # some types don't have Default implemented if type_name == 'CompoundTag': - default = 'azalea_nbt::Nbt::Compound(Default::default())' + default = 'simdnbt::owned::NbtCompound::default()' elif type_name == 'CatVariant': default = 'azalea_registry::CatVariant::Tabby' elif type_name == 'PaintingVariant': @@ -434,7 +434,7 @@ impl From for UpdateMetadataError { elif type_name == 'OptionalFormattedText': default = f'Some({default})' if default != 'Empty' else 'None' elif type_name == 'CompoundTag': - default = f'azalea_nbt::Nbt::Compound({default})' if default != 'Empty' else 'azalea_nbt::Nbt::Compound(Default::default())' + default = f'simdnbt::owned::NbtCompound({default})' if default != 'Empty' else 'simdnbt::owned::NbtCompound::default()' elif type_name == 'Quaternion': default = f'Quaternion {{ x: {float(default["x"])}, y: {float(default["y"])}, z: {float(default["z"])}, w: {float(default["w"])} }}' elif type_name == 'Vector3': diff --git a/codegen/lib/code/utils.py b/codegen/lib/code/utils.py index 0050ce7b..cb835ecb 100755 --- a/codegen/lib/code/utils.py +++ b/codegen/lib/code/utils.py @@ -56,7 +56,7 @@ def burger_type_to_rust_type(burger_type, field_name: Optional[str] = None, inst field_type_rs = 'BlockPos' uses.add('azalea_core::position::BlockPos') elif burger_type == 'nbtcompound': - field_type_rs = 'azalea_nbt::Nbt' + field_type_rs = 'simdnbt::owned::NbtCompound' elif burger_type == 'itemstack': field_type_rs = 'Slot' uses.add('azalea_core::slot::Slot')