use super::{ChunkLevel, ChunkPos, HashMapType, LevelChannel}; use crate::chunk_system::chunk_state::StagedChunkEnum; // Fixed path use itertools::Itertools; use std::cmp::{Ordering, PartialEq, min}; use std::collections::BinaryHeap; use std::collections::hash_map::Entry; use std::mem::swap; use std::sync::Arc; use tracing::debug; pub struct HeapNode(i8, ChunkPos); impl PartialEq for HeapNode { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for HeapNode {} impl PartialOrd for HeapNode { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for HeapNode { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0).reverse() } } impl From<(ChunkPos, i8)> for HeapNode { fn from(value: (ChunkPos, i8)) -> Self { Self(value.1, value.0) } } impl From for (ChunkPos, i8) { fn from(val: HeapNode) -> Self { (val.1, val.0) } } struct LevelCache(i32, i32, usize, [(i8, i8); 256 * 256]); impl LevelCache { const fn new() -> Self { Self(0, 0, 0, [(0, 0); 256 * 256]) } } impl LevelCache { fn clean(&mut self, pos: ChunkPos, level: i8) { let dst = ChunkLoading::MAX_LEVEL - level + 1; self.0 = pos.x - dst as i32; self.1 = pos.y - dst as i32; self.2 = (dst as usize) << 1 | 1; self.3[..self.2 * self.2].fill((-127, -127)); } fn get(&mut self, map: &ChunkLevel, pos: ChunkPos) -> i8 { let dx = (pos.x - self.0) as usize; let dy = (pos.y - self.1) as usize; debug_assert!(pos.x >= self.0 && pos.y >= self.1); let value = &mut self.3[dx * self.2 + dy]; if value.0 == -127 { value.0 = *map.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL); value.1 = value.0; } value.1 } fn set(&mut self, map: &ChunkLevel, pos: ChunkPos, level: i8) { let dx = (pos.x - self.0) as usize; let dy = (pos.y - self.1) as usize; debug_assert!(pos.x >= self.0 && pos.y >= self.1); let value = &mut self.3[dx * self.2 + dy]; if value.0 == -127 { value.0 = *map.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL); } value.1 = level; } fn write( &self, map: &mut ChunkLevel, change: &mut HashMapType, ) { for i in 0..self.2 { for j in 0..self.2 { let value = self.3[i * self.2 + j]; if value.0 != value.1 { let pos = ChunkPos::new(i as i32 + self.0, j as i32 + self.1); if value.1 == ChunkLoading::MAX_LEVEL { map.remove(&pos); } else { map.insert(pos, value.1); } let value = ( StagedChunkEnum::level_to_stage(value.0), StagedChunkEnum::level_to_stage(value.1), ); if value.0 == value.1 { continue; } match change.entry(pos) { Entry::Occupied(mut entry) => { let i = entry.get_mut(); debug_assert!(i.1 == value.0); if i.0 == value.1 { entry.remove(); } else { i.1 = value.1; } } Entry::Vacant(entry) => { entry.insert(value); } } } } } } } pub struct ChunkLoading { pub is_priority_dirty: bool, pub pos_level: ChunkLevel, change: HashMapType, pub ticket: HashMapType>, // TODO lifetime & id pub high_priority: Vec, pub sender: Arc, pub increase_update: BinaryHeap, pub decrease_update: BinaryHeap, cache: LevelCache, } impl ChunkLoading { // pub const FULL_CHUNK_LEVEL: i8 = 33; pub const FULL_CHUNK_LEVEL: i8 = 43; pub const MAX_LEVEL: i8 = 49; // level 49 will be unloaded. fn debug_check_error(&self) -> bool { let mut temp = ChunkLevel::default(); for (ticket_pos, levels) in &self.ticket { let level = *levels.iter().min().unwrap(); let range = Self::MAX_LEVEL - level - 1; for dx in -range..=range { for dy in -range..=range { let new_pos = ticket_pos.add_raw(dx as i32, dy as i32); let level_from_source = level + dx.abs().max(dy.abs()); let i = temp.entry(new_pos).or_insert(Self::MAX_LEVEL); *i = min(*i, level_from_source); } } } if temp.len() != self.pos_level.len() { debug!("temp: \n{temp:?}"); debug!("pos_level: \n{:?}", self.pos_level); } assert_eq!(temp.len(), self.pos_level.len()); for val in &temp { if val != self.pos_level.get_key_value(val.0).unwrap() { Self::dump_level_debug( &self.high_priority, &self.pos_level, val.0.x - 40, val.0.x + 40, val.0.y - 40, val.0.y + 40, ); } assert_eq!(val, self.pos_level.get_key_value(val.0).unwrap()); } true } pub fn dump_level_debug( pri: &[ChunkPos], map: &ChunkLevel, sx: i32, tx: i32, sy: i32, ty: i32, ) { debug!("high_priority {pri:?}"); let mut header = "X/Y".to_string(); for y in sy..=ty { header.push_str(&format!("{y:4}")); } let grid: String = (sx..=tx) .map(|x| { let mut row = format!("{x:3}"); row.push_str( &(sy..=ty) .map(|y| { format!( "{:4}", map.get(&ChunkPos::new(x, y)).unwrap_or(&Self::MAX_LEVEL) ) }) .collect::(), ); row }) .collect::>() .join("\n"); debug!("\nloading level:\n{header}\n{grid}"); } #[inline] #[must_use] pub const fn get_level_from_view_distance(view_distance: u8) -> i8 { Self::FULL_CHUNK_LEVEL - (view_distance as i8) } pub fn new(sender: Arc) -> Self { Self { is_priority_dirty: true, pos_level: ChunkLevel::default(), change: HashMapType::default(), ticket: HashMapType::default(), high_priority: Vec::new(), sender, increase_update: BinaryHeap::default(), decrease_update: BinaryHeap::default(), cache: LevelCache::new(), } } pub fn send_change(&mut self) { // debug!("sending change"); if !self.change.is_empty() { let mut tmp = HashMapType::default(); swap(&mut tmp, &mut self.change); if self.is_priority_dirty { self.is_priority_dirty = false; self.sender .set_both((tmp, self.pos_level.clone()), self.high_priority.clone()); } else { self.sender.set_level((tmp, self.pos_level.clone())); } } if self.is_priority_dirty { self.is_priority_dirty = false; self.sender.set_priority(self.high_priority.clone()); } } fn run_increase_update(&mut self) { while let Some(node) = self.increase_update.pop() { let (pos, level) = node.into(); debug_assert!(level < Self::MAX_LEVEL); if level > self.cache.get(&self.pos_level, pos) { continue; } debug_assert_eq!(level, self.cache.get(&self.pos_level, pos)); let spread_level = level + 1; if spread_level >= Self::MAX_LEVEL { continue; } for dx in -1..2 { for dy in -1..2 { let new_pos = pos.add_raw(dx, dy); if new_pos != pos { self.check_then_push(new_pos, spread_level); } } } } } fn check_then_push(&mut self, pos: ChunkPos, level: i8) { debug_assert!(level < Self::MAX_LEVEL); let old = self.cache.get(&self.pos_level, pos); if old <= level { return; } self.cache.set(&self.pos_level, pos, level); self.increase_update.push((pos, level).into()); } fn run_decrease_update(&mut self, pos: ChunkPos, range: i32) { while let Some(node) = self.decrease_update.pop() { let (pos, level) = node.into(); debug_assert!(level < Self::MAX_LEVEL); let spread_level = level + 1; for dx in -1..2 { for dy in -1..2 { let new_pos = pos.add_raw(dx, dy); if new_pos == pos { continue; } let new_pos_level = self.cache.get(&self.pos_level, new_pos); if new_pos_level == Self::MAX_LEVEL { continue; } debug_assert!(new_pos_level <= spread_level); if new_pos_level == spread_level { self.cache.set(&self.pos_level, new_pos, Self::MAX_LEVEL); if spread_level < Self::MAX_LEVEL { self.decrease_update.push((new_pos, spread_level).into()); } } else { self.increase_update.push((new_pos, new_pos_level).into()); } } } } for (ticket_pos, levels) in &self.ticket { if (ticket_pos.x - pos.x).abs() <= range && (ticket_pos.y - pos.y).abs() <= range { let level = *levels.iter().min().unwrap(); debug_assert!(level < Self::MAX_LEVEL); let old = self.cache.get(&self.pos_level, *ticket_pos); if old <= level { continue; } self.cache.set(&self.pos_level, *ticket_pos, level); self.increase_update.push((*ticket_pos, level).into()); } } self.run_increase_update(); } pub fn add_force_ticket(&mut self, pos: ChunkPos) { // debug!("add force ticket at {pos:?}"); self.high_priority.push(pos); self.is_priority_dirty = true; self.add_ticket(pos, Self::FULL_CHUNK_LEVEL); } pub fn remove_force_ticket(&mut self, pos: ChunkPos) { // debug!("remove force ticket at {pos:?}"); let index = self .high_priority .iter() .find_position(|x| **x == pos) .unwrap() .0; self.high_priority.remove(index); self.is_priority_dirty = true; self.remove_ticket(pos, Self::FULL_CHUNK_LEVEL); } pub fn add_ticket(&mut self, pos: ChunkPos, level: i8) { // debug!("add ticket at {pos:?} level {level}"); debug_assert!(level < Self::MAX_LEVEL); match self.ticket.entry(pos) { Entry::Occupied(mut vec) => { vec.get_mut().push(level); } Entry::Vacant(empty) => { empty.insert(vec![level]); } } let old = *self.pos_level.get(&pos).unwrap_or(&Self::MAX_LEVEL); if old <= level { return; } self.cache.clean(pos, level); self.cache.set(&self.pos_level, pos, level); debug_assert!(self.increase_update.is_empty()); self.increase_update.push((pos, level).into()); self.run_increase_update(); self.cache.write(&mut self.pos_level, &mut self.change); debug_assert!(self.debug_check_error()); } pub fn remove_ticket(&mut self, pos: ChunkPos, level: i8) { // debug!("remove ticket at {pos:?} level {level}"); debug_assert!(level < Self::MAX_LEVEL); let Some(vec) = self.ticket.get_mut(&pos) else { // warn!("No ticket found at {pos:?}"); return; }; let Some((index, _)) = vec.iter().find_position(|x| **x == level) else { // warn!("No ticket found at {pos:?}"); return; }; vec.remove(index); match self.pos_level.entry(pos) { Entry::Occupied(entry) => { let old_level = *entry.get(); let source = *vec.iter().min().unwrap_or(&Self::MAX_LEVEL); if vec.is_empty() { self.ticket.remove(&pos); } if level == old_level && source != level { self.cache.clean(pos, old_level); self.cache.set(&self.pos_level, pos, Self::MAX_LEVEL); debug_assert!(self.decrease_update.is_empty()); self.decrease_update.push((pos, level).into()); self.run_decrease_update(pos, (Self::MAX_LEVEL - level - 1) as i32); self.cache.write(&mut self.pos_level, &mut self.change); } } Entry::Vacant(_) => panic!(), } debug_assert!(self.debug_check_error()); } } #[test] fn test() { let mut a = ChunkLoading::new(Arc::new(LevelChannel::new())); a.add_ticket((0, 0).into(), 44); a.add_ticket((0, 1).into(), 44); a.remove_ticket((0, 0).into(), 44); a.add_ticket((0, 0).into(), 30); a.add_ticket((0, 10).into(), 25); a.add_ticket((10, 10).into(), 26); a.add_ticket((10, 10).into(), 26); a.remove_ticket((0, 0).into(), 30); a.remove_ticket((0, 10).into(), 25); a.remove_ticket((10, 10).into(), 26); a.remove_ticket((10, 10).into(), 26); a.add_ticket((-72, 457).into(), 24); a.add_ticket((-72, 455).into(), 33); a.add_ticket((-72, 456).into(), 24); a.remove_ticket((-72, 457).into(), 24); a.add_ticket((-72, 455).into(), 24); a.add_ticket((-59, 495).into(), 33); a.add_ticket((-51, 504).into(), 24); a.remove_ticket((-51, 504).into(), 24); let sx = -59; let tx = -51; let sy = 495; let ty = 504; { let mut header = "X/Y".to_string(); for y in sy..=ty { header.push_str(&format!("{y:4}")); } let grid: String = (sx..=tx) .map(|x| { let mut row = format!("{x:3}"); row.push_str( &(sy..=ty) .map(|y| { format!( "{:4}", a.pos_level .get(&ChunkPos::new(x, y)) .unwrap_or(&ChunkLoading::MAX_LEVEL) ) }) .collect::(), ); row }) .collect::>() .join("\n"); println!("\nloading level:\n{header}\n{grid}"); } }