From dd557c8f293dbef3e2e881bcb1a85a7697a1ebbb Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 23 Feb 2025 08:47:17 +0000 Subject: [PATCH] fix memory leak in simulation tests (lol) also, change some vecs into boxed slices, and add RelativeEntityUpdate::new --- azalea-buf/src/read.rs | 15 ++++ azalea-buf/src/write.rs | 17 ++++- azalea-client/src/lib.rs | 1 + azalea-client/src/plugins/chat/handler.rs | 2 +- .../src/plugins/packet/config/events.rs | 4 +- .../src/plugins/packet/config/mod.rs | 4 +- azalea-client/src/plugins/packet/game/mod.rs | 70 +++++++++---------- azalea-client/src/test_simulation.rs | 21 ++++-- azalea-entity/src/plugin/relative_updates.rs | 11 +++ azalea-world/src/bit_storage.rs | 6 +- azalea-world/src/chunk_storage.rs | 9 +-- azalea-world/src/palette.rs | 2 +- azalea/examples/testbot/commands/debug.rs | 7 ++ 13 files changed, 113 insertions(+), 56 deletions(-) diff --git a/azalea-buf/src/read.rs b/azalea-buf/src/read.rs index 324eab87..b1b95f4d 100755 --- a/azalea-buf/src/read.rs +++ b/azalea-buf/src/read.rs @@ -199,6 +199,11 @@ impl AzaleaRead for Vec { Ok(contents) } } +impl AzaleaRead for Box<[T]> { + default fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { + Vec::::azalea_read(buf).map(Vec::into_boxed_slice) + } +} impl AzaleaReadLimited for Vec { fn azalea_read_limited(buf: &mut Cursor<&[u8]>, limit: usize) -> Result { let length = u32::azalea_read_var(buf)? as usize; @@ -216,6 +221,11 @@ impl AzaleaReadLimited for Vec { Ok(contents) } } +impl AzaleaReadLimited for Box<[T]> { + fn azalea_read_limited(buf: &mut Cursor<&[u8]>, limit: usize) -> Result { + Vec::::azalea_read_limited(buf, limit).map(Vec::into_boxed_slice) + } +} impl AzaleaRead for HashMap { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { @@ -297,6 +307,11 @@ impl AzaleaReadVar for Vec { Ok(contents) } } +impl AzaleaReadVar for Box<[T]> { + fn azalea_read_var(buf: &mut Cursor<&[u8]>) -> Result { + Vec::::azalea_read_var(buf).map(Vec::into_boxed_slice) + } +} impl AzaleaRead for i64 { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { diff --git a/azalea-buf/src/write.rs b/azalea-buf/src/write.rs index 73aefe40..c56b0062 100755 --- a/azalea-buf/src/write.rs +++ b/azalea-buf/src/write.rs @@ -64,6 +64,11 @@ impl AzaleaWrite for Vec { self[..].azalea_write(buf) } } +impl AzaleaWrite for Box<[T]> { + default fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> { + self[..].azalea_write(buf) + } +} impl AzaleaWrite for [T] { fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> { @@ -167,7 +172,7 @@ impl AzaleaWriteVar for u16 { } } -impl AzaleaWriteVar for Vec { +impl AzaleaWriteVar for [T] { fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> { u32::azalea_write_var(&(self.len() as u32), buf)?; for i in self { @@ -176,6 +181,16 @@ impl AzaleaWriteVar for Vec { Ok(()) } } +impl AzaleaWriteVar for Vec { + fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> { + self[..].azalea_write_var(buf) + } +} +impl AzaleaWriteVar for Box<[T]> { + fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> { + self[..].azalea_write_var(buf) + } +} impl AzaleaWrite for u8 { fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> { diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index d2302b78..bcea00bc 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -6,6 +6,7 @@ //! [`azalea`]: https://docs.rs/azalea #![feature(error_generic_member_access)] +#![feature(never_type)] mod account; mod client; diff --git a/azalea-client/src/plugins/chat/handler.rs b/azalea-client/src/plugins/chat/handler.rs index d598acdb..be99b667 100644 --- a/azalea-client/src/plugins/chat/handler.rs +++ b/azalea-client/src/plugins/chat/handler.rs @@ -1,8 +1,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use azalea_protocol::packets::{ - game::{s_chat::LastSeenMessagesUpdate, ServerboundChat, ServerboundChatCommand}, Packet, + game::{ServerboundChat, ServerboundChatCommand, s_chat::LastSeenMessagesUpdate}, }; use bevy_ecs::prelude::*; diff --git a/azalea-client/src/plugins/packet/config/events.rs b/azalea-client/src/plugins/packet/config/events.rs index 6b647d74..a9b2179a 100644 --- a/azalea-client/src/plugins/packet/config/events.rs +++ b/azalea-client/src/plugins/packet/config/events.rs @@ -2,15 +2,15 @@ use std::io::Cursor; use azalea_protocol::{ packets::{ - config::{ClientboundConfigPacket, ServerboundConfigPacket}, Packet, + config::{ClientboundConfigPacket, ServerboundConfigPacket}, }, read::deserialize_packet, }; use bevy_ecs::prelude::*; use tracing::{debug, error}; -use crate::{raw_connection::RawConnection, InConfigState}; +use crate::{InConfigState, raw_connection::RawConnection}; #[derive(Event, Debug, Clone)] pub struct ReceiveConfigPacketEvent { diff --git a/azalea-client/src/plugins/packet/config/mod.rs b/azalea-client/src/plugins/packet/config/mod.rs index 5cb19b9d..2e0ce354 100644 --- a/azalea-client/src/plugins/packet/config/mod.rs +++ b/azalea-client/src/plugins/packet/config/mod.rs @@ -1,7 +1,7 @@ mod events; -use azalea_protocol::packets::config::*; use azalea_protocol::packets::ConnectionProtocol; +use azalea_protocol::packets::config::*; use bevy_ecs::prelude::*; use bevy_ecs::system::SystemState; pub use events::*; @@ -12,7 +12,7 @@ use crate::client::InConfigState; use crate::disconnect::DisconnectEvent; use crate::packet::game::KeepAliveEvent; use crate::raw_connection::RawConnection; -use crate::{declare_packet_handlers, InstanceHolder}; +use crate::{InstanceHolder, declare_packet_handlers}; pub fn process_packet_events(ecs: &mut World) { let mut events_owned = Vec::new(); diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index 98f76d13..5f3e125f 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -752,9 +752,9 @@ impl GamePacketHandler<'_> { // we use RelativeEntityUpdate because it makes sure changes aren't made // multiple times - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity| { let entity_id = entity.id(); entity.world_scope(|world| { let mut commands_system_state = SystemState::::new(world); @@ -767,8 +767,8 @@ impl GamePacketHandler<'_> { } commands_system_state.apply(world); }); - }), - }); + }, + )); }); } @@ -805,14 +805,14 @@ impl GamePacketHandler<'_> { z: p.delta.za as f64 / 8000., }); - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity_mut| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity_mut| { entity_mut.world_scope(|world| { world.send_event(KnockbackEvent { entity, knockback }) }); - }), - }); + }, + )); }, ); } @@ -868,9 +868,9 @@ impl GamePacketHandler<'_> { x_rot: (p.change.look_direction.x_rot as i32 * 360) as f32 / 256., y_rot: (p.change.look_direction.y_rot as i32 * 360) as f32 / 256., }; - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity| { let mut position = entity.get_mut::().unwrap(); if new_pos != **position { **position = new_pos; @@ -883,8 +883,8 @@ impl GamePacketHandler<'_> { // old_pos is set to the current position when we're teleported let mut physics = entity.get_mut::().unwrap(); physics.set_old_pos(&position); - }), - }); + }, + )); }, ); } @@ -913,9 +913,9 @@ impl GamePacketHandler<'_> { let new_delta = p.delta.clone(); let new_on_ground = p.on_ground; - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity_mut| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity_mut| { let mut physics = entity_mut.get_mut::().unwrap(); let new_pos = physics.vec_delta_codec.decode( new_delta.xa as i64, @@ -929,8 +929,8 @@ impl GamePacketHandler<'_> { if new_pos != **position { **position = new_pos; } - }), - }); + }, + )); }, ); } @@ -962,9 +962,9 @@ impl GamePacketHandler<'_> { let new_on_ground = p.on_ground; - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity_mut| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity_mut| { let mut physics = entity_mut.get_mut::().unwrap(); let new_pos = physics.vec_delta_codec.decode( new_delta.xa as i64, @@ -983,8 +983,8 @@ impl GamePacketHandler<'_> { if new_look_direction != *look_direction { *look_direction = new_look_direction; } - }), - }); + }, + )); }, ); } @@ -1003,9 +1003,9 @@ impl GamePacketHandler<'_> { }; let new_on_ground = p.on_ground; - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity_mut| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity_mut| { let mut physics = entity_mut.get_mut::().unwrap(); physics.set_on_ground(new_on_ground); @@ -1013,8 +1013,8 @@ impl GamePacketHandler<'_> { if new_look_direction != *look_direction { *look_direction = new_look_direction; } - }), - }); + }, + )); } else { warn!( "Got move entity rot packet for unknown entity id {}", @@ -1507,9 +1507,9 @@ impl GamePacketHandler<'_> { let new_on_ground = p.on_ground; let new_look_direction = p.values.look_direction; - commands.entity(entity).queue(RelativeEntityUpdate { - partial_world: instance_holder.partial_instance.clone(), - update: Box::new(move |entity_mut| { + commands.entity(entity).queue(RelativeEntityUpdate::new( + instance_holder.partial_instance.clone(), + move |entity_mut| { let is_local_entity = entity_mut.get::().is_some(); let mut physics = entity_mut.get_mut::().unwrap(); @@ -1530,8 +1530,8 @@ impl GamePacketHandler<'_> { let mut look_direction = entity_mut.get_mut::().unwrap(); *look_direction = new_look_direction; - }), - }); + }, + )); }, ); } diff --git a/azalea-client/src/test_simulation.rs b/azalea-client/src/test_simulation.rs index 2479e953..27cf4a57 100644 --- a/azalea-client/src/test_simulation.rs +++ b/azalea-client/src/test_simulation.rs @@ -21,6 +21,7 @@ use bevy_app::App; use bevy_ecs::{prelude::*, schedule::ExecutorKind}; use parking_lot::{Mutex, RwLock}; use simdnbt::owned::Nbt; +use tokio::task::JoinHandle; use tokio::{sync::mpsc, time::sleep}; use uuid::Uuid; @@ -39,14 +40,14 @@ pub struct Simulation { pub rt: tokio::runtime::Runtime, pub incoming_packet_queue: Arc>>>, - pub outgoing_packets_receiver: mpsc::UnboundedReceiver>, + pub clear_outgoing_packets_receiver_task: JoinHandle, } impl Simulation { pub fn new(initial_connection_protocol: ConnectionProtocol) -> Self { let mut app = create_simulation_app(); let mut entity = app.world_mut().spawn_empty(); - let (player, outgoing_packets_receiver, incoming_packet_queue, rt) = + let (player, clear_outgoing_packets_receiver_task, incoming_packet_queue, rt) = create_local_player_bundle(entity.id(), initial_connection_protocol); entity.insert(player); @@ -68,7 +69,7 @@ impl Simulation { entity, rt, incoming_packet_queue, - outgoing_packets_receiver, + clear_outgoing_packets_receiver_task, } } @@ -105,14 +106,14 @@ fn create_local_player_bundle( connection_protocol: ConnectionProtocol, ) -> ( LocalPlayerBundle, - mpsc::UnboundedReceiver>, + JoinHandle, Arc>>>, tokio::runtime::Runtime, ) { // unused since we'll trigger ticks ourselves - let (run_schedule_sender, _run_schedule_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (run_schedule_sender, _run_schedule_receiver) = mpsc::unbounded_channel(); - let (outgoing_packets_sender, outgoing_packets_receiver) = mpsc::unbounded_channel(); + let (outgoing_packets_sender, mut outgoing_packets_receiver) = mpsc::unbounded_channel(); let incoming_packet_queue = Arc::new(Mutex::new(Vec::new())); let reader = RawConnectionReader { incoming_packet_queue: incoming_packet_queue.clone(), @@ -136,6 +137,12 @@ fn create_local_player_bundle( } }); + let clear_outgoing_packets_receiver_task = rt.spawn(async move { + loop { + let _ = outgoing_packets_receiver.recv().await; + } + }); + let raw_connection = RawConnection { reader, writer, @@ -160,7 +167,7 @@ fn create_local_player_bundle( ( local_player_bundle, - outgoing_packets_receiver, + clear_outgoing_packets_receiver_task, incoming_packet_queue, rt, ) diff --git a/azalea-entity/src/plugin/relative_updates.rs b/azalea-entity/src/plugin/relative_updates.rs index 63b00195..5ad7d7a0 100644 --- a/azalea-entity/src/plugin/relative_updates.rs +++ b/azalea-entity/src/plugin/relative_updates.rs @@ -46,6 +46,17 @@ pub struct RelativeEntityUpdate { // a function that takes the entity and updates it pub update: Box, } +impl RelativeEntityUpdate { + pub fn new( + partial_world: Arc>, + update: impl FnOnce(&mut EntityWorldMut) + Send + Sync + 'static, + ) -> Self { + Self { + partial_world, + update: Box::new(update), + } + } +} /// A component that counts the number of times this entity has been modified. /// This is used for making sure two clients don't do the same relative update diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index 2af7427d..1b0c1a56 100755 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -72,7 +72,7 @@ const MAGIC: [(i32, i32, i32); 64] = [ /// A compact list of integers with the given number of bits per entry. #[derive(Clone, Debug, Default)] pub struct BitStorage { - pub data: Vec, + pub data: Box<[u64]>, bits: usize, mask: u64, size: usize, @@ -106,7 +106,7 @@ impl BitStorage { // 0 bit storage if data.is_empty() { return Ok(BitStorage { - data: Vec::new(), + data: Box::new([]), bits, size, ..Default::default() @@ -136,7 +136,7 @@ impl BitStorage { }; Ok(BitStorage { - data: using_data, + data: using_data.into(), bits, mask, size, diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 455d87e7..9592abbf 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -31,7 +31,7 @@ pub struct PartialChunkStorage { chunk_radius: u32, view_range: u32, // chunks is a list of size chunk_radius * chunk_radius - chunks: Vec>>>, + chunks: Box<[Option>>]>, } /// A storage for chunks where they're only stored weakly, so if they're not @@ -50,7 +50,7 @@ pub struct ChunkStorage { /// coordinate. #[derive(Debug)] pub struct Chunk { - pub sections: Vec
, + pub sections: Box<[Section]>, /// Heightmaps are used for identifying the surface blocks in a chunk. /// Usually for clients only `WorldSurface` and `MotionBlocking` are /// present. @@ -84,7 +84,7 @@ impl Default for Section { impl Default for Chunk { fn default() -> Self { Chunk { - sections: vec![Section::default(); (384 / 16) as usize], + sections: vec![Section::default(); (384 / 16) as usize].into(), heightmaps: HashMap::new(), } } @@ -97,7 +97,7 @@ impl PartialChunkStorage { view_center: ChunkPos::new(0, 0), chunk_radius, view_range, - chunks: vec![None; (view_range * view_range) as usize], + chunks: vec![None; (view_range * view_range) as usize].into(), } } @@ -341,6 +341,7 @@ impl Chunk { let section = Section::azalea_read(buf)?; sections.push(section); } + let sections = sections.into_boxed_slice(); let mut heightmaps = HashMap::new(); for (name, heightmap) in heightmaps_nbt.iter() { diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index 9b1a8642..dd5f7daa 100755 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -49,7 +49,7 @@ impl PalettedContainer { let palette_type = PaletteKind::from_bits_and_type(server_bits_per_entry, container_type); let palette = palette_type.read(buf)?; let size = container_type.size(); - let data = Vec::::azalea_read(buf)?; + let data = Box::<[u64]>::azalea_read(buf)?; // we can only trust the bits per entry that we're sent if there's enough data // that it'd be global. if it's not global, then we have to calculate it diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs index 1b3b2d61..9de4d97d 100644 --- a/azalea/examples/testbot/commands/debug.rs +++ b/azalea/examples/testbot/commands/debug.rs @@ -172,6 +172,9 @@ pub fn register(commands: &mut CommandDispatcher>) { let source = ctx.source.lock(); source.reply("Ok!"); + + + source.bot.disconnect(); let ecs = source.bot.ecs.clone(); @@ -181,6 +184,8 @@ pub fn register(commands: &mut CommandDispatcher>) { let ecs = ecs.lock(); + + let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt"); let mut report = File::create(&report_path).unwrap(); @@ -203,6 +208,7 @@ pub fn register(commands: &mut CommandDispatcher>) { writeln!(report).unwrap(); + for (info, _) in ecs.iter_resources() { let name = info.name(); writeln!(report, "Resource: {name}").unwrap(); @@ -212,6 +218,7 @@ pub fn register(commands: &mut CommandDispatcher>) { match name { "azalea_world::container::InstanceContainer" => { let instance_container = ecs.resource::(); + for (instance_name, instance) in &instance_container.instances { writeln!(report, "- Name: {}", instance_name).unwrap(); writeln!(report, "- Reference count: {}", instance.strong_count())