Skip to main content

mcproto_types/
lightdata.rs

1//! Chunk-section sky and block lighting data.
2
3use std::io::{Read, Write};
4
5use mcproto_codec::{
6    error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason},
7    io::{read_exact_counted, write_all_counted},
8    varint::{VarIntRead, VarIntWrite},
9};
10
11use crate::{BitSet, PrefixedArray, TypeCodec};
12
13/// Number of packed bytes in one light array.
14pub const LIGHT_ARRAY_LENGTH: usize = 2048;
15
16/// Number of four-bit light values represented by one [`LightArray`].
17pub const LIGHT_VALUES_PER_ARRAY: usize = LIGHT_ARRAY_LENGTH * 2;
18
19/// Packed light levels for one 16x16x16 chunk section.
20///
21/// The wire representation is a VarInt length of exactly 2048 followed by
22/// 2048 bytes. Each byte stores two light levels, with the lower-indexed value
23/// in the low nibble. Every light level is therefore in the range `0..=15`.
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub struct LightArray(
26    /// The 2048 packed light bytes.
27    pub [u8; LIGHT_ARRAY_LENGTH],
28);
29
30impl LightArray {
31    /// Returns the light level at a packed value index from `0` through `4095`.
32    #[must_use]
33    pub fn light_level(&self, index: usize) -> Option<u8> {
34        if index >= LIGHT_VALUES_PER_ARRAY {
35            return None;
36        }
37        let byte = self.0[index / 2];
38        Some(if index % 2 == 0 {
39            byte & 0x0f
40        } else {
41            byte >> 4
42        })
43    }
44
45    /// Returns the packed 2048-byte payload without its length prefix.
46    #[must_use]
47    pub const fn as_bytes(&self) -> &[u8; LIGHT_ARRAY_LENGTH] {
48        &self.0
49    }
50
51    /// Extracts the packed 2048-byte payload.
52    #[must_use]
53    pub fn into_bytes(self) -> [u8; LIGHT_ARRAY_LENGTH] {
54        self.0
55    }
56}
57
58impl Default for LightArray {
59    fn default() -> Self {
60        Self([0; LIGHT_ARRAY_LENGTH])
61    }
62}
63
64impl From<[u8; LIGHT_ARRAY_LENGTH]> for LightArray {
65    fn from(bytes: [u8; LIGHT_ARRAY_LENGTH]) -> Self {
66        Self(bytes)
67    }
68}
69
70impl TypeCodec for LightArray {
71    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
72        let prefix_size = writer
73            .write_varint_with_size(LIGHT_ARRAY_LENGTH as i32)
74            .map_err(|error| error.with_context(CodecKind::LightArray))?;
75        write_all_counted(writer, &self.0, CodecKind::LightArray, prefix_size)
76    }
77
78    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
79        let (length, prefix_size) = reader
80            .read_varint_with_size()
81            .map_err(|error| error.with_context(CodecKind::LightArray))?;
82        if length < 0 {
83            return Err(CodecError::invalid_encoding(
84                CodecKind::LightArray,
85                prefix_size,
86                InvalidEncodingReason::NegativeLength { value: length },
87            ));
88        }
89        if length as usize != LIGHT_ARRAY_LENGTH {
90            return Err(CodecError::invalid_encoding(
91                CodecKind::LightArray,
92                prefix_size,
93                InvalidEncodingReason::ArrayLengthMismatch {
94                    expected: LIGHT_ARRAY_LENGTH,
95                    actual: length as usize,
96                },
97            ));
98        }
99
100        let mut bytes = [0; LIGHT_ARRAY_LENGTH];
101        read_exact_counted(reader, &mut bytes, CodecKind::LightArray, prefix_size)?;
102        Ok(Self(bytes))
103    }
104}
105
106/// Lighting masks and packed light arrays for a chunk column.
107///
108/// Mask bit zero addresses the section immediately below the world's minimum
109/// height. Subsequent bits address chunk sections from bottom to top, ending
110/// with the section immediately above the world's maximum height. Sky and
111/// block arrays are ordered by their corresponding set bits, least-significant
112/// bit first. The empty masks identify sections whose light data is all zero.
113///
114/// Encoding and decoding reject a sky or block array count that differs from
115/// the number of set bits in its data mask.
116///
117/// # Examples
118///
119/// ```
120/// use mcproto_types::{LightData, TypeCodec};
121///
122/// let light = LightData::default();
123/// let mut encoded = Vec::new();
124/// light.encode(&mut encoded)?;
125/// assert_eq!(encoded, [0, 0, 0, 0, 0, 0]);
126///
127/// let mut input = encoded.as_slice();
128/// assert_eq!(LightData::decode(&mut input)?, light);
129/// assert!(input.is_empty());
130/// # Ok::<(), mcproto_codec::error::CodecError>(())
131/// ```
132///
133/// See the official [Light Data] protocol documentation.
134///
135/// [Light Data]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Light_Data
136#[derive(Debug, Clone, PartialEq, Eq, Default)]
137pub struct LightData {
138    /// Sections with entries in [`sky_light_arrays`](Self::sky_light_arrays).
139    pub sky_light_mask: BitSet,
140    /// Sections with entries in [`block_light_arrays`](Self::block_light_arrays).
141    pub block_light_mask: BitSet,
142    /// Sections whose sky light data consists entirely of zeroes.
143    pub empty_sky_light_mask: BitSet,
144    /// Sections whose block light data consists entirely of zeroes.
145    pub empty_block_light_mask: BitSet,
146    /// One packed array for each set bit in [`sky_light_mask`](Self::sky_light_mask).
147    pub sky_light_arrays: PrefixedArray<LightArray>,
148    /// One packed array for each set bit in [`block_light_mask`](Self::block_light_mask).
149    pub block_light_arrays: PrefixedArray<LightArray>,
150}
151
152impl LightData {
153    /// Returns the number of sky light arrays required by the sky mask.
154    #[must_use]
155    pub fn expected_sky_light_array_count(&self) -> usize {
156        set_bit_count(&self.sky_light_mask)
157    }
158
159    /// Returns the number of block light arrays required by the block mask.
160    #[must_use]
161    pub fn expected_block_light_array_count(&self) -> usize {
162        set_bit_count(&self.block_light_mask)
163    }
164
165    fn validate_array_counts(&self, operation: CodecOperation) -> Result<(), CodecError> {
166        validate_array_count(
167            self.expected_sky_light_array_count(),
168            self.sky_light_arrays.len(),
169            operation,
170        )?;
171        validate_array_count(
172            self.expected_block_light_array_count(),
173            self.block_light_arrays.len(),
174            operation,
175        )
176    }
177}
178
179impl TypeCodec for LightData {
180    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
181        self.validate_array_counts(CodecOperation::Write)?;
182        self.sky_light_mask
183            .encode(writer)
184            .map_err(|error| error.with_context(CodecKind::LightData))?;
185        self.block_light_mask
186            .encode(writer)
187            .map_err(|error| error.with_context(CodecKind::LightData))?;
188        self.empty_sky_light_mask
189            .encode(writer)
190            .map_err(|error| error.with_context(CodecKind::LightData))?;
191        self.empty_block_light_mask
192            .encode(writer)
193            .map_err(|error| error.with_context(CodecKind::LightData))?;
194        self.sky_light_arrays
195            .encode(writer)
196            .map_err(|error| error.with_context(CodecKind::LightData))?;
197        self.block_light_arrays
198            .encode(writer)
199            .map_err(|error| error.with_context(CodecKind::LightData))
200    }
201
202    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
203        let value = Self {
204            sky_light_mask: decode_field(reader)?,
205            block_light_mask: decode_field(reader)?,
206            empty_sky_light_mask: decode_field(reader)?,
207            empty_block_light_mask: decode_field(reader)?,
208            sky_light_arrays: decode_field(reader)?,
209            block_light_arrays: decode_field(reader)?,
210        };
211        value.validate_array_counts(CodecOperation::Read)?;
212        Ok(value)
213    }
214}
215
216fn decode_field<T: TypeCodec>(reader: &mut impl Read) -> Result<T, CodecError> {
217    T::decode(reader).map_err(|error| error.with_context(CodecKind::LightData))
218}
219
220fn set_bit_count(mask: &BitSet) -> usize {
221    mask.0.iter().map(|word| word.count_ones() as usize).sum()
222}
223
224fn validate_array_count(
225    expected: usize,
226    actual: usize,
227    operation: CodecOperation,
228) -> Result<(), CodecError> {
229    if expected == actual {
230        return Ok(());
231    }
232    Err(CodecError::invalid_encoding_for_operation(
233        CodecKind::LightData,
234        operation,
235        0,
236        InvalidEncodingReason::ArrayLengthMismatch { expected, actual },
237    ))
238}