1use std::{collections::hash_map, path::Path};
22
23use crate::asset::{Resource, ResourceData};
24use fxhash::FxHashMap;
25use fyrox_core::visitor::BinaryBlob;
26
27use crate::core::{
28 algebra::Vector2, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
29};
30
31use super::*;
32
33const CHUNK_WIDTH: usize = 16;
34const CHUNK_HEIGHT: usize = 16;
35const WIDTH_BITS: i32 = (CHUNK_WIDTH - 1) as i32;
36const HEIGHT_BITS: i32 = (CHUNK_HEIGHT - 1) as i32;
37
38pub type TileMapDataResource = Resource<TileMapData>;
40
41fn tile_position_to_chunk_position(position: Vector2<i32>) -> (Vector2<i32>, Vector2<i32>) {
45 let x = position.x;
46 let y = position.y;
47 let x_chunk = x & !WIDTH_BITS;
48 let y_chunk = y & !HEIGHT_BITS;
49 (
50 Vector2::new(x_chunk, y_chunk),
51 Vector2::new(x - x_chunk, y - y_chunk),
52 )
53}
54
55#[derive(Clone, Debug, Reflect)]
56struct Chunk([TileDefinitionHandle; CHUNK_WIDTH * CHUNK_HEIGHT]);
57
58impl Visit for Chunk {
59 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
60 if visitor.is_reading() {
61 let mut data = Vec::default();
62 BinaryBlob { vec: &mut data }.visit(name, visitor)?;
63 if data.len() != CHUNK_WIDTH * CHUNK_HEIGHT {
64 return Err(VisitError::User(
65 "Wrong number of handles in a chunk".into(),
66 ));
67 }
68 self.0
69 .clone_from_slice(&data[0..CHUNK_WIDTH * CHUNK_HEIGHT]);
70 Ok(())
71 } else {
72 BinaryBlob {
73 vec: &mut self.0.to_vec(),
74 }
75 .visit(name, visitor)
76 }
77 }
78}
79
80impl Default for Chunk {
81 fn default() -> Self {
82 Self([TileDefinitionHandle::EMPTY; CHUNK_WIDTH * CHUNK_HEIGHT])
83 }
84}
85
86impl std::ops::Index<Vector2<i32>> for Chunk {
87 type Output = TileDefinitionHandle;
88
89 fn index(&self, index: Vector2<i32>) -> &Self::Output {
90 let x: usize = index.x.try_into().unwrap();
91 let y: usize = index.y.try_into().unwrap();
92 &self.0[x + y * CHUNK_WIDTH]
93 }
94}
95
96impl std::ops::IndexMut<Vector2<i32>> for Chunk {
97 fn index_mut(&mut self, index: Vector2<i32>) -> &mut Self::Output {
98 let x: usize = index.x.try_into().unwrap();
99 let y: usize = index.y.try_into().unwrap();
100 &mut self.0[x + y * CHUNK_WIDTH]
101 }
102}
103
104impl Chunk {
105 fn iter(&self, offset: Vector2<i32>) -> ChunkIterator {
106 ChunkIterator {
107 position: Vector2::new(0, 0),
108 chunk: self,
109 offset,
110 }
111 }
112 fn is_empty(&self) -> bool {
113 self.0.iter().all(|h| *h == TileDefinitionHandle::EMPTY)
114 }
115}
116
117struct ChunkIterator<'a> {
118 position: Vector2<i32>,
119 offset: Vector2<i32>,
120 chunk: &'a Chunk,
121}
122
123impl Iterator for ChunkIterator<'_> {
124 type Item = (Vector2<i32>, TileDefinitionHandle);
125
126 fn next(&mut self) -> Option<Self::Item> {
127 loop {
128 if self.position.y >= CHUNK_HEIGHT as i32 {
129 return None;
130 }
131 let result_position = self.position;
132 let result = self.chunk[result_position];
133 if self.position.x < (CHUNK_WIDTH - 1) as i32 {
134 self.position.x += 1;
135 } else {
136 self.position.x = 0;
137 self.position.y += 1;
138 }
139 if !result.is_empty() {
140 return Some((result_position + self.offset, result));
141 }
142 }
143 }
144}
145
146pub struct TileMapDataIterator<'a, P: 'a> {
148 predicate: P,
149 map_iter: hash_map::Iter<'a, Vector2<i32>, Chunk>,
150 chunk_iter: Option<ChunkIterator<'a>>,
151}
152
153impl<P: FnMut(Vector2<i32>) -> bool> Iterator for TileMapDataIterator<'_, P> {
154 type Item = (Vector2<i32>, TileDefinitionHandle);
155 fn next(&mut self) -> Option<Self::Item> {
156 let chunk_iter = match &mut self.chunk_iter {
157 Some(iter) => iter,
158 None => self.next_chunk()?,
159 };
160 if let Some(result) = chunk_iter.next() {
161 Some(result)
162 } else {
163 self.next_chunk()?.next()
164 }
165 }
166}
167
168impl<'a, P: FnMut(Vector2<i32>) -> bool> TileMapDataIterator<'a, P> {
169 fn next_chunk(&mut self) -> Option<&mut ChunkIterator<'a>> {
170 loop {
171 let (pos, chunk) = self.map_iter.next()?;
172 if (self.predicate)(*pos) {
173 return Some(self.chunk_iter.insert(chunk.iter(*pos)));
174 }
175 }
176 }
177}
178
179#[derive(Clone, Default, Debug, Reflect, TypeUuidProvider, ComponentProvider)]
181#[type_uuid(id = "a8e4b6b4-c1bd-4ed9-a753-0d5a3dfe1729")]
182pub struct TileMapData {
183 content: FxHashMap<Vector2<i32>, Chunk>,
184}
185
186impl Visit for TileMapData {
187 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
188 if !visitor.is_reading() {
189 self.shrink_to_fit();
190 }
191 self.content.visit(name, visitor)
192 }
193}
194
195impl ResourceData for TileMapData {
196 fn type_uuid(&self) -> Uuid {
197 <Self as TypeUuidProvider>::type_uuid()
198 }
199
200 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
201 let mut visitor = Visitor::new();
202 self.visit("TileMapData", &mut visitor)?;
203 visitor.save_binary(path)?;
204 Ok(())
205 }
206
207 fn can_be_saved(&self) -> bool {
208 false
209 }
210}
211
212impl TileSource for TileMapData {
213 fn transformation(&self) -> OrthoTransformation {
214 OrthoTransformation::default()
215 }
216
217 fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
218 self.get(position)
219 }
220}
221
222impl BoundedTileSource for TileMapData {
223 fn bounding_rect(&self) -> OptionTileRect {
224 let mut rect = OptionTileRect::default();
225 for (pos, _) in self.iter() {
226 rect.push(pos);
227 }
228 rect
229 }
230}
231
232impl TileMapData {
233 pub fn iter(&self) -> impl Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + '_ {
235 let map_iter = self.content.iter();
236 TileMapDataIterator {
237 predicate: |_| true,
238 map_iter,
239 chunk_iter: None,
240 }
241 }
242 pub fn bounded_iter(
244 &self,
245 bounds: OptionTileRect,
246 ) -> impl Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + '_ {
247 let map_iter = self.content.iter();
248 TileMapDataIterator {
249 predicate: move |pos: Vector2<i32>| {
250 bounds.intersects(TileRect::new(
251 pos.x,
252 pos.y,
253 CHUNK_WIDTH as i32,
254 CHUNK_HEIGHT as i32,
255 ))
256 },
257 map_iter,
258 chunk_iter: None,
259 }
260 }
261 pub fn swap_tiles(&mut self, tiles: &mut TilesUpdate) {
265 for (p, h) in tiles.iter_mut() {
266 *h = self.replace(*p, *h);
267 }
268 }
269 pub fn get(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
271 let (chunk, pos) = tile_position_to_chunk_position(position);
272 let chunk = self.content.get(&chunk)?;
273 let handle = chunk[pos];
274 if handle.is_empty() {
275 None
276 } else {
277 Some(handle)
278 }
279 }
280 pub fn replace(
283 &mut self,
284 position: Vector2<i32>,
285 value: Option<TileDefinitionHandle>,
286 ) -> Option<TileDefinitionHandle> {
287 let (chunk, pos) = tile_position_to_chunk_position(position);
288 if let Some(chunk) = self.content.get_mut(&chunk) {
289 let handle = &mut chunk[pos];
290 let result = *handle;
291 *handle = value.unwrap_or(TileDefinitionHandle::EMPTY);
292 if result.is_empty() {
293 None
294 } else {
295 Some(result)
296 }
297 } else if let Some(value) = value {
298 let chunk = self.content.entry(chunk).or_default();
299 chunk[pos] = value;
300 None
301 } else {
302 None
303 }
304 }
305 pub fn set(&mut self, position: Vector2<i32>, value: TileDefinitionHandle) {
307 let (chunk, pos) = tile_position_to_chunk_position(position);
308 let chunk = self.content.entry(chunk).or_default();
309 chunk[pos] = value;
310 }
311 pub fn remove(&mut self, position: Vector2<i32>) {
313 let (chunk, pos) = tile_position_to_chunk_position(position);
314 if let Some(chunk) = self.content.get_mut(&chunk) {
315 chunk[pos] = TileDefinitionHandle::EMPTY;
316 }
317 }
318 pub fn shrink_to_fit(&mut self) {
320 self.content.retain(|_, v| !v.is_empty())
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use std::cmp::Ordering;
327
328 use super::*;
329
330 fn v(x: i32, y: i32) -> Vector2<i32> {
331 Vector2::new(x, y)
332 }
333
334 fn h(a: i16, b: i16, c: i16, d: i16) -> TileDefinitionHandle {
335 TileDefinitionHandle::new(a, b, c, d)
336 }
337
338 fn v_ord(a: &Vector2<i32>, b: &Vector2<i32>) -> Ordering {
339 a.y.cmp(&b.y).reverse().then(a.x.cmp(&b.x))
340 }
341
342 #[test]
343 fn position_to_chunk() {
344 assert_eq!(
345 tile_position_to_chunk_position(v(16, 16)),
346 (v(16, 16), v(0, 0))
347 );
348 assert_eq!(tile_position_to_chunk_position(v(0, 0)), (v(0, 0), v(0, 0)));
349 assert_eq!(
350 tile_position_to_chunk_position(v(-5, 5)),
351 (v(-16, 0), v(11, 5))
352 );
353 assert_eq!(
354 tile_position_to_chunk_position(v(-16, 5)),
355 (v(-16, 0), v(0, 5))
356 );
357 assert_eq!(
358 tile_position_to_chunk_position(v(-17, 5)),
359 (v(-32, 0), v(15, 5))
360 );
361 }
362 #[test]
363 fn create_chunks() {
364 let mut data = TileMapData::default();
365 let coords = vec![
366 (v(0, 0), h(1, 2, 3, 4)),
367 (v(-1, -2), h(1, 2, 3, 0)),
368 (v(16, 16), h(1, 2, 3, 5)),
369 (v(-1, -1), h(1, 2, 3, 6)),
370 (v(-17, 0), h(1, 2, 3, 7)),
371 ];
372 for (pos, handle) in coords.iter() {
373 data.set(*pos, *handle);
374 }
375 let mut coords = coords
376 .into_iter()
377 .map(|(p, _)| tile_position_to_chunk_position(p).0)
378 .collect::<Vec<_>>();
379 coords.sort_by(v_ord);
380 coords.dedup();
381 let mut result = data.content.keys().copied().collect::<Vec<_>>();
382 result.sort_by(v_ord);
383 assert_eq!(result, coords);
384 }
385 #[test]
386 fn iter_full_chunk() {
387 let mut data = TileMapData::default();
388 let mut required = FxHashSet::default();
389 let mut extra = Vec::default();
390 for x in 0..CHUNK_WIDTH as i32 {
391 for y in 0..CHUNK_HEIGHT as i32 {
392 data.set(v(x, y), h(0, 0, 0, 0));
393 required.insert(v(x, y));
394 }
395 }
396 for (result, _) in data.iter() {
397 if !required.remove(&result) {
398 extra.push(result);
399 }
400 }
401 let required = required.into_iter().collect::<Vec<_>>();
402 assert_eq!((required, extra), (vec![], vec![]));
403 }
404 #[test]
405 fn iter() {
406 let mut data = TileMapData::default();
407 let mut coords = vec![
408 (v(0, 0), h(1, 2, 3, 4)),
409 (v(-1, -2), h(1, 2, 3, 0)),
410 (v(16, 16), h(1, 2, 3, 5)),
411 (v(-1, -1), h(1, 2, 3, 6)),
412 (v(-17, 0), h(1, 2, 3, 7)),
413 ];
414 for (pos, handle) in coords.iter() {
415 data.set(*pos, *handle);
416 }
417 let mut result = data.iter().collect::<Vec<_>>();
418 result.sort_by(|(a, _), (b, _)| v_ord(a, b));
419 coords.sort_by(|(a, _), (b, _)| v_ord(a, b));
420 assert_eq!(result, coords);
421 }
422}