diff --git a/cosmos_client/src/rendering/lod_renderer.rs b/cosmos_client/src/rendering/lod_renderer.rs index 213001bb6..0776e8022 100644 --- a/cosmos_client/src/rendering/lod_renderer.rs +++ b/cosmos_client/src/rendering/lod_renderer.rs @@ -7,14 +7,14 @@ use cosmos_core::{ structure::{ block_storage::BlockStorer, chunk::{CHUNK_DIMENSIONS, CHUNK_DIMENSIONSF}, - coordinates::ChunkBlockCoordinate, + coordinates::{ChunkBlockCoordinate, ChunkCoordinate}, lod::Lod, lod_chunk::LodChunk, Structure, }, utils::array_utils::expand, }; -use rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use crate::{ asset::asset_loading::{BlockTextureIndex, MainAtlas}, @@ -316,7 +316,7 @@ impl ChunkRenderer { struct LodMeshes(Vec); fn recursively_process_lod( - lod: &Lod, + lod: &mut Lod, offset: Vec3, to_process: &Mutex>>, entity: Entity, @@ -330,7 +330,7 @@ fn recursively_process_lod( match lod { Lod::None => {} Lod::Children(children) => { - children.par_iter().enumerate().for_each(|(i, c)| { + children.par_iter_mut().enumerate().for_each(|(i, c)| { let s4 = scale / 4.0; let offset = match i { @@ -359,7 +359,13 @@ fn recursively_process_lod( ); }); } - Lod::Single(lod_chunk) => { + Lod::Single(lod_chunk, dirty) => { + if !*dirty { + return; + } + + *dirty = false; + let mut renderer = ChunkRenderer::new(); renderer.render( @@ -394,6 +400,38 @@ fn recursively_process_lod( }; } +fn find_non_dirty(lod: &Lod, offset: Vec3, to_process: &mut Vec, scale: f32) { + match lod { + Lod::None => {} + Lod::Children(children) => { + children.iter().enumerate().for_each(|(i, c)| { + let s4 = scale / 4.0; + + let offset = match i { + 0 => offset + Vec3::new(-s4, -s4, -s4), + 1 => offset + Vec3::new(-s4, -s4, s4), + 2 => offset + Vec3::new(s4, -s4, s4), + 3 => offset + Vec3::new(s4, -s4, -s4), + 4 => offset + Vec3::new(-s4, s4, -s4), + 5 => offset + Vec3::new(-s4, s4, s4), + 6 => offset + Vec3::new(s4, s4, s4), + 7 => offset + Vec3::new(s4, s4, -s4), + _ => unreachable!(), + }; + + find_non_dirty(c, offset, to_process, scale / 2.0); + }); + } + Lod::Single(_, dirty) => { + if *dirty { + return; + } + + to_process.push(offset); + } + }; +} + /// Performance hot spot fn monitor_lods_needs_rendered_system( mut commands: Commands, @@ -405,20 +443,34 @@ fn monitor_lods_needs_rendered_system( meshes_registry: Res, chunk_meshes_query: Query<&LodMeshes>, block_textures: Res>, + transform_query: Query<&Transform>, - lods_needed: Query<(Entity, &Lod, &Structure), Changed>, + mut lods_needed: Query<(Entity, &mut Lod, &Structure), Changed>, ) { // by making the Vec an Option I can take ownership of it later, which I cannot do with // just a plain Mutex. // https://stackoverflow.com/questions/30573188/cannot-move-data-out-of-a-mutex let to_process: Mutex>> = Mutex::new(Some(Vec::new())); + let to_keep: Mutex>>> = Mutex::new(Some(HashMap::new())); - let todo = Vec::from_iter(lods_needed.iter()); + let mut todo = Vec::from_iter(lods_needed.iter_mut()); // Render lods in parallel - todo.par_iter().for_each(|(entity, lod, structure)| { + todo.par_iter_mut().for_each(|(entity, lod, structure)| { + let scale = structure.chunk_dimensions().x as f32; + + let mut non_dirty = vec![]; + find_non_dirty(lod, Vec3::ZERO, &mut non_dirty, scale); + + to_keep + .lock() + .expect("failed to lock mutex") + .as_mut() + .unwrap() + .insert(*entity, non_dirty); + recursively_process_lod( - lod, + lod.as_mut(), Vec3::ZERO, &to_process, *entity, @@ -427,7 +479,7 @@ fn monitor_lods_needs_rendered_system( &materials, &meshes_registry, &block_textures, - structure.chunk_dimensions().x as f32, + scale, ); }); @@ -444,6 +496,10 @@ fn monitor_lods_needs_rendered_system( for (entity, mut lod_meshes) in ent_meshes { let mut old_mesh_entities = Vec::new(); + let to_keep_locations = to_keep.lock().unwrap().take().unwrap_or_default(); + + let to_keep_locations = to_keep_locations.get(&entity); + if let Ok(chunk_meshes_component) = chunk_meshes_query.get(entity) { for ent in chunk_meshes_component.0.iter() { let old_mesh_handle = mesh_query.get(*ent).expect("This should have a mesh component."); @@ -533,6 +589,13 @@ fn monitor_lods_needs_rendered_system( // Any leftover entities are useless now, so kill them for mesh_entity in old_mesh_entities { + if let Ok(transform) = transform_query.get(mesh_entity) { + if to_keep_locations.map(|x| x.contains(&transform.translation)).unwrap_or(false) { + structure_meshes_component.0.push(mesh_entity); + continue; + } + } + commands.entity(mesh_entity).despawn_recursive(); } diff --git a/cosmos_client/src/structure/planet/lod.rs b/cosmos_client/src/structure/planet/lod.rs index 578d08764..478bba26e 100644 --- a/cosmos_client/src/structure/planet/lod.rs +++ b/cosmos_client/src/structure/planet/lod.rs @@ -1,23 +1,41 @@ -use bevy::prelude::{in_state, App, Commands, IntoSystemConfigs, Res, ResMut, Update}; +use bevy::prelude::{in_state, App, Commands, IntoSystemConfigs, Query, Res, ResMut, Update}; use bevy_renet::renet::RenetClient; use cosmos_core::{ netty::{cosmos_encoder, NettyChannelServer}, - structure::lod::{Lod, LodNetworkMessage}, + structure::lod::{Lod, LodDelta, LodNetworkMessage}, }; use crate::{netty::mapping::NetworkMapping, state::game_state::GameState}; -fn listen_for_new_lods(netty_mapping: Res, mut client: ResMut, mut commands: Commands) { - while let Some(message) = client.receive_message(NettyChannelServer::Lod) { +/// Note: this method will crash if the server sends 2 lod updates immediately, which could happen. +/// +/// This should be updated to account for that. Perhaps send all deltas at once in a vec? +/// +/// It will crash because it will insert the lod, but the query won't then return it for the next delta, which will rely on it +fn listen_for_new_lods( + netty_mapping: Res, + mut lod_query: Query<&mut Lod>, + mut client: ResMut, + mut commands: Commands, +) { + while let Some(message) = client.receive_message(NettyChannelServer::DeltaLod) { let msg: LodNetworkMessage = cosmos_encoder::deserialize(&message).expect("Invalid LOD packet recieved from server!"); match msg { LodNetworkMessage::SetLod(lod) => { if let Some(structure_entity) = netty_mapping.client_from_server(&lod.structure) { if let Some(mut ecmds) = commands.get_entity(structure_entity) { - let lod = cosmos_encoder::deserialize::(&lod.serialized_lod).expect("Unable to deserialize lod"); + let cur_lod = lod_query.get_mut(structure_entity); - ecmds.insert(lod); + let delta_lod = cosmos_encoder::deserialize::(&lod.serialized_lod).expect("Unable to deserialize lod"); + + if let Ok(mut cur_lod) = cur_lod { + delta_lod.apply_changes(&mut cur_lod); + } else { + println!("Creating delta: {delta_lod:?}"); + ecmds.insert(delta_lod.create_lod()); + ecmds.log_components(); + } } } } diff --git a/cosmos_core/src/netty/mod.rs b/cosmos_core/src/netty/mod.rs index c5d84ad27..a8296fdde 100644 --- a/cosmos_core/src/netty/mod.rs +++ b/cosmos_core/src/netty/mod.rs @@ -33,7 +33,7 @@ pub enum NettyChannelServer { /// Used for asteroids Asteroid, /// Sending LOD information to the client - Lod, + DeltaLod, } /// Network channels that clients send to the server @@ -82,7 +82,7 @@ impl From for u8 { NettyChannelServer::Unreliable => 1, NettyChannelServer::LaserCannonSystem => 2, NettyChannelServer::Asteroid => 3, - NettyChannelServer::Lod => 4, + NettyChannelServer::DeltaLod => 4, } } } @@ -116,7 +116,7 @@ impl NettyChannelServer { }, }, ChannelConfig { - channel_id: Self::Lod.into(), + channel_id: Self::DeltaLod.into(), max_memory_usage_bytes: 5 * 1024 * 1024, send_type: SendType::ReliableOrdered { resend_time: Duration::from_millis(200), diff --git a/cosmos_core/src/structure/lod.rs b/cosmos_core/src/structure/lod.rs index 2d518993f..c848e83d8 100644 --- a/cosmos_core/src/structure/lod.rs +++ b/cosmos_core/src/structure/lod.rs @@ -1,13 +1,42 @@ //! Ways of displaying reduced-detail versions of dynamic structures +use std::backtrace::Backtrace; + use bevy::prelude::{Component, Entity}; use serde::{Deserialize, Serialize}; use super::lod_chunk::LodChunk; -#[derive(Serialize, Deserialize, Component, Debug)] +#[derive(Serialize, Deserialize, Component, Debug, Clone)] /// Represents a reduced-detail version of a planet pub enum Lod { + /// No Lod here - this means there should be an actual chunk here + None, + /// Represents a single chunk of blocks at any scale. + Single(Box, bool), + /// Breaks a single cube into 8 sub-cubes. + /// + /// The indicies of each cube follow a clockwise direction starting on the bottom-left-back + /// + /// ``` + /// +-----------+ + /// / 5 6 /| + /// / 4 7 / | + /// +-----------+ | + /// | | | + /// | | + + /// | 1 2 | / + /// | 0 3 |/ + /// +-----------+ + /// ``` + Children(Box<[Self; 8]>), +} + +#[derive(Serialize, Deserialize, Component, Debug, Clone)] +/// Represents a change in the reduced-detail version of a planet +pub enum LodDelta { + /// Keep the current version of the lod + NoChange, /// No Lod here - this means there should be an actual chunk here None, /// Represents a single chunk of blocks at any scale. @@ -30,12 +59,82 @@ pub enum Lod { Children(Box<[Self; 8]>), } +impl LodDelta { + pub fn create_lod(self) -> Lod { + match self { + LodDelta::Children(children) => { + let [c0, c1, c2, c3, c4, c5, c6, c7] = *children; + + Lod::Children(Box::new([ + c0.create_lod(), + c1.create_lod(), + c2.create_lod(), + c3.create_lod(), + c4.create_lod(), + c5.create_lod(), + c6.create_lod(), + c7.create_lod(), + ])) + } + LodDelta::None => Lod::None, + LodDelta::Single(chunk) => Lod::Single(chunk, true), + LodDelta::NoChange => { + // or forcibly capture the backtrace regardless of environment variable configuration + println!("Error backtrace: {}", Backtrace::force_capture()); + + // panic!("Cannot have no change with no lod given!"); + Lod::None + } + } + } + + pub fn apply_changes(self, lod: &mut Lod) { + match self { + LodDelta::Children(children) => { + let [c0, c1, c2, c3, c4, c5, c6, c7] = *children; + + match lod { + Lod::Children(lod_children) => { + c0.apply_changes(&mut lod_children[0]); + c1.apply_changes(&mut lod_children[1]); + c2.apply_changes(&mut lod_children[2]); + c3.apply_changes(&mut lod_children[3]); + c4.apply_changes(&mut lod_children[4]); + c5.apply_changes(&mut lod_children[5]); + c6.apply_changes(&mut lod_children[6]); + c7.apply_changes(&mut lod_children[7]); + } + _ => { + *lod = Lod::Children(Box::new([ + c0.create_lod(), + c1.create_lod(), + c2.create_lod(), + c3.create_lod(), + c4.create_lod(), + c5.create_lod(), + c6.create_lod(), + c7.create_lod(), + ])); + } + } + } + LodDelta::None => { + *lod = Lod::None; + } + LodDelta::Single(chunk) => { + *lod = Lod::Single(chunk, true); + } + LodDelta::NoChange => {} + } + } +} + #[derive(Debug, Serialize, Deserialize)] /// Sends an Lod to the client pub struct SetLodMessage { /// The structure this lod belongs to pub structure: Entity, - /// The lod serialized + /// The LodDelta serialized pub serialized_lod: Vec, } diff --git a/cosmos_core/src/structure/lod_chunk.rs b/cosmos_core/src/structure/lod_chunk.rs index 3a4ba226c..cb67347ba 100644 --- a/cosmos_core/src/structure/lod_chunk.rs +++ b/cosmos_core/src/structure/lod_chunk.rs @@ -1,5 +1,7 @@ //! Used to store the various blocks an Lod would be made of +use std::fmt::Debug; + use bevy::reflect::Reflect; use serde::{Deserialize, Serialize}; @@ -14,12 +16,18 @@ use super::{ coordinates::ChunkBlockCoordinate, }; -#[derive(Debug, Reflect, Serialize, Deserialize, Clone)] +#[derive(Reflect, Serialize, Deserialize, Clone)] /// A chunk that is scaled. The Lod's scale depends on the position in the octree and size of its structure. /// /// Lods only function properly on structures whos sizes are powers of two. pub struct LodChunk(BlockStorage); +impl Debug for LodChunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("LodChunk (empty: {})", self.0.is_empty())) + } +} + impl LodChunk { /// Creates a new Lod chunk pub fn new() -> Self { diff --git a/cosmos_server/src/structure/planet/lods/generate_lods.rs b/cosmos_server/src/structure/planet/lods/generate_lods.rs index a1faae775..69604a5bf 100644 --- a/cosmos_server/src/structure/planet/lods/generate_lods.rs +++ b/cosmos_server/src/structure/planet/lods/generate_lods.rs @@ -15,7 +15,7 @@ use cosmos_core::{ structure::{ chunk::CHUNK_DIMENSIONS, coordinates::{BlockCoordinate, ChunkCoordinate, CoordinateType, UnboundChunkCoordinate, UnboundCoordinateType}, - lod::Lod, + lod::{Lod, LodDelta}, lod_chunk::LodChunk, planet::Planet, Structure, @@ -52,6 +52,8 @@ pub enum GeneratingLod { BeingGenerated, /// Represents a single chunk of blocks at any scale. DoneGenerating(Box), + /// Represents no change required + Same, /// Breaks a single cube into 8 sub-cubes. /// /// The indicies of each cube follow a clockwise direction starting on the bottom-left-back @@ -97,86 +99,93 @@ pub struct GenerateLodRequest { fn check_done(generating_lod: &GeneratingLod) -> bool { match generating_lod { GeneratingLod::Children(children) => children.iter().all(check_done), - GeneratingLod::None | GeneratingLod::DoneGenerating(_) => true, + GeneratingLod::None | GeneratingLod::DoneGenerating(_) | GeneratingLod::Same => true, _ => false, } } -fn recursively_create_lod(generated_lod: GeneratingLod) -> Lod { +fn recursively_create_lod_delta(generated_lod: GeneratingLod) -> LodDelta { match generated_lod { + GeneratingLod::Same => LodDelta::NoChange, GeneratingLod::Children(children) => { let [c0, c1, c2, c3, c4, c5, c6, c7] = *children; - Lod::Children(Box::new([ - recursively_create_lod(c0), - recursively_create_lod(c1), - recursively_create_lod(c2), - recursively_create_lod(c3), - recursively_create_lod(c4), - recursively_create_lod(c5), - recursively_create_lod(c6), - recursively_create_lod(c7), + LodDelta::Children(Box::new([ + recursively_create_lod_delta(c0), + recursively_create_lod_delta(c1), + recursively_create_lod_delta(c2), + recursively_create_lod_delta(c3), + recursively_create_lod_delta(c4), + recursively_create_lod_delta(c5), + recursively_create_lod_delta(c6), + recursively_create_lod_delta(c7), ])) } - GeneratingLod::DoneGenerating(lod_chunk) => Lod::Single(lod_chunk), - GeneratingLod::None => Lod::None, + GeneratingLod::DoneGenerating(lod_chunk) => LodDelta::Single(lod_chunk), + GeneratingLod::None => LodDelta::None, _ => { warn!("Invalid lod state: {generated_lod:?}"); - Lod::None + LodDelta::None } } } -fn check_done_generating(mut commands: Commands, query: Query<(Entity, &PlayerGeneratingLod)>) { +fn check_done_generating( + mut commands: Commands, + children_query: Query<&Children>, + mut lod_query: Query<(Entity, &mut PlayerLod)>, + query: Query<(Entity, &PlayerGeneratingLod)>, +) { for (entity, player_generating_lod) in query.iter() { if check_done(&player_generating_lod.generating_lod) { commands.entity(entity).despawn_recursive(); - let actual_lod = recursively_create_lod(player_generating_lod.generating_lod.clone()); - - commands.entity(player_generating_lod.structure_entity).with_children(|cmds| { - cmds.spawn(PlayerLod { - lod: actual_lod, - player: player_generating_lod.player_entity, + let current_lod = children_query + .get(player_generating_lod.structure_entity) + .map(|children| { + children + .iter() + .flat_map(|&child_entity| lod_query.get(child_entity)) + .find(|&(_, player_lod)| player_lod.player == player_generating_lod.player_entity) + .map(|(entity, _)| entity) + }) + .unwrap_or(None) + .map(|e| lod_query.get_mut(e).map(|(_, player_lod)| player_lod)); + + let lod_delta = recursively_create_lod_delta(player_generating_lod.generating_lod.clone()); + + let cloned_delta = lod_delta.clone(); + + if let Some(Ok(mut current_lod)) = current_lod { + println!("{lod_delta:?}"); + println!("=========================="); + println!("{current_lod:?}"); + cloned_delta.apply_changes(&mut current_lod.lod); + current_lod.deltas.push(lod_delta); + } else { + commands.get_entity(player_generating_lod.structure_entity).map(|mut ecmds| { + ecmds.with_children(|cmds| { + cmds.spawn(PlayerLod { + lod: cloned_delta.create_lod(), + deltas: vec![lod_delta], + player: player_generating_lod.player_entity, + }); + }); }); - }); + } } } } -fn fill_done_lod(lod: &Lod) -> GeneratingLod { - match lod { - Lod::None => GeneratingLod::None, - Lod::Single(single) => GeneratingLod::DoneGenerating(single.clone()), - Lod::Children(children) => GeneratingLod::Children(Box::new([ - fill_done_lod(&children[0]), - fill_done_lod(&children[1]), - fill_done_lod(&children[2]), - fill_done_lod(&children[3]), - fill_done_lod(&children[4]), - fill_done_lod(&children[5]), - fill_done_lod(&children[6]), - fill_done_lod(&children[7]), - ])), - } -} - fn create_generating_lod( structure_entity: Entity, blocks: &Registry, event_writer: &mut EventWriter, request: &LodRequest, (min_block_range_inclusive, max_block_range_exclusive): (BlockCoordinate, BlockCoordinate), - current_lod: Option<&Lod>, ) -> GeneratingLod { match request { - LodRequest::Same => { - let Some(current_lod) = current_lod else { - panic!("Invalid current lod state - cannot be none!"); - }; - - fill_done_lod(current_lod) - } + LodRequest::Same => GeneratingLod::Same, LodRequest::None => GeneratingLod::None, LodRequest::Single => { debug_assert!( @@ -205,20 +214,6 @@ fn create_generating_lod( let min = min_block_range_inclusive; let max = max_block_range_exclusive; - let cur_lod_children = match current_lod { - Some(Lod::Children(children)) => [ - Some(&children[0]), - Some(&children[1]), - Some(&children[2]), - Some(&children[3]), - Some(&children[4]), - Some(&children[5]), - Some(&children[6]), - Some(&children[7]), - ], - _ => [None, None, None, None, None, None, None, None], - }; - GeneratingLod::Children(Box::new([ create_generating_lod( structure_entity, @@ -226,7 +221,6 @@ fn create_generating_lod( event_writer, &child_requests[0], ((min.x, min.y, min.z).into(), (max.x - dx, max.y - dy, max.z - dz).into()), - cur_lod_children[0], ), create_generating_lod( structure_entity, @@ -234,7 +228,6 @@ fn create_generating_lod( event_writer, &child_requests[1], ((min.x, min.y, min.z + dz).into(), (max.x - dx, max.y - dy, max.z).into()), - cur_lod_children[1], ), create_generating_lod( structure_entity, @@ -242,7 +235,6 @@ fn create_generating_lod( event_writer, &child_requests[2], ((min.x + dx, min.y, min.z + dz).into(), (max.x, max.y - dy, max.z).into()), - cur_lod_children[2], ), create_generating_lod( structure_entity, @@ -250,7 +242,6 @@ fn create_generating_lod( event_writer, &child_requests[3], ((min.x + dx, min.y, min.z).into(), (max.x, max.y - dy, max.z - dz).into()), - cur_lod_children[3], ), create_generating_lod( structure_entity, @@ -258,7 +249,6 @@ fn create_generating_lod( event_writer, &child_requests[4], ((min.x, min.y + dy, min.z).into(), (max.x - dx, max.y, max.z - dz).into()), - cur_lod_children[4], ), create_generating_lod( structure_entity, @@ -266,7 +256,6 @@ fn create_generating_lod( event_writer, &child_requests[5], ((min.x, min.y + dy, min.z + dz).into(), (max.x - dx, max.y, max.z).into()), - cur_lod_children[5], ), create_generating_lod( structure_entity, @@ -274,7 +263,6 @@ fn create_generating_lod( event_writer, &child_requests[6], ((min.x + dx, min.y + dy, min.z + dz).into(), (max.x, max.y, max.z).into()), - cur_lod_children[6], ), create_generating_lod( structure_entity, @@ -282,7 +270,6 @@ fn create_generating_lod( event_writer, &child_requests[7], ((min.x + dx, min.y + dy, min.z).into(), (max.x, max.y, max.z - dz).into()), - cur_lod_children[7], ), ])) } @@ -293,28 +280,20 @@ fn poll_generating( mut commands: Commands, blocks: Res>, mut event_writer: EventWriter, - structure_query: Query<(&Children, &Structure)>, + structure_query: Query<&Structure>, query: Query<(Entity, &LodGenerationRequest)>, - player_lod: Query<&PlayerLod>, ) { for (entity, lod_request) in query.iter() { - let Ok((structure_children, structure)) = structure_query.get(lod_request.structure_entity) else { + let Ok(structure) = structure_query.get(lod_request.structure_entity) else { continue; }; - let current_lod = structure_children - .iter() - .flat_map(|&child_entity| player_lod.get(child_entity)) - .find(|p_lod| p_lod.player == lod_request.player_entity) - .map(|p_lod| &p_lod.lod); - let generating_lod = create_generating_lod( lod_request.structure_entity, &blocks, &mut event_writer, &lod_request.request, (BlockCoordinate::new(0, 0, 0), structure.block_dimensions()), - current_lod, ); commands.spawn(PlayerGeneratingLod { @@ -347,7 +326,7 @@ fn create_lod_request( if !first && (rel_coords.x.abs() >= max_dist || rel_coords.y.abs() >= max_dist || rel_coords.z.abs() >= max_dist) { match current_lod { - Some(Lod::Single(_)) => LodRequest::Same, + Some(Lod::Single(_, _)) => LodRequest::Same, _ => LodRequest::Single, } } else { diff --git a/cosmos_server/src/structure/planet/lods/player_lod.rs b/cosmos_server/src/structure/planet/lods/player_lod.rs index ae00f65f1..716b48b22 100644 --- a/cosmos_server/src/structure/planet/lods/player_lod.rs +++ b/cosmos_server/src/structure/planet/lods/player_lod.rs @@ -1,7 +1,7 @@ //! This stores LODs and the players they correspond to use bevy::prelude::{Component, Entity}; -use cosmos_core::structure::lod::Lod; +use cosmos_core::structure::lod::{Lod, LodDelta}; #[derive(Debug, Component)] /// Stores LODs and the players they correspond to @@ -9,5 +9,6 @@ use cosmos_core::structure::lod::Lod; /// The PlayerLod's parent should always be the structure it is an lod of pub struct PlayerLod { pub lod: Lod, + pub deltas: Vec, pub player: Entity, } diff --git a/cosmos_server/src/structure/planet/lods/send_lods.rs b/cosmos_server/src/structure/planet/lods/send_lods.rs index 73492900e..9b9a04823 100644 --- a/cosmos_server/src/structure/planet/lods/send_lods.rs +++ b/cosmos_server/src/structure/planet/lods/send_lods.rs @@ -10,17 +10,22 @@ use crate::state::GameState; use super::player_lod::PlayerLod; -fn send_lods(mut server: ResMut, changed_lods: Query<(&Parent, &PlayerLod), Changed>, players: Query<&Player>) { - for (parent, player_lod) in changed_lods.iter() { +fn send_lods(mut server: ResMut, mut changed_lods: Query<(&Parent, &mut PlayerLod)>, players: Query<&Player>) { + for (parent, mut player_lod) in changed_lods.iter_mut() { + if player_lod.deltas.is_empty() { + continue; + } + let Ok(player) = players.get(player_lod.player) else { continue; }; + let delta = player_lod.deltas.remove(0); server.send_message( player.id(), - NettyChannelServer::Lod, + NettyChannelServer::DeltaLod, cosmos_encoder::serialize(&LodNetworkMessage::SetLod(SetLodMessage { - serialized_lod: cosmos_encoder::serialize(&player_lod.lod), + serialized_lod: cosmos_encoder::serialize(&delta), structure: parent.get(), })), );