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#[derive(Component, Debug)]
37pub struct ChunkLayer {
38 messages: ChunkLayerMessages,
39 chunks: FxHashMap<ChunkPos, LoadedChunk>,
40 info: ChunkLayerInfo,
41}
42
43pub(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 .finish()
62 }
63}
64
65type ChunkLayerMessages = Messages<GlobalMsg, LocalMsg>;
66
67#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
68pub(crate) enum GlobalMsg {
69 Packet,
71 PacketExcept { except: Entity },
74}
75
76#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
77pub(crate) enum LocalMsg {
78 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 ChangeChunkState {
101 pos: ChunkPos,
102 },
103 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 #[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 pub fn dimension_type(&self) -> &DimensionTypeId {
161 &self.info.dimension_type
162 }
163
164 pub fn height(&self) -> u32 {
166 self.info.height
167 }
168
169 pub fn min_y(&self) -> i32 {
171 self.info.min_y
172 }
173
174 pub fn chunk<P: Into<ChunkPos>>(&self, pos: P) -> Option<&LoadedChunk> {
176 self.chunks.get(&pos.into())
177 }
178
179 pub fn chunk_mut<P: Into<ChunkPos>>(&mut self, pos: P) -> Option<&mut LoadedChunk> {
182 self.chunks.get_mut(&pos.into())
183 }
184
185 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 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 pub fn clear_chunks(&mut self) {
212 self.retain_chunks(|_, _| false)
213 }
214
215 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 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 pub fn chunks(&self) -> impl Iterator<Item = (ChunkPos, &LoadedChunk)> + Clone + '_ {
252 self.chunks.iter().map(|(pos, chunk)| (*pos, chunk))
253 }
254
255 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 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 #[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 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}