1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

Replace azalea-nbt with simdnbt (#111)

* delete azalea-nbt and replace with simdnbt

* use simdnbt from crates.io

* remove serde dependency on azalea-registry
This commit is contained in:
mat 2023-11-19 22:07:38 -06:00 committed by GitHub
parent 84e036ce37
commit 2c610826fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 278 additions and 1714 deletions

166
Cargo.lock generated
View file

@ -255,6 +255,7 @@ dependencies = [
"azalea-buf-macros", "azalea-buf-macros",
"byteorder", "byteorder",
"serde_json", "serde_json",
"simdnbt",
"thiserror", "thiserror",
"tracing", "tracing",
"uuid", "uuid",
@ -295,7 +296,6 @@ dependencies = [
"azalea-crypto", "azalea-crypto",
"azalea-entity", "azalea-entity",
"azalea-inventory", "azalea-inventory",
"azalea-nbt",
"azalea-physics", "azalea-physics",
"azalea-protocol", "azalea-protocol",
"azalea-registry", "azalea-registry",
@ -314,6 +314,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"simdnbt",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@ -326,13 +327,14 @@ version = "0.8.0"
dependencies = [ dependencies = [
"azalea-buf", "azalea-buf",
"azalea-inventory", "azalea-inventory",
"azalea-nbt",
"azalea-registry", "azalea-registry",
"bevy_ecs", "bevy_ecs",
"nohash-hasher", "nohash-hasher",
"num-traits", "num-traits",
"serde", "serde",
"serde_json", "serde_json",
"simdnbt",
"tracing",
"uuid", "uuid",
] ]
@ -362,7 +364,6 @@ dependencies = [
"azalea-chat", "azalea-chat",
"azalea-core", "azalea-core",
"azalea-inventory", "azalea-inventory",
"azalea-nbt",
"azalea-registry", "azalea-registry",
"azalea-world", "azalea-world",
"bevy_app", "bevy_app",
@ -371,6 +372,7 @@ dependencies = [
"enum-as-inner", "enum-as-inner",
"nohash-hasher", "nohash-hasher",
"parking_lot", "parking_lot",
"simdnbt",
"thiserror", "thiserror",
"tracing", "tracing",
"uuid", "uuid",
@ -382,8 +384,8 @@ version = "0.8.0"
dependencies = [ dependencies = [
"azalea-buf", "azalea-buf",
"azalea-inventory-macros", "azalea-inventory-macros",
"azalea-nbt",
"azalea-registry", "azalea-registry",
"simdnbt",
] ]
[[package]] [[package]]
@ -404,24 +406,6 @@ dependencies = [
"serde_json", "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]] [[package]]
name = "azalea-physics" name = "azalea-physics"
version = "0.8.0" version = "0.8.0"
@ -457,7 +441,6 @@ dependencies = [
"azalea-crypto", "azalea-crypto",
"azalea-entity", "azalea-entity",
"azalea-inventory", "azalea-inventory",
"azalea-nbt",
"azalea-protocol-macros", "azalea-protocol-macros",
"azalea-registry", "azalea-registry",
"azalea-world", "azalea-world",
@ -471,6 +454,7 @@ dependencies = [
"once_cell", "once_cell",
"serde", "serde",
"serde_json", "serde_json",
"simdnbt",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-util", "tokio-util",
@ -496,7 +480,7 @@ dependencies = [
"azalea-buf", "azalea-buf",
"azalea-registry-macros", "azalea-registry-macros",
"once_cell", "once_cell",
"serde", "simdnbt",
] ]
[[package]] [[package]]
@ -517,7 +501,6 @@ dependencies = [
"azalea-client", "azalea-client",
"azalea-core", "azalea-core",
"azalea-inventory", "azalea-inventory",
"azalea-nbt",
"azalea-registry", "azalea-registry",
"bevy_ecs", "bevy_ecs",
"criterion", "criterion",
@ -528,6 +511,7 @@ dependencies = [
"parking_lot", "parking_lot",
"serde", "serde",
"serde_json", "serde_json",
"simdnbt",
"thiserror", "thiserror",
"tracing", "tracing",
"uuid", "uuid",
@ -805,15 +789,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -823,12 +798,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]] [[package]]
name = "cfb8" name = "cfb8"
version = "0.8.1" version = "0.8.1"
@ -916,20 +885,6 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" 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]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.3.0" version = "2.3.0"
@ -1202,18 +1157,6 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 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]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "1.9.0"
@ -1417,31 +1360,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "h2" name = "h2"
version = "0.3.21" version = "0.3.21"
@ -2233,6 +2151,21 @@ dependencies = [
"winreg", "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]] [[package]]
name = "ring" name = "ring"
version = "0.16.20" version = "0.16.20"
@ -2345,12 +2278,6 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.15" version = "1.0.15"
@ -2397,15 +2324,6 @@ dependencies = [
"serde_derive", "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]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.192" version = "1.0.192"
@ -2490,6 +2408,30 @@ dependencies = [
"rand_core", "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]] [[package]]
name = "simple_asn1" name = "simple_asn1"
version = "0.5.4" version = "0.5.4"
@ -2565,12 +2507,6 @@ dependencies = [
"der", "der",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.5.0" version = "2.5.0"
@ -2959,12 +2895,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "valence_nbt"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3cddc3222ed5ead4fa446881b3deeeee0dba60b0088b2bf12fedbac7eda2312"
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"

View file

@ -6,7 +6,6 @@ members = [
"azalea-chat", "azalea-chat",
"azalea-core", "azalea-core",
"azalea-auth", "azalea-auth",
"azalea-nbt",
"azalea-brigadier", "azalea-brigadier",
"azalea-crypto", "azalea-crypto",
"azalea-world", "azalea-world",

View file

@ -15,6 +15,7 @@ tracing = "0.1.40"
serde_json = { version = "^1.0", optional = true } serde_json = { version = "^1.0", optional = true }
thiserror = "1.0.50" thiserror = "1.0.50"
uuid = "^1.5.0" uuid = "^1.5.0"
simdnbt = { version = "0.2.1" }
[features] [features]
serde_json = ["dep:serde_json"] serde_json = ["dep:serde_json"]

View file

@ -50,6 +50,18 @@ pub enum BufReadError {
#[backtrace] #[backtrace]
source: serde_json::Error, 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> { fn read_bytes<'a>(buf: &'a mut Cursor<&[u8]>, length: usize) -> Result<&'a [u8], BufReadError> {
@ -340,3 +352,24 @@ impl<T: McBufReadable, const N: usize> McBufReadable for [T; N] {
}) })
} }
} }
impl McBufReadable for simdnbt::owned::NbtTag {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
Ok(simdnbt::owned::NbtTag::read(buf)?)
}
}
impl McBufReadable for simdnbt::owned::NbtCompound {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
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<Self, BufReadError> {
Ok(simdnbt::owned::Nbt::read_unnamed(buf)?)
}
}

View file

@ -257,3 +257,27 @@ impl<T: McBufWritable, const N: usize> McBufWritable for [T; N] {
Ok(()) 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)
}
}

View file

@ -14,7 +14,7 @@ anyhow = "1.0.75"
async-trait = "0.1.74" async-trait = "0.1.74"
azalea-auth = { path = "../azalea-auth", version = "0.8.0" } azalea-auth = { path = "../azalea-auth", version = "0.8.0" }
azalea-block = { path = "../azalea-block", 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-chat = { path = "../azalea-chat", version = "0.8.0" }
azalea-core = { path = "../azalea-core", version = "0.8.0" } azalea-core = { path = "../azalea-core", version = "0.8.0" }
azalea-crypto = { path = "../azalea-crypto", version = "0.8.0" } azalea-crypto = { path = "../azalea-crypto", version = "0.8.0" }

View file

@ -4,17 +4,18 @@
use std::{ use std::{
io::Cursor, io::Cursor,
ops::Deref,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use azalea_core::position::ChunkPos; use azalea_core::position::ChunkPos;
use azalea_nbt::NbtCompound;
use azalea_protocol::packets::game::{ use azalea_protocol::packets::game::{
clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket, clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket,
serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket, serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket,
}; };
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use simdnbt::owned::BaseNbt;
use tracing::{error, trace}; use tracing::{error, trace};
use crate::{ 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 // necessary to make the unwrap_or work
let empty_nbt_compound = NbtCompound::default(); let empty_nbt = BaseNbt::default();
let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); let heightmaps = heightmaps_nbt.unwrap_or(&empty_nbt).deref();
if let Err(e) = partial_instance.chunks.replace_with_packet_data( if let Err(e) = partial_instance.chunks.replace_with_packet_data(
&pos, &pos,

View file

@ -11,7 +11,6 @@ use azalea_entity::{
clamp_look_direction, view_vector, Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector, Attributes, EyeHeight, LocalEntity, LookDirection, Position,
}; };
use azalea_inventory::{ItemSlot, ItemSlotData}; use azalea_inventory::{ItemSlot, ItemSlotData};
use azalea_nbt::NbtList;
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType}; use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
use azalea_protocol::packets::game::{ use azalea_protocol::packets::game::{
serverbound_interact_packet::InteractionHand, serverbound_interact_packet::InteractionHand,
@ -29,6 +28,7 @@ use bevy_ecs::{
system::{Commands, Query, Res}, system::{Commands, Query, Res},
}; };
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use simdnbt::owned::NbtList;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
@ -272,9 +272,8 @@ pub fn check_block_can_be_broken_by_item_in_adventure_mode(
let Some(can_destroy) = item let Some(can_destroy) = item
.nbt .nbt
.as_compound() .compound("tag")
.and_then(|nbt| nbt.get("tag").and_then(|nbt| nbt.as_compound())) .and_then(|nbt| nbt.list("CanDestroy"))
.and_then(|nbt| nbt.get("CanDestroy").and_then(|nbt| nbt.as_list()))
else { else {
// no CanDestroy tag // no CanDestroy tag
return false; return false;

View file

@ -11,7 +11,7 @@ version = "0.8.0"
[dependencies] [dependencies]
azalea-buf = { path = "../azalea-buf", version = "0.8.0" } azalea-buf = { path = "../azalea-buf", version = "0.8.0" }
azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } 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" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" }
bevy_ecs = { version = "0.12.0", default-features = false, optional = true } bevy_ecs = { version = "0.12.0", default-features = false, optional = true }
nohash-hasher = "0.2.0" nohash-hasher = "0.2.0"
@ -19,6 +19,7 @@ num-traits = "0.2.17"
serde = { version = "^1.0", optional = true } serde = { version = "^1.0", optional = true }
uuid = "^1.5.0" uuid = "^1.5.0"
serde_json = "^1.0.108" serde_json = "^1.0.108"
tracing = "0.1.40"
[features] [features]
bevy_ecs = ["dep:bevy_ecs"] bevy_ecs = ["dep:bevy_ecs"]

View file

@ -6,100 +6,103 @@
//! biomes. //! biomes.
use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_nbt::Nbt; use simdnbt::{
use serde::{ owned::{NbtCompound, NbtTag},
de::{self, DeserializeOwned}, Deserialize, FromNbtTag, Serialize, ToNbtTag,
Deserialize, Deserializer, Serialize, Serializer,
}; };
use std::{collections::HashMap, io::Cursor}; use std::{collections::HashMap, io::Cursor};
use tracing::error;
use crate::resource_location::ResourceLocation; use crate::resource_location::ResourceLocation;
/// The base of the registry. /// The base of the registry.
/// ///
/// This is the registry that is sent to the client upon login. /// 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 struct RegistryHolder {
pub map: HashMap<ResourceLocation, Nbt>, pub map: HashMap<ResourceLocation, NbtCompound>,
} }
impl RegistryHolder { impl RegistryHolder {
fn get<T: DeserializeOwned>(&self, name: &ResourceLocation) -> Option<T> { fn get<T: Deserialize>(
let nbt = self.map.get(name)?; &self,
serde_json::from_value(serde_json::to_value(nbt).ok()?).ok() name: &ResourceLocation,
) -> Option<Result<T, simdnbt::DeserializeError>> {
self.map.get(name).map(|nbt| T::from_compound(nbt.clone()))
} }
/// Get the dimension type registry, or `None` if it doesn't exist. You /// Get the dimension type registry, or `None` if it doesn't exist. You
/// should do some type of error handling if this returns `None`. /// should do some type of error handling if this returns `None`.
pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> { pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
self.get(&ResourceLocation::new("minecraft:dimension_type")) 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 TryFrom<Nbt> for RegistryHolder {
type Error = serde_json::Error;
fn try_from(value: Nbt) -> Result<Self, Self::Error> {
Ok(RegistryHolder {
map: serde_json::from_value(serde_json::to_value(value)?)?,
})
} }
}
impl TryInto<Nbt> for RegistryHolder {
type Error = serde_json::Error;
fn try_into(self) -> Result<Nbt, Self::Error> {
serde_json::from_value(serde_json::to_value(self.map)?)
} }
} }
impl McBufReadable for RegistryHolder { impl McBufReadable for RegistryHolder {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
RegistryHolder::try_from(Nbt::read_from(buf)?) let nbt_compound = NbtCompound::read_from(buf)?;
.map_err(|e| BufReadError::Deserialization { source: e }) Ok(RegistryHolder {
map: simdnbt::Deserialize::from_compound(nbt_compound)?,
})
} }
} }
impl McBufWritable for RegistryHolder { impl McBufWritable for RegistryHolder {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
TryInto::<Nbt>::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. /// A collection of values for a certain type of registry data.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 RegistryType<T> { pub struct RegistryType<T>
#[serde(rename = "type")] where
T: Serialize + Deserialize,
{
#[simdnbt(rename = "type")]
pub kind: ResourceLocation, pub kind: ResourceLocation,
pub value: Vec<TypeValue<T>>, pub value: Vec<TypeValue<T>>,
} }
/// A value for a certain type of registry data. /// A value for a certain type of registry data.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 TypeValue<T> { pub struct TypeValue<T>
where
T: Serialize + Deserialize,
{
pub id: u32, pub id: u32,
pub name: ResourceLocation, pub name: ResourceLocation,
pub element: T, pub element: T,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct TrimMaterialElement {
pub asset_name: String, pub asset_name: String,
pub ingredient: ResourceLocation, pub ingredient: ResourceLocation,
pub item_model_index: f32, pub item_model_index: f32,
pub override_armor_materials: HashMap<String, String>, pub override_armor_materials: HashMap<String, String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
} }
/// Data about a kind of chat message /// Data about a kind of chat message
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct ChatTypeElement {
pub chat: ChatTypeData, pub chat: ChatTypeData,
pub narration: ChatTypeData, pub narration: ChatTypeData,
@ -107,48 +110,29 @@ pub struct ChatTypeElement {
/// Data about a chat message. /// Data about a chat message.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct ChatTypeData {
pub translation_key: String, pub translation_key: String,
pub parameters: Vec<String>, pub parameters: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<ChatTypeStyle>, pub style: Option<ChatTypeStyle>,
} }
/// The style of a chat message. /// The style of a chat message.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 { pub struct ChatTypeStyle {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>, pub color: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub bold: Option<bool>, pub bold: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub italic: Option<bool>, pub italic: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub underlined: Option<bool>, pub underlined: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub strikethrough: Option<bool>, pub strikethrough: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub obfuscated: Option<bool>, pub obfuscated: Option<bool>,
} }
/// Dimension attributes. /// Dimension attributes.
#[cfg(feature = "strict_registry")] #[cfg(feature = "strict_registry")]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)] #[simdnbt(deny_unknown_fields)]
pub struct DimensionTypeElement { pub struct DimensionTypeElement {
pub ambient_light: f32, pub ambient_light: f32,
#[serde(with = "Convert")] #[serde(with = "Convert")]
@ -186,99 +170,124 @@ pub struct DimensionTypeElement {
pub struct DimensionTypeElement { pub struct DimensionTypeElement {
pub height: u32, pub height: u32,
pub min_y: i32, pub min_y: i32,
#[serde(flatten)] #[simdnbt(flatten)]
pub _extra: HashMap<String, Nbt>, pub _extra: HashMap<String, NbtTag>,
} }
/// The light level at which monsters can spawn. /// The light level at which monsters can spawn.
/// ///
/// This can be either a single minimum value, or a formula with a min and /// This can be either a single minimum value, or a formula with a min and
/// max. /// max.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone)]
#[serde(untagged)] // #[serde(untagged)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] #[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
pub enum MonsterSpawnLightLevel { pub enum MonsterSpawnLightLevel {
/// A simple minimum value. /// A simple minimum value.
Simple(u32), Simple(u32),
/// A complex value with a type, minimum, and maximum. /// A complex value with a type, minimum, and maximum.
/// Vanilla minecraft only uses one type, "minecraft:uniform". /// Vanilla minecraft only uses one type, "minecraft:uniform".
Complex { Complex {
#[serde(rename = "type")]
kind: ResourceLocation, kind: ResourceLocation,
value: MonsterSpawnLightLevelValues, value: MonsterSpawnLightLevelValues,
}, },
} }
impl FromNbtTag for MonsterSpawnLightLevel {
fn from_nbt_tag(tag: simdnbt::owned::NbtTag) -> Option<Self> {
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. /// The min and max light levels at which monsters can spawn.
/// ///
/// Values are inclusive. /// Values are inclusive.
#[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[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 { pub struct MonsterSpawnLightLevelValues {
#[serde(rename = "min_inclusive")] #[simdnbt(rename = "min_inclusive")]
pub min: u32, pub min: u32,
#[serde(rename = "max_inclusive")] #[simdnbt(rename = "max_inclusive")]
pub max: u32, pub max: u32,
} }
/// Biome attributes. /// Biome attributes.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 { pub struct WorldTypeElement {
#[serde(with = "Convert")]
pub has_precipitation: bool, pub has_precipitation: bool,
pub temperature: f32, pub temperature: f32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature_modifier: Option<String>, pub temperature_modifier: Option<String>,
pub downfall: f32, pub downfall: f32,
pub effects: BiomeEffects, pub effects: BiomeEffects,
} }
/// The precipitation of a biome. /// The precipitation of a biome.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub enum BiomePrecipitation { pub enum BiomePrecipitation {
#[serde(rename = "none")]
None, None,
#[serde(rename = "rain")]
Rain, Rain,
#[serde(rename = "snow")]
Snow, Snow,
} }
impl FromNbtTag for BiomePrecipitation {
fn from_nbt_tag(tag: NbtTag) -> Option<Self> {
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. /// The effects of a biome.
/// ///
/// This includes the sky, fog, water, and grass color, /// This includes the sky, fog, water, and grass color,
/// as well as music and other sound effects. /// as well as music and other sound effects.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct BiomeEffects {
pub sky_color: u32, pub sky_color: u32,
pub fog_color: u32, pub fog_color: u32,
pub water_color: u32, pub water_color: u32,
pub water_fog_color: u32, pub water_fog_color: u32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub foliage_color: Option<u32>, pub foliage_color: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color: Option<u32>, pub grass_color: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color_modifier: Option<String>, pub grass_color_modifier: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub music: Option<BiomeMusic>, pub music: Option<BiomeMusic>,
pub mood_sound: BiomeMoodSound, pub mood_sound: BiomeMoodSound,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub additions_sound: Option<AdditionsSound>, pub additions_sound: Option<AdditionsSound>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub ambient_sound: Option<ResourceLocation>, pub ambient_sound: Option<ResourceLocation>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub particle: Option<BiomeParticle>, pub particle: Option<BiomeParticle>,
} }
@ -286,9 +295,8 @@ pub struct BiomeEffects {
/// ///
/// Some biomes have unique music that only play when inside them. /// Some biomes have unique music that only play when inside them.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 { pub struct BiomeMusic {
#[serde(with = "Convert")]
pub replace_current_music: bool, pub replace_current_music: bool,
pub max_delay: u32, pub max_delay: u32,
pub min_delay: u32, pub min_delay: u32,
@ -296,7 +304,7 @@ pub struct BiomeMusic {
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct BiomeMoodSound {
pub tick_delay: u32, pub tick_delay: u32,
pub block_search_extent: u32, pub block_search_extent: u32,
@ -305,7 +313,7 @@ pub struct BiomeMoodSound {
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct AdditionsSound {
pub tick_chance: f32, pub tick_chance: f32,
pub sound: azalea_registry::SoundEvent, pub sound: azalea_registry::SoundEvent,
@ -315,98 +323,25 @@ pub struct AdditionsSound {
/// ///
/// Some biomes have particles that spawn in the air. /// Some biomes have particles that spawn in the air.
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct BiomeParticle {
pub probability: f32, pub probability: f32,
pub options: HashMap<String, String>, pub options: HashMap<String, String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 { pub struct TrimPatternElement {
#[serde(flatten)] #[simdnbt(flatten)]
pub pattern: HashMap<String, String>, pub pattern: HashMap<String, String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[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 struct DamageTypeElement {
pub message_id: String, pub message_id: String,
pub scaling: String, pub scaling: String,
pub exhaustion: f32, pub exhaustion: f32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub effects: Option<String>, pub effects: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub death_message_type: Option<String>, pub death_message_type: Option<String>,
} }
// Using a trait because you can't implement methods for
// types you don't own, in this case Option<bool> and bool.
trait Convert: Sized {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer;
fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}
// Convert between bool and u8
impl Convert for bool {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(if *self { 1 } else { 0 })
}
fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
convert::<D>(u8::deserialize(deserializer)?)
}
}
// Convert between Option<bool> and u8
impl Convert for Option<bool> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(value) = self {
Convert::serialize(value, serializer)
} else {
serializer.serialize_none()
}
}
fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
where
D: Deserializer<'de>,
{
if let Some(value) = Option::<u8>::deserialize(deserializer)? {
Ok(Some(convert::<D>(value)?))
} else {
Ok(None)
}
}
}
// Deserializing logic here to deduplicate code
fn convert<'de, D>(value: u8) -> Result<bool, D::Error>
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",
)),
}
}

View file

@ -1,7 +1,11 @@
//! A resource, like minecraft:stone //! A resource, like minecraft:stone
use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; 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")] #[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@ -47,6 +51,13 @@ impl std::fmt::Debug for ResourceLocation {
write!(f, "{}:{}", self.namespace, self.path) write!(f, "{}:{}", self.namespace, self.path)
} }
} }
impl FromStr for ResourceLocation {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ResourceLocation::new(s))
}
}
impl McBufReadable for ResourceLocation { impl McBufReadable for ResourceLocation {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
@ -88,6 +99,18 @@ impl<'de> Deserialize<'de> for ResourceLocation {
} }
} }
impl FromNbtTag for ResourceLocation {
fn from_nbt_tag(tag: NbtTag) -> Option<Self> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -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<Self, BufReadError> {
let slot = Option::<SlotData>::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(())
}
}

View file

@ -14,7 +14,7 @@ azalea-buf = { version = "0.8.0", path = "../azalea-buf" }
azalea-chat = { version = "0.8.0", path = "../azalea-chat" } azalea-chat = { version = "0.8.0", path = "../azalea-chat" }
azalea-core = { version = "0.8.0", path = "../azalea-core" } azalea-core = { version = "0.8.0", path = "../azalea-core" }
azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } 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-registry = { version = "0.8.0", path = "../azalea-registry" }
azalea-world = { version = "0.8.0", path = "../azalea-world" } azalea-world = { version = "0.8.0", path = "../azalea-world" }
bevy_app = "0.12.0" bevy_app = "0.12.0"

View file

@ -81,7 +81,7 @@ pub enum EntityDataValue {
BlockState(azalea_block::BlockState), BlockState(azalea_block::BlockState),
/// If this is air, that means it's absent, /// If this is air, that means it's absent,
OptionalBlockState(azalea_block::BlockState), OptionalBlockState(azalea_block::BlockState),
CompoundTag(azalea_nbt::Nbt), CompoundTag(simdnbt::owned::NbtCompound),
Particle(Particle), Particle(Particle),
VillagerData(VillagerData), VillagerData(VillagerData),
// 0 for absent; 1 + actual value otherwise. Used for entity IDs. // 0 for absent; 1 + actual value otherwise. Used for entity IDs.

View file

@ -6054,9 +6054,9 @@ pub struct PlayerModeCustomisation(pub u8);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct PlayerMainHand(pub u8); pub struct PlayerMainHand(pub u8);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct ShoulderLeft(pub azalea_nbt::Nbt); pub struct ShoulderLeft(pub simdnbt::owned::NbtCompound);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct ShoulderRight(pub azalea_nbt::Nbt); pub struct ShoulderRight(pub simdnbt::owned::NbtCompound);
#[derive(Component)] #[derive(Component)]
pub struct Player; pub struct Player;
impl Player { impl Player {
@ -6137,8 +6137,8 @@ impl Default for PlayerMetadataBundle {
score: Score(0), score: Score(0),
player_mode_customisation: PlayerModeCustomisation(0), player_mode_customisation: PlayerModeCustomisation(0),
player_main_hand: PlayerMainHand(Default::default()), player_main_hand: PlayerMainHand(Default::default()),
shoulder_left: ShoulderLeft(azalea_nbt::Nbt::Compound(Default::default())), shoulder_left: ShoulderLeft(simdnbt::owned::NbtCompound::default()),
shoulder_right: ShoulderRight(azalea_nbt::Nbt::Compound(Default::default())), shoulder_right: ShoulderRight(simdnbt::owned::NbtCompound::default()),
} }
} }
} }

View file

@ -11,5 +11,5 @@ version = "0.8.0"
[dependencies] [dependencies]
azalea-buf = { version = "0.8.0", path = "../azalea-buf" } azalea-buf = { version = "0.8.0", path = "../azalea-buf" }
azalea-inventory-macros = { version = "0.8.0", path = "./azalea-inventory-macros" } 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" } azalea-registry = { version = "0.8.0", path = "../azalea-registry" }

View file

@ -1,5 +1,5 @@
use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable};
use azalea_nbt::Nbt; use simdnbt::owned::Nbt;
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
/// Either an item in an inventory or nothing. /// Either an item in an inventory or nothing.

View file

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

View file

@ -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'`.

View file

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

View file

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

View file

@ -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<NbtString, Error> {
let length = stream.read_u16::<BE>()? 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<NbtByteArray, Error> {
let length = stream.read_u32::<BE>()? as usize;
let bytes = read_bytes(stream, length)?.to_vec();
Ok(bytes)
}
// https://stackoverflow.com/a/59707887
fn vec_u8_into_i8(v: Vec<u8>) -> Vec<i8> {
// 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<NbtList, Error> {
let type_id = stream.read_u8()?;
let length = stream.read_u32::<BE>()?;
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::<BE>())
.collect::<Result<Vec<_>, _>>()?
}),
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::<BE>())
.collect::<Result<Vec<_>, _>>()?
}),
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::<BE>())
.collect::<Result<Vec<_>, _>>()?
}),
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::<BE>())
.collect::<Result<Vec<_>, _>>()?
}),
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::<BE>())
.collect::<Result<Vec<_>, _>>()?
}),
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::<Result<Vec<_>, _>>()?
}),
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::<Result<Vec<_>, _>>()?
}),
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::<Result<Vec<_>, _>>()?
}),
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::<Result<Vec<_>, _>>()?
}),
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::<Result<Vec<_>, _>>()?
}),
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::<Result<Vec<_>, _>>()?
}),
_ => return Err(Error::InvalidTagType(type_id)),
};
Ok(list)
}
#[inline]
fn read_compound(stream: &mut Cursor<&[u8]>) -> Result<NbtCompound, Error> {
// 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<NbtIntArray, Error> {
let length = stream.read_u32::<BE>()? 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::<BE>()?);
}
Ok(ints)
}
#[inline]
fn read_long_array(stream: &mut Cursor<&[u8]>) -> Result<NbtLongArray, Error> {
let length = stream.read_u32::<BE>()? 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::<BE>()?);
}
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<Nbt, Error> {
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::<BE>()?),
// A single signed, big endian 32 bit integer
INT_ID => Nbt::Int(stream.read_i32::<BE>()?),
// A single signed, big endian 64 bit integer
LONG_ID => Nbt::Long(stream.read_i64::<BE>()?),
// A single, big endian IEEE-754 single-precision floating point
// number (NaN possible)
FLOAT_ID => Nbt::Float(stream.read_f32::<BE>()?),
// A single, big endian IEEE-754 double-precision floating point
// number (NaN possible)
DOUBLE_ID => Nbt::Double(stream.read_f64::<BE>()?),
// 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<Nbt, Error> {
// 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<Nbt, Error> {
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<Nbt, Error> {
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<Vec<u8>>) -> Result<Nbt, Error> {
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<Self, BufReadError> {
Ok(Nbt::read_any_tag(buf)?)
}
}
impl From<Error> for BufReadError {
fn from(e: Error) -> Self {
BufReadError::Custom(e.to_string())
}
}

View file

@ -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::<BE>(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::<BE>(*value).unwrap();
}
Nbt::Int(value) => {
writer.write_i32::<BE>(*value).unwrap();
}
Nbt::Long(value) => {
writer.write_i64::<BE>(*value).unwrap();
}
Nbt::Float(value) => {
writer.write_f32::<BE>(*value).unwrap();
}
Nbt::Double(value) => {
writer.write_f64::<BE>(*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::<BE>(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::<BE>(l.len() as i32).unwrap();
for &v in l {
writer.write_i16::<BE>(v).unwrap();
}
}
NbtList::Int(l) => write_int_array(writer, l),
NbtList::Long(l) => write_long_array(writer, l),
NbtList::Float(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for &v in l {
writer.write_f32::<BE>(v).unwrap();
}
}
NbtList::Double(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for &v in l {
writer.write_f64::<BE>(v).unwrap();
}
}
NbtList::ByteArray(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for v in l {
write_byte_array(writer, v);
}
}
NbtList::String(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for v in l {
write_string(writer, v);
}
}
NbtList::List(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for v in l {
write_list(writer, v);
}
}
NbtList::Compound(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for v in l {
write_compound(writer, v, true);
}
}
NbtList::IntArray(l) => {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for v in l {
write_int_array(writer, v);
}
}
NbtList::LongArray(l) => {
writer.write_i32::<BE>(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::<BE>(value.len() as u32).unwrap();
writer.write_all(value).unwrap();
}
#[inline]
fn write_int_array(writer: &mut impl Write, array: &[i32]) {
writer.write_i32::<BE>(array.len() as i32).unwrap();
for &item in array {
writer.write_i32::<BE>(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::<i32x16,
// [u8; 64]>(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::<i32x8, [u8; 32]>(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::<i32x4, [u8; 16]>(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::<i32x2, [u8; 8]>(l) };
// writer.write_all(&l).unwrap();
// position += 2;
// }
// // x1 ... just a normal write_i32
// if array.len() - position >= 1 {
// writer.write_i32::<BE>(array[position]).unwrap();
// }
}
#[inline]
fn write_long_array(writer: &mut impl Write, l: &[i64]) {
writer.write_i32::<BE>(l.len() as i32).unwrap();
for &item in l {
writer.write_i64::<BE>(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::<i64x8, [u8; 64]>(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::<i64x4, [u8; 32]>(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::<i64x2, [u8; 16]>(l) };
// writer.write_all(&l).unwrap();
// position += 2;
// }
// // x1 ... just a normal write_i32
// if l.len() - position >= 1 {
// writer.write_i64::<BE>(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(())
}
}

View file

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

View file

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

View file

@ -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<u8>;
pub type NbtString = CompactString;
pub type NbtIntArray = Vec<i32>;
pub type NbtLongArray = Vec<i64>;
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::<u8>() }
}
}
/// 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<NbtByte>) = BYTE_ID,
Short(Vec<NbtShort>) = SHORT_ID,
Int(Vec<NbtInt>) = INT_ID,
Long(Vec<NbtLong>) = LONG_ID,
Float(Vec<NbtFloat>) = FLOAT_ID,
Double(Vec<NbtDouble>) = DOUBLE_ID,
ByteArray(Vec<NbtByteArray>) = BYTE_ARRAY_ID,
String(Vec<NbtString>) = STRING_ID,
List(Vec<NbtList>) = LIST_ID,
Compound(Vec<NbtCompound>) = COMPOUND_ID,
IntArray(Vec<NbtIntArray>) = INT_ARRAY_ID,
LongArray(Vec<NbtLongArray>) = 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::<u8>() }
}
}
impl From<Vec<NbtByte>> for NbtList {
fn from(v: Vec<NbtByte>) -> Self {
Self::Byte(v)
}
}
impl From<Vec<NbtShort>> for NbtList {
fn from(v: Vec<NbtShort>) -> Self {
Self::Short(v)
}
}
impl From<Vec<NbtInt>> for NbtList {
fn from(v: Vec<NbtInt>) -> Self {
Self::Int(v)
}
}
impl From<Vec<NbtLong>> for NbtList {
fn from(v: Vec<NbtLong>) -> Self {
Self::Long(v)
}
}
impl From<Vec<NbtFloat>> for NbtList {
fn from(v: Vec<NbtFloat>) -> Self {
Self::Float(v)
}
}
impl From<Vec<NbtDouble>> for NbtList {
fn from(v: Vec<NbtDouble>) -> Self {
Self::Double(v)
}
}
impl From<Vec<NbtByteArray>> for NbtList {
fn from(v: Vec<NbtByteArray>) -> Self {
Self::ByteArray(v)
}
}
impl From<Vec<NbtString>> for NbtList {
fn from(v: Vec<NbtString>) -> Self {
Self::String(v)
}
}
impl From<Vec<NbtList>> for NbtList {
fn from(v: Vec<NbtList>) -> Self {
Self::List(v)
}
}
impl From<Vec<NbtCompound>> for NbtList {
fn from(v: Vec<NbtCompound>) -> Self {
Self::Compound(v)
}
}
impl From<Vec<NbtIntArray>> for NbtList {
fn from(v: Vec<NbtIntArray>) -> Self {
Self::IntArray(v)
}
}
impl From<Vec<NbtLongArray>> for NbtList {
fn from(v: Vec<NbtLongArray>) -> 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<usize, usize> {
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<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
#[cfg(feature = "serde")]
impl Serialize for NbtCompound {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use std::collections::BTreeMap;
let map = <BTreeMap<NbtString, Nbt> as Deserialize>::deserialize(deserializer)?;
Ok(Self {
inner: map.into_iter().collect(),
})
}
}
impl FromIterator<(NbtString, Nbt)> for NbtCompound {
fn from_iter<T: IntoIterator<Item = (NbtString, Nbt)>>(iter: T) -> Self {
let inner = iter.into_iter().collect::<Vec<_>>();
Self { inner }
}
}
impl From<Vec<(NbtString, Nbt)>> for NbtCompound {
fn from(inner: Vec<(NbtString, Nbt)>) -> Self {
Self { inner }
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -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-crypto = { path = "../azalea-crypto", version = "0.8.0" }
azalea-entity = { version = "0.8.0", path = "../azalea-entity" } azalea-entity = { version = "0.8.0", path = "../azalea-entity" }
azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" }
azalea-nbt = { path = "../azalea-nbt", version = "^0.8.0", features = [ simdnbt = { version = "0.2.1" }
"serde",
] }
azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.8.0" } azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.8.0" }
azalea-registry = { path = "../azalea-registry", version = "0.8.0" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" }
azalea-world = { path = "../azalea-world", version = "0.8.0" } azalea-world = { path = "../azalea-world", version = "0.8.0" }

View file

@ -1,10 +1,11 @@
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_core::position::BlockPos; use azalea_core::position::BlockPos;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use simdnbt::owned::NbtTag;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundBlockEntityDataPacket { pub struct ClientboundBlockEntityDataPacket {
pub pos: BlockPos, pub pos: BlockPos,
pub block_entity_type: azalea_registry::BlockEntityKind, pub block_entity_type: azalea_registry::BlockEntityKind,
pub tag: azalea_nbt::Nbt, pub tag: NbtTag,
} }

View file

@ -1,6 +1,6 @@
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_nbt::Nbt;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use simdnbt::owned::Nbt;
use super::clientbound_light_update_packet::ClientboundLightUpdatePacketData; use super::clientbound_light_update_packet::ClientboundLightUpdatePacketData;

View file

@ -1,9 +1,10 @@
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use simdnbt::owned::NbtTag;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundTagQueryPacket { pub struct ClientboundTagQueryPacket {
#[var] #[var]
pub transaction_id: u32, pub transaction_id: u32,
pub tag: azalea_nbt::Nbt, pub tag: NbtTag,
} }

View file

@ -1,5 +1,6 @@
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use simdnbt::owned::NbtTag;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundUpdateMobEffectPacket { pub struct ClientboundUpdateMobEffectPacket {
@ -10,5 +11,5 @@ pub struct ClientboundUpdateMobEffectPacket {
#[var] #[var]
pub effect_duration_ticks: u32, pub effect_duration_ticks: u32,
pub flags: u8, pub flags: u8,
pub factor_data: Option<azalea_nbt::Nbt>, pub factor_data: Option<NbtTag>,
} }

View file

@ -12,8 +12,8 @@ version = "0.8.0"
azalea-buf = { path = "../azalea-buf", version = "0.8.0" } azalea-buf = { path = "../azalea-buf", version = "0.8.0" }
azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.8.0" } azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.8.0" }
once_cell = "1.18.0" once_cell = "1.18.0"
serde = { version = "^1.0", optional = true } simdnbt = { version = "0.2.1" }
[features] [features]
serde = ["dep:serde", "azalea-registry-macros/serde"] serde = ["azalea-registry-macros/serde"]
default = ["serde"] default = ["serde"]

View file

@ -75,7 +75,7 @@ pub fn registry(input: TokenStream) -> TokenStream {
let attributes = input.attributes; let attributes = input.attributes;
generated.extend(quote! { generated.extend(quote! {
#(#attributes)* #(#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)] #[repr(u32)]
pub enum #name { pub enum #name {
#enum_items #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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> serde::Deserialize<'de> for #name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
});
}
generated.into() generated.into()
} }

View file

@ -15,7 +15,7 @@ azalea-core = { path = "../azalea-core", version = "^0.8.0", features = [
"bevy_ecs", "bevy_ecs",
] } ] }
azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } 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" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" }
bevy_ecs = "0.12.0" bevy_ecs = "0.12.0"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }

View file

@ -5,9 +5,9 @@ use crate::palette::PalettedContainerKind;
use azalea_block::BlockState; use azalea_block::BlockState;
use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use azalea_nbt::NbtCompound;
use nohash_hasher::IntMap; use nohash_hasher::IntMap;
use parking_lot::RwLock; use parking_lot::RwLock;
use simdnbt::owned::NbtCompound;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::str::FromStr; use std::str::FromStr;
use std::{ use std::{
@ -323,11 +323,11 @@ impl Chunk {
let mut heightmaps = HashMap::new(); let mut heightmaps = HashMap::new();
for (name, heightmap) in heightmaps_nbt.iter() { 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}"); warn!("Unknown heightmap kind: {name}");
continue; continue;
}; };
let Some(data) = heightmap.as_long_array() else { let Some(data) = heightmap.long_array() else {
warn!("Heightmap {name} is not a long array"); warn!("Heightmap {name} is not a long array");
continue; continue;
}; };

View file

@ -400,7 +400,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
if default is None: if default is None:
# some types don't have Default implemented # some types don't have Default implemented
if type_name == 'CompoundTag': if type_name == 'CompoundTag':
default = 'azalea_nbt::Nbt::Compound(Default::default())' default = 'simdnbt::owned::NbtCompound::default()'
elif type_name == 'CatVariant': elif type_name == 'CatVariant':
default = 'azalea_registry::CatVariant::Tabby' default = 'azalea_registry::CatVariant::Tabby'
elif type_name == 'PaintingVariant': elif type_name == 'PaintingVariant':
@ -434,7 +434,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
elif type_name == 'OptionalFormattedText': elif type_name == 'OptionalFormattedText':
default = f'Some({default})' if default != 'Empty' else 'None' default = f'Some({default})' if default != 'Empty' else 'None'
elif type_name == 'CompoundTag': 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': elif type_name == 'Quaternion':
default = f'Quaternion {{ x: {float(default["x"])}, y: {float(default["y"])}, z: {float(default["z"])}, w: {float(default["w"])} }}' default = f'Quaternion {{ x: {float(default["x"])}, y: {float(default["y"])}, z: {float(default["z"])}, w: {float(default["w"])} }}'
elif type_name == 'Vector3': elif type_name == 'Vector3':

View file

@ -56,7 +56,7 @@ def burger_type_to_rust_type(burger_type, field_name: Optional[str] = None, inst
field_type_rs = 'BlockPos' field_type_rs = 'BlockPos'
uses.add('azalea_core::position::BlockPos') uses.add('azalea_core::position::BlockPos')
elif burger_type == 'nbtcompound': elif burger_type == 'nbtcompound':
field_type_rs = 'azalea_nbt::Nbt' field_type_rs = 'simdnbt::owned::NbtCompound'
elif burger_type == 'itemstack': elif burger_type == 'itemstack':
field_type_rs = 'Slot' field_type_rs = 'Slot'
uses.add('azalea_core::slot::Slot') uses.add('azalea_core::slot::Slot')