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