1use std::{
2 collections::HashMap,
3 io::Write,
4 time::{SystemTime, UNIX_EPOCH},
5};
6
7use crate::{chunk::PendingChunk, CompressionType, McaError, REGION_SIZE, SECTOR_SIZE};
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
11pub struct RegionWriter {
12 chunks: Vec<PendingChunk>,
13}
14
15impl RegionWriter {
16 fn get_current_timestamp() -> u32 {
18 let start = SystemTime::now();
19 let since_the_epoch = start.duration_since(UNIX_EPOCH).unwrap().as_secs() as u32;
20 since_the_epoch.to_be()
21 }
22
23 pub fn new() -> RegionWriter {
25 RegionWriter { chunks: vec![] }
26 }
27
28 pub fn push_chunk<B>(&mut self, raw_data: &[u8], coordinate: (B, B)) -> Result<(), McaError>
33 where
34 B: Into<u8>,
35 {
36 let chunk = PendingChunk::new(
37 &raw_data,
38 CompressionType::LZ4,
39 RegionWriter::get_current_timestamp(),
40 coordinate,
41 )?;
42 self.chunks.push(chunk);
43
44 Ok(())
45 }
46
47 pub fn push_chunk_with_compression<B>(
50 &mut self,
51 raw_data: &[u8],
52 coordinate: (B, B),
53 compression_type: CompressionType,
54 ) -> Result<(), McaError>
55 where
56 B: Into<u8>,
57 {
58 let chunk = PendingChunk::new(
59 &raw_data,
60 compression_type,
61 RegionWriter::get_current_timestamp(),
62 coordinate,
63 )?;
64 self.chunks.push(chunk);
65
66 Ok(())
67 }
68
69 pub fn push_pending_chunk(&mut self, chunk: PendingChunk) {
71 self.chunks.push(chunk)
72 }
73
74 pub fn write<'a, W>(&self, w: &'a mut W) -> Result<(), McaError>
91 where
92 W: Write,
93 {
94 let mut chunk_offsets: HashMap<(u8, u8), usize> = HashMap::new();
96 let mut chunk_map: HashMap<(u8, u8), &PendingChunk> = HashMap::new(); let mut curr_chunk_offset: usize = SECTOR_SIZE * 2; let mut payloads: Vec<u8> = vec![];
100
101 for chunk in self.chunks.iter() {
102 let len_b = (chunk.compressed_data.len() as u32 + 1).to_be_bytes(); let len = [len_b[0], len_b[1], len_b[2], len_b[3]];
104
105 let compression = chunk.compression.to_u8();
106
107 let mut payload_len = 0;
108 payload_len += payloads.write(&len)?;
109 payload_len += payloads.write(&[compression])?;
110 payload_len += payloads.write(&chunk.compressed_data)?;
111
112 let remaining = SECTOR_SIZE - (payload_len % SECTOR_SIZE);
114 let padding = std::iter::repeat(0u8).take(remaining).collect::<Vec<u8>>();
115 payload_len += payloads.write(&padding)?;
116
117 chunk_offsets.insert(chunk.coordinate, curr_chunk_offset);
118 chunk_map.insert(chunk.coordinate, chunk);
119
120 curr_chunk_offset = curr_chunk_offset + payload_len;
122 }
123
124 for x in 0..REGION_SIZE {
126 for z in 0..REGION_SIZE {
127 let offset = match chunk_offsets.get(&(z as u8, x as u8)) {
128 Some(offset) => offset,
129 None => {
130 w.write(&[0, 0, 0, 0])?;
131 continue;
132 }
133 };
134
135 let chunk = chunk_map.get(&(z as u8, x as u8)).unwrap(); let offset_bytes = {
138 let be = ((*offset / SECTOR_SIZE) as u32).to_be_bytes();
139 [be[1], be[2], be[3]]
140 };
141
142 w.write(&offset_bytes)?;
143
144 let sector_count = ((chunk.compressed_data.len() + 4 + 1) as f32
145 / SECTOR_SIZE as f32)
146 .ceil() as u8;
147
148 w.write(&[sector_count])?;
149 }
150 }
151
152 for x in 0..REGION_SIZE {
154 for z in 0..REGION_SIZE {
155 match &self.chunks.get(x * REGION_SIZE + z) {
156 Some(chunk) => {
157 let timestamp = {
158 let b = chunk.timestamp.to_be_bytes();
159 [b[0], b[1], b[2], b[3]]
160 };
161 w.write(×tamp)?
162 }
163 None => w.write(&[0, 0, 0, 0])?,
164 };
165 }
166 }
167
168 w.write(&payloads)?;
169
170 w.flush()?;
171
172 Ok(())
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::RegionReader;
180
181 const REGION: &[u8] = include_bytes!("../benches/r.0.0.mca");
182
183 #[test]
184 fn round_trip() {
185 let region = RegionReader::new(REGION).unwrap();
186 let mut writer = RegionWriter::new();
187
188 for (idx, chunk) in region.iter().enumerate() {
189 let chunk = match chunk.unwrap() {
190 Some(data) => data,
191 None => continue,
192 };
193
194 let data = chunk.decompress().unwrap();
195 writer
196 .push_chunk_with_compression(
197 &data,
198 ((idx % REGION_SIZE) as u8, (idx / REGION_SIZE) as u8),
199 CompressionType::Zlib,
200 )
201 .unwrap();
202 }
203
204 let mut buf = vec![];
205 writer.write(&mut buf).unwrap();
206
207 let new_region = RegionReader::new(&buf).unwrap();
208 let chunk = new_region.get_chunk(0, 0).unwrap().unwrap();
209
210 let data = chunk.decompress().unwrap();
211 let _ = sculk::chunk::Chunk::from_bytes(&data).unwrap();
212 }
213}