Pumpkin/pumpkin-world/src/chunk_system/chunk_loading.rs
2026-05-09 20:59:34 +02:00

455 lines
16 KiB
Rust

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<Ordering> {
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<HeapNode> 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<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
) {
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<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
pub ticket: HashMapType<ChunkPos, Vec<i8>>, // TODO lifetime & id
pub high_priority: Vec<ChunkPos>,
pub sender: Arc<LevelChannel>,
pub increase_update: BinaryHeap<HeapNode>,
pub decrease_update: BinaryHeap<HeapNode>,
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::<String>(),
);
row
})
.collect::<Vec<_>>()
.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<LevelChannel>) -> 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::<String>(),
);
row
})
.collect::<Vec<_>>()
.join("\n");
println!("\nloading level:\n{header}\n{grid}");
}
}