From 03cc28d8e71ed969b21a0824a93dd8e2671e3178 Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 12 Nov 2023 17:13:43 -0600 Subject: [PATCH 01/19] improve docs a bit --- azalea-client/src/chat.rs | 9 +++++---- azalea-client/src/client.rs | 7 ++++--- azalea-client/src/entity_query.rs | 2 ++ azalea-world/src/world.rs | 2 ++ azalea/README.md | 22 +++++++++++++--------- azalea/src/pathfinder/mod.rs | 2 +- 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/azalea-client/src/chat.rs b/azalea-client/src/chat.rs index f238dd471..dbc2843ca 100755 --- a/azalea-client/src/chat.rs +++ b/azalea-client/src/chat.rs @@ -122,10 +122,11 @@ impl ChatPacket { } impl Client { - /// Sends chat message to the server. This only sends the chat packet and - /// not the command packet. The [`Client::chat`] function handles checking - /// whether the message is a command and using the proper packet for you, - /// so you should use that instead. + /// Send a chat message to the server. This only sends the chat packet and + /// not the command packet, which means on some servers you can use this to + /// send chat messages that start with a `/`. The [`Client::chat`] function + /// handles checking whether the message is a command and using the + /// proper packet for you, so you should use that instead. pub fn send_chat_packet(&self, message: &str) { self.ecs.lock().send_event(SendChatKindEvent { entity: self.entity, diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index f4622eed1..7f4a6170e 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -133,7 +133,8 @@ pub enum JoinError { } impl Client { - /// Create a new client from the given GameProfile, Connection, and World. + /// Create a new client from the given [`GameProfile`], ECS Entity, ECS + /// World, and schedule runner function. /// You should only use this if you want to change these fields from the /// defaults, otherwise use [`Client::join`]. pub fn new( @@ -562,9 +563,9 @@ impl Client { /// Get the username of this client. /// /// This is a shortcut for - /// `bot.component::().name.clone()`. + /// `bot.component::().name.to_owned()`. pub fn username(&self) -> String { - self.component::().name.clone() + self.component::().name.to_owned() } /// Get the Minecraft UUID of this client. diff --git a/azalea-client/src/entity_query.rs b/azalea-client/src/entity_query.rs index 484da6f8a..42b7b0cac 100644 --- a/azalea-client/src/entity_query.rs +++ b/azalea-client/src/entity_query.rs @@ -56,6 +56,8 @@ impl Client { /// } /// # } /// ``` + /// + /// [`Entity`]: bevy_ecs::entity::Entity pub fn entity_by( &mut self, predicate: impl EntityPredicate, diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 63e1d7707..a41b25301 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -36,6 +36,8 @@ impl PartialInstance { /// An entity ID used by Minecraft. These are not guaranteed to be unique in /// shared worlds, that's what [`Entity`] is for. +/// +/// [`Entity`]: bevy_ecs::entity::Entity #[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)] pub struct MinecraftEntityId(pub u32); diff --git a/azalea/README.md b/azalea/README.md index 5a3751d55..1ec82cd3b 100755 --- a/azalea/README.md +++ b/azalea/README.md @@ -1,9 +1,7 @@ Azalea is a framework for creating Minecraft bots. -Internally, it's just a wrapper over [`azalea_client`], adding useful -functions for making bots. Because of this, lots of the documentation will -refer to `azalea_client`. You can just replace these with `azalea` in your -code, since everything from azalea_client is re-exported in azalea. +This page is primarily meant for developers that already know they want to use Azalea. +See the [readme](https://github.com/azalea-rs/azalea) for an overview of why you might want to use it. # Installation @@ -12,10 +10,8 @@ default nightly`. Then, add one of the following lines to your Cargo.toml: -Latest bleeding-edge version (recommended): -`azalea = { git="https://github.com/azalea-rs/azalea" }`\ -Latest "stable" release: -`azalea = "0.8.0"` +- Latest bleeding-edge version (recommended): `azalea = { git="https://github.com/azalea-rs/azalea" }`\ +- Latest "stable" release: `azalea = "0.8.0"` ## Optimization @@ -32,6 +28,14 @@ opt-level = 1 [profile.dev.package."*"] opt-level = 3 ``` +# Documentation + +The documentation for the latest Azalea crates.io release is available at [docs.rs/azalea](https://docs.rs/azalea/latest/azalea/) and the docs for the latest bleeding-edge (git) version are at [azalea.matdoes.dev](https://azalea.matdoes.dev/azalea/). + +Note that the `azalea` crate is technically just a wrapper over [`azalea_client`] that adds some extra functions. +Because of this, some of the documentation will refer to `azalea_client`. +You can just replace these with `azalea` in your code since everything from `azalea_client` is re-exported in azalea. + # Examples @@ -73,7 +77,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { # Swarms -Azalea lets you create "swarms", which are a group of bots in the same world that can perform actions together. See [testbot](https://github.com/azalea-rs/azalea/blob/main/azalea/examples/testbot.rs) for an example. Also, if you're using swarms, you should also have both `azalea::prelude::*` and `azalea::swarm::prelude::*`. +Azalea lets you create "swarms", which are a group of bots in the same world that can perform actions together. See [testbot](https://github.com/azalea-rs/azalea/blob/main/azalea/examples/testbot.rs) for an example. Also, if you're using swarms, you should also `use` both `azalea::prelude::*` and `azalea::swarm::prelude::*`. # Plugins diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 5763f3798..c7f05eaee 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -58,9 +58,9 @@ impl Plugin for PathfinderPlugin { .add_event::() .add_event::() .add_systems( - FixedUpdate, // putting systems in the FixedUpdate schedule makes them run every Minecraft tick // (every 50 milliseconds). + FixedUpdate, ( timeout_movement, check_node_reached, From e39de79a6b5913cab2ee59cff492317a24726ba4 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 00:07:25 -0600 Subject: [PATCH 02/19] simplify some code --- .gitignore | 9 ++++++--- azalea-client/src/client.rs | 3 +++ azalea-client/src/packet_handling/configuration.rs | 2 +- azalea-client/src/packet_handling/game.rs | 11 +++++++---- azalea/examples/testbot.rs | 7 ++----- azalea/src/swarm/mod.rs | 9 +++++++-- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index e97aa75a5..c5b441e73 100755 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ /target /doc -flamegraph.svg -perf.data -perf.data.old .vscode # created by azalea-auth/examples/auth, defined in the main .gitignore because # the example could be run from anywhere example_cache.json + +# these are created by profiling tools +flamegraph.svg +perf.data +perf.data.old +heaptrack.* diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 7f4a6170e..13d180fe3 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -681,6 +681,9 @@ async fn run_schedule_loop( loop { // whenever we get an event from run_schedule_receiver, run the schedule run_schedule_receiver.recv().await; + // get rid of any queued events + while let Ok(()) = run_schedule_receiver.try_recv() {} + let mut ecs = ecs.lock(); ecs.run_schedule(outer_schedule_label); ecs.clear_trackers(); diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index e26e3f3b0..b61b2e7e2 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -54,7 +54,7 @@ pub fn send_packet_events( }; packet_events.send(PacketEvent { entity: player_entity, - packet: packet.clone(), + packet, }); } // clear the packets right after we read them diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index a17dd13f5..8f3b0e997 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -163,10 +163,13 @@ pub fn send_packet_events( continue; } }; - packet_events.send(PacketEvent { - entity: player_entity, - packet: packet.clone(), - }); + if let ClientboundGamePacket::LevelChunkWithLight(_) = packet { + } else { + packet_events.send(PacketEvent { + entity: player_entity, + packet, + }); + } } // clear the packets right after we read them packets.clear(); diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index 10bb9e4a0..a7f34a776 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -47,7 +47,7 @@ async fn main() -> anyhow::Result<()> { let mut accounts = Vec::new(); - for i in 0..1 { + for i in 0..200 { accounts.push(Account::offline(&format!("bot{i}"))); } @@ -98,10 +98,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< // .find(|e| e.name() == Some(sender)); // let entity = bot.entity_by::>(|name: &Name| name == sender); let entity = bot.entity_by::, (&GameProfileComponent,)>( - |(profile,): &(&GameProfileComponent,)| { - println!("entity {profile:?}"); - profile.name == sender - }, + |(profile,): &(&GameProfileComponent,)| profile.name == sender, ); println!("sender entity: {entity:?}"); match m.content().as_str() { diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 1f31db98c..585e26084 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -393,7 +393,10 @@ where if let Some(handler) = &self.handler { let first_bot_state = first_bot.component::(); let first_bot_entity = first_bot.entity; - tokio::spawn((handler)(first_bot, first_event, first_bot_state.clone())); + + let mut tasks = Vec::new(); + + tasks.push((handler)(first_bot, first_event, first_bot_state.clone())); // this makes it not have to keep locking the ecs let mut states = HashMap::new(); @@ -402,8 +405,10 @@ where let state = states .entry(bot.entity) .or_insert_with(|| bot.component::().clone()); - tokio::spawn((handler)(bot, event, state.clone())); + tasks.push((handler)(bot, event, state.clone())); } + + tokio::spawn(join_all(tasks)); } } From 71cd3f021e5aeec81d9b473860702da15668b7c9 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 00:49:44 -0600 Subject: [PATCH 03/19] make packet an Arc in PacketEvent --- azalea-client/src/events.rs | 2 +- azalea-client/src/local_player.rs | 4 +- azalea-client/src/packet_handling/game.rs | 51 +++++++++++------------ azalea/examples/testbot.rs | 2 +- azalea/src/container.rs | 2 +- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/azalea-client/src/events.rs b/azalea-client/src/events.rs index 46c0189a0..e5e30b6a1 100644 --- a/azalea-client/src/events.rs +++ b/azalea-client/src/events.rs @@ -165,7 +165,7 @@ fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader for PlayerAbilities { - fn from(packet: ClientboundPlayerAbilitiesPacket) -> Self { +impl From<&ClientboundPlayerAbilitiesPacket> for PlayerAbilities { + fn from(packet: &ClientboundPlayerAbilitiesPacket) -> Self { Self { invulnerable: packet.flags.invulnerable, flying: packet.flags.flying, diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 8f3b0e997..dad4a555c 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -74,7 +74,7 @@ pub struct PacketEvent { /// The client entity that received the packet. pub entity: Entity, /// The packet that was actually received. - pub packet: ClientboundGamePacket, + pub packet: Arc, } /// A player joined the game (or more specifically, was added to the tab @@ -163,13 +163,10 @@ pub fn send_packet_events( continue; } }; - if let ClientboundGamePacket::LevelChunkWithLight(_) = packet { - } else { - packet_events.send(PacketEvent { - entity: player_entity, - packet, - }); - } + packet_events.send(PacketEvent { + entity: player_entity, + packet: Arc::new(packet), + }); } // clear the packets right after we read them packets.clear(); @@ -190,7 +187,9 @@ pub fn process_packet_events(ecs: &mut World) { events_owned.push((*player_entity, packet.clone())); } for (player_entity, packet) in events_owned { - match packet { + let packet_clone = packet.clone(); + let packet_ref = packet_clone.as_ref(); + match packet_ref { ClientboundGamePacket::Login(p) => { debug!("Got login packet"); @@ -756,6 +755,8 @@ pub fn process_packet_events(ecs: &mut World) { }; let entity_kind = *entity_kind_query.get(entity).unwrap(); + let packed_items = p.packed_items.clone().to_vec(); + // we use RelativeEntityUpdate because it makes sure changes aren't made // multiple times commands.entity(entity).add(RelativeEntityUpdate { @@ -766,11 +767,9 @@ pub fn process_packet_events(ecs: &mut World) { let mut commands_system_state = SystemState::::new(world); let mut commands = commands_system_state.get_mut(world); let mut entity_comands = commands.entity(entity_id); - if let Err(e) = apply_metadata( - &mut entity_comands, - *entity_kind, - (*p.packed_items).clone(), - ) { + if let Err(e) = + apply_metadata(&mut entity_comands, *entity_kind, packed_items) + { warn!("{e}"); } }); @@ -803,18 +802,18 @@ pub fn process_packet_events(ecs: &mut World) { // this is to make sure the same entity velocity update doesn't get sent // multiple times when in swarms + + let knockback = KnockbackType::Set(Vec3 { + x: p.xa as f64 / 8000., + y: p.ya as f64 / 8000., + z: p.za as f64 / 8000., + }); + commands.entity(entity).add(RelativeEntityUpdate { partial_world: instance_holder.partial_instance.clone(), update: Box::new(move |entity_mut| { entity_mut.world_scope(|world| { - world.send_event(KnockbackEvent { - entity, - knockback: KnockbackType::Set(Vec3 { - x: p.xa as f64 / 8000., - y: p.ya as f64 / 8000., - z: p.za as f64 / 8000., - }), - }) + world.send_event(KnockbackEvent { entity, knockback }) }); }), }); @@ -1226,7 +1225,7 @@ pub fn process_packet_events(ecs: &mut World) { entity: player_entity, window_id: p.container_id, menu_type: p.menu_type, - title: p.title, + title: p.title.to_owned(), }) } ClientboundGamePacket::OpenSignEditor(_) => {} @@ -1281,10 +1280,10 @@ pub fn process_packet_events(ecs: &mut World) { resource_pack_events.send(ResourcePackEvent { entity: player_entity, - url: p.url, - hash: p.hash, + url: p.url.to_owned(), + hash: p.hash.to_owned(), required: p.required, - prompt: p.prompt, + prompt: p.prompt.to_owned(), }); system_state.apply(ecs); diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index a7f34a776..38f8d499b 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -47,7 +47,7 @@ async fn main() -> anyhow::Result<()> { let mut accounts = Vec::new(); - for i in 0..200 { + for i in 0..3 { accounts.push(Account::offline(&format!("bot{i}"))); } diff --git a/azalea/src/container.rs b/azalea/src/container.rs index 08c60dd92..5406170a9 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -180,7 +180,7 @@ pub struct WaitingForInventoryOpen; fn handle_menu_opened_event(mut commands: Commands, mut events: EventReader) { for event in events.read() { - if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet { + if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet.as_ref() { commands .entity(event.entity) .remove::(); From b79ae025f08935044c621259d4e0c4bb72bbcd7f Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 00:52:54 -0600 Subject: [PATCH 04/19] show error if user is on stable rust --- azalea/build.rs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 azalea/build.rs diff --git a/azalea/build.rs b/azalea/build.rs new file mode 100644 index 000000000..f97dce653 --- /dev/null +++ b/azalea/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let rust_toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap(); + if rust_toolchain.starts_with("stable") { + panic!("Azalea currently requires nightly Rust. You can use `rustup override set nightly` to set the toolchain for this directory."); + } +} From 9633508a3a31a70c657329fdeca0050b7082959e Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 00:58:47 -0600 Subject: [PATCH 05/19] replace log with tracing --- Cargo.lock | 19 +++++++-------- azalea-auth/Cargo.toml | 2 +- azalea-auth/src/auth.rs | 24 +++++++++---------- azalea-auth/src/cache.rs | 4 ++-- azalea-auth/src/certs.rs | 2 +- azalea-auth/src/sessionserver.rs | 2 +- azalea-buf/Cargo.toml | 2 +- azalea-buf/src/read.rs | 2 +- azalea-chat/Cargo.toml | 2 +- azalea-chat/src/component.rs | 2 +- azalea-client/Cargo.toml | 2 +- azalea-client/src/account.rs | 2 +- azalea-client/src/client.rs | 4 ++-- azalea-client/src/interact.rs | 2 +- azalea-client/src/inventory.rs | 2 +- azalea-client/src/local_player.rs | 2 +- .../src/packet_handling/configuration.rs | 2 +- azalea-client/src/packet_handling/game.rs | 2 +- azalea-client/src/raw_connection.rs | 2 +- azalea-entity/Cargo.toml | 2 +- azalea-entity/src/plugin/indexing.rs | 2 +- azalea-entity/src/plugin/mod.rs | 2 +- azalea-entity/src/plugin/relative_updates.rs | 2 +- azalea-nbt/Cargo.toml | 2 +- azalea-nbt/src/decode.rs | 2 +- azalea-physics/Cargo.toml | 2 +- azalea-protocol/Cargo.toml | 2 +- azalea-protocol/examples/handshake_proxy.rs | 2 +- azalea-protocol/src/connect.rs | 2 +- .../game/clientbound_commands_packet.rs | 2 +- azalea-protocol/src/read.rs | 6 ++--- azalea-protocol/src/write.rs | 2 +- azalea-world/Cargo.toml | 2 +- azalea-world/src/chunk_storage.rs | 2 +- azalea-world/src/container.rs | 2 +- azalea/Cargo.toml | 2 +- azalea/src/bot.rs | 2 +- azalea/src/pathfinder/astar.rs | 2 +- azalea/src/pathfinder/mod.rs | 2 +- azalea/src/swarm/mod.rs | 2 +- 40 files changed, 63 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a8cbd425..e416e35c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,7 +188,6 @@ dependencies = [ "derive_more", "futures", "futures-lite 2.0.1", - "log", "nohash-hasher", "num-traits", "parking_lot", @@ -197,6 +196,7 @@ dependencies = [ "rustc-hash", "thiserror", "tokio", + "tracing", "uuid", ] @@ -209,7 +209,6 @@ dependencies = [ "base64", "chrono", "env_logger", - "log", "num-bigint", "once_cell", "reqwest", @@ -218,6 +217,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "tracing", "uuid", ] @@ -254,9 +254,9 @@ version = "0.8.0" dependencies = [ "azalea-buf-macros", "byteorder", - "log", "serde_json", "thiserror", + "tracing", "uuid", ] @@ -275,10 +275,10 @@ version = "0.8.0" dependencies = [ "azalea-buf", "azalea-language", - "log", "once_cell", "serde", "serde_json", + "tracing", ] [[package]] @@ -307,7 +307,6 @@ dependencies = [ "bevy_time", "derive_more", "futures", - "log", "nohash-hasher", "once_cell", "parking_lot", @@ -317,6 +316,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "tracing", "uuid", ] @@ -368,10 +368,10 @@ dependencies = [ "bevy_ecs", "derive_more", "enum-as-inner", - "log", "nohash-hasher", "parking_lot", "thiserror", + "tracing", "uuid", ] @@ -415,9 +415,9 @@ dependencies = [ "fastnbt", "flate2", "graphite_binary", - "log", "serde", "thiserror", + "tracing", "valence_nbt", ] @@ -434,9 +434,9 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_time", - "log", "once_cell", "parking_lot", + "tracing", "uuid", ] @@ -467,7 +467,6 @@ dependencies = [ "futures", "futures-lite 2.0.1", "futures-util", - "log", "once_cell", "serde", "serde_json", @@ -523,11 +522,11 @@ dependencies = [ "criterion", "derive_more", "enum-as-inner", - "log", "nohash-hasher", "once_cell", "parking_lot", "thiserror", + "tracing", "uuid", ] diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 8f2acc274..8bdb8731f 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -13,7 +13,7 @@ azalea-buf = { path = "../azalea-buf", version = "0.8.0" } azalea-crypto = { path = "../azalea-crypto", version = "0.8.0" } base64 = "0.21.5" chrono = { version = "0.4.31", default-features = false, features = ["serde"] } -log = "0.4.20" +tracing = "0.1.40" num-bigint = "0.4.4" once_cell = "1.18.0" reqwest = { version = "0.11.22", default-features = false, features = [ diff --git a/azalea-auth/src/auth.rs b/azalea-auth/src/auth.rs index b0b30e1f6..0c3b98c67 100755 --- a/azalea-auth/src/auth.rs +++ b/azalea-auth/src/auth.rs @@ -83,12 +83,12 @@ pub async fn auth(email: &str, opts: AuthOpts) -> Result interactive_get_ms_auth_token(&client, email).await? }; if msa.is_expired() { - log::trace!("refreshing Microsoft auth token"); + tracing::trace!("refreshing Microsoft auth token"); msa = refresh_ms_auth_token(&client, &msa.data.refresh_token).await?; } let msa_token = &msa.data.access_token; - log::trace!("Got access token: {msa_token}"); + tracing::trace!("Got access token: {msa_token}"); let res = get_minecraft_token(&client, msa_token).await?; @@ -115,7 +115,7 @@ pub async fn auth(email: &str, opts: AuthOpts) -> Result ) .await { - log::error!("{}", e); + tracing::error!("{}", e); } } @@ -309,7 +309,7 @@ pub async fn get_ms_auth_token( while Instant::now() < login_expires_at { tokio::time::sleep(std::time::Duration::from_secs(res.interval)).await; - log::trace!("Polling to check if user has logged in..."); + tracing::trace!("Polling to check if user has logged in..."); if let Ok(access_token_response) = client .post(format!( "https://login.live.com/oauth20_token.srf?client_id={CLIENT_ID}" @@ -324,7 +324,7 @@ pub async fn get_ms_auth_token( .json::() .await { - log::trace!("access_token_response: {:?}", access_token_response); + tracing::trace!("access_token_response: {:?}", access_token_response); let expires_at = SystemTime::now() + std::time::Duration::from_secs(access_token_response.expires_in); return Ok(ExpiringValue { @@ -348,7 +348,7 @@ pub async fn interactive_get_ms_auth_token( email: &str, ) -> Result, GetMicrosoftAuthTokenError> { let res = get_ms_link_code(client).await?; - log::trace!("Device code response: {:?}", res); + tracing::trace!("Device code response: {:?}", res); println!( "Go to \x1b[1m{}\x1b[m and enter the code \x1b[1m{}\x1b[m for \x1b[1m{}\x1b[m", res.verification_uri, res.user_code, email @@ -415,7 +415,7 @@ async fn auth_with_xbox_live( "TokenType": "JWT" }); let payload = auth_json.to_string(); - log::trace!("auth_json: {:#?}", auth_json); + tracing::trace!("auth_json: {:#?}", auth_json); let res = client .post("https://user.auth.xboxlive.com/user/authenticate") .header("Content-Type", "application/json") @@ -428,7 +428,7 @@ async fn auth_with_xbox_live( .await? .json::() .await?; - log::trace!("Xbox Live auth response: {:?}", res); + tracing::trace!("Xbox Live auth response: {:?}", res); // not_after looks like 2020-12-21T19:52:08.4463796Z let expires_at = DateTime::parse_from_rfc3339(&res.not_after) @@ -469,7 +469,7 @@ async fn obtain_xsts_for_minecraft( .await? .json::() .await?; - log::trace!("Xbox Live auth response (for XSTS): {:?}", res); + tracing::trace!("Xbox Live auth response (for XSTS): {:?}", res); Ok(res.token) } @@ -495,7 +495,7 @@ async fn auth_with_minecraft( .await? .json::() .await?; - log::trace!("{:?}", res); + tracing::trace!("{:?}", res); let expires_at = SystemTime::now() + std::time::Duration::from_secs(res.expires_in); Ok(ExpiringValue { @@ -522,7 +522,7 @@ async fn check_ownership( .await? .json::() .await?; - log::trace!("{:?}", res); + tracing::trace!("{:?}", res); // vanilla checks here to make sure the signatures are right, but it's not // actually required so we just don't @@ -547,7 +547,7 @@ pub async fn get_profile( .await? .json::() .await?; - log::trace!("{:?}", res); + tracing::trace!("{:?}", res); Ok(res) } diff --git a/azalea-auth/src/cache.rs b/azalea-auth/src/cache.rs index 1e8aee101..85d25f939 100755 --- a/azalea-auth/src/cache.rs +++ b/azalea-auth/src/cache.rs @@ -82,13 +82,13 @@ async fn get_entire_cache(cache_file: &Path) -> Result, Cache Ok(cache) } async fn set_entire_cache(cache_file: &Path, cache: Vec) -> Result<(), CacheError> { - log::trace!("saving cache: {:?}", cache); + tracing::trace!("saving cache: {:?}", cache); if !cache_file.exists() { let cache_file_parent = cache_file .parent() .expect("Cache file is root directory and also doesn't exist."); - log::debug!( + tracing::debug!( "Making cache file parent directory at {}", cache_file_parent.to_string_lossy() ); diff --git a/azalea-auth/src/certs.rs b/azalea-auth/src/certs.rs index 6030b8868..6214142be 100644 --- a/azalea-auth/src/certs.rs +++ b/azalea-auth/src/certs.rs @@ -26,7 +26,7 @@ pub async fn fetch_certificates( .await? .json::() .await?; - log::trace!("{:?}", res); + tracing::trace!("{:?}", res); // using RsaPrivateKey::from_pkcs8_pem gives an error with decoding base64 so we // just decode it ourselves diff --git a/azalea-auth/src/sessionserver.rs b/azalea-auth/src/sessionserver.rs index d6e20bc57..e6469cefe 100755 --- a/azalea-auth/src/sessionserver.rs +++ b/azalea-auth/src/sessionserver.rs @@ -1,10 +1,10 @@ //! Tell Mojang you're joining a multiplayer server. -use log::debug; use once_cell::sync::Lazy; use reqwest::StatusCode; use serde::Deserialize; use serde_json::json; use thiserror::Error; +use tracing::debug; use uuid::Uuid; use crate::game_profile::{GameProfile, SerializableGameProfile}; diff --git a/azalea-buf/Cargo.toml b/azalea-buf/Cargo.toml index 22b6701d5..95f3b4ac0 100644 --- a/azalea-buf/Cargo.toml +++ b/azalea-buf/Cargo.toml @@ -11,7 +11,7 @@ version = "0.8.0" [dependencies] azalea-buf-macros = { path = "./azalea-buf-macros", version = "0.8.0" } byteorder = "^1.5.0" -log = "0.4.20" +tracing = "0.1.40" serde_json = { version = "^1.0", optional = true } thiserror = "1.0.50" uuid = "^1.5.0" diff --git a/azalea-buf/src/read.rs b/azalea-buf/src/read.rs index 8b37ff6a0..b4b549179 100755 --- a/azalea-buf/src/read.rs +++ b/azalea-buf/src/read.rs @@ -1,6 +1,5 @@ use super::{UnsizedByteArray, MAX_STRING_LENGTH}; use byteorder::{ReadBytesExt, BE}; -use log::warn; use std::{ backtrace::Backtrace, collections::HashMap, @@ -8,6 +7,7 @@ use std::{ io::{Cursor, Read}, }; use thiserror::Error; +use tracing::warn; #[derive(Error, Debug)] pub enum BufReadError { diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml index da2741256..96b3da9f1 100644 --- a/azalea-chat/Cargo.toml +++ b/azalea-chat/Cargo.toml @@ -16,7 +16,7 @@ azalea-buf = { path = "../azalea-buf", features = [ "serde_json", ], version = "^0.8.0", optional = true } azalea-language = { path = "../azalea-language", version = "0.8.0" } -log = "0.4.20" +tracing = "0.1.40" once_cell = "1.18.0" serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0.108" diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index fb7e0522f..e80e7e4bc 100755 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -6,13 +6,13 @@ use crate::{ }; #[cfg(feature = "azalea-buf")] use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; -use log::debug; use once_cell::sync::Lazy; use serde::{de, Deserialize, Deserializer, Serialize}; use std::{ fmt::Display, io::{Cursor, Write}, }; +use tracing::debug; /// A chat component, basically anything you can see in chat. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)] diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 655cd1a57..b95edeed7 100644 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -31,7 +31,7 @@ bevy_time = "0.12.0" azalea-inventory = { path = "../azalea-inventory", version = "0.8.0" } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } futures = "0.3.29" -log = "0.4.20" +tracing = "0.1.40" nohash-hasher = "0.2.0" once_cell = "1.18.0" parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index d045cc72f..0be4146be 100755 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -145,7 +145,7 @@ impl Account { let client = reqwest::Client::new(); if msa.is_expired() { - log::trace!("refreshing Microsoft auth token"); + tracing::trace!("refreshing Microsoft auth token"); msa = azalea_auth::refresh_ms_auth_token(&client, &msa.data.refresh_token).await?; } diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 13d180fe3..96e4eb1c2 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -65,7 +65,6 @@ use bevy_ecs::{ }; use bevy_time::{Fixed, Time, TimePlugin}; use derive_more::Deref; -use log::{debug, error}; use parking_lot::{Mutex, RwLock}; use std::{ collections::HashMap, fmt::Debug, io, net::SocketAddr, ops::Deref, sync::Arc, time::Duration, @@ -75,6 +74,7 @@ use tokio::{ sync::{broadcast, mpsc}, time, }; +use tracing::{debug, error}; use uuid::Uuid; /// `Client` has the things that a user interacting with the library will want. @@ -518,7 +518,7 @@ impl Client { } if self.logged_in() { - log::debug!( + tracing::debug!( "Sending client information (already logged in): {:?}", client_information ); diff --git a/azalea-client/src/interact.rs b/azalea-client/src/interact.rs index eaced255b..64cbd7be0 100644 --- a/azalea-client/src/interact.rs +++ b/azalea-client/src/interact.rs @@ -29,7 +29,7 @@ use bevy_ecs::{ system::{Commands, Query, Res}, }; use derive_more::{Deref, DerefMut}; -use log::warn; +use tracing::warn; use crate::{ attack::handle_attack_event, diff --git a/azalea-client/src/inventory.rs b/azalea-client/src/inventory.rs index 356e0c0ba..e1ac9dd41 100644 --- a/azalea-client/src/inventory.rs +++ b/azalea-client/src/inventory.rs @@ -23,7 +23,7 @@ use bevy_ecs::{ schedule::{IntoSystemConfigs, SystemSet}, system::Query, }; -use log::warn; +use tracing::warn; use crate::{ local_player::{handle_send_packet_event, PlayerAbilities, SendPacketEvent}, diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index ac0e4ea17..db0a14f1d 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -12,10 +12,10 @@ use bevy_ecs::{ system::Query, }; use derive_more::{Deref, DerefMut}; -use log::error; use parking_lot::RwLock; use thiserror::Error; use tokio::sync::mpsc; +use tracing::error; use uuid::Uuid; use crate::{ diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index b61b2e7e2..b82ed76ff 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -12,8 +12,8 @@ use azalea_protocol::read::deserialize_packet; use azalea_world::Instance; use bevy_ecs::prelude::*; use bevy_ecs::system::SystemState; -use log::{debug, error, warn}; use parking_lot::RwLock; +use tracing::{debug, error, warn}; use crate::client::InConfigurationState; use crate::disconnect::DisconnectEvent; diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index dad4a555c..dba0e0702 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -29,8 +29,8 @@ use azalea_protocol::{ }; use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance}; use bevy_ecs::{prelude::*, system::SystemState}; -use log::{debug, error, trace, warn}; use parking_lot::RwLock; +use tracing::{debug, error, trace, warn}; use crate::{ chat::{ChatPacket, ChatReceivedEvent}, diff --git a/azalea-client/src/raw_connection.rs b/azalea-client/src/raw_connection.rs index 0df13a60b..e2daaba2a 100644 --- a/azalea-client/src/raw_connection.rs +++ b/azalea-client/src/raw_connection.rs @@ -8,10 +8,10 @@ use azalea_protocol::{ write::serialize_packet, }; use bevy_ecs::prelude::*; -use log::error; use parking_lot::Mutex; use thiserror::Error; use tokio::sync::mpsc; +use tracing::error; /// A component for clients that can read and write packets to the server. This /// works with raw bytes, so you'll have to serialize/deserialize packets diff --git a/azalea-entity/Cargo.toml b/azalea-entity/Cargo.toml index 7cde8c71d..4399ddf36 100644 --- a/azalea-entity/Cargo.toml +++ b/azalea-entity/Cargo.toml @@ -21,7 +21,7 @@ bevy_app = "0.12.0" bevy_ecs = "0.12.0" derive_more = "0.99.17" enum-as-inner = "0.6.0" -log = "0.4.20" +tracing = "0.1.40" nohash-hasher = "0.2.0" parking_lot = "0.12.1" thiserror = "1.0.50" diff --git a/azalea-entity/src/plugin/indexing.rs b/azalea-entity/src/plugin/indexing.rs index 3ae17f7b8..86e91ff21 100644 --- a/azalea-entity/src/plugin/indexing.rs +++ b/azalea-entity/src/plugin/indexing.rs @@ -8,9 +8,9 @@ use bevy_ecs::{ query::Changed, system::{Commands, Query, Res, ResMut, Resource}, }; -use log::{debug, warn}; use nohash_hasher::IntMap; use std::{collections::HashMap, fmt::Debug}; +use tracing::{debug, warn}; use uuid::Uuid; use crate::{EntityUuid, LastSentPosition, Position}; diff --git a/azalea-entity/src/plugin/mod.rs b/azalea-entity/src/plugin/mod.rs index 2e5a3244e..9950e6ba3 100644 --- a/azalea-entity/src/plugin/mod.rs +++ b/azalea-entity/src/plugin/mod.rs @@ -8,7 +8,7 @@ use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId}; use bevy_app::{App, Plugin, PreUpdate, Update}; use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; -use log::debug; +use tracing::debug; use crate::{ metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, Physics, Position, diff --git a/azalea-entity/src/plugin/relative_updates.rs b/azalea-entity/src/plugin/relative_updates.rs index 140c54e92..63b00195f 100644 --- a/azalea-entity/src/plugin/relative_updates.rs +++ b/azalea-entity/src/plugin/relative_updates.rs @@ -25,8 +25,8 @@ use bevy_ecs::{ world::{EntityWorldMut, World}, }; use derive_more::{Deref, DerefMut}; -use log::warn; use parking_lot::RwLock; +use tracing::warn; use crate::LocalEntity; diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml index 4f78adb96..788d4a07e 100644 --- a/azalea-nbt/Cargo.toml +++ b/azalea-nbt/Cargo.toml @@ -14,7 +14,7 @@ byteorder = "^1.5.0" compact_str = { version = "0.7.1", features = ["serde"] } enum-as-inner = "0.6.0" flate2 = "^1.0.28" -log = "0.4.20" +tracing = "0.1.40" serde = { version = "^1.0", features = ["derive"], optional = true } thiserror = "1.0.50" diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs index b2cc8c772..23247b741 100755 --- a/azalea-nbt/src/decode.rs +++ b/azalea-nbt/src/decode.rs @@ -3,8 +3,8 @@ use crate::Error; use azalea_buf::{BufReadError, McBufReadable}; use byteorder::{ReadBytesExt, BE}; use flate2::read::{GzDecoder, ZlibDecoder}; -use log::warn; 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> { diff --git a/azalea-physics/Cargo.toml b/azalea-physics/Cargo.toml index 97751f231..f79ddf43d 100644 --- a/azalea-physics/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -17,7 +17,7 @@ azalea-registry = { path = "../azalea-registry", version = "0.8.0" } azalea-world = { path = "../azalea-world", version = "0.8.0" } bevy_app = "0.12.0" bevy_ecs = "0.12.0" -log = "0.4.20" +tracing = "0.1.40" once_cell = "1.18.0" parking_lot = "^0.12.1" diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 4c849573c..934152e58 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -40,7 +40,7 @@ flate2 = "1.0.28" futures = "0.3.29" futures-lite = "2.0.1" futures-util = "0.3.29" -log = "0.4.20" +tracing = "0.1.40" serde = { version = "^1.0", features = ["serde_derive"] } serde_json = "^1.0.108" thiserror = "1.0.50" diff --git a/azalea-protocol/examples/handshake_proxy.rs b/azalea-protocol/examples/handshake_proxy.rs index f7fb0f5c3..4e9719b02 100644 --- a/azalea-protocol/examples/handshake_proxy.rs +++ b/azalea-protocol/examples/handshake_proxy.rs @@ -21,7 +21,6 @@ use azalea_protocol::{ read::ReadPacketError, }; use futures::FutureExt; -use log::{error, info, warn}; use once_cell::sync::Lazy; use std::error::Error; use tokio::{ @@ -29,6 +28,7 @@ use tokio::{ net::{TcpListener, TcpStream}, }; use tracing::Level; +use tracing::{error, info, warn}; const LISTEN_ADDR: &str = "127.0.0.1:25566"; const PROXY_ADDR: &str = "127.0.0.1:25565"; diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs index 9c5735064..86b926938 100755 --- a/azalea-protocol/src/connect.rs +++ b/azalea-protocol/src/connect.rs @@ -15,7 +15,6 @@ use azalea_auth::game_profile::GameProfile; use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerError}; use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc}; use bytes::BytesMut; -use log::{error, info}; use std::fmt::Debug; use std::io::Cursor; use std::marker::PhantomData; @@ -24,6 +23,7 @@ use thiserror::Error; use tokio::io::AsyncWriteExt; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError}; use tokio::net::TcpStream; +use tracing::{error, info}; use uuid::Uuid; pub struct RawReadConnection { diff --git a/azalea-protocol/src/packets/game/clientbound_commands_packet.rs b/azalea-protocol/src/packets/game/clientbound_commands_packet.rs index 0b14fbd12..0bc436be5 100755 --- a/azalea-protocol/src/packets/game/clientbound_commands_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_commands_packet.rs @@ -3,8 +3,8 @@ use azalea_buf::{ }; use azalea_core::{bitset::FixedBitSet, resource_location::ResourceLocation}; use azalea_protocol_macros::ClientboundGamePacket; -use log::warn; use std::io::{Cursor, Write}; +use tracing::warn; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundCommandsPacket { diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index 4002f4cb1..d1985b315 100755 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -9,7 +9,6 @@ use bytes::BytesMut; use flate2::read::ZlibDecoder; use futures::StreamExt; use futures_lite::future; -use log::{log_enabled, trace}; use std::backtrace::Backtrace; use std::{ fmt::Debug, @@ -18,6 +17,7 @@ use std::{ use thiserror::Error; use tokio::io::AsyncRead; use tokio_util::codec::{BytesCodec, FramedRead}; +use tracing::if_log_enabled; #[derive(Error, Debug)] pub enum ReadPacketError { @@ -346,7 +346,7 @@ where .map_err(ReadPacketError::from)?; } - if log_enabled!(log::Level::Trace) { + if_log_enabled!(tracing::Level::TRACE, { let buf_string: String = { if buf.len() > 500 { let cut_off_buf = &buf[..500]; @@ -356,7 +356,7 @@ where } }; trace!("Reading packet with bytes: {buf_string}"); - } + }); Ok(Some(buf)) } diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs index 0c3131a05..6ce01bf7c 100755 --- a/azalea-protocol/src/write.rs +++ b/azalea-protocol/src/write.rs @@ -4,10 +4,10 @@ use crate::{packets::ProtocolPacket, read::MAXIMUM_UNCOMPRESSED_LENGTH}; use async_compression::tokio::bufread::ZlibEncoder; use azalea_buf::McBufVarWritable; use azalea_crypto::Aes128CfbEnc; -use log::trace; use std::fmt::Debug; use thiserror::Error; use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tracing::trace; /// Prepend the length of the packet to it. fn frame_prepender(mut data: Vec) -> Result, std::io::Error> { diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 02f4d3edd..2d83e1b2b 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -20,7 +20,7 @@ azalea-registry = { path = "../azalea-registry", version = "0.8.0" } bevy_ecs = "0.12.0" derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } enum-as-inner = "0.6.0" -log = "0.4.20" +tracing = "0.1.40" nohash-hasher = "0.2.0" once_cell = "1.18.0" parking_lot = "^0.12.1" diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 03bdcbb56..80854f0af 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -6,7 +6,6 @@ use azalea_block::BlockState; use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use azalea_nbt::NbtCompound; -use log::{debug, trace, warn}; use nohash_hasher::IntMap; use parking_lot::RwLock; use std::str::FromStr; @@ -16,6 +15,7 @@ use std::{ io::{Cursor, Write}, sync::{Arc, Weak}, }; +use tracing::{debug, trace, warn}; const SECTION_HEIGHT: u32 = 16; diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 471e1d5e0..1e2dfef78 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -1,13 +1,13 @@ use azalea_core::resource_location::ResourceLocation; use bevy_ecs::{component::Component, system::Resource}; use derive_more::{Deref, DerefMut}; -use log::error; use nohash_hasher::IntMap; use parking_lot::RwLock; use std::{ collections::HashMap, sync::{Arc, Weak}, }; +use tracing::error; use crate::{ChunkStorage, Instance}; diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index c3133d135..c411aaaea 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -31,7 +31,7 @@ bevy_tasks = { version = "0.12.0", features = ["multi-threaded"] } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } futures = "0.3.29" futures-lite = "2.0.1" -log = "0.4.20" +tracing = "0.1.40" nohash-hasher = "0.2.0" num-traits = "0.2.17" parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 4fe10e74b..2281deaff 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -22,8 +22,8 @@ use bevy_app::{FixedUpdate, Update}; use bevy_ecs::prelude::Event; use bevy_ecs::schedule::IntoSystemConfigs; use futures_lite::Future; -use log::trace; use std::f64::consts::PI; +use tracing::trace; use crate::pathfinder::PathfinderPlugin; diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs index 98525e03b..163189af8 100644 --- a/azalea/src/pathfinder/astar.rs +++ b/azalea/src/pathfinder/astar.rs @@ -5,9 +5,9 @@ use std::{ time::{Duration, Instant}, }; -use log::{debug, trace, warn}; use priority_queue::PriorityQueue; use rustc_hash::FxHashMap; +use tracing::{debug, trace, warn}; pub struct Path where diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index c7f05eaee..db5080047 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -41,11 +41,11 @@ use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::system::{Local, ResMut}; use bevy_tasks::{AsyncComputeTaskPool, Task}; use futures_lite::future; -use log::{debug, error, info, trace, warn}; use std::collections::VecDeque; use std::sync::atomic::{self, AtomicUsize}; use std::sync::Arc; use std::time::{Duration, Instant}; +use tracing::{debug, error, info, trace, warn}; use self::mining::MiningCache; use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn}; diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 585e26084..05aabe685 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -16,11 +16,11 @@ use azalea_world::InstanceContainer; use bevy_app::{App, PluginGroup, PluginGroupBuilder, Plugins}; use bevy_ecs::{component::Component, entity::Entity, system::Resource, world::World}; use futures::future::{join_all, BoxFuture}; -use log::error; use parking_lot::{Mutex, RwLock}; use std::{collections::HashMap, future::Future, net::SocketAddr, sync::Arc, time::Duration}; use thiserror::Error; use tokio::sync::mpsc; +use tracing::error; use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, NoState}; From 89f5053b475b23bbd5b6316b189aea575f689ded Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 01:02:52 -0600 Subject: [PATCH 06/19] upgrade all dependencies --- Cargo.lock | 122 ++++++++++++---------- azalea-auth/Cargo.toml | 8 +- azalea-client/Cargo.toml | 4 +- azalea-client/src/packet_handling/game.rs | 2 +- azalea-protocol/Cargo.toml | 6 +- azalea/Cargo.toml | 2 +- 6 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e416e35c5..5f7698093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2" +checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ "flate2", "futures-core", @@ -628,7 +628,7 @@ dependencies = [ "bevy_ecs", "bevy_utils", "console_error_panic_hook", - "tracing-log", + "tracing-log 0.1.3", "tracing-subscriber", "tracing-wasm", ] @@ -731,7 +731,7 @@ dependencies = [ "ahash", "bevy_utils_proc_macros", "getrandom", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "instant", "nonmax", "petgraph", @@ -759,9 +759,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -1157,9 +1157,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -1185,25 +1185,14 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -1483,9 +1472,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "ahash", "allocator-api2", @@ -1604,12 +1593,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.2", ] [[package]] @@ -1685,9 +1674,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" @@ -1703,9 +1692,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" @@ -1989,7 +1978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.2", + "indexmap 2.1.0", ] [[package]] @@ -2311,11 +2300,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -2398,9 +2387,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -2416,9 +2405,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -2630,9 +2619,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] @@ -2705,9 +2694,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -2724,9 +2713,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -2769,7 +2758,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -2823,11 +2812,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -2838,7 +2838,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", ] [[package]] @@ -3105,9 +3105,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -3205,9 +3205,23 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.0" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df56f6afc7151a3a7c037f711788ecf8d0c1554787f4c292d6756b46d243ed9" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] [[package]] name = "zeroize" diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 8bdb8731f..3e25b6a43 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -21,12 +21,12 @@ reqwest = { version = "0.11.22", default-features = false, features = [ "rustls-tls", ] } rsa = "0.9.3" -serde = { version = "1.0.190", features = ["derive"] } +serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" thiserror = "1.0.50" -tokio = { version = "1.33.0", features = ["fs"] } +tokio = { version = "1.34.0", features = ["fs"] } uuid = { version = "1.5.0", features = ["serde"] } [dev-dependencies] -env_logger = "0.10.0" -tokio = { version = "1.33.0", features = ["full"] } +env_logger = "0.10.1" +tokio = { version = "1.34.0", features = ["full"] } diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index b95edeed7..71f117edf 100644 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -37,11 +37,11 @@ once_cell = "1.18.0" parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } regex = "1.10.2" thiserror = "^1.0.50" -tokio = { version = "^1.33.0", features = ["sync"] } +tokio = { version = "^1.34.0", features = ["sync"] } uuid = "^1.5.0" azalea-entity = { version = "0.8.0", path = "../azalea-entity" } serde_json = "1.0.108" -serde = "1.0.190" +serde = "1.0.192" [features] default = ["log"] diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index dba0e0702..3d61d7900 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -60,7 +60,7 @@ use crate::{ /// entity, /// packet, /// } in events.read() { -/// match packet { +/// match packet.as_ref() { /// ClientboundGamePacket::LevelParticles(p) => { /// // ... /// } diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 934152e58..9a143ed56 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -9,7 +9,7 @@ version = "0.8.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-compression = { version = "^0.4.4", features = [ +async-compression = { version = "^0.4.5", features = [ "tokio", "zlib", ], optional = true } @@ -44,7 +44,7 @@ tracing = "0.1.40" serde = { version = "^1.0", features = ["serde_derive"] } serde_json = "^1.0.108" thiserror = "1.0.50" -tokio = { version = "^1.33.0", features = ["io-util", "net", "macros"] } +tokio = { version = "^1.34.0", features = ["io-util", "net", "macros"] } tokio-util = { version = "0.7.10", features = ["codec"] } trust-dns-resolver = { version = "^0.23.2", default-features = false, features = [ "tokio-runtime", @@ -60,5 +60,5 @@ strict_registry = ["packets"] [dev-dependencies] anyhow = "^1.0.75" tracing = "^0.1.40" -tracing-subscriber = "^0.3.17" +tracing-subscriber = "^0.3.18" once_cell = "1.18.0" diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index c411aaaea..dad1043be 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -37,7 +37,7 @@ num-traits = "0.2.17" parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } priority-queue = "1.3.2" thiserror = "^1.0.50" -tokio = "^1.33.0" +tokio = "^1.34.0" uuid = "1.5.0" bevy_log = "0.12.0" azalea-entity = { version = "0.8.0", path = "../azalea-entity" } From 8d0acecdcfa49a5cc25d1c201fab8601a692b5e5 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 02:24:34 -0600 Subject: [PATCH 07/19] fix memory leak when loading chunks in swarms --- azalea-world/src/chunk_storage.rs | 31 ++++++++++++++++++++++++++++++- azalea/examples/testbot.rs | 7 ++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 80854f0af..bbcbeb32f 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -8,6 +8,7 @@ use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlock use azalea_nbt::NbtCompound; use nohash_hasher::IntMap; use parking_lot::RwLock; +use std::collections::hash_map::Entry; use std::str::FromStr; use std::{ collections::HashMap, @@ -184,7 +185,35 @@ impl PartialChunkStorage { /// # Panics /// If the chunk is not in the render distance. pub fn set(&mut self, pos: &ChunkPos, chunk: Option, chunk_storage: &mut ChunkStorage) { - self.set_with_shared_reference(pos, chunk.map(|c| Arc::new(RwLock::new(c))), chunk_storage); + let new_chunk; + + if let Some(chunk) = chunk { + match chunk_storage.map.entry(*pos) { + Entry::Occupied(mut e) => { + if let Some(old_chunk) = e.get_mut().upgrade() { + *old_chunk.write() = chunk; + new_chunk = Some(old_chunk.clone()) + } else { + let chunk_lock = Arc::new(RwLock::new(chunk)); + e.insert(Arc::downgrade(&chunk_lock)); + new_chunk = Some(chunk_lock); + } + } + Entry::Vacant(e) => { + let chunk_lock = Arc::new(RwLock::new(chunk)); + e.insert(Arc::downgrade(&chunk_lock)); + new_chunk = Some(chunk_lock); + } + } + } else { + // don't remove it from the shared storage, since it'll be removed + // automatically if this was the last reference + + new_chunk = None; + } + if let Some(chunk_mut) = self.limited_get_mut(pos) { + *chunk_mut = new_chunk; + } } /// Set a chunk in the shared storage and reference it from the limited diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index 38f8d499b..c47aa0618 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> { .add_accounts(accounts.clone()) .set_handler(handle) .set_swarm_handler(swarm_handle) - .join_delay(Duration::from_millis(1000)) + .join_delay(Duration::from_millis(100)) .start("localhost") .await; // let e = azalea::ClientBuilder::new() @@ -154,6 +154,11 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< "lag" => { std::thread::sleep(Duration::from_millis(1000)); } + "quit" => { + bot.disconnect(); + tokio::time::sleep(Duration::from_millis(1000)).await; + std::process::exit(0); + } "inventory" => { println!("inventory: {:?}", bot.menu()); } From 000abfa13665abccf543b875d10c8c2a48dd75be Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 14:54:01 -0600 Subject: [PATCH 08/19] make loading chunks its own bevy system --- .../src/{chunk_batching.rs => chunks.rs} | 99 ++++++++++++++++--- azalea-client/src/client.rs | 4 +- azalea-client/src/lib.rs | 2 +- .../src/packet_handling/configuration.rs | 2 +- azalea-client/src/packet_handling/game.rs | 69 +++---------- azalea/src/accept_resource_packs.rs | 2 +- 6 files changed, 103 insertions(+), 75 deletions(-) rename azalea-client/src/{chunk_batching.rs => chunks.rs} (64%) diff --git a/azalea-client/src/chunk_batching.rs b/azalea-client/src/chunks.rs similarity index 64% rename from azalea-client/src/chunk_batching.rs rename to azalea-client/src/chunks.rs index eda16442f..80c350c88 100644 --- a/azalea-client/src/chunk_batching.rs +++ b/azalea-client/src/chunks.rs @@ -2,26 +2,37 @@ //! for making the server spread out how often it sends us chunk packets //! depending on our receiving speed. -use std::time::{Duration, Instant}; +use std::{ + io::Cursor, + time::{Duration, Instant}, +}; -use azalea_protocol::packets::game::serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket; +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 tracing::{error, trace}; use crate::{ interact::handle_block_interact_event, inventory::InventorySet, local_player::{handle_send_packet_event, SendPacketEvent}, respawn::perform_respawn, + InstanceHolder, }; -pub struct ChunkBatchingPlugin; -impl Plugin for ChunkBatchingPlugin { +pub struct ChunkPlugin; +impl Plugin for ChunkPlugin { fn build(&self, app: &mut App) { app.add_systems( Update, ( handle_chunk_batch_start_event, + handle_receive_chunk_events, handle_chunk_batch_finished_event, ) .chain() @@ -30,11 +41,18 @@ impl Plugin for ChunkBatchingPlugin { .before(handle_block_interact_event) .before(perform_respawn), ) + .add_event::() .add_event::() .add_event::(); } } +#[derive(Event)] +pub struct ReceiveChunkEvent { + pub entity: Entity, + pub packet: ClientboundLevelChunkWithLightPacket, +} + #[derive(Component, Clone, Debug)] pub struct ChunkBatchInfo { pub start_time: Instant, @@ -42,6 +60,69 @@ pub struct ChunkBatchInfo { pub old_samples_weight: u32, } +#[derive(Event)] +pub struct ChunkBatchStartEvent { + pub entity: Entity, +} +#[derive(Event)] +pub struct ChunkBatchFinishedEvent { + pub entity: Entity, + pub batch_size: u32, +} + +fn handle_receive_chunk_events( + mut events: EventReader, + mut query: Query<&mut InstanceHolder>, +) { + for event in events.read() { + let pos = ChunkPos::new(event.packet.x, event.packet.z); + + let local_player = query.get_mut(event.entity).unwrap(); + + // OPTIMIZATION: if we already know about the chunk from the + // shared world (and not ourselves), then we don't need to + // parse it again. This is only used when we have a shared + // world, since we check that the chunk isn't currently owned + // by this client. + let shared_chunk = local_player.instance.read().chunks.get(&pos); + let this_client_has_chunk = local_player + .partial_instance + .read() + .chunks + .limited_get(&pos) + .is_some(); + + let mut world = local_player.instance.write(); + let mut partial_world = local_player.partial_instance.write(); + + if !this_client_has_chunk { + if let Some(shared_chunk) = shared_chunk { + trace!("Skipping parsing chunk {pos:?} because we already know about it"); + partial_world.chunks.set_with_shared_reference( + &pos, + Some(shared_chunk.clone()), + &mut world.chunks, + ); + continue; + } + } + + let heightmaps = event.packet.chunk_data.heightmaps.as_compound(); + // necessary to make the unwrap_or work + let empty_nbt_compound = NbtCompound::default(); + let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); + + if let Err(e) = partial_world.chunks.replace_with_packet_data( + &pos, + &mut Cursor::new(&event.packet.chunk_data.data), + heightmaps, + &mut world.chunks, + ) { + error!("Couldn't set chunk data: {e}"); + } + } +} + impl ChunkBatchInfo { pub fn batch_finished(&mut self, batch_size: u32) { if batch_size == 0 { @@ -65,16 +146,6 @@ impl ChunkBatchInfo { } } -#[derive(Event)] -pub struct ChunkBatchStartEvent { - pub entity: Entity, -} -#[derive(Event)] -pub struct ChunkBatchFinishedEvent { - pub entity: Entity, - pub batch_size: u32, -} - pub fn handle_chunk_batch_start_event( mut query: Query<&mut ChunkBatchInfo>, mut events: EventReader, diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 96e4eb1c2..4f1c4b9ea 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,7 +1,7 @@ use crate::{ attack::{self, AttackPlugin}, chat::ChatPlugin, - chunk_batching::{ChunkBatchInfo, ChunkBatchingPlugin}, + chunks::{ChunkBatchInfo, ChunkPlugin}, disconnect::{DisconnectEvent, DisconnectPlugin}, events::{Event, EventPlugin, LocalPlayerEvents}, interact::{CurrentSequenceNumber, InteractPlugin}, @@ -782,7 +782,7 @@ impl PluginGroup for DefaultPlugins { .add(RespawnPlugin) .add(MinePlugin) .add(AttackPlugin) - .add(ChunkBatchingPlugin) + .add(ChunkPlugin) .add(TickBroadcastPlugin); #[cfg(feature = "log")] { diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index a7231ffc7..bc5616e88 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -12,7 +12,7 @@ mod account; pub mod attack; pub mod chat; -pub mod chunk_batching; +pub mod chunks; mod client; pub mod disconnect; mod entity_query; diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index b82ed76ff..f35e25b2d 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -140,7 +140,7 @@ pub fn process_packet_events(ecs: &mut World) { abilities: crate::local_player::PlayerAbilities::default(), permission_level: crate::local_player::PermissionLevel::default(), hunger: Hunger::default(), - chunk_batch_info: crate::chunk_batching::ChunkBatchInfo::default(), + chunk_batch_info: crate::chunks::ChunkBatchInfo::default(), entity_id_index: EntityIdIndex::default(), diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 3d61d7900..717261424 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -16,7 +16,6 @@ use azalea_entity::{ Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection, Physics, PlayerBundle, Position, RelativeEntityUpdate, }; -use azalea_nbt::NbtCompound; use azalea_protocol::{ packets::game::{ clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, @@ -34,7 +33,7 @@ use tracing::{debug, error, trace, warn}; use crate::{ chat::{ChatPacket, ChatReceivedEvent}, - chunk_batching, + chunks, disconnect::DisconnectEvent, inventory::{ ClientSideCloseContainerEvent, InventoryComponent, MenuOpenedEvent, @@ -339,24 +338,22 @@ pub fn process_packet_events(ecs: &mut World) { ClientboundGamePacket::ChunkBatchStart(_p) => { // the packet is empty, just a marker to tell us when the batch starts and ends debug!("Got chunk batch start"); - let mut system_state: SystemState< - EventWriter, - > = SystemState::new(ecs); + let mut system_state: SystemState> = + SystemState::new(ecs); let mut chunk_batch_start_events = system_state.get_mut(ecs); - chunk_batch_start_events.send(chunk_batching::ChunkBatchStartEvent { + chunk_batch_start_events.send(chunks::ChunkBatchStartEvent { entity: player_entity, }); } ClientboundGamePacket::ChunkBatchFinished(p) => { debug!("Got chunk batch finished {p:?}"); - let mut system_state: SystemState< - EventWriter, - > = SystemState::new(ecs); + let mut system_state: SystemState> = + SystemState::new(ecs); let mut chunk_batch_start_events = system_state.get_mut(ecs); - chunk_batch_start_events.send(chunk_batching::ChunkBatchFinishedEvent { + chunk_batch_start_events.send(chunks::ChunkBatchFinishedEvent { entity: player_entity, batch_size: p.batch_size, }); @@ -597,54 +594,14 @@ pub fn process_packet_events(ecs: &mut World) { } ClientboundGamePacket::LevelChunkWithLight(p) => { debug!("Got chunk with light packet {} {}", p.x, p.z); - let pos = ChunkPos::new(p.x, p.z); - let mut system_state: SystemState> = + let mut system_state: SystemState> = SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - // OPTIMIZATION: if we already know about the chunk from the - // shared world (and not ourselves), then we don't need to - // parse it again. This is only used when we have a shared - // world, since we check that the chunk isn't currently owned - // by this client. - let shared_chunk = local_player.instance.read().chunks.get(&pos); - let this_client_has_chunk = local_player - .partial_instance - .read() - .chunks - .limited_get(&pos) - .is_some(); - - let mut world = local_player.instance.write(); - let mut partial_world = local_player.partial_instance.write(); - - if !this_client_has_chunk { - if let Some(shared_chunk) = shared_chunk { - trace!("Skipping parsing chunk {pos:?} because we already know about it"); - partial_world.chunks.set_with_shared_reference( - &pos, - Some(shared_chunk.clone()), - &mut world.chunks, - ); - continue; - } - } - - let heightmaps = p.chunk_data.heightmaps.as_compound(); - // necessary to make the unwrap_or work - let empty_nbt_compound = NbtCompound::default(); - let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); - - if let Err(e) = partial_world.chunks.replace_with_packet_data( - &pos, - &mut Cursor::new(&p.chunk_data.data), - heightmaps, - &mut world.chunks, - ) { - error!("Couldn't set chunk data: {e}"); - } + let mut receive_chunk_events = system_state.get_mut(ecs); + receive_chunk_events.send(chunks::ReceiveChunkEvent { + entity: player_entity, + packet: p.clone(), + }); } ClientboundGamePacket::AddEntity(p) => { debug!("Got add entity packet {p:?}"); diff --git a/azalea/src/accept_resource_packs.rs b/azalea/src/accept_resource_packs.rs index 281af420f..6fdb40dbd 100644 --- a/azalea/src/accept_resource_packs.rs +++ b/azalea/src/accept_resource_packs.rs @@ -1,5 +1,5 @@ use crate::app::{App, Plugin}; -use azalea_client::chunk_batching::handle_chunk_batch_finished_event; +use azalea_client::chunks::handle_chunk_batch_finished_event; use azalea_client::inventory::InventorySet; use azalea_client::packet_handling::{death_event_on_0_health, game::ResourcePackEvent}; use azalea_client::respawn::perform_respawn; From f0b58c7e748e1e94ad0dd08124cfc186e865709c Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 20:44:49 -0600 Subject: [PATCH 09/19] share registries in swarms and fix some bugs --- Cargo.lock | 3 + azalea-buf/src/read.rs | 11 +- azalea-client/src/chunks.rs | 34 +- azalea-client/src/client.rs | 14 +- azalea-client/src/events.rs | 2 +- azalea-client/src/lib.rs | 2 - azalea-client/src/local_player.rs | 2 +- .../src/packet_handling/configuration.rs | 29 +- azalea-client/src/packet_handling/game.rs | 70 ++- azalea-client/src/received_registries.rs | 28 -- azalea-core/Cargo.toml | 1 + azalea-core/src/lib.rs | 1 + azalea-core/src/registry_holder.rs | 412 ++++++++++++++++++ azalea-physics/src/collision/shape.rs | 23 +- .../clientbound_registry_data_packet.rs | 402 +---------------- azalea-world/Cargo.toml | 2 + azalea-world/src/chunk_storage.rs | 91 ++-- azalea-world/src/container.rs | 3 +- azalea-world/src/world.rs | 4 + azalea/examples/testbot.rs | 48 +- azalea/src/swarm/mod.rs | 8 +- 21 files changed, 634 insertions(+), 556 deletions(-) delete mode 100644 azalea-client/src/received_registries.rs create mode 100644 azalea-core/src/registry_holder.rs diff --git a/Cargo.lock b/Cargo.lock index 5f7698093..2331c5eeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,6 +332,7 @@ dependencies = [ "nohash-hasher", "num-traits", "serde", + "serde_json", "uuid", ] @@ -525,6 +526,8 @@ dependencies = [ "nohash-hasher", "once_cell", "parking_lot", + "serde", + "serde_json", "thiserror", "tracing", "uuid", diff --git a/azalea-buf/src/read.rs b/azalea-buf/src/read.rs index b4b549179..78db73573 100755 --- a/azalea-buf/src/read.rs +++ b/azalea-buf/src/read.rs @@ -173,9 +173,8 @@ impl McBufReadable for UnsizedByteArray { impl McBufReadable for Vec { default fn read_from(buf: &mut Cursor<&[u8]>) -> Result { let length = u32::var_read_from(buf)? as usize; - // we don't set the capacity here so we can't get exploited into - // allocating a bunch - let mut contents = Vec::new(); + // we limit the capacity to not get exploited into allocating a bunch + let mut contents = Vec::with_capacity(usize::min(length, 65536)); for _ in 0..length { contents.push(T::read_from(buf)?); } @@ -186,7 +185,7 @@ impl McBufReadable for Vec { impl McBufReadable for HashMap { fn read_from(buf: &mut Cursor<&[u8]>) -> Result { let length = i32::var_read_from(buf)? as usize; - let mut contents = HashMap::new(); + let mut contents = HashMap::with_capacity(usize::min(length, 65536)); for _ in 0..length { contents.insert(K::read_from(buf)?, V::read_from(buf)?); } @@ -199,7 +198,7 @@ impl McBufVarRe { fn var_read_from(buf: &mut Cursor<&[u8]>) -> Result { let length = i32::var_read_from(buf)? as usize; - let mut contents = HashMap::new(); + let mut contents = HashMap::with_capacity(usize::min(length, 65536)); for _ in 0..length { contents.insert(K::read_from(buf)?, V::var_read_from(buf)?); } @@ -253,7 +252,7 @@ impl McBufVarReadable for u16 { impl McBufVarReadable for Vec { fn var_read_from(buf: &mut Cursor<&[u8]>) -> Result { let length = i32::var_read_from(buf)? as usize; - let mut contents = Vec::new(); + let mut contents = Vec::with_capacity(usize::min(length, 65536)); for _ in 0..length { contents.push(T::var_read_from(buf)?); } diff --git a/azalea-client/src/chunks.rs b/azalea-client/src/chunks.rs index 80c350c88..4d2641f55 100644 --- a/azalea-client/src/chunks.rs +++ b/azalea-client/src/chunks.rs @@ -79,30 +79,22 @@ fn handle_receive_chunk_events( let local_player = query.get_mut(event.entity).unwrap(); - // OPTIMIZATION: if we already know about the chunk from the - // shared world (and not ourselves), then we don't need to - // parse it again. This is only used when we have a shared - // world, since we check that the chunk isn't currently owned - // by this client. - let shared_chunk = local_player.instance.read().chunks.get(&pos); - let this_client_has_chunk = local_player - .partial_instance - .read() - .chunks - .limited_get(&pos) - .is_some(); + let mut instance = local_player.instance.write(); + let mut partial_instance = local_player.partial_instance.write(); - let mut world = local_player.instance.write(); - let mut partial_world = local_player.partial_instance.write(); + // OPTIMIZATION: if we already know about the chunk from the shared world (and + // not ourselves), then we don't need to parse it again. This is only used when + // we have a shared world, since we check that the chunk isn't currently owned + // by this client. + let shared_chunk = instance.chunks.get(&pos); + let this_client_has_chunk = partial_instance.chunks.limited_get(&pos).is_some(); if !this_client_has_chunk { if let Some(shared_chunk) = shared_chunk { trace!("Skipping parsing chunk {pos:?} because we already know about it"); - partial_world.chunks.set_with_shared_reference( - &pos, - Some(shared_chunk.clone()), - &mut world.chunks, - ); + partial_instance + .chunks + .limited_set(&pos, Some(shared_chunk)); continue; } } @@ -112,11 +104,11 @@ fn handle_receive_chunk_events( let empty_nbt_compound = NbtCompound::default(); let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); - if let Err(e) = partial_world.chunks.replace_with_packet_data( + if let Err(e) = partial_instance.chunks.replace_with_packet_data( &pos, &mut Cursor::new(&event.packet.chunk_data.data), heightmaps, - &mut world.chunks, + &mut instance.chunks, ) { error!("Couldn't set chunk data: {e}"); } diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 4f1c4b9ea..787e61445 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -17,7 +17,7 @@ use crate::{ raw_connection::RawConnection, respawn::RespawnPlugin, task_pool::TaskPoolPlugin, - Account, PlayerInfo, ReceivedRegistries, + Account, PlayerInfo, }; use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError}; @@ -99,8 +99,6 @@ pub struct Client { pub profile: GameProfile, /// The entity for this client in the ECS. pub entity: Entity, - /// The world that this client is in. - pub world: Arc>, /// The entity component system. You probably don't need to access this /// directly. Note that if you're using a shared world (i.e. a swarm), this @@ -147,7 +145,6 @@ impl Client { profile, // default our id to 0, it'll be set later entity, - world: Arc::new(RwLock::new(PartialInstance::default())), ecs, @@ -268,7 +265,6 @@ impl Client { read_conn, write_conn, ), - received_registries: ReceivedRegistries::default(), local_player_events: LocalPlayerEvents(tx), game_profile: GameProfileComponent(game_profile), client_information: crate::ClientInformation::default(), @@ -592,7 +588,6 @@ impl Client { #[derive(Bundle)] pub struct LocalPlayerBundle { pub raw_connection: RawConnection, - pub received_registries: ReceivedRegistries, pub local_player_events: LocalPlayerEvents, pub game_profile: GameProfileComponent, pub client_information: ClientInformation, @@ -604,7 +599,7 @@ pub struct LocalPlayerBundle { /// use [`LocalEntity`]. #[derive(Bundle)] pub struct JoinedClientBundle { - pub instance_holder: InstanceHolder, + // note that InstanceHolder isn't here because it's set slightly before we fully join the world pub physics_state: PhysicsState, pub inventory: InventoryComponent, pub tab_list: TabList, @@ -679,11 +674,12 @@ async fn run_schedule_loop( mut run_schedule_receiver: mpsc::UnboundedReceiver<()>, ) { loop { - // whenever we get an event from run_schedule_receiver, run the schedule - run_schedule_receiver.recv().await; // get rid of any queued events while let Ok(()) = run_schedule_receiver.try_recv() {} + // whenever we get an event from run_schedule_receiver, run the schedule + run_schedule_receiver.recv().await; + let mut ecs = ecs.lock(); ecs.run_schedule(outer_schedule_label); ecs.clear_trackers(); diff --git a/azalea-client/src/events.rs b/azalea-client/src/events.rs index e5e30b6a1..c85f142e0 100644 --- a/azalea-client/src/events.rs +++ b/azalea-client/src/events.rs @@ -163,7 +163,7 @@ fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader { - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let mut received_registries = query.get_mut(player_entity).unwrap(); + let mut instance = Instance::default(); - let new_received_registries = p.registry_holder.registries; // override the old registries with the new ones // but if a registry wasn't sent, keep the old one - for (registry_name, registry) in new_received_registries { - received_registries - .registries - .insert(registry_name, registry); + for (registry_name, registry) in p.registry_holder.map { + instance.registries.map.insert(registry_name, registry); } + + let instance_holder = crate::local_player::InstanceHolder::new( + player_entity, + // default to an empty world, it'll be set correctly later when we + // get the login packet + Arc::new(RwLock::new(instance)), + ); + ecs.entity_mut(player_entity).insert(instance_holder); } ClientboundConfigurationPacket::CustomPayload(p) => { @@ -113,13 +114,6 @@ pub fn process_packet_events(ecs: &mut World) { let mut query = system_state.get_mut(ecs); let mut raw_connection = query.get_mut(player_entity).unwrap(); - let instance_holder = crate::local_player::InstanceHolder::new( - player_entity, - // default to an empty world, it'll be set correctly later when we - // get the login packet - Arc::new(RwLock::new(Instance::default())), - ); - raw_connection .write_packet(ServerboundFinishConfigurationPacket {}.get()) .expect( @@ -131,7 +125,6 @@ pub fn process_packet_events(ecs: &mut World) { ecs.entity_mut(player_entity) .remove::() .insert(crate::JoinedClientBundle { - instance_holder, physics_state: crate::PhysicsState::default(), inventory: crate::inventory::InventoryComponent::default(), tab_list: crate::local_player::TabList::default(), diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 717261424..9f7c5e696 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -45,7 +45,7 @@ use crate::{ }, movement::{KnockbackEvent, KnockbackType}, raw_connection::RawConnection, - ClientInformation, PlayerInfo, ReceivedRegistries, + ClientInformation, PlayerInfo, }; /// An event that's sent when we receive a packet. @@ -174,16 +174,18 @@ pub fn send_packet_events( } pub fn process_packet_events(ecs: &mut World) { - let mut events_owned = Vec::new(); - let mut system_state: SystemState> = SystemState::new(ecs); - let mut events = system_state.get_mut(ecs); - for PacketEvent { - entity: player_entity, - packet, - } in events.read() + let mut events_owned = Vec::<(Entity, Arc)>::new(); { - // we do this so `ecs` isn't borrowed for the whole loop - events_owned.push((*player_entity, packet.clone())); + let mut system_state = SystemState::>::new(ecs); + let mut events = system_state.get_mut(ecs); + for PacketEvent { + entity: player_entity, + packet, + } in events.read() + { + // we do this so `ecs` isn't borrowed for the whole loop + events_owned.push((*player_entity, packet.clone())); + } } for (player_entity, packet) in events_owned { let packet_clone = packet.clone(); @@ -198,7 +200,6 @@ pub fn process_packet_events(ecs: &mut World) { Query<( &GameProfileComponent, &ClientInformation, - &ReceivedRegistries, Option<&mut InstanceName>, Option<&mut LoadedBy>, &mut EntityIdIndex, @@ -220,7 +221,6 @@ pub fn process_packet_events(ecs: &mut World) { let ( game_profile, client_information, - received_registries, instance_name, loaded_by, mut entity_id_index, @@ -238,7 +238,9 @@ pub fn process_packet_events(ecs: &mut World) { .insert(InstanceName(new_instance_name.clone())); } - let Some(dimension_type) = received_registries.dimension_type() else { + let Some(dimension_type) = + instance_holder.instance.read().registries.dimension_type() + else { error!("Server didn't send dimension type registry, can't log in"); continue; }; @@ -276,6 +278,17 @@ pub fn process_packet_events(ecs: &mut World) { // in a shared instance Some(player_entity), ); + { + let new_registries = &mut weak_instance.write().registries; + // add the registries from this instance to the weak instance + for (registry_name, registry) in + &instance_holder.instance.read().registries.map + { + new_registries + .map + .insert(registry_name.clone(), registry.clone()); + } + } instance_holder.instance = weak_instance; let player_bundle = PlayerBundle { @@ -295,8 +308,6 @@ pub fn process_packet_events(ecs: &mut World) { current: p.common.game_type, previous: p.common.previous_game_type.into(), }, - // this gets overwritten later by the SetHealth packet - received_registries.clone(), player_bundle, )); @@ -583,10 +594,12 @@ pub fn process_packet_events(ecs: &mut World) { let mut system_state: SystemState> = SystemState::new(ecs); let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - let mut partial_world = local_player.partial_instance.write(); + let instance_holder = query.get_mut(player_entity).unwrap(); + let mut partial_world = instance_holder.partial_instance.write(); - partial_world.chunks.view_center = ChunkPos::new(p.x, p.z); + partial_world + .chunks + .update_view_center(ChunkPos::new(p.x, p.z)); } ClientboundGamePacket::ChunksBiomes(_) => {} ClientboundGamePacket::LightUpdate(_p) => { @@ -1167,7 +1180,18 @@ pub fn process_packet_events(ecs: &mut World) { system_state.apply(ecs); } - ClientboundGamePacket::ForgetLevelChunk(_) => {} + ClientboundGamePacket::ForgetLevelChunk(p) => { + debug!("Got forget level chunk packet {p:?}"); + + let mut system_state: SystemState> = + SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let mut partial_instance = local_player.partial_instance.write(); + + partial_instance.chunks.limited_set(&p.pos, None); + } ClientboundGamePacket::HorseScreenOpen(_) => {} ClientboundGamePacket::MapItemData(_) => {} ClientboundGamePacket::MerchantOffers(_) => {} @@ -1255,20 +1279,21 @@ pub fn process_packet_events(ecs: &mut World) { &mut InstanceHolder, &GameProfileComponent, &ClientInformation, - &ReceivedRegistries, )>, EventWriter, ResMut, )> = SystemState::new(ecs); let (mut commands, mut query, mut instance_loaded_events, mut instance_container) = system_state.get_mut(ecs); - let (mut instance_holder, game_profile, client_information, received_registries) = + let (mut instance_holder, game_profile, client_information) = query.get_mut(player_entity).unwrap(); { let new_instance_name = p.common.dimension.clone(); - let Some(dimension_type) = received_registries.dimension_type() else { + let Some(dimension_type) = + instance_holder.instance.read().registries.dimension_type() + else { error!("Server didn't send dimension type registry, can't log in"); continue; }; @@ -1298,6 +1323,7 @@ pub fn process_packet_events(ecs: &mut World) { // set the partial_world to an empty world // (when we add chunks or entities those will be in the // instance_container) + *instance_holder.partial_instance.write() = PartialInstance::new( azalea_world::chunk_storage::calculate_chunk_storage_range( client_information.view_distance.into(), diff --git a/azalea-client/src/received_registries.rs b/azalea-client/src/received_registries.rs deleted file mode 100644 index f959a5904..000000000 --- a/azalea-client/src/received_registries.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::collections::HashMap; - -use azalea_core::resource_location::ResourceLocation; -use azalea_nbt::Nbt; -use azalea_protocol::packets::configuration::clientbound_registry_data_packet::registry::{ - DimensionTypeElement, RegistryType, -}; -use bevy_ecs::prelude::*; -use serde::de::DeserializeOwned; - -/// The registries that were sent to us during the configuration state. -#[derive(Default, Component, Clone)] -pub struct ReceivedRegistries { - pub registries: HashMap, -} - -impl ReceivedRegistries { - fn get(&self, name: &ResourceLocation) -> Option { - let nbt = self.registries.get(name)?; - serde_json::from_value(serde_json::to_value(nbt).ok()?).ok() - } - - /// 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")) - } -} diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index eb04ed01d..a25485a2e 100644 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -18,6 +18,7 @@ nohash-hasher = "0.2.0" num-traits = "0.2.17" serde = { version = "^1.0", optional = true } uuid = "^1.5.0" +serde_json = "^1.0.108" [features] bevy_ecs = ["dep:bevy_ecs"] diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs index 3b29f6a22..cb67da586 100755 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -14,5 +14,6 @@ pub mod game_type; pub mod math; pub mod particle; pub mod position; +pub mod registry_holder; pub mod resource_location; pub mod tier; diff --git a/azalea-core/src/registry_holder.rs b/azalea-core/src/registry_holder.rs new file mode 100644 index 000000000..7f811e238 --- /dev/null +++ b/azalea-core/src/registry_holder.rs @@ -0,0 +1,412 @@ +//! The data sent to the client in the `ClientboundRegistryDataPacket`. +//! +//! This module contains the structures used to represent the registry +//! sent to the client upon login. This contains a lot of information about +//! the game, including the types of chat messages, dimensions, and +//! biomes. + +use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; +use azalea_nbt::Nbt; +use serde::{ + de::{self, DeserializeOwned}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{collections::HashMap, io::Cursor}; + +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)] +pub struct RegistryHolder { + 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() + } + + /// 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)?) + } +} + +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 }) + } +} + +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) + } +} + +/// 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")] + 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 { + pub id: u32, + pub name: ResourceLocation, + pub element: T, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(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))] +pub struct ChatTypeElement { + pub chat: ChatTypeData, + pub narration: ChatTypeData, +} + +/// Data about a chat message. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(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))] +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)] +pub struct DimensionTypeElement { + pub ambient_light: f32, + #[serde(with = "Convert")] + pub bed_works: bool, + pub coordinate_scale: f32, + pub effects: ResourceLocation, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub fixed_time: Option, + #[serde(with = "Convert")] + pub has_ceiling: bool, + #[serde(with = "Convert")] + pub has_raids: bool, + #[serde(with = "Convert")] + pub has_skylight: bool, + pub height: u32, + pub infiniburn: ResourceLocation, + pub logical_height: u32, + pub min_y: i32, + pub monster_spawn_block_light_limit: u32, + pub monster_spawn_light_level: MonsterSpawnLightLevel, + #[serde(with = "Convert")] + pub natural: bool, + #[serde(with = "Convert")] + pub piglin_safe: bool, + #[serde(with = "Convert")] + pub respawn_anchor_works: bool, + #[serde(with = "Convert")] + pub ultrawarm: bool, +} + +/// Dimension attributes. +#[cfg(not(feature = "strict_registry"))] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DimensionTypeElement { + pub height: u32, + pub min_y: i32, + #[serde(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))] +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, + }, +} + +/// 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))] +pub struct MonsterSpawnLightLevelValues { + #[serde(rename = "min_inclusive")] + pub min: u32, + #[serde(rename = "max_inclusive")] + pub max: u32, +} + +/// Biome attributes. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(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))] +pub enum BiomePrecipitation { + #[serde(rename = "none")] + None, + #[serde(rename = "rain")] + Rain, + #[serde(rename = "snow")] + Snow, +} + +/// 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))] +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, +} + +/// The music of the biome. +/// +/// 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))] +pub struct BiomeMusic { + #[serde(with = "Convert")] + pub replace_current_music: bool, + pub max_delay: u32, + pub min_delay: u32, + pub sound: azalea_registry::SoundEvent, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +pub struct BiomeMoodSound { + pub tick_delay: u32, + pub block_search_extent: u32, + pub offset: f32, + pub sound: azalea_registry::SoundEvent, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] +pub struct AdditionsSound { + pub tick_chance: f32, + pub sound: azalea_registry::SoundEvent, +} + +/// Biome particles. +/// +/// Some biomes have particles that spawn in the air. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(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))] +pub struct TrimPatternElement { + #[serde(flatten)] + pub pattern: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "strict_registry", serde(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-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index 6329184fe..94b0cfdef 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -52,7 +52,7 @@ pub fn box_shape_unchecked( if x_bits < 0 || y_bits < 0 || z_bits < 0 { return VoxelShape::Array(ArrayVoxelShape::new( - BLOCK_SHAPE.shape(), + BLOCK_SHAPE.shape().to_owned(), vec![min_x, max_x], vec![min_y, max_y], vec![min_z, max_z], @@ -253,7 +253,14 @@ impl Shapes { op_false_true, ); - Self::matches_anywhere_with_mergers(x_merger, y_merger, z_merger, a.shape(), b.shape(), op) + Self::matches_anywhere_with_mergers( + x_merger, + y_merger, + z_merger, + a.shape().to_owned(), + b.shape().to_owned(), + op, + ) } pub fn matches_anywhere_with_mergers( @@ -347,7 +354,7 @@ impl VoxelShape { } } - pub fn shape(&self) -> DiscreteVoxelShape { + pub fn shape(&self) -> &DiscreteVoxelShape { match self { VoxelShape::Array(s) => s.shape(), VoxelShape::Cube(s) => s.shape(), @@ -372,7 +379,7 @@ impl VoxelShape { } VoxelShape::Array(ArrayVoxelShape::new( - self.shape(), + self.shape().to_owned(), self.get_coords(Axis::X).iter().map(|c| c + x).collect(), self.get_coords(Axis::Y).iter().map(|c| c + y).collect(), self.get_coords(Axis::Z).iter().map(|c| c + z).collect(), @@ -648,8 +655,8 @@ impl CubeVoxelShape { } impl ArrayVoxelShape { - fn shape(&self) -> DiscreteVoxelShape { - self.shape.clone() + fn shape(&self) -> &DiscreteVoxelShape { + &self.shape } fn get_coords(&self, axis: Axis) -> Vec { @@ -658,8 +665,8 @@ impl ArrayVoxelShape { } impl CubeVoxelShape { - fn shape(&self) -> DiscreteVoxelShape { - self.shape.clone() + fn shape(&self) -> &DiscreteVoxelShape { + &self.shape } fn get_coords(&self, axis: Axis) -> Vec { diff --git a/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs index 08a1e880c..0e782092f 100644 --- a/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs +++ b/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs @@ -1,408 +1,8 @@ use azalea_buf::McBuf; +use azalea_core::registry_holder::RegistryHolder; use azalea_protocol_macros::ClientboundConfigurationPacket; -use self::registry::RegistryHolder; - #[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] pub struct ClientboundRegistryDataPacket { pub registry_holder: RegistryHolder, } - -pub mod registry { - //! [ClientboundRegistryDataPacket](super::ClientboundRegistryDataPacket) - //! Registry Structures - //! - //! This module contains the structures used to represent the registry - //! sent to the client upon login. This contains a lot of information about - //! the game, including the types of chat messages, dimensions, and - //! biomes. - - use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; - use azalea_core::resource_location::ResourceLocation; - use azalea_nbt::Nbt; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - use std::{collections::HashMap, io::Cursor}; - - /// The base of the registry. - /// - /// This is the registry that is sent to the client upon login. - #[derive(Debug, Clone, Serialize, Deserialize)] - pub struct RegistryHolder { - pub registries: HashMap, - } - - impl TryFrom for RegistryHolder { - type Error = serde_json::Error; - - fn try_from(value: Nbt) -> Result { - Ok(RegistryHolder { - registries: 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.registries)?) - } - } - - 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 }) - } - } - - 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) - } - } - - /// 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")] - 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 { - pub id: u32, - pub name: ResourceLocation, - pub element: T, - } - - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(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))] - pub struct ChatTypeElement { - pub chat: ChatTypeData, - pub narration: ChatTypeData, - } - - /// Data about a chat message. - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(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))] - 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)] - pub struct DimensionTypeElement { - pub ambient_light: f32, - #[serde(with = "Convert")] - pub bed_works: bool, - pub coordinate_scale: f32, - pub effects: ResourceLocation, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub fixed_time: Option, - #[serde(with = "Convert")] - pub has_ceiling: bool, - #[serde(with = "Convert")] - pub has_raids: bool, - #[serde(with = "Convert")] - pub has_skylight: bool, - pub height: u32, - pub infiniburn: ResourceLocation, - pub logical_height: u32, - pub min_y: i32, - pub monster_spawn_block_light_limit: u32, - pub monster_spawn_light_level: MonsterSpawnLightLevel, - #[serde(with = "Convert")] - pub natural: bool, - #[serde(with = "Convert")] - pub piglin_safe: bool, - #[serde(with = "Convert")] - pub respawn_anchor_works: bool, - #[serde(with = "Convert")] - pub ultrawarm: bool, - } - - /// Dimension attributes. - #[cfg(not(feature = "strict_registry"))] - #[derive(Debug, Clone, Serialize, Deserialize)] - pub struct DimensionTypeElement { - pub height: u32, - pub min_y: i32, - #[serde(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))] - 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, - }, - } - - /// 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))] - pub struct MonsterSpawnLightLevelValues { - #[serde(rename = "min_inclusive")] - pub min: u32, - #[serde(rename = "max_inclusive")] - pub max: u32, - } - - /// Biome attributes. - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(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))] - pub enum BiomePrecipitation { - #[serde(rename = "none")] - None, - #[serde(rename = "rain")] - Rain, - #[serde(rename = "snow")] - Snow, - } - - /// 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))] - 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, - } - - /// The music of the biome. - /// - /// 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))] - pub struct BiomeMusic { - #[serde(with = "Convert")] - pub replace_current_music: bool, - pub max_delay: u32, - pub min_delay: u32, - pub sound: azalea_registry::SoundEvent, - } - - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] - pub struct BiomeMoodSound { - pub tick_delay: u32, - pub block_search_extent: u32, - pub offset: f32, - pub sound: azalea_registry::SoundEvent, - } - - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] - pub struct AdditionsSound { - pub tick_chance: f32, - pub sound: azalea_registry::SoundEvent, - } - - /// Biome particles. - /// - /// Some biomes have particles that spawn in the air. - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(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))] - pub struct TrimPatternElement { - #[serde(flatten)] - pub pattern: HashMap, - } - - #[derive(Debug, Clone, Serialize, Deserialize)] - #[cfg_attr(feature = "strict_registry", serde(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-world/Cargo.toml b/azalea-world/Cargo.toml index 2d83e1b2b..4e0b4efa5 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -26,6 +26,8 @@ once_cell = "1.18.0" parking_lot = "^0.12.1" thiserror = "1.0.50" uuid = "1.5.0" +serde_json = "1.0.108" +serde = "1.0.192" [dev-dependencies] azalea-client = { path = "../azalea-client" } diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index bbcbeb32f..7301fdd17 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -23,9 +23,8 @@ const SECTION_HEIGHT: u32 = 16; /// An efficient storage of chunks for a client that has a limited render /// distance. This has support for using a shared [`ChunkStorage`]. pub struct PartialChunkStorage { - /// The center of the view, i.e. the chunk the player is currently in. You - /// can safely modify this. - pub view_center: ChunkPos, + /// The center of the view, i.e. the chunk the player is currently in. + view_center: ChunkPos, chunk_radius: u32, view_range: u32, // chunks is a list of size chunk_radius * chunk_radius @@ -99,14 +98,47 @@ impl PartialChunkStorage { } } - fn get_index(&self, chunk_pos: &ChunkPos) -> usize { + /// Update the chunk to center the view on. This should be called when the + /// client receives a `SetChunkCacheCenter` packet. + pub fn update_view_center(&mut self, view_center: ChunkPos) { + // this code block makes it force unload the chunks that are out of range after + // updating the view center. it's usually fine without it but the commented code + // is there in case you want to temporarily uncomment to test something + + // ``` + // for index in 0..self.chunks.len() { + // let chunk_pos = self.chunk_pos_from_index(index); + // if !in_range_for_view_center_and_radius(&chunk_pos, view_center, self.chunk_radius) { + // self.chunks[index] = None; + // } + // } + // ``` + + self.view_center = view_center; + } + + /// Get the center of the view. This is usually the chunk that the player is + /// in. + pub fn view_center(&self) -> ChunkPos { + self.view_center + } + + pub fn index_from_chunk_pos(&self, chunk_pos: &ChunkPos) -> usize { (i32::rem_euclid(chunk_pos.x, self.view_range as i32) * (self.view_range as i32) + i32::rem_euclid(chunk_pos.z, self.view_range as i32)) as usize } + pub fn chunk_pos_from_index(&self, index: usize) -> ChunkPos { + let x = index as i32 % self.view_range as i32; + let z = index as i32 / self.view_range as i32; + ChunkPos::new( + x + self.view_center.x - self.chunk_radius as i32, + z + self.view_center.z - self.chunk_radius as i32, + ) + } + pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool { - (chunk_pos.x - self.view_center.x).unsigned_abs() <= self.chunk_radius - && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius + in_range_for_view_center_and_radius(chunk_pos, self.view_center, self.chunk_radius) } pub fn set_block_state( @@ -163,7 +195,7 @@ impl PartialChunkStorage { return None; } - let index = self.get_index(pos); + let index = self.index_from_chunk_pos(pos); self.chunks[index].as_ref() } /// Get a mutable reference to a [`Chunk`] within render distance, or @@ -174,7 +206,7 @@ impl PartialChunkStorage { return None; } - let index = self.get_index(pos); + let index = self.index_from_chunk_pos(pos); Some(&mut self.chunks[index]) } @@ -187,12 +219,13 @@ impl PartialChunkStorage { pub fn set(&mut self, pos: &ChunkPos, chunk: Option, chunk_storage: &mut ChunkStorage) { let new_chunk; + // add the chunk to the shared storage if let Some(chunk) = chunk { match chunk_storage.map.entry(*pos) { Entry::Occupied(mut e) => { if let Some(old_chunk) = e.get_mut().upgrade() { *old_chunk.write() = chunk; - new_chunk = Some(old_chunk.clone()) + new_chunk = Some(old_chunk); } else { let chunk_lock = Arc::new(RwLock::new(chunk)); e.insert(Arc::downgrade(&chunk_lock)); @@ -211,33 +244,28 @@ impl PartialChunkStorage { new_chunk = None; } - if let Some(chunk_mut) = self.limited_get_mut(pos) { - *chunk_mut = new_chunk; - } + + self.limited_set(pos, new_chunk); } - /// Set a chunk in the shared storage and reference it from the limited - /// storage. Use [`Self::set`] if you don't already have an - /// `Arc>` (it'll make it for you). + /// Set a chunk in our limited storage, useful if your chunk is already + /// referenced somewhere else and you want to make it also be referenced by + /// this storage. + /// + /// Use [`Self::set`] if you don't already have an `Arc>`. /// /// # Panics /// If the chunk is not in the render distance. - pub fn set_with_shared_reference( - &mut self, - pos: &ChunkPos, - chunk: Option>>, - chunk_storage: &mut ChunkStorage, - ) { - if let Some(chunk) = &chunk { - chunk_storage.map.insert(*pos, Arc::downgrade(chunk)); - } else { - // don't remove it from the shared storage, since it'll be removed - // automatically if this was the last reference - } + pub fn limited_set(&mut self, pos: &ChunkPos, chunk: Option>>) { if let Some(chunk_mut) = self.limited_get_mut(pos) { *chunk_mut = chunk; } } + + /// Get an iterator over all the chunks in the storage. + pub fn chunks(&self) -> impl Iterator>>> { + self.chunks.iter() + } } impl ChunkStorage { pub fn new(height: u32, min_y: i32) -> Self { @@ -270,6 +298,15 @@ impl ChunkStorage { } } +pub fn in_range_for_view_center_and_radius( + chunk_pos: &ChunkPos, + view_center: ChunkPos, + chunk_radius: u32, +) -> bool { + (chunk_pos.x - view_center.x).unsigned_abs() <= chunk_radius + && (chunk_pos.z - view_center.z).unsigned_abs() <= chunk_radius +} + impl Chunk { pub fn read_with_dimension_height( buf: &mut Cursor<&[u8]>, diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 1e2dfef78..0b68ead68 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -1,4 +1,4 @@ -use azalea_core::resource_location::ResourceLocation; +use azalea_core::{registry_holder::RegistryHolder, resource_location::ResourceLocation}; use bevy_ecs::{component::Component, system::Resource}; use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; @@ -70,6 +70,7 @@ impl InstanceContainer { chunks: ChunkStorage::new(height, min_y), entities_by_chunk: HashMap::new(), entity_by_id: IntMap::default(), + registries: RegistryHolder::default(), })); self.instances.insert(name, Arc::downgrade(&world)); world diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index a41b25301..7b6854f72 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -1,6 +1,7 @@ use crate::{iterators::ChunkIterator, palette::Palette, ChunkStorage, PartialChunkStorage}; use azalea_block::{BlockState, BlockStates, FluidState}; use azalea_core::position::{BlockPos, ChunkPos}; +use azalea_core::registry_holder::RegistryHolder; use bevy_ecs::{component::Component, entity::Entity}; use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; @@ -87,6 +88,8 @@ pub struct Instance { /// An index of Minecraft entity IDs to Azalea ECS entities. You should /// avoid using this and instead use `azalea_entity::EntityIdIndex` pub entity_by_id: IntMap, + + pub registries: RegistryHolder, } impl Instance { @@ -237,6 +240,7 @@ impl From for Instance { chunks, entities_by_chunk: HashMap::new(), entity_by_id: IntMap::default(), + registries: RegistryHolder::default(), } } } diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index c47aa0618..2dd5b3069 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -7,7 +7,7 @@ use azalea::inventory::ItemSlot; use azalea::pathfinder::goals::BlockPosGoal; use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection}; use azalea::{Account, Client, Event}; -use azalea_client::SprintDirection; +use azalea_client::{InstanceHolder, SprintDirection}; use azalea_core::position::{ChunkBlockPos, ChunkPos, Vec3}; use azalea_protocol::packets::game::ClientboundGamePacket; use azalea_world::heightmap::HeightmapKind; @@ -47,7 +47,7 @@ async fn main() -> anyhow::Result<()> { let mut accounts = Vec::new(); - for i in 0..3 { + for i in 0..100 { accounts.push(Account::offline(&format!("bot{i}"))); } @@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> { .add_accounts(accounts.clone()) .set_handler(handle) .set_swarm_handler(swarm_handle) - .join_delay(Duration::from_millis(100)) + // .join_delay(Duration::from_millis(1000)) .start("localhost") .await; // let e = azalea::ClientBuilder::new() @@ -80,7 +80,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< bot.chat("Hello world"); } Event::Chat(m) => { - println!("client chat message: {}", m.content()); + // println!("client chat message: {}", m.content()); if m.content() == bot.profile.name { bot.chat("Bye"); tokio::time::sleep(Duration::from_millis(50)).await; @@ -100,7 +100,6 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< let entity = bot.entity_by::, (&GameProfileComponent,)>( |(profile,): &(&GameProfileComponent,)| profile.name == sender, ); - println!("sender entity: {entity:?}"); match m.content().as_str() { "whereami" => { let Some(entity) = entity else { @@ -308,6 +307,45 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< bot.chat("no chunk found"); } } + "debugchunks" => { + println!("shared:"); + + let partial_instance_lock = bot.component::().partial_instance; + let local_chunk_storage = &partial_instance_lock.read().chunks; + + let mut total_loaded_chunks_count = 0; + for (chunk_pos, chunk) in &bot.world().read().chunks.map { + if let Some(chunk) = chunk.upgrade() { + let in_range = local_chunk_storage.in_range(chunk_pos); + println!( + "{chunk_pos:?} has {} references{}", + std::sync::Arc::strong_count(&chunk) - 1, + if in_range { "" } else { " (out of range)" } + ); + total_loaded_chunks_count += 1; + } + } + + println!("local:"); + + let mut local_loaded_chunks_count = 0; + for (i, chunk) in local_chunk_storage.chunks().enumerate() { + if let Some(chunk) = chunk { + let chunk_pos = local_chunk_storage.chunk_pos_from_index(i); + println!( + "{chunk_pos:?} has {} references", + std::sync::Arc::strong_count(&chunk) + ); + local_loaded_chunks_count += 1; + } + } + + println!("total loaded chunks: {total_loaded_chunks_count}"); + println!( + "local loaded chunks: {local_loaded_chunks_count}/{}", + local_chunk_storage.chunks().collect::>().len() + ); + } _ => {} } } diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 05aabe685..0a263f39b 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -394,9 +394,7 @@ where let first_bot_state = first_bot.component::(); let first_bot_entity = first_bot.entity; - let mut tasks = Vec::new(); - - tasks.push((handler)(first_bot, first_event, first_bot_state.clone())); + tokio::spawn((handler)(first_bot, first_event, first_bot_state.clone())); // this makes it not have to keep locking the ecs let mut states = HashMap::new(); @@ -405,10 +403,8 @@ where let state = states .entry(bot.entity) .or_insert_with(|| bot.component::().clone()); - tasks.push((handler)(bot, event, state.clone())); + tokio::spawn((handler)(bot, event, state.clone())); } - - tokio::spawn(join_all(tasks)); } } From 84e036ce3752ecf57904b0f5aff1f33d43e95a32 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 21:20:34 -0600 Subject: [PATCH 10/19] clippy --- azalea-client/src/client.rs | 2 +- azalea-physics/src/collision/shape.rs | 2 +- azalea/examples/testbot.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 787e61445..c7094eb2d 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -53,7 +53,7 @@ use azalea_protocol::{ }, resolver, ServerAddress, }; -use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance}; +use azalea_world::{Instance, InstanceContainer, InstanceName}; use bevy_app::{App, FixedUpdate, Plugin, PluginGroup, PluginGroupBuilder, Update}; use bevy_ecs::{ bundle::Bundle, diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index 94b0cfdef..41ade73cb 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -188,7 +188,7 @@ impl Shapes { op_true_false, op_false_true, ); - let var8 = BitSetDiscreteVoxelShape::join(&a.shape(), &b.shape(), &var5, &var6, &var7, op); + let var8 = BitSetDiscreteVoxelShape::join(a.shape(), b.shape(), &var5, &var6, &var7, op); // if var5.is_discrete_cube_merger() if matches!(var5, IndexMerger::DiscreteCube { .. }) diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index 2dd5b3069..b2f09dfe9 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -47,7 +47,7 @@ async fn main() -> anyhow::Result<()> { let mut accounts = Vec::new(); - for i in 0..100 { + for i in 0..1 { accounts.push(Account::offline(&format!("bot{i}"))); } @@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> { .add_accounts(accounts.clone()) .set_handler(handle) .set_swarm_handler(swarm_handle) - // .join_delay(Duration::from_millis(1000)) + .join_delay(Duration::from_millis(100)) .start("localhost") .await; // let e = azalea::ClientBuilder::new() From 2c610826fc9f8e16897f52313faa8e0602d1dc3d Mon Sep 17 00:00:00 2001 From: mat <27899617+mat-1@users.noreply.github.com> Date: Sun, 19 Nov 2023 22:07:38 -0600 Subject: [PATCH 11/19] Replace azalea-nbt with simdnbt (#111) * delete azalea-nbt and replace with simdnbt * use simdnbt from crates.io * remove serde dependency on azalea-registry --- Cargo.lock | 166 +++------ Cargo.toml | 1 - azalea-buf/Cargo.toml | 1 + azalea-buf/src/read.rs | 33 ++ azalea-buf/src/write.rs | 24 ++ azalea-client/Cargo.toml | 2 +- azalea-client/src/chunks.rs | 9 +- azalea-client/src/interact.rs | 7 +- azalea-core/Cargo.toml | 3 +- azalea-core/src/registry_holder.rs | 287 +++++++--------- azalea-core/src/resource_location.rs | 25 +- azalea-core/src/slot.rs | 40 --- azalea-entity/Cargo.toml | 2 +- azalea-entity/src/data.rs | 2 +- azalea-entity/src/metadata.rs | 8 +- azalea-inventory/Cargo.toml | 2 +- azalea-inventory/src/slot.rs | 2 +- azalea-nbt/Cargo.toml | 37 --- azalea-nbt/README.md | 35 -- azalea-nbt/benches/compare.rs | 91 ----- azalea-nbt/benches/nbt.rs | 75 ----- azalea-nbt/src/decode.rs | 314 ------------------ azalea-nbt/src/encode.rs | 296 ----------------- azalea-nbt/src/error.rs | 15 - azalea-nbt/src/lib.rs | 46 --- azalea-nbt/src/tag.rs | 271 --------------- azalea-nbt/tests/bigtest.nbt | Bin 507 -> 0 bytes azalea-nbt/tests/complex_player.dat | Bin 1379 -> 0 bytes azalea-nbt/tests/hello_world.nbt | Bin 33 -> 0 bytes azalea-nbt/tests/inttest1023.nbt | Bin 4104 -> 0 bytes azalea-nbt/tests/inttest16.nbt | Bin 95 -> 0 bytes azalea-nbt/tests/inttest3.nbt | Bin 24 -> 0 bytes azalea-nbt/tests/level.dat | Bin 1922 -> 0 bytes azalea-nbt/tests/simple_player.dat | Bin 440 -> 0 bytes azalea-nbt/tests/stringtest.nbt | Bin 251 -> 0 bytes azalea-nbt/tests/tests.rs | 140 -------- azalea-protocol/Cargo.toml | 4 +- .../clientbound_block_entity_data_packet.rs | 3 +- ...ientbound_level_chunk_with_light_packet.rs | 2 +- .../game/clientbound_tag_query_packet.rs | 3 +- .../clientbound_update_mob_effect_packet.rs | 3 +- azalea-registry/Cargo.toml | 4 +- .../azalea-registry-macros/src/lib.rs | 25 +- azalea-world/Cargo.toml | 2 +- azalea-world/src/chunk_storage.rs | 6 +- codegen/lib/code/entity.py | 4 +- codegen/lib/code/utils.py | 2 +- 47 files changed, 278 insertions(+), 1714 deletions(-) delete mode 100755 azalea-core/src/slot.rs delete mode 100644 azalea-nbt/Cargo.toml delete mode 100755 azalea-nbt/README.md delete mode 100755 azalea-nbt/benches/compare.rs delete mode 100755 azalea-nbt/benches/nbt.rs delete mode 100755 azalea-nbt/src/decode.rs delete mode 100755 azalea-nbt/src/encode.rs delete mode 100755 azalea-nbt/src/error.rs delete mode 100755 azalea-nbt/src/lib.rs delete mode 100755 azalea-nbt/src/tag.rs delete mode 100755 azalea-nbt/tests/bigtest.nbt delete mode 100755 azalea-nbt/tests/complex_player.dat delete mode 100755 azalea-nbt/tests/hello_world.nbt delete mode 100644 azalea-nbt/tests/inttest1023.nbt delete mode 100755 azalea-nbt/tests/inttest16.nbt delete mode 100644 azalea-nbt/tests/inttest3.nbt delete mode 100755 azalea-nbt/tests/level.dat delete mode 100755 azalea-nbt/tests/simple_player.dat delete mode 100755 azalea-nbt/tests/stringtest.nbt delete mode 100755 azalea-nbt/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 2331c5eeb..67364af59 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 c9bbddbb7..9ce1fc842 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 95f3b4ac0..dc25632b0 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 78db73573..d5c4d0a85 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 f48bf2ec9..03d40d793 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 71f117edf..4694fbd90 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 4d2641f55..e91e6b019 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 64cbd7be0..bdb178272 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 a25485a2e..957eaaf5f 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 7f811e238..6d58f77a3 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 cc669841e..e6a702478 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 229614376..000000000 --- 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 4399ddf36..7cdf53431 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 54487ef11..83779b217 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 39ba9527c..006020d1e 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 64db64232..994dbb2bf 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 cef555d72..814b6c37a 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 788d4a07e..000000000 --- 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 e13bcf228..000000000 --- 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 863e0b9a1..000000000 --- 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 60f620b56..000000000 --- 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 23247b741..000000000 --- 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 34c451d22..000000000 --- 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 ace7fcd30..000000000 --- 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 1a636520c..000000000 --- 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 224db2d33..000000000 --- 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 dc3769bcd06cca599974396faaf5ce42ef4143b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 507 zcmVFFmV&Xw5M#`%wS=YH%|a20v4|EdnhhG* zs4<4PE5m{=yJmN=@zhIuYI4wZzg#H1XOZQ zEDm_fu}zJ5^y6@1J_vgq$EA}P4}wSC?t}tjwW6xWcy?S@%cxZk8_3okYL$kD4Xu7y zdyj+9gHMBR&jS!{TaG@yr8rDv{SfNfbfzOf+-5Fm;kDDdbNY4*DccL+@8~@qI9u-# z2v+spUEd2p;9j@-WVZwWj6qCu#t2nR(;zN=q`=6+5VN}8SPN65?nI7712C~CQ;baU z=@g?=jD_LZpX0OgM1iGzGu_y`$EtM`Unm?1*DldnKd&7dU?ExG`tb$+!Or}hy#T!N zK*{)pLO@3Tp6lullR9XJV7u!wH=`&Dj=S~HX=BPx+v)7)<|{kBCB9@y2|cR2l>Hcf z=+X|_Zxu|jXg(|9o1BE1yo3b_Wmy(Q0RJy2t}pV!vZO8@Aa`0jxs1x^Dc?~+$riQ6 zZJ{cyEX6T@x_Xk1UY%d~5(Oh0(e}4@s?C*hyq@1!T}zj)k{7u|(16JLKEJcvRLci- xZlkt#S(1~f+)+@OYs@v~8vj=#2tv#08`gNj?Ed_E`+a!Rgx}Qir27a4007^G{X+l% diff --git a/azalea-nbt/tests/complex_player.dat b/azalea-nbt/tests/complex_player.dat deleted file mode 100755 index d7f3fcbd3ff02bf1cfcc9ac32e2fee97894566e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1379 zcmV-p1)TaHiwFP!000000M(bzZyQAv$KUSy*Sc|%rfEw>5fO+Y5aNdl0#QY=6E|(7 zwu$N#0#ums?${oAcW2ohCrvpZ?v)VYP%3di0)Ye-7s>$%0peI%^}vY};(&xIRPi6c zo84J^@w!PlvKME&~`a-JlM&1hrQLbsG*BfKLw0bLUp-rzi=3K{fTAI z<5sUltln7~vQQAuqKW|+VfGJJ=bO}}o@n@#+RA$2n=4m7LSqxm)7)llMuSM3me4ak z7mRxm9D#Gsq@8>JwWr?h77NcAV6OT!Akr3by#R%p%ln>K_$A&T2PFxe=An4+%gcl0T#Gi$~Vt4MUaO!1BeD2M;YBnwJlgl6O`*{BATO;ydAg_8j zUOgr6r-gGr+vitbkXsf`N8T4eW?DVpUz^GQ`0n|~l5rmPwK=zsU+9v4D6LP?P3lw+ zb!rAw9=^XE(zw9@w~G9KGCC4f0FUMXd~mARPnBXQ@wEu!KI!c$$!- zp4E*`q8=@}UkAh4bWmeFo>t?W8qcb+uEq}y?8{KJS?D`tJIP9^(PiGnevipi!JdXs zw!CE?Sk#u8T=6#XAo5^a?tbY1=aL4w_*IXkAIeJiG3n$29f${G7x53K#Otw`IVC24 z58^8La$*q(9u4Of2^}cZl*0W(g%;;MYByVywukB z?;C;vwL`%@+Qf`o-7&1D*xWd^t(HS0d<uq_8WJPY_)p#jHH) z*)(YSlmyNgd6Nn(W%shW=+qdbq7YLd(?bN)?e)SPa#qeVUG8Ju+act8b`66zDs4g` zl_A2caoH&$m|Hx!uzGSLm;LhQ-(x9FnV_L zZn;X%#onw+2P^!zus@=Z8HG#^k=A&>l1=tyllx|$1mfZt#!$?`tWQaOYH=>Yd#_H# zY-xp&Qd$}*wfe~XPStDj3=ve8E>y*6I4ECs)&Ul9uj$ZEhk2cp1J_3!Na@w${d0p- zpR7*R>W9;LBch9Mbd+*bC{igow(Ay<*`l4ThDipx)NyI*Sbe(d^4cJR$?_e%Oe-}@ zuuXcr-NqgvYK`#7m^IgTqIL++cd_QS7~Z!U*5-PD&;?!AKwq(OzS8LOtu;JL%)(Nu z!GFPxad@fWQ0ilXP--E%`ZoY(ZzY3QkUpV9ZkYx2??01YU&ZF=k^z(QT+rYI9*gyD zpT<3tDf_6TYual-zG3kI=N@0zU;>+ozJvWE!HWVzZ`NfFWy2*qxbksXp=IL~s&38L z@YfcOxqswgy4DJL;7fnvZcrNNP>$5Ajt!kz^-cshYh(CJC;Nejmriwz(|6ujzkT$~ zq5)HKesNn`U{mh;sNh#rU&pZ*s@@ZmSYv_qgbBtP52MzydhOW(4fpbRMYA+ z2hR``T(XT*_-%rvBZr>i%%Zf?@IFP8sNbJIb>n6p%q`;d;_;0eH!+G6%fxZ&>aWM3 lv0djVNu~v_ue11FYR*fam#)74d(9>X;2*242YWOP000^ZsP+H= diff --git a/azalea-nbt/tests/hello_world.nbt b/azalea-nbt/tests/hello_world.nbt deleted file mode 100755 index f3f5e21aacfc8ce832a459c6c6d827127109cb5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33 ocmd;L;Lb?R$;nqJ&o9bJ;b36NOUzAW;B-pNOUx@u%uQqf0G#LvsQ>@~ diff --git a/azalea-nbt/tests/inttest1023.nbt b/azalea-nbt/tests/inttest1023.nbt deleted file mode 100644 index 481dde192bb9170fdefbe6f66a831aa8450a3622..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4104 zcmXBWgAO1_5J17TZQHhO+qP}{Zf)DPZQHhO`zpCfs$bGS&=WF1kTxwlcL^LIK;XXt z{(k`_5Ox8A5R_m9Cj=o0MQFkhmT-h80uhNsWTFt2XhbIlF^NTN;t-d3#3um>Nkn3j zkd$O3Cj}`YE-8NHK|2y>QI+@)TaRrX+&e1(3EC0rv)u(MQhs7mUgtK z10Cr^XS&dpZgi&yJ?TYn`p}nt^k)DA8N^_Q@DKkolwk~K1S1*6XvQ#>ag1jI6Pd(h zrZAOhOlJl&nZ<18Fqe7EX8{XY#A24Plw~Yu1uI#_YSyrpb*yIt8`;EWwy>3LY-a~M z*~M=5u$O)8=Ku#e#9@wblw%y{1SdJgY0hw#bDZY_7rDe`u5guWT;~Qixy5bnaF=`B z=K&9S#ABZDlxIBW1uuEUYu@mdcf98VANj;*zVMZAeCG#0`NePk@Haq!fD+IM7>K|G zAt=EJP6$F0iqM21Ea3=G1R@fN$V4G3(TGkAViJqk#33&6h))6%l8D44At}j7P6|?z ziqxbbE$K*41~QU~%w!=e*~m@~a*~VODP6JlYEp~Z)S)i*s80hL(ul@1p()L1P77Mniq^EDE$wJe2RhP;&UB$G z-RMpadeV#D^r0{P=+6KKGKj$p;UE5GD8m@e2u3oB(Trg%;~38bCNhc1Okpb1n9dAl zGK<;FVJ`ES&jJ>*h{Y^nDa%;S3Rbd;)vRGH>sZeQHnNG$Y+)*>T;VF$xXul3a*NyC;V$>M&jTLvh{rtP zDbIM$3tsYy*Sz5^?|9D#KJtmreBmqK_|6Z0@{8a6;qU)vKTtpoL|}ptlwbrW1R)7U zXu=SdaD*oU5s5@(q7ap6L?;F@iA8MU5SMtwCjkjbL}HSVlw>3)1u02IYSNIFbfhN( z8OcOuvXGT*WG4qX$whARke7VqrvL>hL}7|hlwuU81SKg&Y06NRa+Ie66{$pJs!)|` zRHp_tsYPw-P?vhtrvVLVL}QxJlx8%i1ubbsYueD3cC@Dh9qB}8y3mzwbf*VB=|yk) z(3gJnX8;2k#9)T-5C1ZhVGL&kBN@eL#xRy~jAsH9nZ#tKFqLUcX9hEw#cbvq#cl3zmwVjj0S|e^W1jGoXFTTxFL}jl z-td-pyypWS`NU_w@Re_T=LbLe#c%%b_x~?{;D8#4zyu*E!3a(WLK2G5gdr^92u}ne z5{bw}Au7>`P7Goai`c{=F7b#@0uqvl#3Ugp$w*ELQj&_)q#-ToNKXbbl8MY@AuHL) zP7ZRCi`?WPFZsw%0SZ!x!W5w>#VAe*N>Yl_l%Xu;C{G0{Qi;k`p(@p=P7P{Oi`vwo zF7>ES0~*qZ#x$WR&1g;wTGEQvw4p8SXio<^(uvM=p)1|!P7iw0i{A91Fa7Ax00uIM z!3^Oa{$(h`7|sYrGK$fRVJzbq&jcniiOEc1D$|(G3}!Nm+00=s^O(;97P5%NEMY0j zSk4MovWnHLVJ+)e&jvQKiOp!G1H~WH diff --git a/azalea-nbt/tests/inttest3.nbt b/azalea-nbt/tests/inttest3.nbt deleted file mode 100644 index 2890f5770876026938586f152f83a3b94cfffc54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24 Ycmd;NV9CiWE@5V1U;tqdU<6_&03&Jv(*OVf diff --git a/azalea-nbt/tests/level.dat b/azalea-nbt/tests/level.dat deleted file mode 100755 index e5d11b8c7888ac809810f12a86313a504c6a701f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1922 zcmV-|2YvV-iwFP!000000M%DZj2uT9{(E}f-r1dfIKd(a6GTuD7)Rz2VVT|8on3pQ zeQ9SMFD7EC>8_b6O?OvQU9;;k2gDUwLWn~!${`X6B#5{q93nviD3=6VIdDQ85C{5hf!iIW_deo8B-IUaC zd0Z-!JXEe<-)wGCTa_br; zp6nGNze*yy@;QiqiRFhA%YJ}-+r;Vb(}+cq1P(PAfHBP3baj4AeHzGCNU3YAmtVho z_hmG$KxLb|ti!0#+^ht>5OT?Qpurk={+X83nTZHF|P;B^o z5Xj{}#vA0JtU%j5lHdEq&z}Eczxjg^#ifKIkrI7s5}$Mvf0q9lyYs#b=Wb9E^5DF0 zX0!Bu{NhWCKf3bEcgN^KN^j+fc=Zgu-!EVPgZsknvuexYw)Vb=7IqTXH(y&Q{O-Fi zJe`aSu-M_;UBA^MgGgC#(PQeZoa;7hsHvF)=JZwZ2++%v4nu8$gG)Im#tDZd8;XZv z3j_Sr#oWgWP*C4D8ZWEboynG18FI{p$9)!1N05&ElFNwC1NSI8;ljlQapPVpwCpQ;7ltdC&3064xTE^D{ z3A>5`<|cs=B}X0!Bo)CF;IaYQ2Kb>7P*69LJfKIIai=#0YZ+{Q8ryc;qZ&R1ST?|d z0nTOEJ`732Uoep3sgObf$r(s#k{(r;$x%p&@TSn4HNf)ZUQQ34ngXdANX0-KjL$T4Ah}md9FcZfyQh-D`G-NUB;FvSKt! z+m}1(024eo@@Th<5-#JwJ7W%H_*(J)`Lxw1t5dW36WP2G*~1g9Q;rN}CMB0o-2x`I z=uCFQBm+I_`7~2jKRI=ID~({Xd>1d%TEmg-n67s^s1dX@!jh>p{Ls^K2~OckZ064f+sL!sqxfpecauwVwo#K1%S zNbsV-&{vu&hp}2m&AqNVv=UB{QET>LxVL=S|62j-jdsLEsQii5pfbooRkJsW4V~Ey zt_qxWG5qCg10mz3Gm5eP;OhtPUD{c-VNT7j>ZbyVaz8`{e@6CA90!rno|wc26V|6I zVDIrrOUsql?mzXb%Xe2-_I{&DuH13n_~u_e@^d3CTSS_$Cn3O)%94V=^;PthS|0~V~<@4GrU;AsrC6A$59jk>dV4iS&trmi(+c9@vqvu&RQF87sy{tW-qgTJr8 zeys@A*tmt*#+{d*H8im^$GLd!*F{(wnQT!J9uU!`GBM6WJ|Sp5DehybqtpCkjr%_C z7Gi|i-6K?##&iQ~Sv~QSxvI-m$5hWrD0GMyDGz+!zA6~) zWW1Ua`Y0$nePY47ggj7$5AgNXr0qd>GwASiiCQ1KUHTw1pVc`{o-0M_Hf+5i9m diff --git a/azalea-nbt/tests/simple_player.dat b/azalea-nbt/tests/simple_player.dat deleted file mode 100755 index e5091b4118cb72a3fe7d98cf07ba86ac13efd041..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440 zcmV;p0Z0BHiwFRI$YoIi|2PQySDT-(INCQuMW#Sb9y3!s2_BvMc!k|H1ky5J2t zxSY-T9B{`UAQ~h(K7xV{iGl)!-#`?!NOZ7gCtTxO&+N|Z=zIVN<|4sTR6A^d?KDE5 zV?nVYcT||jz;a-^6V-8u!XOTmny6wPYm~Yc=$Y>$4~l!CW}1hxKn#21{-gf_J>h{@ zlS86~Dz!{+0ZQ9Uh)rr{@+1@T!DC%6xaJs>=zygg2^WE7fzKt;x~3vKE+IXUnj3w% zJf&gYB@^eZ2r*b4jovLNaXCzcM8&!S2`>^ehwGzvGVZn_b}0`?_00x7Cdm8VeA@O<>L5m@%D9CX$$f5$W%oOQ0O75D=hc@4r(0ssKt!pM{W diff --git a/azalea-nbt/tests/stringtest.nbt b/azalea-nbt/tests/stringtest.nbt deleted file mode 100755 index ac111433cfaf20ee3b4ccffda9981690669fa5d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251 zcmVLW!C**oy>i(-m!@`OG5 z?)4l?Ws6T0C$eZLRTDfq#R)7J0M9jfgTc~MO>I*ikyJ(F$+42J1`7vf!j?9L-H`#f z^(9q>wv~j6D$hqMHkpdd$9OR03=%Bo1K~=JSr361Mg!frPk!rK(jaCMwc_Iey1CQ3 zFpO~V`m#H!X%Bbn^9A=cqR#8H3ODjZ!Q$uh8-x@}2(eqO^#5Dm{s2fuoGV@d006SG Ba0dVY diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs deleted file mode 100755 index 5c82c3c7a..000000000 --- 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 9a143ed56..81a3dde17 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 f9c889f82..95e9c6c38 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 c10fa7377..f8927ca42 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 db238f661..d1073cd59 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 c43d64bb3..37ffd9ce8 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 b32c57593..ffd59035a 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 3bf798a5c..f3289d961 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 4e0b4efa5..ef2d65176 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 7301fdd17..ac81fd09f 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 7be6b01d5..8fa114307 100644 --- a/codegen/lib/code/entity.py +++ b/codegen/lib/code/entity.py @@ -400,7 +400,7 @@ def generate_fields(this_entity_id: str): 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 @@ def generate_fields(this_entity_id: str): 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 0050ce7be..cb835ecbf 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') From 135d25c7537ce81044a9aaf6a3da0a31d19ded4d Mon Sep 17 00:00:00 2001 From: mat Date: Tue, 28 Nov 2023 19:12:49 -0600 Subject: [PATCH 12/19] add Client::partial_world() --- azalea-client/src/client.rs | 42 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index c7094eb2d..dde6ffd3e 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -53,7 +53,7 @@ use azalea_protocol::{ }, resolver, ServerAddress, }; -use azalea_world::{Instance, InstanceContainer, InstanceName}; +use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance}; use bevy_app::{App, FixedUpdate, Plugin, PluginGroup, PluginGroupBuilder, Update}; use bevy_ecs::{ bundle::Bundle, @@ -425,16 +425,6 @@ impl Client { }); } - pub fn local_player<'a>(&'a self, ecs: &'a mut World) -> &'a InstanceHolder { - self.query::<&InstanceHolder>(ecs) - } - pub fn local_player_mut<'a>( - &'a self, - ecs: &'a mut World, - ) -> bevy_ecs::world::Mut<'a, InstanceHolder> { - self.query::<&mut InstanceHolder>(ecs) - } - pub fn raw_connection<'a>(&'a self, ecs: &'a mut World) -> &'a RawConnection { self.query::<&RawConnection>(ecs) } @@ -468,17 +458,29 @@ impl Client { self.query::>(&mut self.ecs.lock()).cloned() } - /// Get a reference to our (potentially shared) world. + /// Get an `RwLock` with a reference to our (potentially shared) world. /// - /// This gets the [`Instance`] from our world container. If it's a normal - /// client, then it'll be the same as the world the client has loaded. - /// If the client using a shared world, then the shared world will be a - /// superset of the client's world. + /// This gets the [`Instance`] from the client's [`InstanceHolder`] + /// component. If it's a normal client, then it'll be the same as the + /// world the client has loaded. If the client is using a shared world, + /// then the shared world will be a superset of the client's world. pub fn world(&self) -> Arc> { - let world_name = self.component::(); - let ecs = self.ecs.lock(); - let instance_container = ecs.resource::(); - instance_container.get(&world_name).unwrap() + let instance_holder = self.component::(); + instance_holder.instance.clone() + } + + /// Get an `RwLock` with a reference to the world that this client has + /// loaded. + /// + /// ``` + /// # use azalea_core::position::ChunkPos; + /// # fn example(client: &azalea_client::Client) { + /// let world = client.partial_world(); + /// let is_0_0_loaded = world.read().chunks.in_range(&ChunkPos::new(0, 0)); + /// # } + pub fn partial_world(&self) -> Arc> { + let instance_holder = self.component::(); + instance_holder.partial_instance.clone() } /// Returns whether we have a received the login packet yet. From 0f9492ff38faab191a181361b8748c45b459fb9d Mon Sep 17 00:00:00 2001 From: mat Date: Tue, 28 Nov 2023 19:27:49 -0600 Subject: [PATCH 13/19] slightly change Client::partial_world example --- azalea-client/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index dde6ffd3e..869ab0f30 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -476,7 +476,7 @@ impl Client { /// # use azalea_core::position::ChunkPos; /// # fn example(client: &azalea_client::Client) { /// let world = client.partial_world(); - /// let is_0_0_loaded = world.read().chunks.in_range(&ChunkPos::new(0, 0)); + /// let is_0_0_loaded = world.read().chunks.limited_get(&ChunkPos::new(0, 0)).is_some(); /// # } pub fn partial_world(&self) -> Arc> { let instance_holder = self.component::(); From 8fb58b7754797b139ead1c68ece4ec1bf411455c Mon Sep 17 00:00:00 2001 From: 1zuna <1zuna@ccbluex.net> Date: Thu, 30 Nov 2023 01:25:58 +0100 Subject: [PATCH 14/19] fix trust_dns_resolver issue (#112) fixes error: "Label contains invalid characters" --- azalea-protocol/src/resolver.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/azalea-protocol/src/resolver.rs b/azalea-protocol/src/resolver.rs index 79dd660ff..f803df23b 100755 --- a/azalea-protocol/src/resolver.rs +++ b/azalea-protocol/src/resolver.rs @@ -6,7 +6,7 @@ use std::net::{IpAddr, SocketAddr}; use thiserror::Error; use trust_dns_resolver::{ config::{ResolverConfig, ResolverOpts}, - TokioAsyncResolver, + TokioAsyncResolver, Name, }; #[derive(Error, Debug)] @@ -45,7 +45,7 @@ pub async fn resolve_address(address: &ServerAddress) -> Result Result Date: Thu, 30 Nov 2023 02:51:19 +0100 Subject: [PATCH 15/19] serde support for 'ServerAddress' (#115) --- azalea-protocol/src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs index aac449c98..973564eb6 100644 --- a/azalea-protocol/src/lib.rs +++ b/azalea-protocol/src/lib.rs @@ -76,6 +76,33 @@ impl Display for ServerAddress { } } +/// +/// Serde Deserialization for ServerAddress +/// This is necessary for config file usage +/// We are not using TryFrom because we want to use the serde error system +/// +impl<'de> serde::Deserialize<'de> for ServerAddress { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + let string = String::deserialize(deserializer)?; + let mut parts = string.split(':'); + let host = parts.next().ok_or(serde::de::Error::custom("No host specified"))?.to_string(); + // default the port to 25565 + let port = parts.next().unwrap_or("25565"); + let port = u16::from_str(port).map_err(|_| serde::de::Error::custom("Invalid port specified"))?; + Ok(ServerAddress { host, port }) + } +} + +/// +/// Serde Serialization for ServerAddress +/// Pretty much like impl Display +/// +impl serde::Serialize for ServerAddress { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(&format!("{}:{}", self.host, self.port)) + } +} + #[cfg(test)] mod tests { use std::io::Cursor; From af818b3b58a792e216c503ff9f421bc2bb0c8bb5 Mon Sep 17 00:00:00 2001 From: mat Date: Wed, 29 Nov 2023 19:51:34 -0600 Subject: [PATCH 16/19] simplify ServerAddress serde implementations --- azalea-protocol/src/lib.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs index 973564eb6..38acd7051 100644 --- a/azalea-protocol/src/lib.rs +++ b/azalea-protocol/src/lib.rs @@ -76,30 +76,26 @@ impl Display for ServerAddress { } } -/// -/// Serde Deserialization for ServerAddress -/// This is necessary for config file usage -/// We are not using TryFrom because we want to use the serde error system -/// +/// Serde deserialization for ServerAddress. This is useful for config file +/// usage. impl<'de> serde::Deserialize<'de> for ServerAddress { - fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { let string = String::deserialize(deserializer)?; - let mut parts = string.split(':'); - let host = parts.next().ok_or(serde::de::Error::custom("No host specified"))?.to_string(); - // default the port to 25565 - let port = parts.next().unwrap_or("25565"); - let port = u16::from_str(port).map_err(|_| serde::de::Error::custom("Invalid port specified"))?; - Ok(ServerAddress { host, port }) + ServerAddress::try_from(string.as_str()).map_err(serde::de::Error::custom) } } -/// -/// Serde Serialization for ServerAddress -/// Pretty much like impl Display -/// +/// Serde serialization for ServerAddress. This uses the Display impl, so it +/// will serialize to a string. impl serde::Serialize for ServerAddress { - fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { - serializer.serialize_str(&format!("{}:{}", self.host, self.port)) + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) } } From f114c78dbaa1ec2402fc71683a260e03f7e7fe2c Mon Sep 17 00:00:00 2001 From: mat Date: Wed, 29 Nov 2023 19:53:14 -0600 Subject: [PATCH 17/19] fix typo in auth docs --- azalea-auth/src/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azalea-auth/src/auth.rs b/azalea-auth/src/auth.rs index 0c3b98c67..49f0c34a5 100755 --- a/azalea-auth/src/auth.rs +++ b/azalea-auth/src/auth.rs @@ -58,7 +58,7 @@ pub enum AuthError { /// /// If you want to use your own code to cache or show the auth code to the user /// in a different way, use [`get_ms_link_code`], [`get_ms_auth_token`], -/// [`get_minecraft_token`] and [`get_profile`] instead instead. +/// [`get_minecraft_token`] and [`get_profile`] instead. pub async fn auth(email: &str, opts: AuthOpts) -> Result { let cached_account = if let Some(cache_file) = &opts.cache_file { cache::get_account_in_cache(cache_file, email).await From a80619449762cf58c6790f886b1afaf54a96e03d Mon Sep 17 00:00:00 2001 From: mat Date: Wed, 29 Nov 2023 20:00:47 -0600 Subject: [PATCH 18/19] fmt --- azalea-protocol/src/resolver.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/azalea-protocol/src/resolver.rs b/azalea-protocol/src/resolver.rs index f803df23b..af59be72c 100755 --- a/azalea-protocol/src/resolver.rs +++ b/azalea-protocol/src/resolver.rs @@ -6,7 +6,7 @@ use std::net::{IpAddr, SocketAddr}; use thiserror::Error; use trust_dns_resolver::{ config::{ResolverConfig, ResolverOpts}, - TokioAsyncResolver, Name, + Name, TokioAsyncResolver, }; #[derive(Error, Debug)] @@ -62,8 +62,7 @@ pub async fn resolve_address(address: &ServerAddress) -> Result Date: Fri, 1 Dec 2023 18:43:04 +0100 Subject: [PATCH 19/19] Make auto_respawn pub (#116) --- azalea/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index fa9d2e5ad..bf00cbbe4 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -5,7 +5,7 @@ #![feature(let_chains)] pub mod accept_resource_packs; -mod auto_respawn; +pub mod auto_respawn; pub mod auto_tool; mod bot; pub mod container;