chore: chunk gen should be better now

This commit is contained in:
Alexander Medvedev 2026-04-13 18:18:30 +02:00
parent c06ae2715b
commit 511318ae6b
10 changed files with 359 additions and 246 deletions

15
Cargo.lock generated
View File

@ -2765,6 +2765,7 @@ dependencies = [
"hmac 0.13.0",
"libloading",
"num-bigint",
"num_cpus",
"ordered-float",
"pkcs8 0.11.0-rc.11",
"postcard",
@ -2776,7 +2777,8 @@ dependencies = [
"pumpkin-protocol",
"pumpkin-util",
"pumpkin-world",
"rand 0.10.0",
"rand 0.10.1",
"rayon",
"rsa",
"rustc-hash",
"rustc_version_runtime",
@ -2841,7 +2843,7 @@ dependencies = [
"phf",
"pumpkin-nbt",
"pumpkin-util",
"rand 0.10.0",
"rand 0.10.1",
"serde",
]
@ -2960,7 +2962,8 @@ dependencies = [
"pumpkin-data",
"pumpkin-nbt",
"pumpkin-util",
"rand 0.10.0",
"rand 0.10.1",
"rayon",
"rustc-hash",
"ruzstd",
"serde",
@ -3019,9 +3022,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
"chacha20",
"getrandom 0.4.2",
@ -3726,7 +3729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",

View File

@ -153,7 +153,7 @@ pumpkin-protocol = { path = "pumpkin-protocol" }
pumpkin-util = { path = "pumpkin-util" }
pumpkin-world = { path = "pumpkin-world" }
quote = "1.0"
rand = "0.10.0"
rand = "0.10.1"
rsa = "=0.10.0-rc.17"
cipher = "0.5.1"
rustc-hash = "2.1.2"

View File

@ -44,6 +44,7 @@ ruzstd.workspace = true
itertools.workspace = true
enum_dispatch.workspace = true
rayon.workspace = true
# thread_local.workspace = true
# lru.workspace = true

View File

@ -12,6 +12,7 @@ use crate::chunk::io::Dirtiable;
use crate::level::{Level, SyncChunk};
use dashmap::DashMap;
use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::chunk_gen_settings::GenerationSettings;
use pumpkin_util::math::vector2::Vector2;
use slotmap::Key;
use std::cmp::{Ordering, max};
@ -66,10 +67,14 @@ pub struct GenerationSchedule {
io_lock: IOLock,
running_task_count: u16,
max_in_flight: u16,
queue_dirty: bool,
recv_chunk: crossfire::compat::MRx<(ChunkPos, RecvChunk)>,
io_read: crossfire::compat::MTx<ChunkPos>,
io_read: crossfire::compat::MTx<Vec<ChunkPos>>,
io_write: crossfire::compat::Tx<Vec<(ChunkPos, Chunk)>>,
generate: crossfire::compat::MTx<(ChunkPos, Cache, StagedChunkEnum)>,
send_chunk: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
gen_pool: Option<Arc<rayon::ThreadPool>>,
listener: Arc<ChunkListener>,
lighting_config: LightingEngineConfig,
}
@ -82,6 +87,7 @@ impl GenerationSchedule {
level_channel: Arc<LevelChannel>,
listener: Arc<ChunkListener>,
thread_tracker: &mut Vec<thread::JoinHandle<()>>,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) {
let (send_chunk, recv_chunk) = crossfire::compat::mpmc::unbounded_blocking();
@ -110,6 +116,7 @@ impl GenerationSchedule {
io_lock.clone(),
));
if gen_pool.is_none() {
for i in 0..gen_thread_count {
let recv_gen = recv_gen.clone();
let send_chunk = send_chunk.clone();
@ -124,6 +131,13 @@ impl GenerationSchedule {
thread_tracker.push(handle);
}
}
let max_in_flight = if gen_pool.is_some() {
(num_cpus::get() * 4) as u16
} else {
gen_thread_count as u16
};
let level_sched = level;
let lighting_config = level_sched.lighting_config;
@ -141,10 +155,14 @@ impl GenerationSchedule {
waiting_for_chunks: HashSetType::default(),
io_lock,
running_task_count: 0,
max_in_flight,
queue_dirty: false,
recv_chunk,
io_read: send_read_io,
io_write: send_write_io,
generate: send_gen,
send_chunk,
gen_pool,
listener,
chunk_map: Default::default(),
lighting_config,
@ -204,21 +222,21 @@ impl GenerationSchedule {
}
fn sort_queue(&mut self) {
let mut new_queue = BinaryHeap::with_capacity(self.queue.len());
for i in &self.queue {
if self.queue.is_empty() {
return;
}
let mut tasks: Vec<_> = self.queue.drain().collect();
for i in &mut tasks {
if let Some(node) = self.graph.nodes.get(i.1) {
new_queue.push(TaskHeapNode(
Self::calc_priority(
i.0 = Self::calc_priority(
&self.last_level,
&self.last_high_priority,
node.pos,
node.stage,
),
i.1,
));
);
}
}
self.queue = new_queue;
self.queue = BinaryHeap::from(tasks);
}
/// Ensure that the dependency chain for `req_stage` exists on `holder` (for chunk at
@ -395,9 +413,9 @@ impl GenerationSchedule {
}
if let Some(high_priority) = new_data.1 {
self.last_high_priority = high_priority;
self.queue_dirty = true;
}
let Some(new_level) = new_data.0 else {
self.sort_queue();
return true;
};
for (pos, (old_stage, new_stage)) in new_level.0 {
@ -503,7 +521,7 @@ impl GenerationSchedule {
self.chunk_map.insert(pos, holder);
}
self.last_level = new_level.1;
self.sort_queue();
self.queue_dirty = true;
true
}
@ -915,6 +933,22 @@ impl GenerationSchedule {
break;
}
// 1. Get latest world state (player moves, etc)
self.resort_work(self.send_level.get());
// 2. Process all pending chunk results from workers
while let Ok((pos, data)) = self.recv_chunk.try_recv() {
self.receive_chunk(pos, data);
}
// 3. Re-sort if world state changed or new tasks added
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
// 4. Process ready tasks in the queue (up to max_in_flight)
let mut io_batch = Vec::with_capacity(16);
'out2: while let Some(task) = self.queue.pop() {
if level.shut_down_chunk_system.load(Relaxed) {
self.queue.push(task);
@ -923,13 +957,22 @@ impl GenerationSchedule {
break 'out2;
}
if self.resort_work(self.send_level.get()) {
if self.running_task_count >= self.max_in_flight {
self.queue.push(task);
break 'out2;
}
// Briefly check for high-priority results or world changes to avoid stalling
while let Ok((pos, data)) = self.recv_chunk.try_recv() {
self.receive_chunk(pos, data);
if self.resort_work(self.send_level.get()) {
// If world state changed, we MUST re-sort before continuing
self.queue.push(task);
self.queue_dirty = true;
break 'out2;
}
}
if let Some(node) = self.graph.nodes.get_mut(task.1) {
if node.in_degree != 0 {
node.in_queue = false;
@ -951,12 +994,24 @@ impl GenerationSchedule {
}
holder.occupied = occupy;
if self.io_read.send(node.pos).is_err() {
io_batch.push(node.pos);
if io_batch.len() >= 16
&& self.io_read.send(std::mem::take(&mut io_batch)).is_err()
{
info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true);
break 'out2;
}
} else {
// Send any pending IO batch before starting generation
if !io_batch.is_empty()
&& self.io_read.send(std::mem::take(&mut io_batch)).is_err()
{
info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true);
break 'out2;
}
let write_radius = node.stage.get_write_radius();
// Pre-validate that every chunk in the write area (including the
@ -1058,7 +1113,21 @@ impl GenerationSchedule {
}
self.running_task_count += 1;
if self.generate.send((node.pos, cache, node.stage)).is_err() {
if let Some(pool) = &self.gen_pool {
let pos = node.pos;
let stage = node.stage;
let send_chunk = self.send_chunk.clone();
let level = level.clone();
let settings =
GenerationSettings::from_dimension(&level.world_gen.dimension);
pool.spawn(move || {
let result = crate::chunk_system::worker_logic::run_generation(
pos, cache, stage, &level, settings,
);
let _ = send_chunk.send((pos, result));
});
} else if self.generate.send((node.pos, cache, node.stage)).is_err() {
self.running_task_count = self.running_task_count.saturating_sub(1);
info!("Generation thread closed, saving remaining chunks...");
self.save_all_chunk(true);
@ -1068,25 +1137,40 @@ impl GenerationSchedule {
}
}
// Flush any remaining IO batch
if !io_batch.is_empty() && self.io_read.send(std::mem::take(&mut io_batch)).is_err() {
info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true);
}
// 3. If queue is empty, wait for work or results
if self.queue.is_empty() {
// Wait while there are in-flight tasks OR tasks parked waiting for chunk data.
while (self.running_task_count > 0 || !self.waiting_for_chunks.is_empty())
&& self.queue.is_empty()
{
if let Ok((pos, data)) = self.recv_chunk.try_recv() {
// If we have tasks in flight, wait for them with timeout
if self.running_task_count > 0 || !self.waiting_for_chunks.is_empty() {
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
Ok((pos, data)) => {
self.receive_chunk(pos, data);
self.resort_work(self.send_level.get());
}
Err(crossfire::compat::RecvTimeoutError::Timeout) => {
// Periodically check LevelChannel for new requests
self.resort_work(self.send_level.get());
}
Err(crossfire::compat::RecvTimeoutError::Disconnected) => break,
}
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
} else {
if level.shut_down_chunk_system.load(Relaxed) {
break;
}
thread::sleep(Duration::from_millis(50));
}
}
if self.queue.is_empty() && self.waiting_for_chunks.is_empty() {
// No tasks in flight, wait indefinitely for LevelChannel changes
debug_assert!(self.debug_check());
debug_assert_eq!(self.running_task_count, 0);
self.resort_work(self.send_level.wait_and_get(&level));
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
}
}
}

View File

@ -55,7 +55,7 @@ fn needs_relighting(chunk: &crate::chunk::ChunkData, config: &LightingEngineConf
}
pub async fn io_read_work(
recv: crossfire::compat::MAsyncRx<ChunkPos>,
recv: crossfire::compat::MAsyncRx<Vec<ChunkPos>>,
send: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
level: Arc<Level>,
lock: IOLock,
@ -64,23 +64,26 @@ pub async fn io_read_work(
debug!("io read thread start");
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(1);
let (t_send, mut t_recv) = tokio::sync::mpsc::channel(16);
// Cleaner loop and async recv
while let Ok(pos) = recv.recv().await {
while let Ok(batch) = recv.recv().await {
for pos in &batch {
// Lock handling
tokio::task::block_in_place(|| {
let mut data = lock.0.lock().unwrap();
while data.contains_key(&pos) {
while data.contains_key(pos) {
data = lock.1.wait(data).unwrap();
}
});
}
level
.chunk_saver
.fetch_chunks(&level.level_folder, &[pos], t_send.clone())
.fetch_chunks(&level.level_folder, &batch, t_send.clone())
.await;
for _ in 0..batch.len() {
let data = match t_recv.recv().await {
Some(res) => res,
None => break,
@ -88,6 +91,7 @@ pub async fn io_read_work(
match data {
Loaded(chunk) => {
let pos = ChunkPos::new(chunk.x, chunk.z);
if chunk.status == ChunkStatus::Full {
// Relighting check
let needs_relight = needs_relighting(&chunk, &level.lighting_config);
@ -108,7 +112,7 @@ pub async fn io_read_work(
// Clear all lighting data
let section_count = proto.light.sky_light.len();
proto.light.sky_light = (0..section_count)
.map(|_| LightContainer::new_empty(0))
.map(|_| LightContainer::new_empty(15))
.collect();
proto.light.block_light = (0..section_count)
.map(|_| LightContainer::new_empty(0))
@ -134,7 +138,8 @@ pub async fn io_read_work(
}
} else {
// Standard ProtoChunk handling for non-full chunks
let val = RecvChunk::IO(Chunk::Proto(Box::new(ProtoChunk::from_chunk_data(
let val =
RecvChunk::IO(Chunk::Proto(Box::new(ProtoChunk::from_chunk_data(
&chunk,
dimension,
level.world_gen.default_block,
@ -144,15 +149,8 @@ pub async fn io_read_work(
break;
}
}
continue;
}
LoadedData::Missing(_) => {}
LoadedData::Error(_) => {
warn!("chunk data read error pos: {pos:?}. regenerating");
}
}
// Final send for missing/error cases (regenerate)
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
if send
.send((
pos,
@ -169,6 +167,9 @@ pub async fn io_read_work(
break;
}
}
}
}
}
debug!("io read thread stop");
}
@ -226,21 +227,13 @@ pub async fn io_write_work(recv: AsyncRx<Vec<(ChunkPos, Chunk)>>, level: Arc<Lev
}
}
pub fn generation_work(
recv: crossfire::compat::MRx<(ChunkPos, Cache, StagedChunkEnum)>,
send: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
level: Arc<Level>,
) {
let settings = GenerationSettings::from_dimension(&level.world_gen.dimension);
loop {
let (pos, mut cache, stage) = if let Ok(data) = recv.recv() {
data
} else {
debug!("generation channel closed, exiting");
break;
};
pub fn run_generation(
pos: ChunkPos,
mut cache: Cache,
stage: StagedChunkEnum,
level: &Level,
settings: &GenerationSettings,
) -> RecvChunk {
// Run generation with panic catching
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cache.advance(
@ -258,11 +251,7 @@ pub fn generation_work(
}));
match result {
Ok(cache) => {
if send.send((pos, RecvChunk::Generation(cache))).is_err() {
break;
}
}
Ok(cache) => RecvChunk::Generation(cache),
Err(payload) => {
let msg = payload
.downcast_ref::<&str>()
@ -276,16 +265,33 @@ pub fn generation_work(
error!("Chunk generation FAILED at {pos:?} ({stage:?}): {msg}");
// Send failure notification
let _ = send.send((
pos,
RecvChunk::GenerationFailure {
pos,
stage,
error: msg.to_string(),
},
));
}
}
}
}
pub fn generation_work(
recv: crossfire::compat::MRx<(ChunkPos, Cache, StagedChunkEnum)>,
send: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
level: Arc<Level>,
) {
let settings = GenerationSettings::from_dimension(&level.world_gen.dimension);
loop {
let (pos, cache, stage) = if let Ok(data) = recv.recv() {
data
} else {
debug!("generation channel closed, exiting");
break;
};
let result = run_generation(pos, cache, stage, &level, settings);
if send.send((pos, result)).is_err() {
break;
}
}
}

View File

@ -11,6 +11,7 @@ pub fn into_level(
mut base_directory: PathBuf,
block_registry: Arc<dyn BlockRegistryExt>,
seed: i64,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Level> {
if dimension == Dimension::OVERWORLD {
} else if dimension == Dimension::THE_NETHER {
@ -24,5 +25,6 @@ pub fn into_level(
block_registry,
seed,
dimension,
gen_pool,
)
}

View File

@ -14,7 +14,6 @@ use crate::{
tick::{OrderedTick, ScheduledTick, TickPriority},
world::BlockRegistryExt,
};
use crossbeam::channel::Sender;
use dashmap::{DashMap, Entry};
use pumpkin_config::{chunk::ChunkConfig, lighting::LightingEngineConfig, world::LevelConfig};
use pumpkin_data::biome::Biome;
@ -96,12 +95,12 @@ pub struct Level {
/// Number of ticks between autosave checks. If 0, autosave is disabled.
pub autosave_ticks: u64,
gen_entity_request_tx: Sender<Vector2<i32>>,
pending_entity_generations: Arc<DashMap<Vector2<i32>, Vec<oneshot::Sender<SyncEntityChunk>>>>,
pub level_channel: Arc<LevelChannel>,
pub thread_tracker: Mutex<Vec<thread::JoinHandle<()>>>,
pub chunk_listener: Arc<ChunkListener>,
pub gen_pool: Option<Arc<rayon::ThreadPool>>,
}
pub struct TickData {
@ -145,6 +144,7 @@ impl Level {
block_registry: Arc<dyn BlockRegistryExt>,
seed: i64,
dimension: Dimension,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Self> {
let region_folder = root_folder.join("region");
let entities_folder = root_folder.join("entities");
@ -178,7 +178,6 @@ impl Level {
>::new(config.clone())),
};
let (gen_entity_request_tx, gen_entity_request_rx) = crossbeam::channel::unbounded();
let pending_entity_generations = Arc::new(DashMap::new());
let level_channel = Arc::new(LevelChannel::new());
let thread_tracker = Mutex::new(Vec::new());
@ -205,44 +204,55 @@ impl Level {
should_save: AtomicBool::new(false),
should_unload: AtomicBool::new(false),
autosave_ticks: level_config.autosave_ticks,
gen_entity_request_tx,
pending_entity_generations: pending_entity_generations.clone(),
level_channel: level_channel.clone(),
thread_tracker,
chunk_listener: listener.clone(),
gen_pool: gen_pool.clone(),
});
// TODO
let total_cores = num_cpus::get().saturating_sub(2).max(1);
let threads_per_dimension = (total_cores / 2).max(1);
let entity_threads = 1;
GenerationSchedule::create(
2,
4,
threads_per_dimension,
level_ref.clone(),
level_channel,
listener,
level_ref.thread_tracker.lock().unwrap().as_mut(),
gen_pool,
);
let mut tracker_lock = level_ref.thread_tracker.lock().unwrap();
for thread_id in 0..entity_threads {
let level_clone = level_ref.clone();
let pending_clone = pending_entity_generations.clone();
let rx = gen_entity_request_rx.clone();
let handle = thread::Builder::new()
.name(format!("Entity Chunk Generation Thread {thread_id}"))
.spawn(move || {
loop {
if level_clone.cancel_token.is_cancelled() {
break;
level_ref
}
match rx.recv_timeout(std::time::Duration::from_millis(500)) {
Ok(pos) => {
pub fn spawn_entity_generation(self: &Arc<Self>, pos: Vector2<i32>) {
let level = self.clone();
if let Some(pool) = &self.gen_pool {
pool.spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
data: tokio::sync::Mutex::new(FxHashMap::default()),
dirty: AtomicBool::new(true),
});
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
if let Some((_, waiters)) = level.pending_entity_generations.remove(&pos) {
for tx in waiters {
let _ = tx.send(arc_chunk.clone());
}
}
});
} else {
// Fallback to spawning a new thread if no pool is available (should not happen in production)
let level_clone = level.clone();
thread::Builder::new()
.name(format!("Entity Gen {:?}", pos))
.spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
@ -254,24 +264,15 @@ impl Level {
.loaded_entity_chunks
.insert(pos, arc_chunk.clone());
if let Some((_, waiters)) = pending_clone.remove(&pos) {
if let Some((_, waiters)) = level_clone.pending_entity_generations.remove(&pos)
{
for tx in waiters {
let _ = tx.send(arc_chunk.clone());
}
}
}
Err(crossbeam::channel::RecvTimeoutError::Timeout) => continue,
Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
}
}
})
.expect("Failed to spawn Entity Generation Thread");
tracker_lock.push(handle);
.expect("Failed to spawn entity generation thread");
}
drop(tracker_lock);
level_ref
}
/// Spawns a task associated with this world. All tasks spawned with this method are awaited
@ -645,7 +646,7 @@ impl Level {
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(vec![tx]);
let _ = level_clone.gen_entity_request_tx.send(pos);
level_clone.spawn_entity_generation(pos);
}
}
if let Ok(chunk) = rx.await {
@ -683,7 +684,7 @@ impl Level {
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(vec![tx]);
let _ = self.gen_entity_request_tx.send(pos);
self.spawn_entity_generation(pos);
}
}
rx.await.expect("Entity generation worker dropped")

View File

@ -19,6 +19,8 @@ doctest = false
[dependencies]
# pumpkin
arc-swap.workspace = true
rayon.workspace = true
num_cpus.workspace = true
pumpkin-util.workspace = true
pumpkin-nbt.workspace = true
pumpkin-config.workspace = true

View File

@ -109,8 +109,6 @@ const MAX_PREVIOUS_MESSAGES: u8 = 20; // Vanilla: 20
pub const DATA_VERSION: i32 = 4786; // 26.1
enum BatchState {
Initial,
Waiting,
Count(u8),
}
@ -168,7 +166,7 @@ impl ChunkManager {
chunk_sent: HashMap::new(),
chunk_queue: BinaryHeap::new(),
entity_chunk_queue: VecDeque::new(),
batches_sent_since_ack: BatchState::Initial,
batches_sent_since_ack: BatchState::Count(0),
last_chunk_batch_sent_at: Instant::now(),
world,
}
@ -191,8 +189,6 @@ impl ChunkManager {
const fn ack_window_open(&self) -> bool {
match self.batches_sent_since_ack {
BatchState::Count(count) => count < Self::NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE,
BatchState::Initial => true,
BatchState::Waiting => false,
}
}
@ -203,7 +199,7 @@ impl ChunkManager {
}
pub fn pull_new_chunks(&mut self) {
// log::debug!("pull new chunks");
let mut new_nodes = Vec::new();
while let Ok((pos, chunk)) = self.chunk_listener.try_recv() {
let dst = (pos.x - self.center.x)
.abs()
@ -212,12 +208,19 @@ impl ChunkManager {
continue;
}
if self.should_enqueue_chunk(pos, &chunk) {
// log::debug!("receive new chunk {pos:?}");
self.chunk_queue.push(HeapNode(dst, pos, chunk));
new_nodes.push(HeapNode(dst, pos, chunk));
}
}
if !new_nodes.is_empty() {
if self.chunk_queue.is_empty() {
self.chunk_queue = BinaryHeap::from(new_nodes);
} else {
for node in new_nodes {
self.chunk_queue.push(node);
}
}
}
// log::debug!("chunk_queue size {}", self.chunk_queue.len());
// log::debug!("chunk_sent size {}", self.chunk_sent.len());
}
pub fn update_center_and_view_distance(
@ -257,23 +260,28 @@ impl ChunkManager {
&& !unloading_chunks.contains(pos)
});
let mut new_queue = BinaryHeap::with_capacity(self.chunk_queue.len());
for node in self.chunk_queue.drain() {
let mut tasks: Vec<_> = self.chunk_queue.drain().collect();
tasks.retain(|node| {
let dst = (node.1.x - center.x).abs().max((node.1.y - center.y).abs());
if dst <= view_distance_i32 && !unloading_chunks.contains(&node.1) {
new_queue.push(HeapNode(dst, node.1, node.2));
dst <= view_distance_i32 && !unloading_chunks.contains(&node.1)
});
for node in &mut tasks {
node.0 = (node.1.x - center.x).abs().max((node.1.y - center.y).abs());
}
}
self.chunk_queue = new_queue;
for pos in loading_chunks {
if !self.chunk_sent.contains_key(pos)
&& let Some(chunk) = level.loaded_chunks.get(pos)
{
self.push_chunk(*pos, chunk.value().clone());
let chunk = chunk.value().clone();
if self.should_enqueue_chunk(*pos, &chunk) {
let dst = (pos.x - center.x).abs().max((pos.y - center.y).abs());
tasks.push(HeapNode(dst, *pos, chunk));
}
}
}
self.chunk_queue = BinaryHeap::from(tasks);
}
pub fn clean_up(&mut self, level: &Arc<Level>) {
let mut lock = level.chunk_loading.lock().unwrap();
@ -290,7 +298,7 @@ impl ChunkManager {
self.chunk_sent.clear();
self.chunk_queue.clear();
self.entity_chunk_queue.clear();
self.batches_sent_since_ack = BatchState::Initial;
self.batches_sent_since_ack = BatchState::Count(0);
self.last_chunk_batch_sent_at = Instant::now();
}
@ -307,7 +315,7 @@ impl ChunkManager {
self.chunk_queue.clear();
self.world = new_world;
// Reset batch state so chunks can be sent immediately in the new dimension
self.batches_sent_since_ack = BatchState::Initial;
self.batches_sent_since_ack = BatchState::Count(0);
self.last_chunk_batch_sent_at = Instant::now();
}
@ -347,8 +355,6 @@ impl ChunkManager {
BatchState::Count(count) => {
*count = count.saturating_add(1);
}
state @ BatchState::Initial => *state = BatchState::Waiting,
BatchState::Waiting => (),
}
self.last_chunk_batch_sent_at = Instant::now();
@ -371,8 +377,6 @@ impl ChunkManager {
BatchState::Count(count) => {
*count = count.saturating_add(1);
}
state @ BatchState::Initial => *state = BatchState::Waiting,
BatchState::Waiting => (),
}
self.last_chunk_batch_sent_at = Instant::now();

View File

@ -254,6 +254,15 @@ impl Server {
};
let server = Arc::new(server);
let total_cores = num_cpus::get().saturating_sub(2).max(1);
let gen_pool = Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(total_cores)
.thread_name(|i| format!("Gen-Pool-{i}"))
.build()
.expect("Failed to build generation thread pool"),
);
let server_clone = server.clone();
tokio::spawn(async move {
server_clone
@ -268,6 +277,7 @@ impl Server {
let l_info = server.level_info.clone(); // Access from struct
let weak = Arc::downgrade(&server);
let config = Arc::new(server.advanced_config.world.clone());
let pool = gen_pool.clone();
tokio::task::spawn_blocking(move || {
info!(
@ -277,7 +287,7 @@ impl Server {
.to_pretty_console()
);
World::load(
into_level(dim, &config, path, registry.clone(), seed),
into_level(dim, &config, path, registry.clone(), seed, Some(pool)),
l_info,
dim,
registry,