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::level::{Level, SyncChunk};
use crate::world::{BlockAccessor, BlockRegistryExt};
use crate::{GlobalRandomConfig, ProtoChunk, ProtoNoiseRouters};
use crate::{BlockStateId, GlobalRandomConfig, ProtoChunk, ProtoNoiseRouters};
use crossbeam::channel::{Receiver, Sender};
use dashmap::DashMap;
use itertools::Itertools;
@ -898,6 +898,13 @@ impl BlockAccessor for Cache {
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>(
&'a self,
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() })
}
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>(
&'a self,
position: &'a BlockPos,

View File

@ -598,30 +598,31 @@ impl Level {
}
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) {
return chunk.clone();
}
log::debug!("Missing Chunk {pos:?}. Fetching.");
let clock = Instant::now();
let recv = self.chunk_listener.add_single_chunk_listener(pos);
{
let mut lock = self.chunk_loading.lock().unwrap();
lock.add_ticket(pos, 31);
lock.send_change();
}
let ret = if let Some(chunk) = self.loaded_chunks.get(&pos) {
chunk.clone()
} else {
log::debug!("Missing Chunk {pos:?}. Fetching.");
let clock = Instant::now();
let recv = self.chunk_listener.add_single_chunk_listener(pos);
{
let mut lock = self.chunk_loading.lock().unwrap();
lock.add_force_ticket(pos);
lock.send_change();
}
recv.await
.expect("Chunk listener dropped without sending chunk")
};
let ret = if let Some(chunk) = self.loaded_chunks.get(&pos) {
// try again here. otherwise deadlock
chunk.clone()
} else {
recv.await.unwrap()
};
let mut lock = self.chunk_loading.lock().unwrap();
lock.remove_force_ticket(pos);
log::debug!("Chunk {pos:?} received after {:?}.", Instant::now() - clock);
ret
}
log::debug!("Chunk {pos:?} received after {:?}.", Instant::now() - clock);
ret
}
async fn load_single_entity_chunk(

View File

@ -138,6 +138,11 @@ pub trait BlockAccessor: Send + Sync {
position: &'a BlockPos,
) -> 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>(
&'a self,
position: &'a BlockPos,

View File

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

View File

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

View File

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

View File

@ -692,21 +692,21 @@ impl BlockRegistry {
block: &Block,
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() {
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);
if let Some(pumpkin_block) = pumpkin_block {
let new_state = pumpkin_block
.get_state_for_neighbor_update(GetStateForNeighborUpdateArgs {
world,
block,
state_id: state.id,
state_id,
position,
direction: direction.opposite(),
neighbor_position: &neighbor_pos,
neighbor_state_id: neighbor_state.id,
neighbor_state_id,
})
.await;
world.set_block_state(&neighbor_pos, new_state, flags).await;

View File

@ -185,40 +185,77 @@ impl ChunkManager {
level: &Arc<Level>,
) {
view_distance += 1;
let mut lock = level.chunk_loading.lock().unwrap();
lock.add_ticket(
center,
ChunkLoading::get_level_from_view_distance(view_distance),
);
lock.remove_ticket(
self.center,
ChunkLoading::get_level_from_view_distance(self.view_distance),
);
lock.send_change();
drop(lock);
let view_distance = i32::from(view_distance);
self.chunk_sent
.retain(|pos| (pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance);
let old_center = self.center;
let old_view_distance = self.view_distance;
{
let mut lock = level.chunk_loading.lock().unwrap();
lock.add_ticket(
center,
ChunkLoading::get_level_from_view_distance(view_distance),
);
if old_center != center || old_view_distance != view_distance {
lock.remove_ticket(
old_center,
ChunkLoading::get_level_from_view_distance(old_view_distance),
);
}
lock.send_change();
};
let view_distance_i32 = i32::from(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());
for node in &self.chunk_queue {
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()));
}
}
self.chunk_queue = new_queue;
self.center = center;
self.view_distance = view_distance as u8;
for dx in (-view_distance)..=view_distance {
for dy in (-view_distance)..=view_distance {
self.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);
if !self.chunk_sent.contains(&new_pos)
&& let Some(chunk) = level.loaded_chunks.get(&new_pos)
{
self.push_chunk(new_pos, chunk.value().clone());
if !self.chunk_sent.contains(&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());
}
}
}
}
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>) {

View File

@ -3452,6 +3452,13 @@ impl BlockAccessor for World {
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>(
&'a self,
position: &'a BlockPos,