fix: the chunk unloading issue

at least hopefully, i think it was an Ticket Overlap, By adding the new ticket before removing the old one, the ChunkLoading system always sees at least one valid path for the chunks that overlap between the two view distances.
The fix seems to made the system slower but atleast it seems to not bug anymore
This commit is contained in:
Alexander Medvedev 2026-01-16 17:48:30 +01:00
parent 1e691c71b9
commit 6f18d16581
10 changed files with 114 additions and 50 deletions

View File

@ -19,7 +19,7 @@ use crate::generation::proto_chunk::{GenerationCache, TerrainCache};
use crate::generation::settings::{GenerationSettings, gen_settings_from_dimension}; use crate::generation::settings::{GenerationSettings, gen_settings_from_dimension};
use crate::level::{Level, SyncChunk}; use crate::level::{Level, SyncChunk};
use crate::world::{BlockAccessor, BlockRegistryExt}; use crate::world::{BlockAccessor, BlockRegistryExt};
use crate::{GlobalRandomConfig, ProtoChunk, ProtoNoiseRouters}; use crate::{BlockStateId, GlobalRandomConfig, ProtoChunk, ProtoNoiseRouters};
use crossbeam::channel::{Receiver, Sender}; use crossbeam::channel::{Receiver, Sender};
use dashmap::DashMap; use dashmap::DashMap;
use itertools::Itertools; use itertools::Itertools;
@ -898,6 +898,13 @@ impl BlockAccessor for Cache {
Box::pin(async move { GenerationCache::get_block_state(self, &position.0).to_state() }) Box::pin(async move { GenerationCache::get_block_state(self, &position.0).to_state() })
} }
fn get_block_state_id<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = BlockStateId> + Send + 'a>> {
Box::pin(async move { GenerationCache::get_block_state(self, &position.0).0 })
}
fn get_block_and_state<'a>( fn get_block_and_state<'a>(
&'a self, &'a self,
position: &'a BlockPos, position: &'a BlockPos,

View File

@ -1133,6 +1133,13 @@ impl BlockAccessor for ProtoChunk {
Box::pin(async move { self.get_block_state(&position.0).to_state() }) Box::pin(async move { self.get_block_state(&position.0).to_state() })
} }
fn get_block_state_id<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = BlockStateId> + Send + 'a>> {
Box::pin(async move { self.get_block_state(&position.0).0 })
}
fn get_block_and_state<'a>( fn get_block_and_state<'a>(
&'a self, &'a self,
position: &'a BlockPos, position: &'a BlockPos,

View File

@ -598,31 +598,32 @@ impl Level {
} }
pub async fn get_chunk(self: &Arc<Self>, pos: Vector2<i32>) -> SyncChunk { pub async fn get_chunk(self: &Arc<Self>, pos: Vector2<i32>) -> SyncChunk {
// Already loaded? // Check if already in memory
if let Some(chunk) = self.loaded_chunks.get(&pos) { if let Some(chunk) = self.loaded_chunks.get(&pos) {
chunk.clone() return chunk.clone();
} else { }
log::debug!("Missing Chunk {pos:?}. Fetching."); log::debug!("Missing Chunk {pos:?}. Fetching.");
let clock = Instant::now(); let clock = Instant::now();
let recv = self.chunk_listener.add_single_chunk_listener(pos); let recv = self.chunk_listener.add_single_chunk_listener(pos);
{ {
let mut lock = self.chunk_loading.lock().unwrap(); let mut lock = self.chunk_loading.lock().unwrap();
lock.add_force_ticket(pos); lock.add_ticket(pos, 31);
lock.send_change(); lock.send_change();
} }
let ret = if let Some(chunk) = self.loaded_chunks.get(&pos) { let ret = if let Some(chunk) = self.loaded_chunks.get(&pos) {
// try again here. otherwise deadlock
chunk.clone() chunk.clone()
} else { } else {
recv.await.unwrap() recv.await
.expect("Chunk listener dropped without sending chunk")
}; };
let mut lock = self.chunk_loading.lock().unwrap();
lock.remove_force_ticket(pos);
log::debug!("Chunk {pos:?} received after {:?}.", Instant::now() - clock); log::debug!("Chunk {pos:?} received after {:?}.", Instant::now() - clock);
ret ret
} }
}
async fn load_single_entity_chunk( async fn load_single_entity_chunk(
&self, &self,

View File

@ -138,6 +138,11 @@ pub trait BlockAccessor: Send + Sync {
position: &'a BlockPos, position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = &'static BlockState> + Send + 'a>>; ) -> Pin<Box<dyn Future<Output = &'static BlockState> + Send + 'a>>;
fn get_block_state_id<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = BlockStateId> + Send + 'a>>;
fn get_block_and_state<'a>( fn get_block_and_state<'a>(
&'a self, &'a self,
position: &'a BlockPos, position: &'a BlockPos,

View File

@ -241,7 +241,7 @@ pub trait RedstoneGateBlock<T: Send + Sync + BlockProperties + RedstoneGateBlock
self, self,
args.world, args.world,
*args.position, *args.position,
BlockState::from_id(args.old_state_id).id, args.old_state_id,
args.block, args.block,
) )
.await; .await;

View File

@ -67,7 +67,7 @@ impl BlockBehaviour for ComparatorBlock {
self, self,
args.world, args.world,
*args.position, *args.position,
BlockState::from_id(args.state_id).id, args.state_id,
args.block, args.block,
) )
.await; .await;

View File

@ -162,7 +162,7 @@ impl BlockBehaviour for RepeaterBlock {
self, self,
args.world, args.world,
*args.position, *args.position,
BlockState::from_id(args.state_id).id, args.state_id,
args.block, args.block,
) )
.await; .await;

View File

@ -692,21 +692,21 @@ impl BlockRegistry {
block: &Block, block: &Block,
flags: BlockFlags, flags: BlockFlags,
) { ) {
let state = world.get_block_state(position).await; let state_id = world.get_block_state_id(position).await;
for direction in BlockDirection::all() { for direction in BlockDirection::all() {
let neighbor_pos = position.offset(direction.to_offset()); let neighbor_pos = position.offset(direction.to_offset());
let neighbor_state = world.get_block_state(&neighbor_pos).await; let neighbor_state_id = world.get_block_state_id(&neighbor_pos).await;
let pumpkin_block = self.get_pumpkin_block(block); let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block { if let Some(pumpkin_block) = pumpkin_block {
let new_state = pumpkin_block let new_state = pumpkin_block
.get_state_for_neighbor_update(GetStateForNeighborUpdateArgs { .get_state_for_neighbor_update(GetStateForNeighborUpdateArgs {
world, world,
block, block,
state_id: state.id, state_id,
position, position,
direction: direction.opposite(), direction: direction.opposite(),
neighbor_position: &neighbor_pos, neighbor_position: &neighbor_pos,
neighbor_state_id: neighbor_state.id, neighbor_state_id,
}) })
.await; .await;
world.set_block_state(&neighbor_pos, new_state, flags).await; world.set_block_state(&neighbor_pos, new_state, flags).await;

View File

@ -185,41 +185,78 @@ impl ChunkManager {
level: &Arc<Level>, level: &Arc<Level>,
) { ) {
view_distance += 1; view_distance += 1;
let old_center = self.center;
let old_view_distance = self.view_distance;
{
let mut lock = level.chunk_loading.lock().unwrap(); let mut lock = level.chunk_loading.lock().unwrap();
lock.add_ticket( lock.add_ticket(
center, center,
ChunkLoading::get_level_from_view_distance(view_distance), ChunkLoading::get_level_from_view_distance(view_distance),
); );
if old_center != center || old_view_distance != view_distance {
lock.remove_ticket( lock.remove_ticket(
self.center, old_center,
ChunkLoading::get_level_from_view_distance(self.view_distance), ChunkLoading::get_level_from_view_distance(old_view_distance),
); );
}
lock.send_change(); lock.send_change();
drop(lock); };
let view_distance = i32::from(view_distance);
self.chunk_sent let view_distance_i32 = i32::from(view_distance);
.retain(|pos| (pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance);
let mut chunks_to_watch = Vec::new();
let mut chunks_to_unwatch = Vec::new();
for pos in &self.chunk_sent {
if (pos.x - center.x).abs().max((pos.y - center.y).abs()) > view_distance_i32 {
chunks_to_unwatch.push(*pos);
}
}
let level_clone = level.clone();
let chunks_to_unwatch_clone = chunks_to_unwatch.clone();
tokio::spawn(async move {
level_clone
.mark_chunks_as_not_watched(&chunks_to_unwatch_clone)
.await;
});
self.chunk_sent.retain(|pos| {
(pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance_i32
});
let mut new_queue = BinaryHeap::with_capacity(self.chunk_queue.len()); let mut new_queue = BinaryHeap::with_capacity(self.chunk_queue.len());
for node in &self.chunk_queue { for node in &self.chunk_queue {
let dst = (node.1.x - center.x).abs().max((node.1.y - center.y).abs()); let dst = (node.1.x - center.x).abs().max((node.1.y - center.y).abs());
if dst <= view_distance { if dst <= view_distance_i32 {
new_queue.push(HeapNode(dst, node.1, node.2.clone())); new_queue.push(HeapNode(dst, node.1, node.2.clone()));
} }
} }
self.chunk_queue = new_queue; self.chunk_queue = new_queue;
self.center = center; self.center = center;
self.view_distance = view_distance as u8; self.view_distance = view_distance;
for dx in (-view_distance)..=view_distance {
for dy in (-view_distance)..=view_distance { for dx in (-view_distance_i32)..=view_distance_i32 {
for dy in (-view_distance_i32)..=view_distance_i32 {
let new_pos = center.add_raw(dx, dy); let new_pos = center.add_raw(dx, dy);
if !self.chunk_sent.contains(&new_pos) if !self.chunk_sent.contains(&new_pos) {
&& let Some(chunk) = level.loaded_chunks.get(&new_pos) chunks_to_watch.push(new_pos);
{ if let Some(chunk) = level.loaded_chunks.get(&new_pos) {
self.push_chunk(new_pos, chunk.value().clone()); self.push_chunk(new_pos, chunk.value().clone());
} }
} }
} }
} }
let level_clone = level.clone();
tokio::spawn(async move {
level_clone
.mark_chunks_as_newly_watched(&chunks_to_watch)
.await;
});
}
pub fn clean_up(&mut self, level: &Arc<Level>) { pub fn clean_up(&mut self, level: &Arc<Level>) {
let mut lock = level.chunk_loading.lock().unwrap(); let mut lock = level.chunk_loading.lock().unwrap();

View File

@ -3452,6 +3452,13 @@ impl BlockAccessor for World {
Box::pin(async move { Self::get_block_state(self, position).await }) Box::pin(async move { Self::get_block_state(self, position).await })
} }
fn get_block_state_id<'a>(
&'a self,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = BlockStateId> + Send + 'a>> {
Box::pin(async move { Self::get_block_state_id(self, position).await })
}
fn get_block_and_state<'a>( fn get_block_and_state<'a>(
&'a self, &'a self,
position: &'a BlockPos, position: &'a BlockPos,