1use std::cell::RefCell;
2use std::iter::FusedIterator;
3use std::mem;
4use std::ops::{Deref, DerefMut};
5
6use steel_utils::{BlockPos, Direction};
7
8use super::{MAX_LIGHT_LEVEL, PackedLightBlockPos};
9
10const QUEUE_ENTRY_LEVEL_MASK: u64 = 0b1111;
11const QUEUE_ENTRY_DIRECTIONS_MASK: u64 = 0b11_1111_0000;
12const QUEUE_ENTRY_FLAG_FROM_EMPTY_SHAPE: u64 = 1 << 10;
13const QUEUE_ENTRY_FLAG_INCREASE_FROM_EMISSION: u64 = 1 << 11;
14const LIGHT_QUEUE_MIN_CAPACITY: usize = 512;
15const PACKED_LIGHT_QUEUE_MIN_CAPACITY: usize = 16 * 16 * 16;
16const PACKED_LIGHT_QUEUE_POSITION_BITS: u64 = 28;
17const PACKED_LIGHT_QUEUE_LEVEL_BITS: u64 = 4;
18const PACKED_LIGHT_QUEUE_DIRECTION_BITS: u64 = 6;
19const PACKED_LIGHT_QUEUE_LEVEL_MASK: u64 = (1 << PACKED_LIGHT_QUEUE_LEVEL_BITS) - 1;
20const PACKED_LIGHT_QUEUE_DIRECTION_MASK: u8 = (1 << PACKED_LIGHT_QUEUE_DIRECTION_BITS) - 1;
21const PACKED_LIGHT_QUEUE_LEVEL_SHIFT: u64 = PACKED_LIGHT_QUEUE_POSITION_BITS;
22const PACKED_LIGHT_QUEUE_DIRECTIONS_SHIFT: u64 =
23 PACKED_LIGHT_QUEUE_LEVEL_SHIFT + PACKED_LIGHT_QUEUE_LEVEL_BITS;
24const PACKED_LIGHT_QUEUE_POSITION_MASK: u64 = (1_u64 << PACKED_LIGHT_QUEUE_POSITION_BITS) - 1;
25const PACKED_LIGHT_QUEUE_FLAGS_MASK: u64 = (1_u64 << 61) | (1_u64 << 62) | (1_u64 << 63);
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum LightAxisDirection {
34 PositiveX,
36 NegativeX,
38 PositiveZ,
40 NegativeZ,
42 PositiveY,
44 NegativeY,
46}
47
48impl LightAxisDirection {
49 pub const ALL: [Self; 6] = [
51 Self::PositiveX,
52 Self::NegativeX,
53 Self::PositiveZ,
54 Self::NegativeZ,
55 Self::PositiveY,
56 Self::NegativeY,
57 ];
58
59 pub const HORIZONTAL: [Self; 4] = [
61 Self::PositiveX,
62 Self::NegativeX,
63 Self::PositiveZ,
64 Self::NegativeZ,
65 ];
66
67 #[must_use]
69 pub const fn from_direction(direction: Direction) -> Self {
70 match direction {
71 Direction::East => Self::PositiveX,
72 Direction::West => Self::NegativeX,
73 Direction::South => Self::PositiveZ,
74 Direction::North => Self::NegativeZ,
75 Direction::Up => Self::PositiveY,
76 Direction::Down => Self::NegativeY,
77 }
78 }
79
80 #[must_use]
82 pub const fn from_bit_index(bit_index: u8) -> Option<Self> {
83 match bit_index {
84 0 => Some(Self::PositiveX),
85 1 => Some(Self::NegativeX),
86 2 => Some(Self::PositiveZ),
87 3 => Some(Self::NegativeZ),
88 4 => Some(Self::PositiveY),
89 5 => Some(Self::NegativeY),
90 _ => None,
91 }
92 }
93
94 #[must_use]
96 pub const fn direction(self) -> Direction {
97 match self {
98 Self::PositiveX => Direction::East,
99 Self::NegativeX => Direction::West,
100 Self::PositiveZ => Direction::South,
101 Self::NegativeZ => Direction::North,
102 Self::PositiveY => Direction::Up,
103 Self::NegativeY => Direction::Down,
104 }
105 }
106
107 #[must_use]
109 pub const fn offset(self) -> (i32, i32, i32) {
110 match self {
111 Self::PositiveX => (1, 0, 0),
112 Self::NegativeX => (-1, 0, 0),
113 Self::PositiveZ => (0, 0, 1),
114 Self::NegativeZ => (0, 0, -1),
115 Self::PositiveY => (0, 1, 0),
116 Self::NegativeY => (0, -1, 0),
117 }
118 }
119
120 #[must_use]
122 pub const fn opposite(self) -> Self {
123 match self {
124 Self::PositiveX => Self::NegativeX,
125 Self::NegativeX => Self::PositiveX,
126 Self::PositiveZ => Self::NegativeZ,
127 Self::NegativeZ => Self::PositiveZ,
128 Self::PositiveY => Self::NegativeY,
129 Self::NegativeY => Self::PositiveY,
130 }
131 }
132
133 #[must_use]
135 pub const fn bit_index(self) -> u8 {
136 match self {
137 Self::PositiveX => 0,
138 Self::NegativeX => 1,
139 Self::PositiveZ => 2,
140 Self::NegativeZ => 3,
141 Self::PositiveY => 4,
142 Self::NegativeY => 5,
143 }
144 }
145
146 const fn bit(self) -> u8 {
147 1 << self.bit_index()
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
153pub struct LightDirectionSet(u8);
154
155impl LightDirectionSet {
156 #[must_use]
158 pub const fn empty() -> Self {
159 Self(0)
160 }
161
162 #[must_use]
164 pub const fn all() -> Self {
165 Self(PACKED_LIGHT_QUEUE_DIRECTION_MASK)
166 }
167
168 #[must_use]
170 pub const fn from_raw(raw: u8) -> Self {
171 Self(raw & PACKED_LIGHT_QUEUE_DIRECTION_MASK)
172 }
173
174 #[must_use]
176 pub const fn only(direction: LightAxisDirection) -> Self {
177 Self(direction.bit())
178 }
179
180 #[must_use]
182 pub const fn with(self, direction: LightAxisDirection) -> Self {
183 Self(self.0 | direction.bit())
184 }
185
186 #[must_use]
188 pub const fn all_except(direction: LightAxisDirection) -> Self {
189 Self(PACKED_LIGHT_QUEUE_DIRECTION_MASK & !direction.bit())
190 }
191
192 #[must_use]
194 pub const fn all_except_opposite(direction: LightAxisDirection) -> Self {
195 Self::all_except(direction.opposite())
196 }
197
198 #[must_use]
200 pub const fn raw(self) -> u8 {
201 self.0
202 }
203
204 #[must_use]
206 pub const fn contains(self, direction: LightAxisDirection) -> bool {
207 self.0 & direction.bit() != 0
208 }
209
210 #[must_use]
212 pub const fn directions(self) -> LightDirectionSetIter {
213 LightDirectionSetIter { remaining: self.0 }
214 }
215}
216
217#[derive(Debug, Clone)]
219pub struct LightDirectionSetIter {
220 remaining: u8,
221}
222
223impl Iterator for LightDirectionSetIter {
224 type Item = LightAxisDirection;
225
226 fn next(&mut self) -> Option<Self::Item> {
227 if self.remaining == 0 {
228 return None;
229 }
230
231 let bit_index = self.remaining.trailing_zeros() as u8;
232 self.remaining &= self.remaining - 1;
233 LightAxisDirection::from_bit_index(bit_index)
234 }
235
236 fn size_hint(&self) -> (usize, Option<usize>) {
237 let len = self.remaining.count_ones() as usize;
238 (len, Some(len))
239 }
240}
241
242impl ExactSizeIterator for LightDirectionSetIter {}
243
244impl FusedIterator for LightDirectionSetIter {}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
248pub struct LightQueueFlags(u64);
249
250impl LightQueueFlags {
251 pub const EMPTY: Self = Self(0);
253 pub const WRITE_LEVEL: Self = Self(1_u64 << 61);
255 pub const RECHECK_LEVEL: Self = Self(1_u64 << 62);
257 pub const HAS_SIDED_TRANSPARENT_BLOCKS: Self = Self(1_u64 << 63);
259
260 #[must_use]
262 pub const fn from_raw(raw: u64) -> Self {
263 Self(raw & PACKED_LIGHT_QUEUE_FLAGS_MASK)
264 }
265
266 #[must_use]
268 pub const fn raw(self) -> u64 {
269 self.0
270 }
271
272 #[must_use]
274 pub const fn with(self, flag: Self) -> Self {
275 Self(self.0 | flag.0)
276 }
277
278 #[must_use]
280 pub const fn contains(self, flag: Self) -> bool {
281 self.0 & flag.0 == flag.0
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
292pub struct PackedLightQueueEntry(u64);
293
294impl PackedLightQueueEntry {
295 #[must_use]
297 pub const fn from_parts(
298 block_pos: PackedLightBlockPos,
299 level: u8,
300 directions: LightDirectionSet,
301 flags: LightQueueFlags,
302 ) -> Self {
303 Self(
304 block_pos.raw() as u64
305 | ((level as u64 & PACKED_LIGHT_QUEUE_LEVEL_MASK)
306 << PACKED_LIGHT_QUEUE_LEVEL_SHIFT)
307 | ((directions.raw() as u64) << PACKED_LIGHT_QUEUE_DIRECTIONS_SHIFT)
308 | flags.raw(),
309 )
310 }
311
312 #[must_use]
314 pub const fn from_raw(raw: u64) -> Self {
315 Self(raw)
316 }
317
318 #[must_use]
320 pub const fn raw(self) -> u64 {
321 self.0
322 }
323
324 #[must_use]
326 pub const fn block_pos(self) -> PackedLightBlockPos {
327 PackedLightBlockPos::from_raw((self.0 & PACKED_LIGHT_QUEUE_POSITION_MASK) as u32)
328 }
329
330 #[must_use]
332 pub const fn level(self) -> u8 {
333 ((self.0 >> PACKED_LIGHT_QUEUE_LEVEL_SHIFT) & PACKED_LIGHT_QUEUE_LEVEL_MASK) as u8
334 }
335
336 #[must_use]
338 pub const fn directions(self) -> LightDirectionSet {
339 LightDirectionSet::from_raw(
340 ((self.0 >> PACKED_LIGHT_QUEUE_DIRECTIONS_SHIFT)
341 & PACKED_LIGHT_QUEUE_DIRECTION_MASK as u64) as u8,
342 )
343 }
344
345 #[must_use]
347 pub const fn flags(self) -> LightQueueFlags {
348 LightQueueFlags::from_raw(self.0)
349 }
350
351 #[must_use]
353 pub const fn should_write_level(self) -> bool {
354 self.flags().contains(LightQueueFlags::WRITE_LEVEL)
355 }
356
357 #[must_use]
359 pub const fn should_recheck_level(self) -> bool {
360 self.flags().contains(LightQueueFlags::RECHECK_LEVEL)
361 }
362
363 #[must_use]
365 pub const fn has_sided_transparent_blocks(self) -> bool {
366 self.flags()
367 .contains(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS)
368 }
369}
370
371#[derive(Debug)]
373pub struct PackedLightPropagationQueue {
374 entries: Vec<PackedLightQueueEntry>,
375 read_index: usize,
376}
377
378impl PackedLightPropagationQueue {
379 #[must_use]
381 pub fn new() -> Self {
382 Self {
383 entries: Vec::with_capacity(PACKED_LIGHT_QUEUE_MIN_CAPACITY),
384 read_index: 0,
385 }
386 }
387
388 #[must_use]
390 pub const fn is_empty(&self) -> bool {
391 self.read_index >= self.entries.len()
392 }
393
394 #[must_use]
396 pub const fn len(&self) -> usize {
397 self.entries.len() - self.read_index
398 }
399
400 pub fn enqueue(&mut self, entry: PackedLightQueueEntry) {
402 self.entries.push(entry);
403 }
404
405 pub fn dequeue(&mut self) -> Option<PackedLightQueueEntry> {
407 if self.is_empty() {
408 self.clear();
409 return None;
410 }
411
412 let entry = self.entries[self.read_index];
413 self.read_index += 1;
414 if self.is_empty() {
415 self.clear();
416 }
417
418 Some(entry)
419 }
420
421 pub fn clear(&mut self) {
423 self.entries.clear();
424 self.read_index = 0;
425 }
426}
427
428impl Default for PackedLightPropagationQueue {
429 fn default() -> Self {
430 Self::new()
431 }
432}
433
434#[derive(Debug, Default)]
436pub struct PackedLightPropagationQueues {
437 increase: PackedLightPropagationQueue,
438 decrease: PackedLightPropagationQueue,
439}
440
441impl PackedLightPropagationQueues {
442 #[must_use]
444 pub fn new() -> Self {
445 Self::default()
446 }
447
448 #[must_use]
450 pub const fn has_work(&self) -> bool {
451 !self.increase.is_empty() || !self.decrease.is_empty()
452 }
453
454 pub fn enqueue_decrease(&mut self, entry: PackedLightQueueEntry) {
456 self.decrease.enqueue(entry);
457 }
458
459 pub fn enqueue_increase(&mut self, entry: PackedLightQueueEntry) {
461 self.increase.enqueue(entry);
462 }
463
464 pub fn dequeue_decrease(&mut self) -> Option<PackedLightQueueEntry> {
466 self.decrease.dequeue()
467 }
468
469 pub fn dequeue_increase(&mut self) -> Option<PackedLightQueueEntry> {
471 self.increase.dequeue()
472 }
473
474 pub fn clear(&mut self) {
476 self.increase.clear();
477 self.decrease.clear();
478 }
479
480 const fn empty_without_capacity() -> Self {
482 Self {
483 increase: PackedLightPropagationQueue {
484 entries: Vec::new(),
485 read_index: 0,
486 },
487 decrease: PackedLightPropagationQueue {
488 entries: Vec::new(),
489 read_index: 0,
490 },
491 }
492 }
493
494 const fn total_capacity(&self) -> usize {
495 self.increase.entries.capacity() + self.decrease.entries.capacity()
496 }
497}
498
499const POOLED_PACKED_QUEUES_MAX_ENTRIES: usize = 256 * 1024;
503
504thread_local! {
505 static POOLED_PACKED_QUEUES: RefCell<Option<PackedLightPropagationQueues>> =
506 const { RefCell::new(None) };
507}
508
509#[must_use]
519pub struct PooledPackedLightQueues {
520 inner: PackedLightPropagationQueues,
521}
522
523impl PooledPackedLightQueues {
524 pub fn take() -> Self {
526 let inner = POOLED_PACKED_QUEUES.with(|pool| match pool.borrow_mut().take() {
527 Some(queues) => queues,
528 None => PackedLightPropagationQueues::new(),
529 });
530
531 Self { inner }
532 }
533}
534
535impl Default for PooledPackedLightQueues {
536 fn default() -> Self {
537 Self::take()
538 }
539}
540
541impl Deref for PooledPackedLightQueues {
542 type Target = PackedLightPropagationQueues;
543
544 #[inline]
545 fn deref(&self) -> &Self::Target {
546 &self.inner
547 }
548}
549
550impl DerefMut for PooledPackedLightQueues {
551 #[inline]
552 fn deref_mut(&mut self) -> &mut Self::Target {
553 &mut self.inner
554 }
555}
556
557impl Drop for PooledPackedLightQueues {
558 fn drop(&mut self) {
559 let mut queues = mem::replace(
560 &mut self.inner,
561 PackedLightPropagationQueues::empty_without_capacity(),
562 );
563
564 queues.clear();
565
566 let recycled =
567 (queues.total_capacity() <= POOLED_PACKED_QUEUES_MAX_ENTRIES).then_some(queues);
568
569 POOLED_PACKED_QUEUES.with(|pool| {
570 if let Some(queues) = recycled {
571 *pool.borrow_mut() = Some(queues);
572 }
573 });
574 }
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Eq)]
583pub struct LightQueueEntry(u64);
584
585impl LightQueueEntry {
586 #[must_use]
588 pub const fn decrease_skip_one_direction(
589 old_from_level: u8,
590 skip_direction: Direction,
591 ) -> Self {
592 Self::with_level(
593 Self::without_direction(QUEUE_ENTRY_DIRECTIONS_MASK, skip_direction),
594 old_from_level,
595 )
596 }
597
598 #[must_use]
600 pub const fn decrease_all_directions(old_from_level: u8) -> Self {
601 Self::with_level(QUEUE_ENTRY_DIRECTIONS_MASK, old_from_level)
602 }
603
604 #[must_use]
606 pub const fn increase_light_from_emission(new_from_level: u8, from_empty_shape: bool) -> Self {
607 let mut entry = QUEUE_ENTRY_DIRECTIONS_MASK | QUEUE_ENTRY_FLAG_INCREASE_FROM_EMISSION;
608 if from_empty_shape {
609 entry |= QUEUE_ENTRY_FLAG_FROM_EMPTY_SHAPE;
610 }
611
612 Self::with_level(entry, new_from_level)
613 }
614
615 #[must_use]
617 pub const fn increase_skip_one_direction(
618 new_from_level: u8,
619 from_empty_shape: bool,
620 skip_direction: Direction,
621 ) -> Self {
622 let mut entry = Self::without_direction(QUEUE_ENTRY_DIRECTIONS_MASK, skip_direction);
623 if from_empty_shape {
624 entry |= QUEUE_ENTRY_FLAG_FROM_EMPTY_SHAPE;
625 }
626
627 Self::with_level(entry, new_from_level)
628 }
629
630 #[must_use]
632 pub const fn increase_only_one_direction(
633 new_from_level: u8,
634 from_empty_shape: bool,
635 direction: Direction,
636 ) -> Self {
637 let mut entry = 0;
638 if from_empty_shape {
639 entry |= QUEUE_ENTRY_FLAG_FROM_EMPTY_SHAPE;
640 }
641
642 Self::with_level(Self::with_direction(entry, direction), new_from_level)
643 }
644
645 #[must_use]
647 pub fn increase_sky_source_in_directions(directions: &[Direction]) -> Self {
648 let mut entry = u64::from(MAX_LIGHT_LEVEL);
649 for &direction in directions {
650 entry = Self::with_direction(entry, direction);
651 }
652
653 Self(entry)
654 }
655
656 #[must_use]
658 pub const fn from_raw(raw: u64) -> Self {
659 Self(raw)
660 }
661
662 #[must_use]
664 pub const fn raw(self) -> u64 {
665 self.0
666 }
667
668 #[must_use]
670 pub const fn level(self) -> u8 {
671 (self.0 & QUEUE_ENTRY_LEVEL_MASK) as u8
672 }
673
674 #[must_use]
676 pub const fn is_from_empty_shape(self) -> bool {
677 self.0 & QUEUE_ENTRY_FLAG_FROM_EMPTY_SHAPE != 0
678 }
679
680 #[must_use]
682 pub const fn is_increase_from_emission(self) -> bool {
683 self.0 & QUEUE_ENTRY_FLAG_INCREASE_FROM_EMISSION != 0
684 }
685
686 #[must_use]
688 pub const fn should_propagate_in_direction(self, direction: Direction) -> bool {
689 self.0 & Self::direction_bit(direction) != 0
690 }
691
692 const fn with_level(entry: u64, level: u8) -> Self {
693 Self(entry & !QUEUE_ENTRY_LEVEL_MASK | (level as u64 & QUEUE_ENTRY_LEVEL_MASK))
694 }
695
696 const fn with_direction(entry: u64, direction: Direction) -> u64 {
697 entry | Self::direction_bit(direction)
698 }
699
700 const fn without_direction(entry: u64, direction: Direction) -> u64 {
701 entry & !Self::direction_bit(direction)
702 }
703
704 const fn direction_bit(direction: Direction) -> u64 {
705 1 << (Self::vanilla_direction_index(direction) + 4)
706 }
707
708 const fn vanilla_direction_index(direction: Direction) -> u64 {
709 match direction {
710 Direction::Down => 0,
711 Direction::Up => 1,
712 Direction::North => 2,
713 Direction::South => 3,
714 Direction::West => 4,
715 Direction::East => 5,
716 }
717 }
718}
719
720#[derive(Debug, Clone, Copy, PartialEq, Eq)]
722pub struct QueuedLightUpdate {
723 pub block_pos: BlockPos,
725 pub entry: LightQueueEntry,
727}
728
729#[derive(Debug)]
736pub struct LightPropagationQueue {
737 entries: Vec<QueuedLightUpdate>,
738 read_index: usize,
739}
740
741impl LightPropagationQueue {
742 #[must_use]
744 pub fn new() -> Self {
745 Self {
746 entries: Vec::with_capacity(LIGHT_QUEUE_MIN_CAPACITY),
747 read_index: 0,
748 }
749 }
750
751 #[must_use]
753 pub const fn is_empty(&self) -> bool {
754 self.read_index >= self.entries.len()
755 }
756
757 #[must_use]
759 pub const fn len(&self) -> usize {
760 self.entries.len() - self.read_index
761 }
762
763 pub fn enqueue(&mut self, block_pos: BlockPos, entry: LightQueueEntry) {
765 self.entries.push(QueuedLightUpdate { block_pos, entry });
766 }
767
768 pub fn dequeue(&mut self) -> Option<QueuedLightUpdate> {
770 if self.is_empty() {
771 self.clear();
772 return None;
773 }
774
775 let update = self.entries[self.read_index];
776 self.read_index += 1;
777 if self.is_empty() {
778 self.clear();
779 }
780
781 Some(update)
782 }
783
784 pub fn clear(&mut self) {
786 self.entries.clear();
787 self.read_index = 0;
788 }
789}
790
791impl Default for LightPropagationQueue {
792 fn default() -> Self {
793 Self::new()
794 }
795}
796
797#[derive(Debug, Default)]
799pub struct LightPropagationQueues {
800 increase: LightPropagationQueue,
801 decrease: LightPropagationQueue,
802}
803
804impl LightPropagationQueues {
805 #[must_use]
807 pub fn new() -> Self {
808 Self::default()
809 }
810
811 #[must_use]
813 pub const fn has_work(&self) -> bool {
814 !self.increase.is_empty() || !self.decrease.is_empty()
815 }
816
817 pub fn enqueue_decrease(&mut self, block_pos: BlockPos, entry: LightQueueEntry) {
819 self.decrease.enqueue(block_pos, entry);
820 }
821
822 pub fn enqueue_increase(&mut self, block_pos: BlockPos, entry: LightQueueEntry) {
824 self.increase.enqueue(block_pos, entry);
825 }
826
827 pub fn dequeue_decrease(&mut self) -> Option<QueuedLightUpdate> {
829 self.decrease.dequeue()
830 }
831
832 pub fn dequeue_increase(&mut self) -> Option<QueuedLightUpdate> {
834 self.increase.dequeue()
835 }
836
837 pub fn clear(&mut self) {
839 self.increase.clear();
840 self.decrease.clear();
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use super::*;
847
848 fn packed_entry(level: u8) -> PackedLightQueueEntry {
849 PackedLightQueueEntry::from_parts(
850 PackedLightBlockPos::from_raw(u32::from(level)),
851 level,
852 LightDirectionSet::all(),
853 LightQueueFlags::EMPTY,
854 )
855 }
856
857 #[test]
858 fn light_axis_direction_matches_scalable_lux_order() {
859 assert_eq!(
860 LightAxisDirection::ALL,
861 [
862 LightAxisDirection::PositiveX,
863 LightAxisDirection::NegativeX,
864 LightAxisDirection::PositiveZ,
865 LightAxisDirection::NegativeZ,
866 LightAxisDirection::PositiveY,
867 LightAxisDirection::NegativeY,
868 ]
869 );
870 assert_eq!(
871 LightAxisDirection::HORIZONTAL,
872 [
873 LightAxisDirection::PositiveX,
874 LightAxisDirection::NegativeX,
875 LightAxisDirection::PositiveZ,
876 LightAxisDirection::NegativeZ,
877 ]
878 );
879
880 assert_eq!(LightAxisDirection::PositiveX.bit_index(), 0);
881 assert_eq!(LightAxisDirection::NegativeX.bit_index(), 1);
882 assert_eq!(LightAxisDirection::PositiveZ.bit_index(), 2);
883 assert_eq!(LightAxisDirection::NegativeZ.bit_index(), 3);
884 assert_eq!(LightAxisDirection::PositiveY.bit_index(), 4);
885 assert_eq!(LightAxisDirection::NegativeY.bit_index(), 5);
886 }
887
888 #[test]
889 fn light_axis_direction_maps_to_steel_direction() {
890 assert_eq!(
891 LightAxisDirection::from_direction(Direction::East),
892 LightAxisDirection::PositiveX
893 );
894 assert_eq!(
895 LightAxisDirection::from_direction(Direction::West),
896 LightAxisDirection::NegativeX
897 );
898 assert_eq!(
899 LightAxisDirection::from_direction(Direction::South),
900 LightAxisDirection::PositiveZ
901 );
902 assert_eq!(
903 LightAxisDirection::from_direction(Direction::North),
904 LightAxisDirection::NegativeZ
905 );
906 assert_eq!(
907 LightAxisDirection::from_direction(Direction::Up),
908 LightAxisDirection::PositiveY
909 );
910 assert_eq!(
911 LightAxisDirection::from_direction(Direction::Down),
912 LightAxisDirection::NegativeY
913 );
914
915 assert_eq!(LightAxisDirection::PositiveX.direction(), Direction::East);
916 assert_eq!(LightAxisDirection::NegativeX.direction(), Direction::West);
917 assert_eq!(LightAxisDirection::PositiveZ.direction(), Direction::South);
918 assert_eq!(LightAxisDirection::NegativeZ.direction(), Direction::North);
919 assert_eq!(LightAxisDirection::PositiveY.direction(), Direction::Up);
920 assert_eq!(LightAxisDirection::NegativeY.direction(), Direction::Down);
921
922 assert_eq!(LightAxisDirection::PositiveX.offset(), (1, 0, 0));
923 assert_eq!(LightAxisDirection::NegativeX.offset(), (-1, 0, 0));
924 assert_eq!(LightAxisDirection::PositiveZ.offset(), (0, 0, 1));
925 assert_eq!(LightAxisDirection::NegativeZ.offset(), (0, 0, -1));
926 assert_eq!(LightAxisDirection::PositiveY.offset(), (0, 1, 0));
927 assert_eq!(LightAxisDirection::NegativeY.offset(), (0, -1, 0));
928 }
929
930 #[test]
931 fn light_axis_direction_opposites_flip_low_bit() {
932 for direction in LightAxisDirection::ALL {
933 assert_eq!(direction.opposite().bit_index(), direction.bit_index() ^ 1);
934 assert_eq!(direction.opposite().opposite(), direction);
935 }
936 }
937
938 #[test]
939 fn light_direction_set_matches_scalable_lux_masks() {
940 assert_eq!(LightDirectionSet::empty().raw(), 0);
941 assert_eq!(LightDirectionSet::all().raw(), 0b11_1111);
942 assert_eq!(LightDirectionSet::from_raw(u8::MAX).raw(), 0b11_1111);
943 assert_eq!(
944 LightDirectionSet::only(LightAxisDirection::PositiveZ).raw(),
945 0b00_0100
946 );
947 assert_eq!(
948 LightDirectionSet::all_except(LightAxisDirection::PositiveZ).raw(),
949 0b11_1011
950 );
951 assert_eq!(
952 LightDirectionSet::all_except_opposite(LightAxisDirection::PositiveZ).raw(),
953 0b11_0111
954 );
955
956 let set = LightDirectionSet::from_raw(0b10_0101);
957 assert!(set.contains(LightAxisDirection::PositiveX));
958 assert!(set.contains(LightAxisDirection::PositiveZ));
959 assert!(set.contains(LightAxisDirection::NegativeY));
960 assert!(!set.contains(LightAxisDirection::NegativeX));
961 assert!(!set.contains(LightAxisDirection::NegativeZ));
962 assert!(!set.contains(LightAxisDirection::PositiveY));
963 }
964
965 #[test]
966 fn light_direction_set_iterates_in_scalable_lux_order() {
967 let mut directions = LightDirectionSet::from_raw(0b10_1101).directions();
968
969 assert_eq!(directions.len(), 4);
970 assert_eq!(directions.next(), Some(LightAxisDirection::PositiveX));
971 assert_eq!(directions.next(), Some(LightAxisDirection::PositiveZ));
972 assert_eq!(directions.len(), 2);
973 assert_eq!(directions.next(), Some(LightAxisDirection::NegativeZ));
974 assert_eq!(directions.next(), Some(LightAxisDirection::NegativeY));
975 assert_eq!(directions.next(), None);
976 assert_eq!(directions.next(), None);
977 }
978
979 #[test]
980 fn light_queue_flags_match_scalable_lux_top_bits() {
981 assert_eq!(LightQueueFlags::WRITE_LEVEL.raw(), 1_u64 << 61);
982 assert_eq!(LightQueueFlags::RECHECK_LEVEL.raw(), 1_u64 << 62);
983 assert_eq!(
984 LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS.raw(),
985 1_u64 << 63
986 );
987
988 let flags = LightQueueFlags::EMPTY
989 .with(LightQueueFlags::WRITE_LEVEL)
990 .with(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS);
991 assert!(flags.contains(LightQueueFlags::WRITE_LEVEL));
992 assert!(!flags.contains(LightQueueFlags::RECHECK_LEVEL));
993 assert!(flags.contains(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS));
994 assert_eq!(
995 LightQueueFlags::from_raw(u64::MAX).raw(),
996 LightQueueFlags::WRITE_LEVEL
997 .with(LightQueueFlags::RECHECK_LEVEL)
998 .with(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS)
999 .raw()
1000 );
1001 }
1002
1003 #[test]
1004 fn packed_light_queue_entry_matches_scalable_lux_bit_layout() {
1005 let position = PackedLightBlockPos::from_raw(0x0abc_def0);
1006 let directions = LightDirectionSet::from_raw(0b10_1011);
1007 let flags = LightQueueFlags::EMPTY
1008 .with(LightQueueFlags::WRITE_LEVEL)
1009 .with(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS);
1010 let entry = PackedLightQueueEntry::from_parts(position, 31, directions, flags);
1011
1012 assert_eq!(entry.block_pos(), position);
1013 assert_eq!(entry.level(), 15);
1014 assert_eq!(entry.directions(), directions);
1015 assert_eq!(entry.flags(), flags);
1016 assert!(entry.should_write_level());
1017 assert!(!entry.should_recheck_level());
1018 assert!(entry.has_sided_transparent_blocks());
1019 assert_eq!(entry.raw() & ((1_u64 << 28) - 1), u64::from(position.raw()));
1020 assert_eq!((entry.raw() >> 28) & 0x0f, 15);
1021 assert_eq!((entry.raw() >> 32) & 0x3f, u64::from(directions.raw()));
1022 assert_eq!(entry.raw() & (1_u64 << 61), 1_u64 << 61);
1023 assert_eq!(entry.raw() & (1_u64 << 62), 0);
1024 assert_eq!(entry.raw() & (1_u64 << 63), 1_u64 << 63);
1025 }
1026
1027 #[test]
1028 fn packed_light_queue_entry_reads_raw_scalable_lux_values() {
1029 let raw = u64::MAX;
1030 let entry = PackedLightQueueEntry::from_raw(raw);
1031
1032 assert_eq!(entry.raw(), raw);
1033 assert_eq!(entry.block_pos().raw(), (1 << 28) - 1);
1034 assert_eq!(entry.level(), 15);
1035 assert_eq!(entry.directions(), LightDirectionSet::all());
1036 assert!(entry.should_write_level());
1037 assert!(entry.should_recheck_level());
1038 assert!(entry.has_sided_transparent_blocks());
1039 }
1040
1041 #[test]
1042 fn packed_light_propagation_queue_preserves_fifo_order() {
1043 let first = packed_entry(1);
1044 let second = packed_entry(2);
1045 let third = packed_entry(3);
1046 let mut queue = PackedLightPropagationQueue::new();
1047
1048 assert!(queue.is_empty());
1049 queue.enqueue(first);
1050 queue.enqueue(second);
1051 assert_eq!(queue.len(), 2);
1052 assert_eq!(queue.dequeue(), Some(first));
1053
1054 queue.enqueue(third);
1055 assert_eq!(queue.len(), 2);
1056 assert_eq!(queue.dequeue(), Some(second));
1057 assert_eq!(queue.dequeue(), Some(third));
1058 assert_eq!(queue.dequeue(), None);
1059 assert!(queue.is_empty());
1060 }
1061
1062 #[test]
1063 fn packed_light_propagation_queues_keep_increase_and_decrease_work_separate() {
1064 let decrease_entry = packed_entry(6);
1065 let increase_entry = packed_entry(7);
1066 let mut queues = PackedLightPropagationQueues::new();
1067
1068 assert!(!queues.has_work());
1069 queues.enqueue_decrease(decrease_entry);
1070 queues.enqueue_increase(increase_entry);
1071 assert!(queues.has_work());
1072
1073 assert_eq!(queues.dequeue_increase(), Some(increase_entry));
1074 assert_eq!(queues.dequeue_increase(), None);
1075 assert!(queues.has_work());
1076
1077 assert_eq!(queues.dequeue_decrease(), Some(decrease_entry));
1078 assert_eq!(queues.dequeue_decrease(), None);
1079 assert!(!queues.has_work());
1080 }
1081
1082 #[test]
1083 fn light_queue_entry_decrease_entries_match_vanilla_bits() {
1084 let all = LightQueueEntry::decrease_all_directions(7);
1085
1086 assert_eq!(all.raw(), 0b11_1111_0000 | 7);
1087 assert_eq!(all.level(), 7);
1088 for direction in Direction::ALL {
1089 assert!(all.should_propagate_in_direction(direction));
1090 }
1091 assert!(!all.is_from_empty_shape());
1092 assert!(!all.is_increase_from_emission());
1093
1094 let skip_north = LightQueueEntry::decrease_skip_one_direction(7, Direction::North);
1095 assert_eq!(skip_north.raw(), 951);
1096 assert!(!skip_north.should_propagate_in_direction(Direction::North));
1097 assert!(skip_north.should_propagate_in_direction(Direction::South));
1098 }
1099
1100 #[test]
1101 fn light_queue_entry_increase_entries_match_vanilla_bits() {
1102 let emission = LightQueueEntry::increase_light_from_emission(15, true);
1103 assert_eq!(emission.raw(), 4095);
1104 assert_eq!(emission.level(), 15);
1105 assert!(emission.is_from_empty_shape());
1106 assert!(emission.is_increase_from_emission());
1107
1108 let skip_up = LightQueueEntry::increase_skip_one_direction(10, false, Direction::Up);
1109 assert_eq!(skip_up.raw(), 986);
1110 assert!(!skip_up.is_from_empty_shape());
1111 assert!(!skip_up.is_increase_from_emission());
1112 assert!(!skip_up.should_propagate_in_direction(Direction::Up));
1113 assert!(skip_up.should_propagate_in_direction(Direction::Down));
1114
1115 let east_only = LightQueueEntry::increase_only_one_direction(4, true, Direction::East);
1116 assert_eq!(east_only.raw(), 1540);
1117 assert!(east_only.is_from_empty_shape());
1118 assert!(east_only.should_propagate_in_direction(Direction::East));
1119 assert!(!east_only.should_propagate_in_direction(Direction::West));
1120 }
1121
1122 #[test]
1123 fn light_queue_entry_sky_source_entry_selects_horizontal_and_down_directions() {
1124 let entry = LightQueueEntry::increase_sky_source_in_directions(&[
1125 Direction::Down,
1126 Direction::North,
1127 Direction::West,
1128 ]);
1129
1130 assert_eq!(entry.raw(), 351);
1131 assert_eq!(entry.level(), 15);
1132 assert!(entry.should_propagate_in_direction(Direction::Down));
1133 assert!(!entry.should_propagate_in_direction(Direction::Up));
1134 assert!(entry.should_propagate_in_direction(Direction::North));
1135 assert!(!entry.should_propagate_in_direction(Direction::South));
1136 assert!(entry.should_propagate_in_direction(Direction::West));
1137 assert!(!entry.should_propagate_in_direction(Direction::East));
1138 }
1139
1140 #[test]
1141 fn light_queue_entry_masks_levels_like_vanilla() {
1142 let entry = LightQueueEntry::increase_light_from_emission(31, false);
1143
1144 assert_eq!(entry.level(), 15);
1145 assert_eq!(entry.raw(), 0b11_1111_0000 | 0b1000_0000_0000 | 15);
1146 }
1147
1148 #[test]
1149 fn light_propagation_queue_preserves_fifo_order() {
1150 let first_pos = BlockPos::new(1, 2, 3);
1151 let second_pos = BlockPos::new(4, 5, 6);
1152 let first_entry = LightQueueEntry::decrease_all_directions(3);
1153 let second_entry =
1154 LightQueueEntry::increase_skip_one_direction(12, false, Direction::North);
1155 let mut queue = LightPropagationQueue::new();
1156
1157 queue.enqueue(first_pos, first_entry);
1158 queue.enqueue(second_pos, second_entry);
1159
1160 assert_eq!(queue.len(), 2);
1161 assert_eq!(
1162 queue.dequeue(),
1163 Some(QueuedLightUpdate {
1164 block_pos: first_pos,
1165 entry: first_entry,
1166 })
1167 );
1168 assert_eq!(
1169 queue.dequeue(),
1170 Some(QueuedLightUpdate {
1171 block_pos: second_pos,
1172 entry: second_entry,
1173 })
1174 );
1175 assert_eq!(queue.dequeue(), None);
1176 assert!(queue.is_empty());
1177 }
1178
1179 #[test]
1180 fn light_propagation_queues_keep_increase_and_decrease_work_separate() {
1181 let decrease_pos = BlockPos::new(1, 2, 3);
1182 let increase_pos = BlockPos::new(4, 5, 6);
1183 let decrease_entry = LightQueueEntry::decrease_all_directions(4);
1184 let increase_entry = LightQueueEntry::increase_only_one_direction(9, true, Direction::East);
1185 let mut queues = LightPropagationQueues::new();
1186
1187 assert!(!queues.has_work());
1188
1189 queues.enqueue_decrease(decrease_pos, decrease_entry);
1190 queues.enqueue_increase(increase_pos, increase_entry);
1191
1192 assert!(queues.has_work());
1193 assert_eq!(
1194 queues.dequeue_increase(),
1195 Some(QueuedLightUpdate {
1196 block_pos: increase_pos,
1197 entry: increase_entry,
1198 })
1199 );
1200 assert!(queues.has_work());
1201 assert_eq!(
1202 queues.dequeue_decrease(),
1203 Some(QueuedLightUpdate {
1204 block_pos: decrease_pos,
1205 entry: decrease_entry,
1206 })
1207 );
1208 assert!(!queues.has_work());
1209 }
1210
1211 #[test]
1212 fn oversized_lease_preserves_parked_pool_entry() {
1213 let mut oversized = PooledPackedLightQueues::take();
1214 for _ in 0..=POOLED_PACKED_QUEUES_MAX_ENTRIES {
1215 oversized.enqueue_increase(packed_entry(15));
1216 }
1217 assert!(oversized.total_capacity() > POOLED_PACKED_QUEUES_MAX_ENTRIES);
1218
1219 let mut parked = PooledPackedLightQueues::take();
1222 for _ in 0..=PACKED_LIGHT_QUEUE_MIN_CAPACITY {
1223 parked.enqueue_increase(packed_entry(15));
1224 }
1225 let parked_capacity = parked.total_capacity();
1226 assert!(parked_capacity > 2 * PACKED_LIGHT_QUEUE_MIN_CAPACITY);
1227 drop(parked);
1228
1229 drop(oversized);
1230
1231 assert_eq!(
1232 PooledPackedLightQueues::take().total_capacity(),
1233 parked_capacity
1234 );
1235 }
1236}