1use serde::{Deserialize, Serialize};
150use std::collections::{HashMap, VecDeque};
151use std::hash::Hash;
152use wasm_bindgen::prelude::*;
153
154use crate::error::{TileCacheError, WasmError, WasmResult};
155
156pub const DEFAULT_MAX_CACHE_SIZE: usize = 100 * 1024 * 1024;
158
159#[allow(dead_code)]
161pub const DEFAULT_TILE_SIZE: u32 = 256;
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
165pub struct TileCoord {
166 pub level: u32,
168 pub x: u32,
170 pub y: u32,
172}
173
174impl TileCoord {
175 pub const fn new(level: u32, x: u32, y: u32) -> Self {
177 Self { level, x, y }
178 }
179
180 pub const fn parent(self) -> Option<Self> {
182 if self.level == 0 {
183 return None;
184 }
185
186 Some(Self {
187 level: self.level - 1,
188 x: self.x / 2,
189 y: self.y / 2,
190 })
191 }
192
193 pub const fn children(self) -> [Self; 4] {
195 let level = self.level + 1;
196 let x2 = self.x * 2;
197 let y2 = self.y * 2;
198
199 [
200 Self {
201 level,
202 x: x2,
203 y: y2,
204 },
205 Self {
206 level,
207 x: x2 + 1,
208 y: y2,
209 },
210 Self {
211 level,
212 x: x2,
213 y: y2 + 1,
214 },
215 Self {
216 level,
217 x: x2 + 1,
218 y: y2 + 1,
219 },
220 ]
221 }
222
223 pub fn key(&self) -> String {
225 format!("{}/{}/{}", self.level, self.x, self.y)
226 }
227
228 pub fn from_key(key: &str) -> Option<Self> {
230 let parts: Vec<&str> = key.split('/').collect();
231 if parts.len() != 3 {
232 return None;
233 }
234
235 let level = parts[0].parse().ok()?;
236 let x = parts[1].parse().ok()?;
237 let y = parts[2].parse().ok()?;
238
239 Some(Self::new(level, x, y))
240 }
241
242 pub const fn is_valid(&self, max_level: u32, max_x: u32, max_y: u32) -> bool {
244 self.level <= max_level && self.x < max_x && self.y < max_y
245 }
246
247 pub const fn neighbors(&self) -> [Option<Self>; 8] {
249 let level = self.level;
250 let x = self.x;
251 let y = self.y;
252
253 [
254 if x > 0 && y > 0 {
256 Some(Self::new(level, x - 1, y - 1))
257 } else {
258 None
259 },
260 if y > 0 {
262 Some(Self::new(level, x, y - 1))
263 } else {
264 None
265 },
266 if y > 0 {
268 Some(Self::new(level, x + 1, y - 1))
269 } else {
270 None
271 },
272 if x > 0 {
274 Some(Self::new(level, x - 1, y))
275 } else {
276 None
277 },
278 Some(Self::new(level, x + 1, y)),
280 if x > 0 {
282 Some(Self::new(level, x - 1, y + 1))
283 } else {
284 None
285 },
286 Some(Self::new(level, x, y + 1)),
288 Some(Self::new(level, x + 1, y + 1)),
290 ]
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub struct TileBounds {
297 pub min_x: u32,
299 pub min_y: u32,
301 pub max_x: u32,
303 pub max_y: u32,
305}
306
307impl TileBounds {
308 pub const fn new(min_x: u32, min_y: u32, max_x: u32, max_y: u32) -> Self {
310 Self {
311 min_x,
312 min_y,
313 max_x,
314 max_y,
315 }
316 }
317
318 pub const fn contains(&self, coord: &TileCoord) -> bool {
320 coord.x >= self.min_x
321 && coord.x < self.max_x
322 && coord.y >= self.min_y
323 && coord.y < self.max_y
324 }
325
326 pub const fn count(&self) -> u64 {
328 (self.max_x - self.min_x) as u64 * (self.max_y - self.min_y) as u64
329 }
330
331 pub fn iter(&self) -> TileBoundsIter {
333 TileBoundsIter {
334 bounds: *self,
335 current_x: self.min_x,
336 current_y: self.min_y,
337 }
338 }
339}
340
341pub struct TileBoundsIter {
343 bounds: TileBounds,
344 current_x: u32,
345 current_y: u32,
346}
347
348impl Iterator for TileBoundsIter {
349 type Item = (u32, u32);
350
351 fn next(&mut self) -> Option<Self::Item> {
352 if self.current_y >= self.bounds.max_y {
353 return None;
354 }
355
356 let result = (self.current_x, self.current_y);
357
358 self.current_x += 1;
359 if self.current_x >= self.bounds.max_x {
360 self.current_x = self.bounds.min_x;
361 self.current_y += 1;
362 }
363
364 Some(result)
365 }
366}
367
368#[derive(Debug, Clone)]
370pub struct TilePyramid {
371 pub width: u64,
373 pub height: u64,
375 pub tile_width: u32,
377 pub tile_height: u32,
379 pub num_levels: u32,
381 pub tiles_per_level: Vec<(u32, u32)>,
383}
384
385impl TilePyramid {
386 pub fn new(width: u64, height: u64, tile_width: u32, tile_height: u32) -> Self {
388 let mut tiles_per_level = Vec::new();
389 let mut level_width = width;
390 let mut level_height = height;
391 let mut num_levels = 0;
392
393 while level_width > 0 && level_height > 0 {
394 let tiles_x = level_width.div_ceil(u64::from(tile_width)) as u32;
395 let tiles_y = level_height.div_ceil(u64::from(tile_height)) as u32;
396 tiles_per_level.push((tiles_x, tiles_y));
397 num_levels += 1;
398
399 if tiles_x == 1 && tiles_y == 1 {
401 break;
402 }
403
404 level_width /= 2;
405 level_height /= 2;
406 }
407
408 Self {
409 width,
410 height,
411 tile_width,
412 tile_height,
413 num_levels,
414 tiles_per_level,
415 }
416 }
417
418 pub fn tiles_at_level(&self, level: u32) -> Option<(u32, u32)> {
420 self.tiles_per_level.get(level as usize).copied()
421 }
422
423 pub fn bounds_at_level(&self, level: u32) -> Option<TileBounds> {
425 self.tiles_at_level(level)
426 .map(|(tiles_x, tiles_y)| TileBounds::new(0, 0, tiles_x, tiles_y))
427 }
428
429 pub fn is_valid_coord(&self, coord: &TileCoord) -> bool {
431 if coord.level >= self.num_levels {
432 return false;
433 }
434
435 if let Some((tiles_x, tiles_y)) = self.tiles_at_level(coord.level) {
436 coord.x < tiles_x && coord.y < tiles_y
437 } else {
438 false
439 }
440 }
441
442 pub fn total_tiles(&self) -> u64 {
444 self.tiles_per_level
445 .iter()
446 .map(|(x, y)| u64::from(*x) * u64::from(*y))
447 .sum()
448 }
449}
450
451#[derive(Debug, Clone)]
453pub struct CachedTile {
454 pub coord: TileCoord,
456 pub data: Vec<u8>,
458 pub size: usize,
460 pub last_access: f64,
462 pub loaded_at: f64,
464 pub access_count: u64,
466}
467
468impl CachedTile {
469 pub fn new(coord: TileCoord, data: Vec<u8>, timestamp: f64) -> Self {
471 let size = data.len();
472 Self {
473 coord,
474 data,
475 size,
476 last_access: timestamp,
477 loaded_at: timestamp,
478 access_count: 1,
479 }
480 }
481
482 pub fn access(&mut self, timestamp: f64) {
484 self.last_access = timestamp;
485 self.access_count += 1;
486 }
487}
488
489pub struct TileCache {
491 cache: HashMap<String, CachedTile>,
493 lru_queue: VecDeque<String>,
495 current_size: usize,
497 max_size: usize,
499 hit_count: u64,
501 miss_count: u64,
503}
504
505impl TileCache {
506 pub fn new(max_size: usize) -> Self {
508 Self {
509 cache: HashMap::new(),
510 lru_queue: VecDeque::new(),
511 current_size: 0,
512 max_size,
513 hit_count: 0,
514 miss_count: 0,
515 }
516 }
517
518 pub fn with_default_size() -> Self {
520 Self::new(DEFAULT_MAX_CACHE_SIZE)
521 }
522
523 pub fn get(&mut self, coord: &TileCoord, timestamp: f64) -> Option<Vec<u8>> {
525 let key = coord.key();
526
527 if let Some(tile) = self.cache.get_mut(&key) {
528 tile.access(timestamp);
529 self.hit_count += 1;
530
531 if let Some(pos) = self.lru_queue.iter().position(|k| k == &key) {
533 self.lru_queue.remove(pos);
534 }
535 self.lru_queue.push_back(key);
536
537 Some(tile.data.clone())
538 } else {
539 self.miss_count += 1;
540 None
541 }
542 }
543
544 pub fn put(&mut self, coord: TileCoord, data: Vec<u8>, timestamp: f64) -> WasmResult<()> {
546 let key = coord.key();
547 let tile_size = data.len();
548
549 while !self.lru_queue.is_empty() && self.current_size + tile_size >= self.max_size {
552 self.evict_oldest()?;
553 }
554
555 if tile_size > self.max_size {
557 return Err(WasmError::TileCache(TileCacheError::Full {
558 current_size: self.current_size,
559 max_size: self.max_size,
560 }));
561 }
562
563 if let Some(old_tile) = self.cache.remove(&key) {
565 self.current_size -= old_tile.size;
566 if let Some(pos) = self.lru_queue.iter().position(|k| k == &key) {
567 self.lru_queue.remove(pos);
568 }
569 }
570
571 let tile = CachedTile::new(coord, data, timestamp);
572 self.current_size += tile_size;
573 self.cache.insert(key.clone(), tile);
574 self.lru_queue.push_back(key);
575
576 Ok(())
577 }
578
579 fn evict_oldest(&mut self) -> WasmResult<()> {
581 if let Some(key) = self.lru_queue.pop_front() {
582 if let Some(tile) = self.cache.remove(&key) {
583 self.current_size -= tile.size;
584 Ok(())
585 } else {
586 Err(WasmError::TileCache(TileCacheError::EvictionFailed {
587 message: format!("Tile {key} not found in cache"),
588 }))
589 }
590 } else {
591 Err(WasmError::TileCache(TileCacheError::EvictionFailed {
592 message: "No tiles to evict".to_string(),
593 }))
594 }
595 }
596
597 pub fn contains(&self, coord: &TileCoord) -> bool {
599 self.cache.contains_key(&coord.key())
600 }
601
602 pub fn remove(&mut self, coord: &TileCoord) -> Option<Vec<u8>> {
604 let key = coord.key();
605 if let Some(tile) = self.cache.remove(&key) {
606 self.current_size -= tile.size;
607 if let Some(pos) = self.lru_queue.iter().position(|k| k == &key) {
608 self.lru_queue.remove(pos);
609 }
610 Some(tile.data)
611 } else {
612 None
613 }
614 }
615
616 pub fn clear(&mut self) {
618 self.cache.clear();
619 self.lru_queue.clear();
620 self.current_size = 0;
621 }
622
623 pub fn stats(&self) -> CacheStats {
625 CacheStats {
626 current_size: self.current_size,
627 max_size: self.max_size,
628 entry_count: self.cache.len(),
629 hit_count: self.hit_count,
630 miss_count: self.miss_count,
631 }
632 }
633
634 pub fn hit_rate(&self) -> f64 {
636 let total = self.hit_count + self.miss_count;
637 if total == 0 {
638 0.0
639 } else {
640 self.hit_count as f64 / total as f64
641 }
642 }
643
644 pub fn prefetch_list(&self, coords: &[TileCoord]) -> Vec<TileCoord> {
646 coords
647 .iter()
648 .filter(|coord| !self.contains(coord))
649 .copied()
650 .collect()
651 }
652}
653
654#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
656pub struct CacheStats {
657 pub current_size: usize,
659 pub max_size: usize,
661 pub entry_count: usize,
663 pub hit_count: u64,
665 pub miss_count: u64,
667}
668
669impl CacheStats {
670 pub fn hit_rate(&self) -> f64 {
672 let total = self.hit_count + self.miss_count;
673 if total == 0 {
674 0.0
675 } else {
676 self.hit_count as f64 / total as f64
677 }
678 }
679
680 pub fn utilization(&self) -> f64 {
682 if self.max_size == 0 {
683 0.0
684 } else {
685 self.current_size as f64 / self.max_size as f64
686 }
687 }
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq)]
692pub enum PrefetchStrategy {
693 None,
695 Neighbors,
697 Radius(u32),
699 Pyramid,
701 Directional {
703 dx: i32,
705 dy: i32,
707 count: u32,
709 },
710}
711
712pub struct TilePrefetcher {
714 strategy: PrefetchStrategy,
716 pyramid: TilePyramid,
718}
719
720impl TilePrefetcher {
721 pub const fn new(strategy: PrefetchStrategy, pyramid: TilePyramid) -> Self {
723 Self { strategy, pyramid }
724 }
725
726 pub fn prefetch_for(&self, center: &TileCoord) -> Vec<TileCoord> {
728 match self.strategy {
729 PrefetchStrategy::None => vec![],
730 PrefetchStrategy::Neighbors => self.prefetch_neighbors(center),
731 PrefetchStrategy::Radius(radius) => self.prefetch_radius(center, radius),
732 PrefetchStrategy::Pyramid => self.prefetch_pyramid(center),
733 PrefetchStrategy::Directional { dx, dy, count } => {
734 self.prefetch_directional(center, dx, dy, count)
735 }
736 }
737 }
738
739 fn prefetch_neighbors(&self, center: &TileCoord) -> Vec<TileCoord> {
741 center
742 .neighbors()
743 .iter()
744 .filter_map(|&n| n)
745 .filter(|coord| self.pyramid.is_valid_coord(coord))
746 .collect()
747 }
748
749 fn prefetch_radius(&self, center: &TileCoord, radius: u32) -> Vec<TileCoord> {
751 let mut tiles = Vec::new();
752 let radius_i32 = radius as i32;
753
754 for dy in -radius_i32..=radius_i32 {
755 for dx in -radius_i32..=radius_i32 {
756 if dx == 0 && dy == 0 {
758 continue;
759 }
760
761 let dist_sq = (dx * dx + dy * dy) as f64;
763 if dist_sq > (radius as f64 * radius as f64) {
764 continue;
765 }
766
767 let x = center.x as i32 + dx;
768 let y = center.y as i32 + dy;
769
770 if x >= 0 && y >= 0 {
771 let coord = TileCoord::new(center.level, x as u32, y as u32);
772 if self.pyramid.is_valid_coord(&coord) {
773 tiles.push(coord);
774 }
775 }
776 }
777 }
778
779 tiles
780 }
781
782 fn prefetch_pyramid(&self, center: &TileCoord) -> Vec<TileCoord> {
784 let mut tiles = Vec::new();
785
786 if let Some(parent) = center.parent() {
788 if self.pyramid.is_valid_coord(&parent) {
789 tiles.push(parent);
790 }
791 }
792
793 for child in center.children() {
795 if self.pyramid.is_valid_coord(&child) {
796 tiles.push(child);
797 }
798 }
799
800 tiles
801 }
802
803 fn prefetch_directional(
805 &self,
806 center: &TileCoord,
807 dx: i32,
808 dy: i32,
809 count: u32,
810 ) -> Vec<TileCoord> {
811 let mut tiles = Vec::new();
812
813 for i in 1..=count {
814 let x = center.x as i32 + dx * i as i32;
815 let y = center.y as i32 + dy * i as i32;
816
817 if x >= 0 && y >= 0 {
818 let coord = TileCoord::new(center.level, x as u32, y as u32);
819 if self.pyramid.is_valid_coord(&coord) {
820 tiles.push(coord);
821 } else {
822 break;
823 }
824 }
825 }
826
827 tiles
828 }
829}
830
831#[wasm_bindgen]
833pub struct WasmTileCache {
834 cache: TileCache,
835}
836
837#[wasm_bindgen]
838impl WasmTileCache {
839 #[wasm_bindgen(constructor)]
841 pub fn new(max_size_mb: usize) -> Self {
842 let max_size = max_size_mb * 1024 * 1024;
843 Self {
844 cache: TileCache::new(max_size),
845 }
846 }
847
848 #[wasm_bindgen(js_name = getStats)]
850 pub fn get_stats(&self) -> String {
851 serde_json::to_string(&self.cache.stats()).unwrap_or_default()
852 }
853
854 #[wasm_bindgen]
856 pub fn clear(&mut self) {
857 self.cache.clear();
858 }
859
860 #[wasm_bindgen(js_name = hitRate)]
862 pub fn hit_rate(&self) -> f64 {
863 self.cache.hit_rate()
864 }
865}
866
867#[cfg(test)]
868mod tests {
869 use super::*;
870
871 #[test]
872 fn test_tile_coord() {
873 let coord = TileCoord::new(5, 10, 20);
874 assert_eq!(coord.level, 5);
875 assert_eq!(coord.x, 10);
876 assert_eq!(coord.y, 20);
877 assert_eq!(coord.key(), "5/10/20");
878 }
879
880 #[test]
881 fn test_tile_coord_parent() {
882 let coord = TileCoord::new(5, 10, 20);
883 let parent = coord.parent().expect("Should have parent");
884 assert_eq!(parent.level, 4);
885 assert_eq!(parent.x, 5);
886 assert_eq!(parent.y, 10);
887
888 let root = TileCoord::new(0, 0, 0);
889 assert!(root.parent().is_none());
890 }
891
892 #[test]
893 fn test_tile_coord_children() {
894 let coord = TileCoord::new(5, 10, 20);
895 let children = coord.children();
896 assert_eq!(children.len(), 4);
897 assert_eq!(children[0], TileCoord::new(6, 20, 40));
898 assert_eq!(children[1], TileCoord::new(6, 21, 40));
899 assert_eq!(children[2], TileCoord::new(6, 20, 41));
900 assert_eq!(children[3], TileCoord::new(6, 21, 41));
901 }
902
903 #[test]
904 fn test_tile_pyramid() {
905 let pyramid = TilePyramid::new(4096, 2048, 256, 256);
906 assert_eq!(pyramid.tile_width, 256);
907 assert_eq!(pyramid.tile_height, 256);
908
909 let (tiles_x, tiles_y) = pyramid.tiles_at_level(0).expect("Level 0 exists");
910 assert_eq!(tiles_x, 16); assert_eq!(tiles_y, 8); }
913
914 #[test]
915 fn test_tile_cache() {
916 let mut cache = TileCache::new(1024);
917 let coord = TileCoord::new(0, 0, 0);
918 let data = vec![1, 2, 3, 4];
919
920 cache
922 .put(coord, data.clone(), 0.0)
923 .expect("Put should succeed");
924 let retrieved = cache.get(&coord, 0.0).expect("Should find tile");
925 assert_eq!(retrieved, data);
926
927 let stats = cache.stats();
929 assert_eq!(stats.entry_count, 1);
930 assert_eq!(stats.hit_count, 1);
931 }
932
933 #[test]
934 fn test_cache_eviction() {
935 let mut cache = TileCache::new(10); let coord1 = TileCoord::new(0, 0, 0);
937 let coord2 = TileCoord::new(0, 0, 1);
938
939 cache
940 .put(coord1, vec![1, 2, 3, 4, 5], 0.0)
941 .expect("Put should succeed");
942 cache
943 .put(coord2, vec![6, 7, 8, 9, 10], 1.0)
944 .expect("Put should succeed");
945
946 assert!(!cache.contains(&coord1));
948 assert!(cache.contains(&coord2));
949 }
950
951 #[test]
952 fn test_tile_bounds() {
953 let bounds = TileBounds::new(0, 0, 10, 10);
954 assert_eq!(bounds.count(), 100);
955
956 let coord_in = TileCoord::new(0, 5, 5);
957 let coord_out = TileCoord::new(0, 15, 15);
958
959 assert!(bounds.contains(&coord_in));
960 assert!(!bounds.contains(&coord_out));
961 }
962
963 #[test]
964 fn test_prefetch_neighbors() {
965 let pyramid = TilePyramid::new(1024, 1024, 256, 256);
966 let prefetcher = TilePrefetcher::new(PrefetchStrategy::Neighbors, pyramid);
967
968 let center = TileCoord::new(0, 1, 1);
969 let to_prefetch = prefetcher.prefetch_for(¢er);
970
971 assert!(!to_prefetch.is_empty());
973 assert!(to_prefetch.len() <= 8);
974 }
975
976 #[test]
977 fn test_from_key() {
978 let coord = TileCoord::new(5, 10, 20);
979 let key = coord.key();
980 let parsed = TileCoord::from_key(&key).expect("Should parse");
981 assert_eq!(parsed, coord);
982
983 assert!(TileCoord::from_key("invalid").is_none());
984 assert!(TileCoord::from_key("1/2").is_none());
985 }
986}