1use std::borrow::Cow;
2use std::collections::{BTreeMap, BTreeSet};
3use std::mem;
4use std::sync::atomic::{AtomicU32, Ordering};
5
6use parking_lot::Mutex; use valence_generated::block::{PropName, PropValue};
8use valence_nbt::{compound, Compound, Value};
9use valence_protocol::encode::{PacketWriter, WritePacket};
10use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity;
11use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry;
12use valence_protocol::packets::play::{
13 BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c,
14};
15use valence_protocol::{BlockPos, BlockState, ChunkPos, ChunkSectionPos, Encode};
16use valence_registry::biome::BiomeId;
17use valence_registry::RegistryIdx;
18
19use super::chunk::{
20 bit_width, check_biome_oob, check_block_oob, check_section_oob, BiomeContainer,
21 BlockStateContainer, Chunk, SECTION_BLOCK_COUNT,
22};
23use super::paletted_container::PalettedContainer;
24use super::unloaded::{self, UnloadedChunk};
25use super::{ChunkLayerInfo, ChunkLayerMessages, LocalMsg};
26
27#[derive(Debug)]
28pub struct LoadedChunk {
29 viewer_count: AtomicU32,
33 sections: Box<[Section]>,
35 block_entities: BTreeMap<u32, Compound>,
37 changed_block_entities: BTreeSet<u32>,
39 changed_biomes: bool,
41 cached_init_packets: Mutex<Vec<u8>>,
45}
46
47#[derive(Clone, Default, Debug)]
48struct Section {
49 block_states: BlockStateContainer,
50 biomes: BiomeContainer,
51 updates: Vec<ChunkDeltaUpdateEntry>,
54}
55
56impl Section {
57 fn count_non_air_blocks(&self) -> u16 {
58 let mut count = 0;
59
60 match &self.block_states {
61 PalettedContainer::Single(s) => {
62 if !s.is_air() {
63 count += SECTION_BLOCK_COUNT as u16;
64 }
65 }
66 PalettedContainer::Indirect(ind) => {
67 for i in 0..SECTION_BLOCK_COUNT {
68 if !ind.get(i).is_air() {
69 count += 1;
70 }
71 }
72 }
73 PalettedContainer::Direct(dir) => {
74 for s in dir.as_ref() {
75 if !s.is_air() {
76 count += 1;
77 }
78 }
79 }
80 }
81 count
82 }
83}
84
85impl LoadedChunk {
86 pub(crate) fn new(height: u32) -> Self {
87 Self {
88 viewer_count: AtomicU32::new(0),
89 sections: vec![Section::default(); height as usize / 16].into(),
90 block_entities: BTreeMap::new(),
91 changed_block_entities: BTreeSet::new(),
92 changed_biomes: false,
93 cached_init_packets: Mutex::new(vec![]),
94 }
95 }
96
97 pub(crate) fn insert(&mut self, mut chunk: UnloadedChunk) -> UnloadedChunk {
105 chunk.set_height(self.height());
106
107 let old_sections = self
108 .sections
109 .iter_mut()
110 .zip(chunk.sections)
111 .map(|(sect, other_sect)| {
112 sect.updates.clear();
113
114 unloaded::Section {
115 block_states: mem::replace(&mut sect.block_states, other_sect.block_states),
116 biomes: mem::replace(&mut sect.biomes, other_sect.biomes),
117 }
118 })
119 .collect();
120 let old_block_entities = mem::replace(&mut self.block_entities, chunk.block_entities);
121 self.changed_block_entities.clear();
122 self.changed_biomes = false;
123 self.cached_init_packets.get_mut().clear();
124 self.assert_no_changes();
125
126 UnloadedChunk {
127 sections: old_sections,
128 block_entities: old_block_entities,
129 }
130 }
131
132 pub(crate) fn remove(&mut self) -> UnloadedChunk {
133 let old_sections = self
134 .sections
135 .iter_mut()
136 .map(|sect| {
137 sect.updates.clear();
138
139 unloaded::Section {
140 block_states: mem::take(&mut sect.block_states),
141 biomes: mem::take(&mut sect.biomes),
142 }
143 })
144 .collect();
145 let old_block_entities = mem::take(&mut self.block_entities);
146 self.changed_block_entities.clear();
147 self.changed_biomes = false;
148 self.cached_init_packets.get_mut().clear();
149
150 self.assert_no_changes();
151
152 UnloadedChunk {
153 sections: old_sections,
154 block_entities: old_block_entities,
155 }
156 }
157
158 pub fn viewer_count(&self) -> u32 {
160 self.viewer_count.load(Ordering::Relaxed)
161 }
162
163 pub fn viewer_count_mut(&mut self) -> u32 {
165 *self.viewer_count.get_mut()
166 }
167
168 pub(crate) fn inc_viewer_count(&self) {
170 self.viewer_count.fetch_add(1, Ordering::Relaxed);
171 }
172
173 #[track_caller]
175 pub(crate) fn dec_viewer_count(&self) {
176 let old = self.viewer_count.fetch_sub(1, Ordering::Relaxed);
177 debug_assert_ne!(old, 0, "viewer count underflow!");
178 }
179
180 pub(crate) fn update_pre_client(
184 &mut self,
185 pos: ChunkPos,
186 info: &ChunkLayerInfo,
187 messages: &mut ChunkLayerMessages,
188 ) {
189 if *self.viewer_count.get_mut() == 0 {
190 self.assert_no_changes();
193
194 return;
195 }
196
197 for (sect_y, sect) in self.sections.iter_mut().enumerate() {
199 match sect.updates.as_slice() {
200 &[] => {}
201 &[entry] => {
202 let global_x = pos.x * 16 + i32::from(entry.off_x());
203 let global_y = info.min_y + sect_y as i32 * 16 + i32::from(entry.off_y());
204 let global_z = pos.z * 16 + i32::from(entry.off_z());
205
206 messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| {
207 let mut writer = PacketWriter::new(buf, info.threshold);
208
209 writer.write_packet(&BlockUpdateS2c {
210 position: BlockPos::new(global_x, global_y, global_z),
211 block_id: BlockState::from_raw(entry.block_state() as u16).unwrap(),
212 });
213 });
214 }
215 entries => {
216 let chunk_sect_pos = ChunkSectionPos {
217 x: pos.x,
218 y: sect_y as i32 + info.min_y.div_euclid(16),
219 z: pos.z,
220 };
221
222 messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| {
223 let mut writer = PacketWriter::new(buf, info.threshold);
224
225 writer.write_packet(&ChunkDeltaUpdateS2c {
226 chunk_sect_pos,
227 blocks: Cow::Borrowed(entries),
228 });
229 });
230 }
231 }
232
233 sect.updates.clear();
234 }
235
236 for &idx in &self.changed_block_entities {
238 let Some(nbt) = self.block_entities.get(&idx) else {
239 continue;
240 };
241
242 let x = idx % 16;
243 let z = (idx / 16) % 16;
244 let y = idx / 16 / 16;
245
246 let state = self.sections[y as usize / 16]
247 .block_states
248 .get(idx as usize % SECTION_BLOCK_COUNT);
249
250 let Some(kind) = state.block_entity_kind() else {
251 continue;
252 };
253
254 let global_x = pos.x * 16 + x as i32;
255 let global_y = info.min_y + y as i32;
256 let global_z = pos.z * 16 + z as i32;
257
258 messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| {
259 let mut writer = PacketWriter::new(buf, info.threshold);
260
261 writer.write_packet(&BlockEntityUpdateS2c {
262 position: BlockPos::new(global_x, global_y, global_z),
263 kind,
264 data: Cow::Borrowed(nbt),
265 });
266 });
267 }
268
269 self.changed_block_entities.clear();
270
271 if self.changed_biomes {
273 self.changed_biomes = false;
274
275 messages.send_local_infallible(LocalMsg::ChangeBiome { pos }, |buf| {
276 for sect in &self.sections {
277 sect.biomes
278 .encode_mc_format(
279 &mut *buf,
280 |b| b.to_index() as u64,
281 0,
282 3,
283 bit_width(info.biome_registry_len - 1),
284 )
285 .expect("paletted container encode should always succeed");
286 }
287 });
288 }
289
290 self.assert_no_changes();
292 }
293
294 #[allow(clippy::needless_range_loop)]
312 fn motion_blocking(&self) -> Vec<Vec<u32>> {
313 let mut heightmap: Vec<Vec<u32>> = vec![vec![0; 16]; 16];
314
315 for z in 0..16 {
316 for x in 0..16 {
317 for y in (0..self.height()).rev() {
318 let state = self.block_state(x as u32, y, z as u32);
319 if state.blocks_motion()
320 || state.is_liquid()
321 || state.get(PropName::Waterlogged) == Some(PropValue::True)
322 {
323 heightmap[z][x] = y + 2;
324 break;
325 }
326 }
327 }
328 }
329
330 heightmap
331 }
332
333 fn encode_heightmap(heightmap: Vec<Vec<u32>>) -> Value {
352 const BITS_PER_ENTRY: u32 = 9;
353 const ENTRIES_PER_LONG: u32 = i64::BITS / BITS_PER_ENTRY;
354
355 const LONGS_PER_PACKET: u32 =
358 16 * 16 / ENTRIES_PER_LONG + (16 * 16 % ENTRIES_PER_LONG != 0) as u32;
359
360 let mut encoded: Vec<i64> = vec![0; LONGS_PER_PACKET as usize];
361 let mut iter = heightmap.into_iter().flatten();
362
363 for long in &mut encoded {
364 for j in 0..ENTRIES_PER_LONG {
365 match iter.next() {
366 None => break,
367 Some(y) => *long += i64::from(y) << (BITS_PER_ENTRY * j),
368 }
369 }
370 }
371
372 Value::LongArray(encoded)
373 }
374
375 pub(crate) fn write_init_packets(
377 &self,
378 mut writer: impl WritePacket,
379 pos: ChunkPos,
380 info: &ChunkLayerInfo,
381 ) {
382 let mut init_packets = self.cached_init_packets.lock();
383
384 if init_packets.is_empty() {
385 let heightmaps = compound! {
386 "MOTION_BLOCKING" => LoadedChunk::encode_heightmap(self.motion_blocking()),
387 };
390
391 let mut blocks_and_biomes: Vec<u8> = vec![];
392
393 for sect in &self.sections {
394 sect.count_non_air_blocks()
395 .encode(&mut blocks_and_biomes)
396 .unwrap();
397
398 sect.block_states
399 .encode_mc_format(
400 &mut blocks_and_biomes,
401 |b| b.to_raw().into(),
402 4,
403 8,
404 bit_width(BlockState::max_raw().into()),
405 )
406 .expect("paletted container encode should always succeed");
407
408 sect.biomes
409 .encode_mc_format(
410 &mut blocks_and_biomes,
411 |b| b.to_index() as u64,
412 0,
413 3,
414 bit_width(info.biome_registry_len - 1),
415 )
416 .expect("paletted container encode should always succeed");
417 }
418
419 let block_entities: Vec<_> = self
420 .block_entities
421 .iter()
422 .filter_map(|(&idx, nbt)| {
423 let x = idx % 16;
424 let z = idx / 16 % 16;
425 let y = idx / 16 / 16;
426
427 let kind = self.sections[y as usize / 16]
428 .block_states
429 .get(idx as usize % SECTION_BLOCK_COUNT)
430 .block_entity_kind();
431
432 kind.map(|kind| ChunkDataBlockEntity {
433 packed_xz: ((x << 4) | z) as i8,
434 y: y as i16 + info.min_y as i16,
435 kind,
436 data: Cow::Borrowed(nbt),
437 })
438 })
439 .collect();
440
441 PacketWriter::new(&mut init_packets, info.threshold).write_packet(&ChunkDataS2c {
442 pos,
443 heightmaps: Cow::Owned(heightmaps),
444 blocks_and_biomes: &blocks_and_biomes,
445 block_entities: Cow::Owned(block_entities),
446 sky_light_mask: Cow::Borrowed(&[]),
447 block_light_mask: Cow::Borrowed(&[]),
448 empty_sky_light_mask: Cow::Borrowed(&[]),
449 empty_block_light_mask: Cow::Borrowed(&[]),
450 sky_light_arrays: Cow::Borrowed(&[]),
451 block_light_arrays: Cow::Borrowed(&[]),
452 })
453 }
454
455 writer.write_packet_bytes(&init_packets);
456 }
457
458 #[track_caller]
460 fn assert_no_changes(&self) {
461 #[cfg(debug_assertions)]
462 {
463 assert!(!self.changed_biomes);
464 assert!(self.changed_block_entities.is_empty());
465
466 for sect in &self.sections {
467 assert!(sect.updates.is_empty());
468 }
469 }
470 }
471}
472
473impl Chunk for LoadedChunk {
474 fn height(&self) -> u32 {
475 self.sections.len() as u32 * 16
476 }
477
478 fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState {
479 check_block_oob(self, x, y, z);
480
481 let idx = x + z * 16 + y % 16 * 16 * 16;
482 self.sections[y as usize / 16]
483 .block_states
484 .get(idx as usize)
485 }
486
487 fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState {
488 check_block_oob(self, x, y, z);
489
490 let sect_y = y / 16;
491 let sect = &mut self.sections[sect_y as usize];
492 let idx = x + z * 16 + y % 16 * 16 * 16;
493
494 let old_block = sect.block_states.set(idx as usize, block);
495
496 if block != old_block {
497 self.cached_init_packets.get_mut().clear();
498
499 if *self.viewer_count.get_mut() > 0 {
500 sect.updates.push(
501 ChunkDeltaUpdateEntry::new()
502 .with_off_x(x as u8)
503 .with_off_y((y % 16) as u8)
504 .with_off_z(z as u8)
505 .with_block_state(block.to_raw().into()),
506 );
507 }
508 }
509
510 old_block
511 }
512
513 fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) {
514 check_section_oob(self, sect_y);
515
516 let sect = &mut self.sections[sect_y as usize];
517
518 if let PalettedContainer::Single(b) = §.block_states {
519 if *b != block {
520 self.cached_init_packets.get_mut().clear();
521
522 if *self.viewer_count.get_mut() > 0 {
523 sect.updates.clear();
526
527 sect.updates.reserve_exact(SECTION_BLOCK_COUNT);
529 for z in 0..16 {
530 for x in 0..16 {
531 for y in 0..16 {
532 sect.updates.push(
533 ChunkDeltaUpdateEntry::new()
534 .with_off_x(x)
535 .with_off_y(y)
536 .with_off_z(z)
537 .with_block_state(block.to_raw().into()),
538 );
539 }
540 }
541 }
542 }
543 }
544 } else {
545 for z in 0..16 {
546 for x in 0..16 {
547 for y in 0..16 {
548 let idx = x + z * 16 + (sect_y * 16 + y) * (16 * 16);
549
550 if block != sect.block_states.get(idx as usize) {
551 self.cached_init_packets.get_mut().clear();
552
553 if *self.viewer_count.get_mut() > 0 {
554 sect.updates.push(
555 ChunkDeltaUpdateEntry::new()
556 .with_off_x(x as u8)
557 .with_off_y(y as u8)
558 .with_off_z(z as u8)
559 .with_block_state(block.to_raw().into()),
560 );
561 }
562 }
563 }
564 }
565 }
566 }
567
568 sect.block_states.fill(block);
569 }
570
571 fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> {
572 check_block_oob(self, x, y, z);
573
574 let idx = x + z * 16 + y * 16 * 16;
575 self.block_entities.get(&idx)
576 }
577
578 fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> {
579 check_block_oob(self, x, y, z);
580
581 let idx = x + z * 16 + y * 16 * 16;
582
583 if let Some(be) = self.block_entities.get_mut(&idx) {
584 if *self.viewer_count.get_mut() > 0 {
585 self.changed_block_entities.insert(idx);
586 }
587 self.cached_init_packets.get_mut().clear();
588
589 Some(be)
590 } else {
591 None
592 }
593 }
594
595 fn set_block_entity(
596 &mut self,
597 x: u32,
598 y: u32,
599 z: u32,
600 block_entity: Option<Compound>,
601 ) -> Option<Compound> {
602 check_block_oob(self, x, y, z);
603
604 let idx = x + z * 16 + y * 16 * 16;
605
606 match block_entity {
607 Some(nbt) => {
608 if *self.viewer_count.get_mut() > 0 {
609 self.changed_block_entities.insert(idx);
610 }
611 self.cached_init_packets.get_mut().clear();
612
613 self.block_entities.insert(idx, nbt)
614 }
615 None => {
616 let res = self.block_entities.remove(&idx);
617
618 if res.is_some() {
619 self.cached_init_packets.get_mut().clear();
620 }
621
622 res
623 }
624 }
625 }
626
627 fn clear_block_entities(&mut self) {
628 if self.block_entities.is_empty() {
629 return;
630 }
631
632 self.cached_init_packets.get_mut().clear();
633
634 if *self.viewer_count.get_mut() > 0 {
635 self.changed_block_entities
636 .extend(mem::take(&mut self.block_entities).into_keys());
637 } else {
638 self.block_entities.clear();
639 }
640 }
641
642 fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId {
643 check_biome_oob(self, x, y, z);
644
645 let idx = x + z * 4 + y % 4 * 4 * 4;
646 self.sections[y as usize / 4].biomes.get(idx as usize)
647 }
648
649 fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId {
650 check_biome_oob(self, x, y, z);
651
652 let idx = x + z * 4 + y % 4 * 4 * 4;
653 let old_biome = self.sections[y as usize / 4]
654 .biomes
655 .set(idx as usize, biome);
656
657 if biome != old_biome {
658 self.cached_init_packets.get_mut().clear();
659
660 if *self.viewer_count.get_mut() > 0 {
661 self.changed_biomes = true;
662 }
663 }
664
665 old_biome
666 }
667
668 fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) {
669 check_section_oob(self, sect_y);
670
671 let sect = &mut self.sections[sect_y as usize];
672
673 if let PalettedContainer::Single(b) = §.biomes {
674 if *b != biome {
675 self.cached_init_packets.get_mut().clear();
676 self.changed_biomes = *self.viewer_count.get_mut() > 0;
677 }
678 } else {
679 self.cached_init_packets.get_mut().clear();
680 self.changed_biomes = *self.viewer_count.get_mut() > 0;
681 }
682
683 sect.biomes.fill(biome);
684 }
685
686 fn shrink_to_fit(&mut self) {
687 self.cached_init_packets.get_mut().shrink_to_fit();
688
689 for sect in &mut self.sections {
690 sect.block_states.shrink_to_fit();
691 sect.biomes.shrink_to_fit();
692 sect.updates.shrink_to_fit();
693 }
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use valence_protocol::{ident, CompressionThreshold};
700
701 use super::*;
702
703 #[test]
704 fn loaded_chunk_unviewed_no_changes() {
705 let mut chunk = LoadedChunk::new(512);
706
707 chunk.set_block(0, 10, 0, BlockState::MAGMA_BLOCK);
708 chunk.assert_no_changes();
709
710 chunk.set_biome(0, 0, 0, BiomeId::from_index(5));
711 chunk.assert_no_changes();
712
713 chunk.fill_block_states(BlockState::ACACIA_BUTTON);
714 chunk.assert_no_changes();
715
716 chunk.fill_biomes(BiomeId::from_index(42));
717 chunk.assert_no_changes();
718 }
719
720 #[test]
721 fn loaded_chunk_changes_clear_packet_cache() {
722 #[track_caller]
723 fn check<T>(chunk: &mut LoadedChunk, change: impl FnOnce(&mut LoadedChunk) -> T) {
724 let info = ChunkLayerInfo {
725 dimension_type_name: ident!("whatever").into(),
726 height: 512,
727 min_y: -16,
728 biome_registry_len: 200,
729 threshold: CompressionThreshold(-1),
730 };
731
732 let mut buf = vec![];
733 let mut writer = PacketWriter::new(&mut buf, CompressionThreshold(-1));
734
735 chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info);
737
738 assert!(!chunk.cached_init_packets.get_mut().is_empty());
740
741 change(chunk);
743 assert!(chunk.cached_init_packets.get_mut().is_empty());
744
745 chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info);
747 assert!(!chunk.cached_init_packets.get_mut().is_empty());
748 }
749
750 let mut chunk = LoadedChunk::new(512);
751
752 check(&mut chunk, |c| {
753 c.set_block_state(0, 4, 0, BlockState::ACACIA_WOOD)
754 });
755 check(&mut chunk, |c| c.set_biome(1, 2, 3, BiomeId::from_index(4)));
756 check(&mut chunk, |c| c.fill_biomes(BiomeId::DEFAULT));
757 check(&mut chunk, |c| c.fill_block_states(BlockState::WET_SPONGE));
758 check(&mut chunk, |c| {
759 c.set_block_entity(3, 40, 5, Some(compound! {}))
760 });
761 check(&mut chunk, |c| {
762 c.block_entity_mut(3, 40, 5).unwrap();
763 });
764 check(&mut chunk, |c| c.set_block_entity(3, 40, 5, None));
765
766 assert_eq!(
769 chunk.set_block_state(0, 0, 0, BlockState::WET_SPONGE),
770 BlockState::WET_SPONGE
771 );
772
773 assert!(!chunk.cached_init_packets.get_mut().is_empty());
774 }
775}