fdm_toolkit/
chunk.rs

1use crate::util::{CompressedChunkBytesIter, FillParams, Rect4};
2use crate::err::ChunkReadError;
3use crate::world::World;
4
5use core::fmt::{Formatter, Display, Result as FmtResult, Debug};
6use core::convert::{AsRef, From, Into};
7use core::marker::PhantomData;
8use core::iter::IntoIterator;
9use core::default::Default;
10use core::borrow::Borrow;
11use core::mem::transmute;
12use core::ops::Deref;
13use std::sync::Arc;
14use std::vec::Vec;
15
16
17
18
19
20pub trait ChunkData {
21	/// Deserializes a slice of bytes into chunk-data.
22	fn from_bytes(bytes:&[u8]) -> Result<Self, ChunkReadError> where Self:Sized;
23	
24	/// Generate the equivalent decompressed chunk-data.
25	fn decompressed(&self)                                  -> Chunk;
26	/// (Try to) Get the ID of a block at a given coordinate in 4D space.
27	fn get_block(&self, loc:(usize, usize, usize, usize)) -> Option<u8>;
28}
29
30
31
32
33
34/// Represents a 8*128*8*8 chunk of the world.
35#[derive(PartialEq, Clone, Hash, Eq)]
36#[repr(transparent)]
37pub struct CompressedChunk<'a>(Arc<[BlockGroup]>, PhantomData<&'a ()>);
38
39impl<'a> CompressedChunk<'a> {
40	/// Creates a new [`CompressedChunk`] filled entirely with a block of the specified ID.
41	pub fn filled_with(block_id:u8) -> Self { Self(Arc::new([]), PhantomData).with_remaining_filled(block_id) }
42	
43	/// Fills the remaining (uninitialized) space in the chunk with a block of the specified ID.
44	pub fn with_remaining_filled(mut self, block_id:u8) -> Self {
45		let mut remaining = Chunk::HYPERVOLUME;
46		let mut vec       = Vec::new();
47		
48		for group in &*self.0 {
49			remaining -= group.span as usize;
50			vec.push(*group);
51		}
52		
53		while remaining > 0 {
54			let span = if remaining < 255 { let r = remaining as u8; remaining = 0; r }
55			           else               { remaining -= 255; 255 };
56			vec.push(BlockGroup {block_id, span});
57		}
58		
59		self.0 = vec.into();
60		self
61	}
62	
63	/// Creates a new [`CompressedChunk`] from anything that can be turned into a [`CompressedChunk`],
64	///  via [`Into`].
65	/// 
66	/// The benefit of using this method, over [`CompressedChunk::from`], is that any empty space remaining
67	///  in the chunk-data is filled with air before the [`CompressedChunk`] is returned.
68	pub fn new<T:Into<Self>>(v:T) -> Self { v.into().with_remaining_filled(0) }
69	
70	/// Returns an iterator over the bytes in this [`CompressedChunk`].
71	pub const fn iter_bytes(&self) -> CompressedChunkBytesIter { CompressedChunkBytesIter::new(self) }
72}
73
74impl<'a> IntoIterator for &'a CompressedChunk<'a> {
75	type IntoIter = ::core::slice::Iter<'a, BlockGroup>;
76	type Item     = &'a BlockGroup;
77	
78	fn into_iter(self) -> Self::IntoIter { (&self.0).into_iter() }
79}
80
81impl<'a> ChunkData for CompressedChunk<'a> {
82	/// Attempts to convert byte-sequence representing one or more [`BlockGroup`]s into a [`CompressedChunk`].
83	fn from_bytes(bytes:&[u8]) -> Result<Self, ChunkReadError> {
84		let l = bytes.len();
85		if l % 2 != 0 { return Err(ChunkReadError::BrokenIdRunlengthPair(l)); }
86		
87		let mut ct:usize = 0;
88		
89		Ok(Self(
90			bytes.chunks_exact(2)
91				.map(|pair| {
92					let pair = match pair {
93						&[block_id, span] => BlockGroup {block_id, span},
94						_ => unreachable!()
95					};
96					
97					ct += pair.span as usize;
98					if ct > Chunk::HYPERVOLUME {
99						return Err(ChunkReadError::TooMuchData {
100							last_group: pair,
101							excess: ct-Chunk::HYPERVOLUME
102						});
103					}
104					Ok(pair)
105				}).collect::<Result<Arc<[BlockGroup]>, _>>()?,
106				PhantomData
107		).with_remaining_filled(0))
108	}
109	
110	fn decompressed(&self) -> Chunk {
111		let mut chunk = Chunk::filled_with(0);
112		
113		let mut pos   = 0;
114		for group in &*self {
115			let mut n = group.span;
116			while n > 0 {
117				chunk.0[pos / 8192][(pos/64) % 128][(pos/8) % 8][pos % 8] = group.block_id;
118				
119				pos += 1;
120				n -= 1;
121			}
122		}
123		
124		chunk
125	}
126	
127	fn get_block(&self, _loc:(usize, usize, usize, usize)) -> Option<u8> { todo!() }
128}
129
130impl<'a> Default for CompressedChunk<'a> {
131	fn default() -> Self { Self::filled_with(0) }
132}
133
134impl<'a> Borrow<Arc<[BlockGroup]>> for CompressedChunk<'a> {
135	fn borrow(&self) -> &Arc<[BlockGroup]> { &self.0 }
136}
137
138impl<'a> AsRef<Arc<[BlockGroup]>> for CompressedChunk<'a> {
139	fn as_ref(&self) -> &Arc<[BlockGroup]> { &self.0 }
140}
141
142impl<'a> Borrow<[BlockGroup]> for CompressedChunk<'a> {
143	fn borrow(&self) -> &[BlockGroup] { &self.0 }
144}
145
146impl<'a> AsRef<[BlockGroup]> for CompressedChunk<'a> {
147	fn as_ref(&self) -> &[BlockGroup] { &self.0 }
148}
149
150impl<'a> Debug for CompressedChunk<'a> {
151	fn fmt(&self, f:&mut Formatter) -> FmtResult {
152		for group in &*self.0 { _ = <BlockGroup as Display>::fmt(group, f); }
153		Ok(())
154	}
155}
156
157impl<'a> Deref for CompressedChunk<'a> {
158	type Target = [BlockGroup];
159	
160	fn deref(&self) -> &Self::Target { &self.0 }
161}
162
163impl<'a> From<Arc<[BlockGroup]>> for CompressedChunk<'a> {
164	fn from(v:Arc<[BlockGroup]>) -> Self { Self(v, PhantomData) }
165}
166
167impl<'a> From<Vec<BlockGroup>> for CompressedChunk<'a> {
168	fn from(v:Vec<BlockGroup>) -> Self { Self(v.into(), PhantomData) }
169}
170
171
172
173/// A group of blocks in a chunk.  
174///  (A block ID and length pair.)
175#[derive(PartialEq, Default, Clone, Debug, Hash, Copy, Eq)]
176#[repr(C)]
177pub struct BlockGroup {
178	pub block_id:u8,
179	pub span:u8
180}
181
182impl Display for BlockGroup {
183	fn fmt(&self, f:&mut Formatter) -> FmtResult {
184		write!(f, "[{} * block#{}]", self.span, self.block_id)
185	}
186}
187
188impl From<[u8; 2]> for BlockGroup {
189	fn from(v:[u8; 2]) -> Self { unsafe { transmute(v) } }
190}
191
192impl Into<[u8; 2]> for BlockGroup {
193	fn into(self) -> [u8; 2] { unsafe { transmute(self) } }
194}
195
196
197
198/// Uncompressed chunk-data.
199#[derive(PartialEq, Clone, Debug, Hash, Eq)]
200#[repr(transparent)]
201pub struct Chunk([[[[u8; Self::WETH]; Self::LENGTH]; World::HEIGHT]; Self::WIDTH]);
202
203impl Chunk {
204	/// The hypervolume of a chunk.
205	pub const HYPERVOLUME:usize = Self::WIDTH*World::HEIGHT*Self::LENGTH*Self::WETH;
206	/// The size of a chunk along the Z axis,
207	pub const LENGTH:usize      = 8;
208	/// The size of a chunk along the X axis.
209	pub const WIDTH:usize       = 8;
210	/// The size of a chunk along the W axis.
211	pub const WETH:usize        = 8;
212	
213	/// Creates a new [`Chunk`] filled entirely with a block of the specified ID.
214	pub const fn filled_with(block_id:u8) -> Self { Self([[[[block_id; Self::WETH]; Self::LENGTH]; World::HEIGHT]; Self::WIDTH]) }
215	
216	pub fn compress(&self) -> CompressedChunk {
217		let mut cbt_ct = 0;
218		let mut data   = Vec::new();
219		let mut cbt    = None;
220		
221		let mut pos = 0;
222		loop {
223			if pos >= Self::HYPERVOLUME { break; }
224			
225			let id = self.0[pos / 8192][(pos/64) % 128][(pos/8) % 8][pos % 8];
226			match cbt {
227				Some(pbt) if pbt == id => {
228					cbt_ct += 1;
229					if cbt_ct == 255 || pos == Self::HYPERVOLUME-1 {
230						data.push(BlockGroup {block_id: id, span: cbt_ct});
231						cbt_ct = 0;
232					}
233				}
234				
235				Some(_) | None => {
236					if cbt_ct > 0 {
237						data.push(BlockGroup {block_id: unsafe { cbt.unwrap_unchecked() }, span: cbt_ct});
238					}
239					
240					cbt_ct = 1;
241					cbt = Some(id);
242				}
243			}
244			
245			pos += 1;
246		}
247		
248		CompressedChunk(data.into(), PhantomData)
249	}
250	
251	/// Fills the specified area from point `a` to point `b` with a block of the specified ID.
252	pub fn fill_with_params(&mut self, mut fill_params:FillParams) {
253		#[inline(always)] const fn sort(a:usize, b:usize) -> (usize, usize) { if a > b { (b, a) } else { (a, b) } }
254		
255		let (sx, dx) = sort(fill_params.rect.start.0, fill_params.rect.end.0);
256		let (sy, dy) = sort(fill_params.rect.start.1, fill_params.rect.end.1);
257		let (sz, dz) = sort(fill_params.rect.start.2, fill_params.rect.end.2);
258		let (sw, dw) = sort(fill_params.rect.start.3, fill_params.rect.end.3);
259		
260		let mut x = sx;
261		loop {
262			let mut y = sy;
263			loop {
264				let mut z = sz;
265				loop {
266					let mut w = sw;
267					loop {
268						self.0[x][y][z][w] = fill_params.determiner.as_mut()((x, y, z, w));
269						
270						if w >= dw { break; }
271						w += 1;
272					}
273					
274					if z >= dz { break; }
275					z += 1
276				}
277				
278				if y >= dy { break; }
279				y += 1;
280			}
281			
282			if x >= dx { break; }
283			x += 1;
284		}
285	}
286	
287	pub fn fill(&mut self, block_id:u8, rect:Rect4) {
288		self.fill_with_params(FillParams::solid(block_id, rect));
289	}
290}
291
292impl ChunkData for Chunk {
293	/// Generate the equivalent decompressed chunk-data.
294	/// 
295	/// Because the data stored in this struct is already decompressed,
296	///  this method just returns a clone of the current [`Chunk`] object.
297	fn decompressed(&self) -> Chunk { self.clone() }
298	
299	fn from_bytes(bytes:&[u8]) -> Result<Self, ChunkReadError> where Self:Sized { Ok(CompressedChunk::from_bytes(bytes)?.decompressed()) }
300	
301	fn get_block(&self, loc:(usize, usize, usize, usize)) -> Option<u8> { self.0.get(loc.0)?.get(loc.1)?.get(loc.2)?.get(loc.3).cloned() }
302}
303
304impl Default for Chunk {
305	fn default() -> Self { Self::filled_with(0) }
306}