From 136118ce3f15bd3daa44463b18ca95ce7fe64430 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 26 Jun 2026 22:26:33 +0300 Subject: [PATCH] Fix leaking entity ticks --- src/server.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/server.rs b/src/server.rs index e33d0a49..35f7e6bf 100644 --- a/src/server.rs +++ b/src/server.rs @@ -125,6 +125,7 @@ impl Plugin for ServerPlugin { .init_resource::() .init_resource::() .init_resource::() + .register_required_components::() .configure_sets( PreUpdate, (ServerSystems::ReceivePackets, ServerSystems::Receive).chain(), @@ -142,6 +143,7 @@ impl Plugin for ServerPlugin { .add_observer(handle_disconnect) .add_observer(check_mutation_ticks) .add_observer(buffer_despawn) + .add_observer(cleanup_unreplicated) .add_observer(cleanup_storage) .add_systems( PreUpdate, @@ -330,6 +332,20 @@ fn buffer_despawn( } } +fn cleanup_unreplicated( + despawn: On, + state: Res>, + replicated: Query<&Replicated>, + mut clients: Query<&mut ClientTicks>, +) { + if *state == ServerState::Running && !replicated.contains(despawn.entity) { + trace!("cleaning up ticks for despawned `{}`", despawn.entity); + for mut ticks in &mut clients { + ticks.entities.remove(&despawn.entity); + } + } +} + fn cleanup_acks( mutations_timeout: Duration, ) -> impl FnMut(Query<&mut ClientTicks>, Res>) { @@ -977,3 +993,15 @@ pub struct AuthorizedClient; /// See its documentation for more details. #[derive(Component, Reflect, Deref, DerefMut, Default, Debug, Clone)] pub struct PriorityMap(EntityHashMap); + +/// Marker for entities stored in [`ClientTicks`]. +/// +/// Marked as required for [`Replicated`] by [`ServerPlugin`]. +/// +/// Despawned entities with [`Replicated`] are automatically removed +/// from all [`ClientTicks`] during [`collect_despawns`]. +/// +/// If [`Replicated`] was removed before despawning, the despawn is not +/// replicated, so this marker is used to clean up [`ClientTicks`]. +#[derive(Component, Default)] +struct TicksTracked;