Pumpkin/pumpkin-world/src/chunk_system.rs
Alexander Medvedev ce652860e0 fix nether
2026-01-04 20:28:51 +01:00

2148 lines
78 KiB
Rust

/*
TODO
1. add proto chunk dirty flag
2. better priority
5. add lifetime to loading ticket
6. solve entity not unload problem
*/
use crate::block::RawBlockState;
use crate::chunk::io::LoadedData::Loaded;
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk};
use pumpkin_data::dimension::Dimension;
use std::default::Default;
use std::pin::Pin;
use crate::generation::height_limit::HeightLimitView;
use crate::generation::proto_chunk::{GenerationCache, TerrainCache};
use crate::generation::settings::{GenerationSettings, gen_settings_from_dimension};
use crate::level::{Level, SyncChunk};
use crate::world::{BlockAccessor, BlockRegistryExt};
use crate::{GlobalRandomConfig, ProtoChunk, ProtoNoiseRouters};
use crossbeam::channel::{Receiver, Sender};
use dashmap::DashMap;
use itertools::Itertools;
use log::{debug, error};
use num_traits::abs;
use pumpkin_data::biome::Biome;
use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::{Block, BlockState};
use pumpkin_util::HeightMap;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3;
use std::cmp::{Ordering, PartialEq, max, min};
use std::collections::hash_map::Entry;
use std::collections::{BinaryHeap, HashMap};
use std::mem::swap;
use std::ops::Deref;
use std::sync::{Arc, Condvar, Mutex};
use crate::chunk::format::LightContainer;
use crate::chunk::io::LoadedData;
use crate::chunk_system::Chunk::Proto;
use crate::chunk_system::StagedChunkEnum::{Biomes, Empty, Features, Full, Noise, Surface};
use crate::generation::biome_coords;
use crossfire::AsyncRx;
use pumpkin_data::chunk::ChunkStatus;
use rustc_hash::{FxHashMap, FxHashSet};
use slotmap::{Key, SlotMap, new_key_type};
use std::sync::atomic::Ordering::{Relaxed, SeqCst};
use std::thread;
use std::thread::JoinHandle;
use tokio::sync::{RwLock, oneshot};
type HashMapType<K, V> = FxHashMap<K, V>;
type HashSetType<K> = FxHashSet<K>;
type ChunkPos = Vector2<i32>;
type ChunkLevel = HashMapType<ChunkPos, i8>;
type IOLock = Arc<(Mutex<HashMapType<ChunkPos, u8>>, Condvar)>;
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 {
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(
&mut 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,
}
#[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}");
}
}
impl ChunkLoading {
// pub const FULL_CHUNK_LEVEL: i8 = 33;
pub const FULL_CHUNK_LEVEL: i8 = 43;
pub const MAX_LEVEL: i8 = 46; // level 46 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 + abs(dx).max(abs(dy));
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: &Vec<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(&ChunkLoading::MAX_LEVEL)
)
})
.collect::<String>(),
);
row
})
.collect::<Vec<_>>()
.join("\n");
debug!("\nloading level:\n{header}\n{grid}");
}
#[inline]
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 abs(ticket_pos.x - pos.x) <= range && abs(ticket_pos.y - pos.y) <= 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) {
// log::debug!("add force ticket at {pos:?}");
self.high_priority.push(pos);
self.is_priority_dirty = true;
self.add_ticket(pos, ChunkLoading::FULL_CHUNK_LEVEL);
}
pub fn remove_force_ticket(&mut self, pos: ChunkPos) {
// log::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, ChunkLoading::FULL_CHUNK_LEVEL);
}
pub fn add_ticket(&mut self, pos: ChunkPos, level: i8) {
// log::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(&ChunkLoading::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) {
// log::debug!("remove ticket at {pos:?} level {level}");
debug_assert!(level < Self::MAX_LEVEL);
let Some(vec) = self.ticket.get_mut(&pos) else {
// log::warn!("No ticket found at {pos:?}");
return;
};
let Some((index, _)) = vec.iter().find_position(|x| **x == level) else {
// log::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());
}
}
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub enum StagedChunkEnum {
None,
/// Initial empty chunk, ready for biome population
Empty = 1, // EMPTY STRUCTURE_STARTS STRUCTURE_REFERENCES
/// Chunk with biomes populated, ready for noise generation
Biomes,
StructureStart,
StructureReferences,
/// Chunk with terrain noise generated, ready for surface building
Noise,
/// Chunk with surface built, ready for features and structures
Surface, // SURFACE CARVERS
/// Chunk with features and structures, ready for finalization
Features, // FEATURES INITIALIZE_LIGHT LIGHT SPAWN
/// Fully generated chunk
Full,
}
impl From<u8> for StagedChunkEnum {
fn from(v: u8) -> Self {
match v {
1 => Self::Empty,
2 => Self::Biomes,
3 => Self::StructureStart,
4 => Self::StructureReferences,
5 => Self::Noise,
6 => Self::Surface,
7 => Self::Features,
8 => Self::Full,
_ => panic!(),
}
}
}
impl From<ChunkStatus> for StagedChunkEnum {
fn from(status: ChunkStatus) -> Self {
match status {
ChunkStatus::Empty => Empty,
ChunkStatus::StructureStarts => StagedChunkEnum::StructureStart,
ChunkStatus::StructureReferences => StagedChunkEnum::StructureReferences,
ChunkStatus::Biomes => Biomes,
ChunkStatus::Noise => Noise,
ChunkStatus::Surface => Surface,
ChunkStatus::Carvers => Surface,
ChunkStatus::Features => Features,
ChunkStatus::InitializeLight => Features,
ChunkStatus::Light => Features,
ChunkStatus::Spawn => Features,
ChunkStatus::Full => Full,
}
}
}
impl From<StagedChunkEnum> for ChunkStatus {
fn from(status: StagedChunkEnum) -> Self {
match status {
StagedChunkEnum::Empty => ChunkStatus::Empty,
StagedChunkEnum::StructureStart => ChunkStatus::StructureStarts,
StagedChunkEnum::StructureReferences => ChunkStatus::StructureReferences,
StagedChunkEnum::Biomes => ChunkStatus::Biomes,
StagedChunkEnum::Noise => ChunkStatus::Noise,
StagedChunkEnum::Surface => ChunkStatus::Surface,
StagedChunkEnum::Features => ChunkStatus::Features,
StagedChunkEnum::Full => ChunkStatus::Full,
_ => panic!(),
}
}
}
impl StagedChunkEnum {
const fn level_to_stage(level: i8) -> Self {
if level <= 43 {
Full
} else if level <= 44 {
Features
} else if level <= 45 {
Surface
} else {
Self::None
}
}
const FULL_DEPENDENCIES: &'static [Self] = &[Full, Features, Surface];
const FULL_RADIUS: i32 = 2;
const fn get_direct_radius(self) -> i32 {
// self exclude
match self {
Self::Empty => 0,
Self::StructureStart => 0,
Self::StructureReferences => 0,
Self::Biomes => 0,
Self::Noise => 0,
Self::Surface => 0,
Self::Features => 1,
Self::Full => 1,
_ => panic!(),
}
}
const fn get_write_radius(self) -> i32 {
// self exclude
match self {
Self::Empty => 0,
Self::StructureStart => 0,
Self::StructureReferences => 0,
Self::Biomes => 0,
Self::Noise => 0,
Self::Surface => 0,
Self::Features => 1,
Self::Full => 0,
_ => panic!(),
}
}
const fn get_direct_dependencies(self) -> &'static [StagedChunkEnum] {
match self {
Self::Biomes => &[Self::Empty],
Self::StructureStart => &[Self::Biomes],
Self::StructureReferences => &[Self::StructureStart],
Self::Noise => &[Self::StructureReferences],
Self::Surface => &[Self::Noise],
Self::Features => &[Self::Surface, Self::Surface],
Self::Full => &[Self::Features, Self::Features],
_ => panic!(),
}
}
}
type LevelChange = (
HashMapType<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
ChunkLevel,
);
pub struct LevelChannel {
pub value: Mutex<(Option<LevelChange>, Option<Vec<ChunkPos>>)>,
pub notify: Condvar,
}
impl Default for LevelChannel {
fn default() -> Self {
Self::new()
}
}
impl LevelChannel {
pub fn new() -> Self {
Self {
value: Mutex::new((None, None)),
notify: Condvar::new(),
}
}
pub fn set_both(
&self,
new_value: (
HashMapType<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
ChunkLevel,
),
pos: Vec<ChunkPos>,
) {
// debug!("set new level and priority");
let mut value = self.value.lock().unwrap();
value.1 = Some(pos);
if let Some(old) = &mut value.0 {
for (pos, change) in new_value.0 {
match old.0.entry(pos) {
Entry::Occupied(mut entry) => {
let tmp = entry.get_mut();
debug_assert_eq!(tmp.1, change.0);
if tmp.0 == change.1 {
entry.remove();
} else {
tmp.1 = change.1;
}
}
Entry::Vacant(entry) => {
debug_assert_ne!(change.0, change.1);
entry.insert(change);
}
}
}
old.1 = new_value.1;
} else {
value.0 = Some(new_value);
}
self.notify.notify_one();
}
pub fn set_level(
&self,
new_value: (
HashMapType<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
ChunkLevel,
),
) {
// debug!("set new level");
let mut value = self.value.lock().unwrap();
if let Some(old) = &mut value.0 {
for (pos, change) in new_value.0 {
match old.0.entry(pos) {
Entry::Occupied(mut entry) => {
let tmp = entry.get_mut();
debug_assert_eq!(tmp.1, change.0);
if tmp.0 == change.1 {
entry.remove();
} else {
tmp.1 = change.1;
}
}
Entry::Vacant(entry) => {
debug_assert_ne!(change.0, change.1);
entry.insert(change);
}
}
}
old.1 = new_value.1;
} else {
value.0 = Some(new_value);
}
self.notify.notify_one();
}
pub fn set_priority(&self, pos: Vec<ChunkPos>) {
// debug!("set new priority");
self.value.lock().unwrap().1 = Some(pos);
self.notify.notify_one();
}
pub fn get(&self) -> (Option<LevelChange>, Option<Vec<ChunkPos>>) {
let mut lock = self.value.lock().unwrap();
let mut ret = (None, None);
swap(&mut ret, &mut *lock);
ret
}
pub fn wait_and_get(&self, level: &Arc<Level>) -> (Option<LevelChange>, Option<Vec<ChunkPos>>) {
let mut lock = self.value.lock().unwrap();
while lock.0.is_none()
&& lock.1.is_none()
&& !level.should_unload.load(SeqCst)
&& !level.should_save.load(SeqCst)
&& !level.shut_down_chunk_system.load(SeqCst)
{
lock = self.notify.wait(lock).unwrap();
}
let mut ret = (None, None);
swap(&mut ret, &mut *lock);
ret
}
pub fn notify(&self) {
let val = self.value.lock().unwrap();
drop(val);
self.notify.notify_one();
}
}
pub enum Chunk {
Level(SyncChunk),
Proto(Box<ProtoChunk>),
}
impl Chunk {
fn get_stage_id(&self) -> u8 {
match self {
Chunk::Proto(data) => data.stage_id(),
Chunk::Level(_) => 8,
}
}
fn get_proto_chunk_mut(&mut self) -> &mut ProtoChunk {
match self {
Chunk::Level(_) => panic!("chunk isn't a ProtoChunk"),
Proto(chunk) => chunk,
}
}
fn upgrade_to_level_chunk(&mut self, dimension: &Dimension) {
let proto_chunk = self.get_proto_chunk_mut();
let total_sections = dimension.height as usize / 16;
let mut sections = ChunkSections::new(
(0..total_sections).map(|_| SubChunk::default()).collect(),
dimension.min_y,
);
let proto_biome_height = biome_coords::from_block(proto_chunk.height());
let biome_min_y = biome_coords::from_block(dimension.min_y);
for y_offset in 0..proto_biome_height {
let section_index = y_offset as usize / 4;
let relative_y = y_offset as usize % 4;
if let Some(section) = sections.sections.get_mut(section_index) {
let absolute_biome_y = biome_min_y + y_offset as i32;
for z in 0..4 {
for x in 0..4 {
let biome = proto_chunk.get_biome(x as i32, absolute_biome_y, z as i32);
section.biomes.set(x, relative_y, z, biome.id);
}
}
}
}
let proto_block_height = proto_chunk.height();
for y_offset in 0..proto_block_height {
let section_index = (y_offset as usize) / 16;
let relative_y = (y_offset as usize) % 16;
if let Some(section) = sections.sections.get_mut(section_index) {
for z in 0..16 {
for x in 0..16 {
let block =
proto_chunk.get_block_state_raw(x as i32, y_offset as i32, z as i32);
section.block_states.set(x, relative_y, z, block);
}
}
}
}
let mut chunk = ChunkData {
light_engine: ChunkLight {
sky_light: (0..sections.sections.len())
.map(|_| {
if dimension.has_skylight {
// Overworld: Start with full sky light before occlusion
LightContainer::new_filled(15)
} else {
// Nether/End: No sky light permitted
LightContainer::new_empty(0)
}
})
.collect(),
block_light: (0..sections.sections.len())
.map(|_| LightContainer::new_empty(0))
.collect(),
},
section: sections,
heightmap: Default::default(),
x: proto_chunk.x,
z: proto_chunk.z,
dirty: true,
block_ticks: Default::default(),
fluid_ticks: Default::default(),
block_entities: Default::default(),
status: proto_chunk.stage.into(),
};
chunk.heightmap = chunk.calculate_heightmap();
*self = Chunk::Level(Arc::new(RwLock::new(chunk)));
}
}
struct Cache {
x: i32,
y: i32,
size: i32,
pub chunks: Vec<Chunk>,
}
impl HeightLimitView for Cache {
fn height(&self) -> u16 {
let mid = ((self.size * self.size) >> 1) as usize;
match &self.chunks[mid] {
Chunk::Proto(chunk) => chunk.height(),
_ => panic!(),
}
}
fn bottom_y(&self) -> i8 {
let mid = ((self.size * self.size) >> 1) as usize;
match &self.chunks[mid] {
Chunk::Proto(chunk) => chunk.bottom_y(),
_ => panic!(),
}
}
}
impl BlockAccessor for Cache {
fn get_block<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = &'static Block> + Send + 'a>> {
Box::pin(async move { GenerationCache::get_block_state(self, &position.0).to_block() })
}
fn get_block_state<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = &'static BlockState> + Send + 'a>> {
Box::pin(async move { GenerationCache::get_block_state(self, &position.0).to_state() })
}
fn get_block_and_state<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = (&'static Block, &'static BlockState)> + Send + 'a>> {
Box::pin(async move {
let id = GenerationCache::get_block_state(self, &position.0);
(id.to_block(), id.to_state())
})
}
}
impl GenerationCache for Cache {
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk {
let mid = ((self.size * self.size) >> 1) as usize;
self.chunks[mid].get_proto_chunk_mut()
}
fn get_fluid_and_fluid_state(&self, pos: &Vector3<i32>) -> (Fluid, FluidState) {
let id = GenerationCache::get_block_state(self, pos).0;
let Some(fluid) = Fluid::from_state_id(id) else {
let block = Block::from_state_id(id);
if let Some(properties) = block.properties(id) {
for (name, value) in properties.to_props() {
if name == "waterlogged" {
if value == "true" {
let fluid = Fluid::FLOWING_WATER;
let state = fluid.states[0].clone();
return (fluid, state);
}
break;
}
}
}
let fluid = Fluid::EMPTY;
let state = fluid.states[0].clone();
return (fluid, state);
};
//let state = fluid.get_state(id);
let state = fluid.states[0].clone();
(fluid.clone(), state)
}
fn get_block_state(&self, pos: &Vector3<i32>) -> RawBlockState {
let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.y;
// debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here
log::error!(
"illegal get_block_state {pos:?} cache pos ({}, {}) size {}",
self.x,
self.y,
self.size
);
return RawBlockState::AIR;
}
match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(data) => {
let chunk = data.blocking_read();
RawBlockState(
chunk
.section
.get_block_absolute_y((pos.x & 15) as usize, pos.y, (pos.z & 15) as usize)
.unwrap_or(0),
)
}
Chunk::Proto(data) => data.get_block_state(pos),
}
}
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState) {
let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.y;
// debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here
log::error!(
"illegal set_block_state {pos:?} cache pos ({}, {}) size {}",
self.x,
self.y,
self.size
);
return;
}
match &mut self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(data) => {
let mut chunk = data.blocking_write();
chunk.section.set_block_absolute_y(
(pos.x & 15) as usize,
pos.y,
(pos.z & 15) as usize,
block_state.id,
);
}
Chunk::Proto(data) => {
data.set_block_state(pos, block_state);
}
}
}
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
match heightmap {
HeightMap::WorldSurfaceWg => self.top_block_height_exclusive(x, z),
HeightMap::WorldSurface => self.top_block_height_exclusive(x, z),
HeightMap::OceanFloorWg => self.ocean_floor_height_exclusive(x, z),
HeightMap::OceanFloor => self.ocean_floor_height_exclusive(x, z),
HeightMap::MotionBlocking => self.top_motion_blocking_block_height_exclusive(x, z),
HeightMap::MotionBlockingNoLeaves => {
self.top_motion_blocking_block_no_leaves_height_exclusive(x, z)
}
}
}
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
let chunk = data.blocking_read();
chunk.heightmap.get_height(
ChunkHeightmapType::MotionBlocking,
x,
z,
chunk.section.min_y,
)
}
Chunk::Proto(data) => data.top_motion_blocking_block_height_exclusive(x, z),
}
}
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
let chunk = data.blocking_read();
chunk.heightmap.get_height(
ChunkHeightmapType::MotionBlockingNoLeaves,
x,
z,
chunk.section.min_y,
)
}
Chunk::Proto(data) => data.top_motion_blocking_block_no_leaves_height_exclusive(x, z),
}
}
fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
let chunk = data.blocking_read();
chunk.heightmap.get_height(
ChunkHeightmapType::WorldSurface,
x,
z,
chunk.section.min_y,
) // can we return this?
}
Chunk::Proto(data) => data.top_block_height_exclusive(x, z),
}
}
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(_data) => {
0 // todo missing
}
Chunk::Proto(data) => data.ocean_floor_height_exclusive(x, z),
}
}
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
// Could this happen?
Biome::from_id(
data.blocking_read()
.section
.get_rough_biome_absolute_y((x & 15) as usize, y, (z & 15) as usize)
.unwrap_or(0),
)
.unwrap()
}
Chunk::Proto(data) => data.get_biome_for_terrain_gen(x, y, z),
}
}
fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
GenerationCache::get_block_state(self, local_pos)
.to_state()
.is_air()
}
}
impl Cache {
fn new(x: i32, y: i32, size: i32) -> Cache {
Cache {
x,
y,
size,
chunks: Vec::with_capacity((size * size) as usize),
}
}
#[expect(clippy::too_many_arguments)]
pub fn advance(
&mut self,
stage: StagedChunkEnum,
block_registry: &dyn BlockRegistryExt,
settings: &GenerationSettings,
random_config: &GlobalRandomConfig,
terrain_cache: &TerrainCache,
noise_router: &ProtoNoiseRouters,
dimension: Dimension,
) {
let mid = ((self.size * self.size) >> 1) as usize;
match stage {
Empty => panic!("empty stage"),
StagedChunkEnum::StructureStart => self.chunks[mid]
.get_proto_chunk_mut()
.set_structure_starts(random_config),
StagedChunkEnum::StructureReferences => self.chunks[mid]
.get_proto_chunk_mut()
.set_structure_references(),
Biomes => self.chunks[mid]
.get_proto_chunk_mut()
.step_to_biomes(dimension, noise_router),
Noise => self.chunks[mid].get_proto_chunk_mut().step_to_noise(
settings,
random_config,
noise_router,
),
Surface => self.chunks[mid].get_proto_chunk_mut().step_to_surface(
settings,
random_config,
terrain_cache,
noise_router,
),
Features => {
ProtoChunk::generate_features_and_structure(self, block_registry, random_config)
}
Full => {
debug_assert_eq!(self.chunks[mid].get_proto_chunk_mut().stage, Features);
self.chunks[mid].get_proto_chunk_mut().stage = Full;
self.chunks[mid].upgrade_to_level_chunk(&dimension);
}
StagedChunkEnum::None => {}
}
}
}
enum RecvChunk {
IO(Chunk),
Generation(Cache),
}
pub struct ChunkListener {
single: Mutex<Vec<(ChunkPos, oneshot::Sender<SyncChunk>)>>,
global: Mutex<Vec<Sender<(ChunkPos, SyncChunk)>>>,
}
impl Default for ChunkListener {
fn default() -> Self {
Self::new()
}
}
impl ChunkListener {
pub fn new() -> Self {
Self {
single: Mutex::new(Vec::new()),
global: Mutex::new(Vec::new()),
}
}
pub fn add_single_chunk_listener(&self, pos: ChunkPos) -> oneshot::Receiver<SyncChunk> {
let (tx, rx) = oneshot::channel();
self.single.lock().unwrap().push((pos, tx));
rx
}
pub fn add_global_chunk_listener(&self) -> Receiver<(ChunkPos, SyncChunk)> {
let (tx, rx) = crossbeam::channel::unbounded();
self.global.lock().unwrap().push(tx);
rx
}
fn process_new_chunk(&self, pos: ChunkPos, chunk: &SyncChunk) {
{
let mut single = self.single.lock().unwrap();
let mut i = 0;
let mut len = single.len();
while i < len {
if single[i].0 == pos {
let (_, send) = single.remove(i);
let _ = send.send(chunk.clone());
// log::debug!("single listener {i} send {pos:?}");
len -= 1;
continue;
}
if single[i].1.is_closed() {
// let listener_pos = single[i].0;
single.remove(i);
// log::debug!("single listener dropped {listener_pos:?}");
len -= 1;
continue;
}
i += 1;
}
}
{
let mut global = self.global.lock().unwrap();
let mut i = 0;
let mut len = global.len();
while i < len {
match global[i].send((pos, chunk.clone())) {
Ok(_) => {
// log::debug!("global listener {i} send {pos:?}");
}
Err(_) => {
// log::debug!("one global listener dropped");
global.remove(i);
len -= 1;
continue;
}
}
i += 1;
}
}
}
}
struct ChunkHolder {
pub target_stage: StagedChunkEnum,
pub current_stage: StagedChunkEnum,
pub chunk: Option<Chunk>,
pub occupied: NodeKey,
pub occupied_by: EdgeKey,
pub public: bool,
pub tasks: [NodeKey; 9],
}
impl Default for ChunkHolder {
fn default() -> Self {
Self {
target_stage: StagedChunkEnum::None,
current_stage: StagedChunkEnum::None,
chunk: None,
occupied: NodeKey::null(),
occupied_by: EdgeKey::null(),
public: false,
tasks: [NodeKey::null(); 9],
}
}
}
#[derive(Clone, Debug)]
struct Node {
pub pos: ChunkPos,
pub stage: StagedChunkEnum,
pub in_degree: u32,
pub in_queue: bool,
pub edge: EdgeKey,
}
impl Node {
fn new(pos: ChunkPos, stage: StagedChunkEnum) -> Self {
Self {
pos,
stage,
in_degree: 0,
in_queue: false,
edge: EdgeKey::null(),
}
}
}
#[derive(Clone, Debug)]
struct Edge {
pub to: NodeKey,
pub next: EdgeKey,
}
impl Edge {
fn new(to: NodeKey, next: EdgeKey) -> Self {
Self { to, next }
}
}
new_key_type! { struct NodeKey; }
new_key_type! { struct EdgeKey; }
#[derive(Default)]
#[expect(clippy::upper_case_acronyms)]
struct DAG {
pub nodes: SlotMap<NodeKey, Node>,
pub edges: SlotMap<EdgeKey, Edge>,
}
impl DAG {
fn fast_drop_node(&mut self, node: NodeKey) {
let mut edge = self.nodes.remove(node).unwrap().edge;
// debug!("drop node {node:?}");
while !edge.is_null() {
edge = self.edges.remove(edge).unwrap().next;
}
}
fn add_edge(&mut self, from: NodeKey, to: NodeKey) {
self.nodes.get_mut(to).unwrap().in_degree += 1;
let edge = &mut self.nodes.get_mut(from).unwrap().edge;
*edge = self.edges.insert(Edge::new(to, *edge));
}
}
struct TaskHeapNode(i8, NodeKey);
impl PartialEq for TaskHeapNode {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for TaskHeapNode {}
impl PartialOrd for TaskHeapNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TaskHeapNode {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0).reverse()
}
}
pub struct GenerationSchedule {
queue: BinaryHeap<TaskHeapNode>,
graph: DAG,
last_level: ChunkLevel,
last_high_priority: Vec<ChunkPos>,
send_level: Arc<LevelChannel>,
public_chunk_map: Arc<DashMap<Vector2<i32>, SyncChunk>>,
chunk_map: HashMap<ChunkPos, ChunkHolder>,
unload_chunks: HashSetType<ChunkPos>,
io_lock: IOLock,
running_task_count: u16,
recv_chunk: crossfire::MRx<(ChunkPos, RecvChunk)>,
io_read: crossfire::MTx<ChunkPos>,
io_write: crossfire::Tx<Vec<(ChunkPos, Chunk)>>,
generate: crossfire::MTx<(ChunkPos, Cache, StagedChunkEnum)>,
listener: Arc<ChunkListener>,
}
impl GenerationSchedule {
pub fn create(
oi_read_thread_count: usize,
gen_thread_count: usize,
level: Arc<Level>,
level_channel: Arc<LevelChannel>,
listener: Arc<ChunkListener>,
thread_tracker: &mut Vec<JoinHandle<()>>,
) {
let tracker = &level.chunk_system_tasks;
let (send_chunk, recv_chunk) = crossfire::mpmc::unbounded_blocking();
let (send_read_io, recv_read_io) =
crossfire::mpmc::bounded_tx_blocking_rx_async(oi_read_thread_count + 2);
let (send_write_io, recv_write_io) = crossfire::spsc::unbounded_async();
let (send_gen, recv_gen) = crossfire::mpmc::bounded_blocking(gen_thread_count + 5);
let io_lock = Arc::new((Mutex::new(HashMapType::default()), Condvar::new()));
for _ in 0..oi_read_thread_count {
tracker.spawn(Self::io_read_work(
recv_read_io.clone(),
send_chunk.clone(),
level.clone(),
io_lock.clone(),
));
}
for i in 0..gen_thread_count {
let recv_gen = recv_gen.clone();
let send_chunk = send_chunk.clone();
let level = level.clone();
let builder = thread::Builder::new().name(format!("Generation Thread {i}"));
thread_tracker.push(
builder
.spawn(move || {
Self::generation_work(recv_gen, send_chunk, level);
})
.unwrap(),
);
}
tracker.spawn(Self::io_write_work(
recv_write_io,
level.clone(),
io_lock.clone(),
));
let builder = thread::Builder::new().name("Schedule Thread".to_string());
thread_tracker.push(
builder
.spawn(move || {
Self {
queue: BinaryHeap::new(),
graph: DAG::default(),
last_level: ChunkLevel::default(),
last_high_priority: Vec::new(),
send_level: level_channel,
public_chunk_map: level.loaded_chunks.clone(),
unload_chunks: HashSetType::default(),
io_lock,
running_task_count: 0,
recv_chunk,
io_read: send_read_io,
io_write: send_write_io,
generate: send_gen,
listener,
chunk_map: Default::default(),
}
.work(level);
})
.unwrap(),
)
}
fn calc_priority(
last_level: &ChunkLevel,
last_high_priority: &Vec<ChunkPos>,
pos: ChunkPos,
stage: StagedChunkEnum,
) -> i8 {
if last_high_priority.is_empty() {
return last_level.get(&pos).unwrap() + (stage as i8);
}
for i in last_high_priority {
let dst = max(abs(i.x - pos.x), abs(i.y - pos.y));
if dst <= StagedChunkEnum::FULL_RADIUS
&& stage <= StagedChunkEnum::FULL_DEPENDENCIES[dst as usize]
{
return last_level.get(&pos).unwrap() + (stage as i8) - 100;
}
}
last_level.get(&pos).unwrap() + (stage as i8)
}
fn sort_queue(&mut self) {
let mut new_queue = BinaryHeap::with_capacity(self.queue.len());
for i in &self.queue {
if let Some(node) = self.graph.nodes.get(i.1) {
new_queue.push(TaskHeapNode(
Self::calc_priority(
&self.last_level,
&self.last_high_priority,
node.pos,
node.stage,
),
i.1,
));
}
}
self.queue = new_queue;
}
fn resort_work(&mut self, new_data: (Option<LevelChange>, Option<Vec<ChunkPos>>)) -> bool {
// true -> updated | false -> not update
if new_data.0.is_none() && new_data.1.is_none() {
return false;
}
if let Some(high_priority) = new_data.1 {
// log::debug!("receive new priority");
self.last_high_priority = high_priority;
}
let Some(new_level) = new_data.0 else {
self.sort_queue();
return true;
};
// log::debug!("receive new level");
for (pos, (old_stage, new_stage)) in new_level.0 {
debug_assert_ne!(old_stage, new_stage);
debug_assert_eq!(
new_stage,
StagedChunkEnum::level_to_stage(
*new_level.1.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL)
)
);
let mut holder = self.chunk_map.remove(&pos).unwrap_or_default();
debug_assert_eq!(holder.target_stage, old_stage);
holder.target_stage = new_stage;
if old_stage > new_stage {
for i in (new_stage.max(holder.current_stage) as usize + 1)..=(old_stage as usize) {
let task = &mut holder.tasks[i];
self.graph.fast_drop_node(*task);
*task = NodeKey::null();
}
if new_stage == StagedChunkEnum::None {
self.unload_chunks.insert(pos);
}
} else {
if old_stage == StagedChunkEnum::None {
self.unload_chunks.remove(&pos);
if holder.current_stage == Full && !holder.public {
holder.public = true;
match holder.chunk.as_ref().unwrap() {
Chunk::Level(chunk) => {
self.public_chunk_map.insert(pos, chunk.clone());
self.listener.process_new_chunk(pos, chunk);
}
Proto(_) => panic!(),
}
}
}
for i in (old_stage.max(holder.current_stage) as u8 + 1)..=(new_stage as u8) {
let task = &mut holder.tasks[i as usize];
if task.is_null() {
*task = self.graph.nodes.insert(Node::new(pos, i.into()));
if !holder.occupied.is_null() {
self.graph.add_edge(holder.occupied, *task);
}
}
let task = *task;
if i > 1 {
let stage = StagedChunkEnum::from(i);
let dependency = stage.get_direct_dependencies();
let radius = stage.get_direct_radius();
for dx in -radius..=radius {
for dy in -radius..=radius {
let new_pos = pos.add_raw(dx, dy);
let req_stage = dependency[dx.abs().max(dy.abs()) as usize];
if new_pos == pos {
// TODO
holder.occupied_by = self
.graph
.edges
.insert(Edge::new(task, holder.occupied_by));
if holder.current_stage >= req_stage {
continue;
}
let ano_task = &mut holder.tasks[req_stage as usize];
if ano_task.is_null() {
*ano_task =
self.graph.nodes.insert(Node::new(new_pos, i.into()));
}
self.graph.add_edge(*ano_task, task); // task depend on ano_task
continue;
}
let ano_chunk = self.chunk_map.entry(new_pos).or_default();
ano_chunk.occupied_by = self
.graph
.edges
.insert(Edge::new(task, ano_chunk.occupied_by));
if !ano_chunk.occupied.is_null() {
self.graph.add_edge(ano_chunk.occupied, task);
}
if ano_chunk.current_stage >= req_stage {
continue;
}
let ano_task = &mut ano_chunk.tasks[req_stage as usize];
if ano_task.is_null() {
*ano_task =
self.graph.nodes.insert(Node::new(new_pos, req_stage));
}
self.graph.add_edge(*ano_task, task); // task depend on ano_task
}
}
}
let node = self.graph.nodes.get_mut(task).unwrap();
if node.in_degree == 0 {
node.in_queue = true;
self.queue.push(TaskHeapNode(0, task));
}
}
}
self.chunk_map.insert(pos, holder);
}
self.last_level = new_level.1;
self.sort_queue();
true
}
async fn io_read_work(
recv: crossfire::MAsyncRx<ChunkPos>,
send: crossfire::MTx<(ChunkPos, RecvChunk)>,
level: Arc<Level>,
lock: IOLock,
) {
log::info!("io read thread start");
use crate::biome::hash_seed;
let biome_mixer_seed = hash_seed(level.world_gen.random_config.seed);
let dimension = &level.world_gen.dimension;
let (t_send, mut t_recv) = tokio::sync::mpsc::channel(2);
while let Ok(pos) = recv.recv().await {
// debug!("io read thread receive chunk pos {pos:?}");
{
let mut data = lock.0.lock().unwrap();
while data.contains_key(&pos) {
data = tokio::task::block_in_place(|| lock.1.wait(data).unwrap());
}
}
level
.chunk_saver
.fetch_chunks(&level.level_folder, &[pos], t_send.clone())
.await;
let data = t_recv.recv().await.unwrap();
match data {
Loaded(chunk) => {
if chunk.read().await.status == ChunkStatus::Full {
if send
.send((pos, RecvChunk::IO(Chunk::Level(chunk))))
.is_err()
{
break;
}
} else {
// debug!("io read thread receive proto chunk {pos:?}",);
let val =
RecvChunk::IO(Chunk::Proto(Box::new(ProtoChunk::from_chunk_data(
chunk.read().await.deref(),
dimension,
level.world_gen.default_block,
biome_mixer_seed,
))));
if send.send((pos, val)).is_err() {
break;
}
}
continue;
}
LoadedData::Missing(_) => {}
LoadedData::Error(_) => {
log::warn!("chunk data read error pos: {pos:?}. regenerating");
}
}
if send
.send((
pos,
RecvChunk::IO(Proto(Box::new(ProtoChunk::new(
pos.x,
pos.y,
dimension,
level.world_gen.default_block,
biome_mixer_seed,
)))),
))
.is_err()
{
break;
}
}
log::info!("io read thread stop");
}
async fn io_write_work(recv: AsyncRx<Vec<(ChunkPos, Chunk)>>, level: Arc<Level>, lock: IOLock) {
log::info!("io write thread start",);
while let Ok(data) = recv.recv().await {
// debug!("io write thread receive chunks size {}", data.len());
let mut vec = Vec::with_capacity(data.len());
for (pos, chunk) in data {
match chunk {
Chunk::Level(chunk) => vec.push((pos, chunk)),
Proto(chunk) => {
let mut temp = Proto(chunk);
temp.upgrade_to_level_chunk(&level.world_gen.dimension);
let Chunk::Level(chunk) = temp else { panic!() };
vec.push((pos, chunk));
}
}
}
let pos = vec.iter().map(|(pos, _)| *pos).collect_vec();
level
.chunk_saver
.save_chunks(&level.level_folder, vec)
.await
.unwrap();
for i in pos {
let mut data = lock.0.lock().unwrap();
match data.entry(i) {
Entry::Occupied(mut entry) => {
let rc = entry.get_mut();
if *rc == 1 {
entry.remove();
drop(data);
lock.1.notify_all();
} else {
*rc -= 1;
}
}
Entry::Vacant(_) => panic!(),
}
}
}
log::info!(
"io write thread stop id: {:?} name: {}",
thread::current().id(),
thread::current().name().unwrap_or("unknown")
);
}
fn generation_work(
recv: crossfire::MRx<(ChunkPos, Cache, StagedChunkEnum)>,
send: crossfire::MTx<(ChunkPos, RecvChunk)>,
level: Arc<Level>,
) {
log::info!(
"generation thread start id: {:?} name: {}",
thread::current().id(),
thread::current().name().unwrap_or("unknown")
);
let settings = gen_settings_from_dimension(&level.world_gen.dimension);
while let Ok((pos, mut cache, stage)) = recv.recv() {
// debug!("generation thread receive chunk pos {pos:?} to stage {stage:?}");
cache.advance(
stage,
level.block_registry.as_ref(),
settings,
&level.world_gen.random_config,
&level.world_gen.terrain_cache,
&level.world_gen.base_router,
level.world_gen.dimension,
);
if send.send((pos, RecvChunk::Generation(cache))).is_err() {
break;
}
}
log::info!(
"generation thread stop id: {:?} name: {}",
thread::current().id(),
thread::current().name().unwrap_or("unknown")
);
}
fn unload_chunk(&mut self) {
let mut unload_chunks = HashSetType::default();
swap(&mut unload_chunks, &mut self.unload_chunks);
let mut chunks = Vec::with_capacity(unload_chunks.len());
for pos in unload_chunks {
let holder = self.chunk_map.get_mut(&pos).unwrap();
debug_assert_eq!(holder.target_stage, StagedChunkEnum::None);
if holder.occupied.is_null() {
let mut tmp = None;
swap(&mut holder.chunk, &mut tmp);
let Some(tmp) = tmp else {
continue;
};
match tmp {
Chunk::Level(chunk) => {
if holder.public {
self.public_chunk_map.remove(&pos);
holder.public = false;
}
if Arc::strong_count(&chunk) != 1 {
// log::debug!(
// "chunk {pos:?} is still used somewhere. it can't be unloaded"
// );
self.unload_chunks.insert(pos);
holder.chunk = Some(Chunk::Level(chunk));
} else {
// log::debug!("unload chunk {pos:?} to file");
chunks.push((pos, Chunk::Level(chunk)));
self.chunk_map.remove(&pos);
}
}
Chunk::Proto(chunk) => {
debug_assert!(!holder.public);
// log::debug!("unload proto chunk {pos:?} to file");
chunks.push((pos, Proto(chunk)));
self.chunk_map.remove(&pos);
}
}
}
}
// log::debug!("send {} unloaded chunks to io write", chunks.len());
if chunks.is_empty() {
return;
}
let mut data = self.io_lock.0.lock().unwrap();
for (pos, _chunk) in &chunks {
*data.entry(*pos).or_insert(0) += 1;
}
drop(data);
self.io_write.send(chunks).expect("io write thread stop");
}
fn save_all_chunk(&self, save_proto_chunk: bool) {
let mut chunks = Vec::with_capacity(self.chunk_map.len());
for (pos, chunk) in &self.chunk_map {
if let Some(chunk) = &chunk.chunk {
match chunk {
Chunk::Level(chunk) => {
chunks.push((*pos, Chunk::Level(chunk.clone())));
}
Chunk::Proto(chunk) => {
if save_proto_chunk {
chunks.push((*pos, Chunk::Proto(chunk.clone())));
}
}
}
}
}
// log::debug!("send {} chunks to io write", chunks.len());
if chunks.is_empty() {
return;
}
let mut data = self.io_lock.0.lock().unwrap();
for (pos, _chunk) in &chunks {
*data.entry(*pos).or_insert(0) += 1;
}
drop(data);
self.io_write.send(chunks).expect("io write thread stop");
}
fn drop_node(&mut self, node: NodeKey) {
let Some(old) = self.graph.nodes.remove(node) else {
return;
};
// debug!("drop node {node:?}");
let mut edge = old.edge;
while !edge.is_null() {
let cur = self.graph.edges.remove(edge).unwrap();
if let Some(node) = self.graph.nodes.get_mut(cur.to) {
debug_assert!(node.in_degree >= 1);
node.in_degree -= 1;
if node.in_degree == 0 && !node.in_queue {
self.queue.push(TaskHeapNode(
Self::calc_priority(
&self.last_level,
&self.last_high_priority,
node.pos,
node.stage,
),
cur.to,
));
node.in_queue = true;
}
}
edge = cur.next;
}
}
fn receive_chunk(&mut self, pos: ChunkPos, data: RecvChunk) {
match data {
RecvChunk::IO(chunk) => {
// debug!("receive io chunk pos {pos:?}");
let mut holder = self.chunk_map.remove(&pos).unwrap();
debug_assert!(holder.chunk.is_none());
debug_assert_eq!(holder.current_stage, StagedChunkEnum::None);
for i in (holder.current_stage as usize + 1)..=(chunk.get_stage_id() as usize) {
self.drop_node(holder.tasks[i]);
holder.tasks[i] = NodeKey::null();
}
holder.current_stage = StagedChunkEnum::from(chunk.get_stage_id());
debug_assert!(self.graph.nodes.contains_key(holder.occupied));
self.drop_node(holder.occupied);
holder.occupied = NodeKey::null();
debug_assert!(!holder.public);
match &chunk {
Chunk::Level(data) => {
let result = self.public_chunk_map.insert(pos, data.clone());
debug_assert!(result.is_none());
holder.public = true;
self.listener.process_new_chunk(pos, data);
}
Chunk::Proto(_) => {}
}
holder.chunk = Some(chunk);
self.chunk_map.insert(pos, holder);
}
RecvChunk::Generation(data) => {
// debug!("receive gen chunk pos {pos:?}");
let mut dx = 0;
let mut dy = 0;
for chunk in data.chunks {
let new_pos = ChunkPos::new(data.x + dx, data.y + dy);
match chunk {
Chunk::Level(chunk) => {
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
if new_pos == pos {
debug_assert_eq!(holder.current_stage, Features);
self.drop_node(holder.tasks[Full as usize]);
holder.tasks[Full as usize] = NodeKey::null();
debug_assert!(self.graph.nodes.contains_key(holder.occupied));
self.drop_node(holder.occupied);
holder.current_stage = Full;
holder.chunk = Some(Chunk::Level(chunk.clone()));
debug_assert!(!holder.public);
let result = self.public_chunk_map.insert(new_pos, chunk.clone());
holder.public = true;
debug_assert!(result.is_none());
self.listener.process_new_chunk(new_pos, &chunk);
}
holder.occupied = NodeKey::null();
self.chunk_map.insert(new_pos, holder);
}
Chunk::Proto(chunk) => {
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
debug_assert!(holder.chunk.is_none());
debug_assert_eq!(
holder.current_stage as u8,
if new_pos == pos {
chunk.stage_id() - 1
} else {
chunk.stage_id()
}
);
if new_pos == pos {
debug_assert_ne!(holder.current_stage, StagedChunkEnum::None);
let stage = chunk.stage_id();
self.drop_node(holder.tasks[stage as usize]);
holder.tasks[stage as usize] = NodeKey::null();
debug_assert!(self.graph.nodes.contains_key(holder.occupied));
self.drop_node(holder.occupied);
holder.current_stage = StagedChunkEnum::from(stage);
}
holder.occupied = NodeKey::null();
holder.chunk = Some(Chunk::Proto(chunk));
self.chunk_map.insert(new_pos, holder);
}
}
dy += 1;
if dy == data.size {
dy = 0;
dx += 1;
}
}
}
}
self.running_task_count -= 1;
}
fn work(mut self, level: Arc<Level>) {
log::info!(
"schedule thread start id: {:?} name: {}",
thread::current().id(),
thread::current().name().unwrap_or("unknown")
);
// let mut clock = Instant::now();
loop {
if level.should_unload.load(Relaxed) {
// log::debug!("unload chunk signal");
self.unload_chunk();
level.should_unload.store(false, Relaxed);
}
if level.should_save.load(Relaxed) {
// log::debug!("save all chunk signal");
self.save_all_chunk(false);
level.should_save.store(false, Relaxed);
}
if level.shut_down_chunk_system.load(Relaxed) {
// log::debug!("shut down signal");
break;
}
'out2: while let Some(task) = self.queue.pop() {
if self.resort_work(self.send_level.get()) {
self.queue.push(task);
break 'out2;
}
while let Ok((pos, data)) = self.recv_chunk.try_recv() {
self.receive_chunk(pos, data);
}
if let Some(node) = self.graph.nodes.get_mut(task.1) {
if node.in_degree != 0 {
node.in_queue = false;
continue;
}
let node = node.clone();
if node.stage == Empty {
self.running_task_count += 1;
let holder = self.chunk_map.get_mut(&node.pos).unwrap();
debug_assert!(holder.occupied.is_null());
debug_assert_eq!(holder.current_stage, StagedChunkEnum::None);
let occupy = self.graph.nodes.insert(Node::new(
ChunkPos::new(i32::MAX, i32::MAX),
StagedChunkEnum::None,
));
for i in
(holder.current_stage as usize + 1)..=(holder.target_stage as usize)
{
self.graph.add_edge(occupy, holder.tasks[i]);
}
holder.occupied = occupy;
// debug!("send task {:?} {node:?}", task.1);
self.io_read
.send(node.pos)
.expect("io thread close unexpectedly");
} else {
let occupy = self.graph.nodes.insert(Node::new(
ChunkPos::new(i32::MAX, i32::MAX),
StagedChunkEnum::None,
));
let write_radius = node.stage.get_write_radius();
let mut cache = Cache::new(
node.pos.x - write_radius,
node.pos.y - write_radius,
write_radius << 1 | 1,
);
#[cfg(debug_assertions)]
{
let dp = node.stage.get_direct_dependencies();
let r = node.stage.get_direct_radius();
for dx in -r..=r {
for dy in -r..=r {
let new_pos = node.pos.add_raw(dx, dy);
let holder = self.chunk_map.get(&new_pos).unwrap();
let dst = dy.abs().max(dx.abs());
debug_assert!(holder.current_stage >= dp[dst as usize]);
if dx == 0 && dy == 0 {
debug_assert_eq!(holder.current_stage, dp[0]);
}
}
}
}
for dx in -write_radius..=write_radius {
for dy in -write_radius..=write_radius {
let new_pos = node.pos.add_raw(dx, dy);
let holder = self.chunk_map.get_mut(&new_pos).unwrap();
let mut tmp = None;
swap(&mut tmp, &mut holder.chunk);
match tmp.unwrap() {
Chunk::Level(chunk) => {
cache.chunks.push(Chunk::Level(chunk.clone()));
holder.chunk = Some(Chunk::Level(chunk));
}
Proto(chunk) => cache.chunks.push(Proto(chunk)),
}
debug_assert!(holder.occupied.is_null());
let mut cur_edge = holder.occupied_by;
let mut prev_edge = EdgeKey::null();
let mut change_head = None;
while !cur_edge.is_null() {
let edge = self.graph.edges.get(cur_edge).unwrap();
if self.graph.nodes.contains_key(edge.to) {
prev_edge = cur_edge;
cur_edge = edge.next;
self.graph.add_edge(occupy, edge.to);
} else {
let next = edge.next;
self.graph.edges.remove(cur_edge);
cur_edge = next;
if prev_edge.is_null() {
change_head = Some(next);
} else {
self.graph.edges.get_mut(prev_edge).unwrap().next =
next;
}
}
}
if let Some(next) = change_head {
holder.occupied_by = next;
}
holder.occupied = occupy;
}
}
// debug!("send task {:?} {node:?}", task.1);
self.running_task_count += 1;
self.generate
.send((node.pos, cache, node.stage))
.expect("generate thread close unexpectedly");
}
}
}
if self.queue.is_empty() {
// debug!("the queue is empty. thread sleep");
'out: while self.running_task_count > 0 {
let (pos, data) = self.recv_chunk.recv().expect("recv_chunk stop");
self.receive_chunk(pos, data);
if !self.queue.is_empty() || self.resort_work(self.send_level.get()) {
break 'out;
}
}
if self.queue.is_empty() {
// debug!("no work to do. thread sleep");
debug_assert!(self.running_task_count > 0 || self.debug_check());
self.resort_work(self.send_level.wait_and_get(&level));
}
}
}
log::info!("waiting all generation task finished");
while self.running_task_count > 0 {
let (pos, data) = self.recv_chunk.recv().expect("recv_chunk stop");
self.receive_chunk(pos, data);
}
log::info!("saving all chunks");
self.save_all_chunk(true);
log::info!("there are {} chunks to write", self.io_write.len());
log::info!(
"schedule thread stop id: {:?} name: {}",
thread::current().id(),
thread::current().name().unwrap_or("unknown")
);
}
fn debug_check(&self) -> bool {
if !self.graph.nodes.is_empty() {
// error!("nodes: {:?}", self.graph.nodes);
for (key, value) in &self.graph.nodes {
error!("unrelease node {key:?}: {value:?}");
}
panic!("nodes count error");
}
for (pos, holder) in &self.chunk_map {
for i in &holder.tasks {
debug_assert!(i.is_null());
}
debug_assert_eq!(
holder.target_stage,
StagedChunkEnum::level_to_stage(
*self.last_level.get(pos).unwrap_or(&ChunkLoading::MAX_LEVEL)
)
);
debug_assert!(holder.current_stage >= holder.target_stage);
debug_assert!(holder.occupied.is_null());
if holder.current_stage != StagedChunkEnum::None {
debug_assert_eq!(
holder.chunk.as_ref().unwrap().get_stage_id(),
holder.current_stage as u8
);
}
}
true
}
}