chunkedge_server/layer/
chunk.rs

1#[allow(clippy::module_inception)]
2mod chunk;
3pub mod loaded;
4mod paletted_container;
5pub mod unloaded;
6
7use std::collections::hash_map::{Entry, OccupiedEntry, VacantEntry};
8use std::fmt;
9
10use bevy_app::prelude::*;
11use bevy_ecs::prelude::*;
12pub use chunk::{MAX_HEIGHT, *};
13use chunkedge_binary::Encode;
14use chunkedge_math::{DVec3, Vec3};
15use chunkedge_nbt::Compound;
16use chunkedge_protocol::encode::{PacketWriter, WritePacket};
17use chunkedge_protocol::packets::play::level_particles_s2c::Particle;
18use chunkedge_protocol::packets::play::{LevelParticlesS2c, SoundS2c};
19use chunkedge_protocol::sound::{Sound, SoundCategory, SoundDirect, SoundId};
20use chunkedge_protocol::{BiomePos, BlockPos, ChunkPos, CompressionThreshold, Ident, Packet};
21use chunkedge_registry::biome::{BiomeId, BiomeRegistry};
22use chunkedge_registry::dimension_type::DimensionTypeId;
23use chunkedge_registry::DimensionTypeRegistry;
24use chunkedge_server_common::Server;
25pub use loaded::LoadedChunk;
26use rustc_hash::FxHashMap;
27pub use unloaded::UnloadedChunk;
28
29use super::bvh::GetChunkPos;
30use super::message::Messages;
31use super::{Layer, UpdateLayersPostClientSet, UpdateLayersPreClientSet};
32
33/// A [`Component`] containing the [chunks](LoadedChunk) and [dimension
34/// information](chunkedge_registry::dimension_type::DimensionTypeId) of a
35/// Minecraft world.
36#[derive(Component, Debug)]
37pub struct ChunkLayer {
38    messages: ChunkLayerMessages,
39    chunks: FxHashMap<ChunkPos, LoadedChunk>,
40    info: ChunkLayerInfo,
41}
42
43/// Chunk layer information.
44pub(crate) struct ChunkLayerInfo {
45    dimension_type: DimensionTypeId,
46    height: u32,
47    min_y: i32,
48    biome_registry_len: usize,
49    threshold: CompressionThreshold,
50}
51
52impl fmt::Debug for ChunkLayerInfo {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.debug_struct("ChunkLayerInfo")
55            .field("dimension_type", &self.dimension_type)
56            .field("height", &self.height)
57            .field("min_y", &self.min_y)
58            .field("biome_registry_len", &self.biome_registry_len)
59            .field("threshold", &self.threshold)
60            // Ignore sky light mask and array.
61            .finish()
62    }
63}
64
65type ChunkLayerMessages = Messages<GlobalMsg, LocalMsg>;
66
67#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
68pub(crate) enum GlobalMsg {
69    /// Send packet data to all clients viewing the layer.
70    Packet,
71    /// Send packet data to all clients viewing the layer, except the client
72    /// identified by `except`.
73    PacketExcept { except: Entity },
74}
75
76#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
77pub(crate) enum LocalMsg {
78    /// Send packet data to all clients viewing the layer in view of `pos`.
79    PacketAt {
80        pos: ChunkPos,
81    },
82    PacketAtExcept {
83        pos: ChunkPos,
84        except: Entity,
85    },
86    RadiusAt {
87        center: BlockPos,
88        radius_squared: u32,
89    },
90    RadiusAtExcept {
91        center: BlockPos,
92        radius_squared: u32,
93        except: Entity,
94    },
95    /// Instruct clients to load or unload the chunk at `pos`. Loading and
96    /// unloading are combined into a single message so that load/unload order
97    /// is not lost when messages are sorted.
98    ///
99    /// Message content is a single byte indicating load (1) or unload (0).
100    ChangeChunkState {
101        pos: ChunkPos,
102    },
103    /// Message content is the data for a single biome in the "change biomes"
104    /// packet.
105    ChangeBiome {
106        pos: ChunkPos,
107    },
108}
109
110impl GetChunkPos for LocalMsg {
111    fn chunk_pos(&self) -> ChunkPos {
112        match *self {
113            LocalMsg::PacketAt { pos } => pos,
114            LocalMsg::PacketAtExcept { pos, .. } => pos,
115            LocalMsg::RadiusAt { center, .. } => center.into(),
116            LocalMsg::RadiusAtExcept { center, .. } => center.into(),
117            LocalMsg::ChangeBiome { pos } => pos,
118            LocalMsg::ChangeChunkState { pos } => pos,
119        }
120    }
121}
122
123impl ChunkLayer {
124    pub(crate) const LOAD: u8 = 0;
125    pub(crate) const UNLOAD: u8 = 1;
126    pub(crate) const OVERWRITE: u8 = 2;
127
128    /// Creates a new chunk layer.
129    #[track_caller]
130    pub fn new(
131        dimension_type: Ident<&str>,
132        dimensions: &DimensionTypeRegistry,
133        biomes: &BiomeRegistry,
134        server: &Server,
135    ) -> Self {
136        let dim = &dimensions
137            .get(dimension_type)
138            .expect("invalid dimension type");
139
140        assert!(
141            (0..MAX_HEIGHT as i32).contains(&dim.height),
142            "invalid dimension height of {}",
143            dim.height
144        );
145
146        Self {
147            messages: Messages::new(),
148            chunks: Default::default(),
149            info: ChunkLayerInfo {
150                dimension_type: dimensions.index_of(dimension_type).unwrap(),
151                height: dim.height as u32,
152                min_y: dim.min_y,
153                biome_registry_len: biomes.iter().len(),
154                threshold: server.compression_threshold(),
155            },
156        }
157    }
158
159    /// The name of the dimension this chunk layer is using.
160    pub fn dimension_type(&self) -> &DimensionTypeId {
161        &self.info.dimension_type
162    }
163
164    /// The height of this instance's dimension.
165    pub fn height(&self) -> u32 {
166        self.info.height
167    }
168
169    /// The `min_y` of this instance's dimension.
170    pub fn min_y(&self) -> i32 {
171        self.info.min_y
172    }
173
174    /// Get a reference to the chunk at the given position, if it is loaded.
175    pub fn chunk<P: Into<ChunkPos>>(&self, pos: P) -> Option<&LoadedChunk> {
176        self.chunks.get(&pos.into())
177    }
178
179    /// Get a mutable reference to the chunk at the given position, if it is
180    /// loaded.
181    pub fn chunk_mut<P: Into<ChunkPos>>(&mut self, pos: P) -> Option<&mut LoadedChunk> {
182        self.chunks.get_mut(&pos.into())
183    }
184
185    /// Insert a chunk into the instance at the given position. The previous
186    /// chunk data is returned.
187    pub fn insert_chunk<P: Into<ChunkPos>>(
188        &mut self,
189        pos: P,
190        chunk: UnloadedChunk,
191    ) -> Option<UnloadedChunk> {
192        match self.chunk_entry(pos) {
193            ChunkEntry::Occupied(mut oe) => Some(oe.insert(chunk)),
194            ChunkEntry::Vacant(ve) => {
195                ve.insert(chunk);
196                None
197            }
198        }
199    }
200
201    /// Unload the chunk at the given position, if it is loaded. Returns the
202    /// chunk if it was loaded.
203    pub fn remove_chunk<P: Into<ChunkPos>>(&mut self, pos: P) -> Option<UnloadedChunk> {
204        match self.chunk_entry(pos) {
205            ChunkEntry::Occupied(oe) => Some(oe.remove()),
206            ChunkEntry::Vacant(_) => None,
207        }
208    }
209
210    /// Unload all chunks in this instance.
211    pub fn clear_chunks(&mut self) {
212        self.retain_chunks(|_, _| false)
213    }
214
215    /// Retain only the chunks for which the given predicate returns `true`.
216    pub fn retain_chunks<F>(&mut self, mut f: F)
217    where
218        F: FnMut(ChunkPos, &mut LoadedChunk) -> bool,
219    {
220        self.chunks.retain(|pos, chunk| {
221            if !f(*pos, chunk) {
222                self.messages
223                    .send_local_infallible(LocalMsg::ChangeChunkState { pos: *pos }, |b| {
224                        b.push(Self::UNLOAD)
225                    });
226
227                false
228            } else {
229                true
230            }
231        });
232    }
233
234    /// Get a [`ChunkEntry`] for the given position.
235    pub fn chunk_entry<P: Into<ChunkPos>>(&mut self, pos: P) -> ChunkEntry<'_> {
236        match self.chunks.entry(pos.into()) {
237            Entry::Occupied(oe) => ChunkEntry::Occupied(OccupiedChunkEntry {
238                messages: &mut self.messages,
239                entry: oe,
240            }),
241            Entry::Vacant(ve) => ChunkEntry::Vacant(VacantChunkEntry {
242                height: self.info.height,
243                messages: &mut self.messages,
244                entry: ve,
245            }),
246        }
247    }
248
249    /// Get an iterator over all loaded chunks in the instance. The order of the
250    /// chunks is undefined.
251    pub fn chunks(&self) -> impl Iterator<Item = (ChunkPos, &LoadedChunk)> + Clone + '_ {
252        self.chunks.iter().map(|(pos, chunk)| (*pos, chunk))
253    }
254
255    /// Get an iterator over all loaded chunks in the instance, mutably. The
256    /// order of the chunks is undefined.
257    pub fn chunks_mut(&mut self) -> impl Iterator<Item = (ChunkPos, &mut LoadedChunk)> + '_ {
258        self.chunks.iter_mut().map(|(pos, chunk)| (*pos, chunk))
259    }
260
261    /// Optimizes the memory usage of the instance.
262    pub fn shrink_to_fit(&mut self) {
263        for (_, chunk) in self.chunks_mut() {
264            chunk.shrink_to_fit();
265        }
266
267        self.chunks.shrink_to_fit();
268        self.messages.shrink_to_fit();
269    }
270
271    pub fn block<P: Into<BlockPos>>(&self, pos: P) -> Option<BlockRef<'_>> {
272        let pos = pos.into();
273
274        let y = pos
275            .y
276            .checked_sub(self.info.min_y)
277            .and_then(|y| y.try_into().ok())?;
278
279        if y >= self.info.height {
280            return None;
281        }
282
283        let chunk = self.chunk(pos)?;
284
285        let x = pos.x.rem_euclid(16) as u32;
286        let z = pos.z.rem_euclid(16) as u32;
287
288        Some(chunk.block(x, y, z))
289    }
290
291    pub fn set_block<P, B>(&mut self, pos: P, block: B) -> Option<Block>
292    where
293        P: Into<BlockPos>,
294        B: IntoBlock,
295    {
296        let pos = pos.into();
297
298        let y = pos
299            .y
300            .checked_sub(self.info.min_y)
301            .and_then(|y| y.try_into().ok())?;
302
303        if y >= self.info.height {
304            return None;
305        }
306
307        let chunk = self.chunk_mut(pos)?;
308
309        let x = pos.x.rem_euclid(16) as u32;
310        let z = pos.z.rem_euclid(16) as u32;
311
312        Some(chunk.set_block(x, y, z, block))
313    }
314
315    pub fn block_entity_mut<P: Into<BlockPos>>(&mut self, pos: P) -> Option<&mut Compound> {
316        let pos = pos.into();
317
318        let y = pos
319            .y
320            .checked_sub(self.info.min_y)
321            .and_then(|y| y.try_into().ok())?;
322
323        if y >= self.info.height {
324            return None;
325        }
326
327        let chunk = self.chunk_mut(pos)?;
328
329        let x = pos.x.rem_euclid(16) as u32;
330        let z = pos.z.rem_euclid(16) as u32;
331
332        chunk.block_entity_mut(x, y, z)
333    }
334
335    pub fn biome<P: Into<BiomePos>>(&self, pos: P) -> Option<BiomeId> {
336        let pos = pos.into();
337
338        let y = pos
339            .y
340            .checked_sub(self.info.min_y / 4)
341            .and_then(|y| y.try_into().ok())?;
342
343        if y >= self.info.height / 4 {
344            return None;
345        }
346
347        let chunk = self.chunk(pos)?;
348
349        let x = pos.x.rem_euclid(4) as u32;
350        let z = pos.z.rem_euclid(4) as u32;
351
352        Some(chunk.biome(x, y, z))
353    }
354
355    pub fn set_biome<P: Into<BiomePos>>(&mut self, pos: P, biome: BiomeId) -> Option<BiomeId> {
356        let pos = pos.into();
357
358        let y = pos
359            .y
360            .checked_sub(self.info.min_y / 4)
361            .and_then(|y| y.try_into().ok())?;
362
363        if y >= self.info.height / 4 {
364            return None;
365        }
366
367        let chunk = self.chunk_mut(pos)?;
368
369        let x = pos.x.rem_euclid(4) as u32;
370        let z = pos.z.rem_euclid(4) as u32;
371
372        Some(chunk.set_biome(x, y, z, biome))
373    }
374
375    pub(crate) fn info(&self) -> &ChunkLayerInfo {
376        &self.info
377    }
378
379    pub(crate) fn messages(&self) -> &ChunkLayerMessages {
380        &self.messages
381    }
382
383    // TODO: move to `chunkedge_particle`.
384    /// Puts a particle effect at the given position in the world. The particle
385    /// effect is visible to all players in the instance with the
386    /// appropriate chunk in view.
387    #[allow(clippy::too_many_arguments)]
388    pub fn play_particle<P, O>(
389        &mut self,
390        particle: &Particle,
391        long_distance: bool,
392        always_visible: bool,
393        position: P,
394        offset: O,
395        max_speed: f32,
396        count: i32,
397    ) where
398        P: Into<DVec3>,
399        O: Into<Vec3>,
400    {
401        let position = position.into();
402
403        self.view_writer(position).write_packet(&LevelParticlesS2c {
404            particle: particle.clone(),
405            long_distance,
406            always_visible,
407            position,
408            offset: offset.into(),
409            max_speed,
410            count,
411        });
412    }
413
414    // TODO: move to `chunkedge_sound`.
415    /// Plays a sound effect at the given position in the world. The sound
416    /// effect is audible to all players in the instance with the
417    /// appropriate chunk in view.
418    pub fn play_sound<P: Into<DVec3>>(
419        &mut self,
420        sound: Sound,
421        category: SoundCategory,
422        position: P,
423        volume: f32,
424        pitch: f32,
425    ) {
426        let position = position.into();
427
428        self.view_writer(position).write_packet(&SoundS2c {
429            id: SoundId::Inline(SoundDirect {
430                id: sound.to_ident().into(),
431                range: None,
432            }),
433            category,
434            position: (position * 8.0).as_ivec3(),
435            volume,
436            pitch,
437            seed: rand::random(),
438        });
439    }
440}
441
442impl Layer for ChunkLayer {
443    type ExceptWriter<'a> = ExceptWriter<'a>;
444
445    type ViewWriter<'a> = ViewWriter<'a>;
446
447    type ViewExceptWriter<'a> = ViewExceptWriter<'a>;
448
449    type RadiusWriter<'a> = RadiusWriter<'a>;
450
451    type RadiusExceptWriter<'a> = RadiusExceptWriter<'a>;
452
453    fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_> {
454        ExceptWriter {
455            layer: self,
456            except,
457        }
458    }
459
460    fn view_writer(&mut self, pos: impl Into<ChunkPos>) -> Self::ViewWriter<'_> {
461        ViewWriter {
462            layer: self,
463            pos: pos.into(),
464        }
465    }
466
467    fn view_except_writer(
468        &mut self,
469        pos: impl Into<ChunkPos>,
470        except: Entity,
471    ) -> Self::ViewExceptWriter<'_> {
472        ViewExceptWriter {
473            layer: self,
474            pos: pos.into(),
475            except,
476        }
477    }
478
479    fn radius_writer(
480        &mut self,
481        center: impl Into<BlockPos>,
482        radius: u32,
483    ) -> Self::RadiusWriter<'_> {
484        RadiusWriter {
485            layer: self,
486            center: center.into(),
487            radius,
488        }
489    }
490
491    fn radius_except_writer(
492        &mut self,
493        center: impl Into<BlockPos>,
494        radius: u32,
495        except: Entity,
496    ) -> Self::RadiusExceptWriter<'_> {
497        RadiusExceptWriter {
498            layer: self,
499            center: center.into(),
500            radius,
501            except,
502        }
503    }
504}
505
506impl WritePacket for ChunkLayer {
507    fn write_packet_fallible<P>(&mut self, packet: &P) -> anyhow::Result<()>
508    where
509        P: Packet + Encode,
510    {
511        self.messages.send_global(GlobalMsg::Packet, |b| {
512            PacketWriter::new(b, self.info.threshold).write_packet_fallible(packet)
513        })
514    }
515
516    fn write_packet_bytes(&mut self, bytes: &[u8]) {
517        self.messages
518            .send_global_infallible(GlobalMsg::Packet, |b| b.extend_from_slice(bytes));
519    }
520}
521
522pub struct ExceptWriter<'a> {
523    layer: &'a mut ChunkLayer,
524    except: Entity,
525}
526
527impl WritePacket for ExceptWriter<'_> {
528    fn write_packet_fallible<P>(&mut self, packet: &P) -> anyhow::Result<()>
529    where
530        P: Packet + Encode,
531    {
532        self.layer.messages.send_global(
533            GlobalMsg::PacketExcept {
534                except: self.except,
535            },
536            |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet),
537        )
538    }
539
540    fn write_packet_bytes(&mut self, bytes: &[u8]) {
541        self.layer.messages.send_global_infallible(
542            GlobalMsg::PacketExcept {
543                except: self.except,
544            },
545            |b| b.extend_from_slice(bytes),
546        )
547    }
548}
549
550pub struct ViewWriter<'a> {
551    layer: &'a mut ChunkLayer,
552    pos: ChunkPos,
553}
554
555impl WritePacket for ViewWriter<'_> {
556    fn write_packet_fallible<P>(&mut self, packet: &P) -> anyhow::Result<()>
557    where
558        P: Packet + Encode,
559    {
560        self.layer
561            .messages
562            .send_local(LocalMsg::PacketAt { pos: self.pos }, |b| {
563                PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet)
564            })
565    }
566
567    fn write_packet_bytes(&mut self, bytes: &[u8]) {
568        self.layer
569            .messages
570            .send_local_infallible(LocalMsg::PacketAt { pos: self.pos }, |b| {
571                b.extend_from_slice(bytes)
572            });
573    }
574}
575
576pub struct ViewExceptWriter<'a> {
577    layer: &'a mut ChunkLayer,
578    pos: ChunkPos,
579    except: Entity,
580}
581
582impl WritePacket for ViewExceptWriter<'_> {
583    fn write_packet_fallible<P>(&mut self, packet: &P) -> anyhow::Result<()>
584    where
585        P: Packet + Encode,
586    {
587        self.layer.messages.send_local(
588            LocalMsg::PacketAtExcept {
589                pos: self.pos,
590                except: self.except,
591            },
592            |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet),
593        )
594    }
595
596    fn write_packet_bytes(&mut self, bytes: &[u8]) {
597        self.layer.messages.send_local_infallible(
598            LocalMsg::PacketAtExcept {
599                pos: self.pos,
600                except: self.except,
601            },
602            |b| b.extend_from_slice(bytes),
603        );
604    }
605}
606
607pub struct RadiusWriter<'a> {
608    layer: &'a mut ChunkLayer,
609    center: BlockPos,
610    radius: u32,
611}
612
613impl WritePacket for RadiusWriter<'_> {
614    fn write_packet_fallible<P>(&mut self, packet: &P) -> anyhow::Result<()>
615    where
616        P: Packet + Encode,
617    {
618        self.layer.messages.send_local(
619            LocalMsg::RadiusAt {
620                center: self.center,
621                radius_squared: self.radius,
622            },
623            |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet),
624        )
625    }
626
627    fn write_packet_bytes(&mut self, bytes: &[u8]) {
628        self.layer.messages.send_local_infallible(
629            LocalMsg::RadiusAt {
630                center: self.center,
631                radius_squared: self.radius,
632            },
633            |b| b.extend_from_slice(bytes),
634        );
635    }
636}
637
638pub struct RadiusExceptWriter<'a> {
639    layer: &'a mut ChunkLayer,
640    center: BlockPos,
641    radius: u32,
642    except: Entity,
643}
644
645impl WritePacket for RadiusExceptWriter<'_> {
646    fn write_packet_fallible<P>(&mut self, packet: &P) -> anyhow::Result<()>
647    where
648        P: Packet + Encode,
649    {
650        self.layer.messages.send_local(
651            LocalMsg::RadiusAtExcept {
652                center: self.center,
653                radius_squared: self.radius,
654                except: self.except,
655            },
656            |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet),
657        )
658    }
659
660    fn write_packet_bytes(&mut self, bytes: &[u8]) {
661        self.layer.messages.send_local_infallible(
662            LocalMsg::RadiusAtExcept {
663                center: self.center,
664                radius_squared: self.radius,
665                except: self.except,
666            },
667            |b| b.extend_from_slice(bytes),
668        );
669    }
670}
671
672#[derive(Debug)]
673pub enum ChunkEntry<'a> {
674    Occupied(OccupiedChunkEntry<'a>),
675    Vacant(VacantChunkEntry<'a>),
676}
677
678impl<'a> ChunkEntry<'a> {
679    pub fn or_default(self) -> &'a mut LoadedChunk {
680        match self {
681            ChunkEntry::Occupied(oe) => oe.into_mut(),
682            ChunkEntry::Vacant(ve) => ve.insert(UnloadedChunk::new()),
683        }
684    }
685}
686
687#[derive(Debug)]
688pub struct OccupiedChunkEntry<'a> {
689    messages: &'a mut ChunkLayerMessages,
690    entry: OccupiedEntry<'a, ChunkPos, LoadedChunk>,
691}
692
693impl<'a> OccupiedChunkEntry<'a> {
694    pub fn get(&self) -> &LoadedChunk {
695        self.entry.get()
696    }
697
698    pub fn get_mut(&mut self) -> &mut LoadedChunk {
699        self.entry.get_mut()
700    }
701
702    pub fn insert(&mut self, chunk: UnloadedChunk) -> UnloadedChunk {
703        self.messages.send_local_infallible(
704            LocalMsg::ChangeChunkState {
705                pos: *self.entry.key(),
706            },
707            |b| b.push(ChunkLayer::OVERWRITE),
708        );
709
710        self.entry.get_mut().insert(chunk)
711    }
712
713    pub fn into_mut(self) -> &'a mut LoadedChunk {
714        self.entry.into_mut()
715    }
716
717    pub fn key(&self) -> &ChunkPos {
718        self.entry.key()
719    }
720
721    pub fn remove(self) -> UnloadedChunk {
722        self.messages.send_local_infallible(
723            LocalMsg::ChangeChunkState {
724                pos: *self.entry.key(),
725            },
726            |b| b.push(ChunkLayer::UNLOAD),
727        );
728
729        self.entry.remove().remove()
730    }
731
732    pub fn remove_entry(mut self) -> (ChunkPos, UnloadedChunk) {
733        let pos = *self.entry.key();
734        let chunk = self.entry.get_mut().remove();
735
736        self.messages.send_local_infallible(
737            LocalMsg::ChangeChunkState {
738                pos: *self.entry.key(),
739            },
740            |b| b.push(ChunkLayer::UNLOAD),
741        );
742
743        (pos, chunk)
744    }
745}
746
747#[derive(Debug)]
748pub struct VacantChunkEntry<'a> {
749    height: u32,
750    messages: &'a mut ChunkLayerMessages,
751    entry: VacantEntry<'a, ChunkPos, LoadedChunk>,
752}
753
754impl<'a> VacantChunkEntry<'a> {
755    pub fn insert(self, chunk: UnloadedChunk) -> &'a mut LoadedChunk {
756        let mut loaded = LoadedChunk::new(self.height);
757        loaded.insert(chunk);
758
759        self.messages.send_local_infallible(
760            LocalMsg::ChangeChunkState {
761                pos: *self.entry.key(),
762            },
763            |b| b.push(ChunkLayer::LOAD),
764        );
765
766        self.entry.insert(loaded)
767    }
768
769    pub fn into_key(self) -> ChunkPos {
770        *self.entry.key()
771    }
772
773    pub fn key(&self) -> &ChunkPos {
774        self.entry.key()
775    }
776}
777
778pub(super) fn build(app: &mut App) {
779    app.add_systems(
780        PostUpdate,
781        (
782            update_chunk_layers_pre_client.in_set(UpdateLayersPreClientSet),
783            update_chunk_layers_post_client.in_set(UpdateLayersPostClientSet),
784        ),
785    );
786}
787
788fn update_chunk_layers_pre_client(mut layers: Query<&mut ChunkLayer>) {
789    for layer in &mut layers {
790        let layer = layer.into_inner();
791
792        for (&pos, chunk) in &mut layer.chunks {
793            chunk.update_pre_client(pos, &layer.info, &mut layer.messages);
794        }
795
796        layer.messages.ready();
797    }
798}
799
800fn update_chunk_layers_post_client(mut layers: Query<&mut ChunkLayer>) {
801    for mut layer in &mut layers {
802        layer.messages.unready();
803    }
804}