From caf954d17043e1f618f4afe254cbcd479492d80b Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 6 Aug 2026 16:16:12 +0200 Subject: [PATCH] fix: mega pine trees --- .../pumpkin-world/src/chunk/format/anvil.rs | 32 ++++--- .../pumpkin-world/src/chunk/format/linear.rs | 82 ++++++++++------ crates/pumpkin-world/src/chunk/format/pump.rs | 52 +++++----- .../src/chunk/io/file_manager.rs | 95 ++++++++++++++++++- crates/pumpkin-world/src/chunk/io/mod.rs | 2 +- .../src/chunk_system/schedule.rs | 29 +++++- .../src/chunk_system/worker_logic.rs | 2 +- .../features/tree/foliage/mega_pine.rs | 18 ++-- .../feature/features/tree/foliage/pine.rs | 2 +- .../feature/features/tree/trunk/giant.rs | 30 +++--- crates/pumpkin-world/src/level.rs | 44 +++++---- .../pumpkin/src/command/commands/pumpkin.rs | 8 +- 12 files changed, 277 insertions(+), 119 deletions(-) diff --git a/crates/pumpkin-world/src/chunk/format/anvil.rs b/crates/pumpkin-world/src/chunk/format/anvil.rs index 7b05e63f4..53e97dcc1 100644 --- a/crates/pumpkin-world/src/chunk/format/anvil.rs +++ b/crates/pumpkin-world/src/chunk/format/anvil.rs @@ -69,6 +69,7 @@ impl Read for CompressionRead { } } +#[derive(Clone)] pub struct AnvilChunkData { compression: Option, // Length is always the length of this + compression byte (1) so we dont need to save a length @@ -321,11 +322,14 @@ impl AnvilChunkData { .map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?; let compression = compression.unwrap_or_else(|| chunk_config.compression.algorithm.into()); + let level = chunk_config.compression.level; - // We need to buffer here anyway so there's no use in making an impl Write for this - let compressed_data = compression - .compress_data(&raw_bytes, chunk_config.compression.level) - .map_err(ChunkWritingError::Compression)?; + // Offload CPU-heavy compression to blocking thread pool + let compressed_data = + tokio::task::spawn_blocking(move || compression.compress_data(&raw_bytes, level)) + .await + .map_err(|err| ChunkWritingError::IoError(std::io::Error::other(err)))? + .map_err(ChunkWritingError::Compression)?; Ok(Self { compression: Some(compression), @@ -504,7 +508,7 @@ impl Default for AnvilChunkFile { } } -pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable { +pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable + 'static { fn to_bytes( &self, ) -> Pin> + Send + '_>>; @@ -512,7 +516,7 @@ pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable { fn position(&self) -> (i32, i32); } -impl ChunkSerializer for AnvilChunkFile { +impl ChunkSerializer for AnvilChunkFile { type Data = S; type WriteBackend = PathBuf; @@ -796,11 +800,17 @@ impl ChunkSerializer for AnvilChunkFile { let is_ok = match &self.chunks_data[index] { None => stream.send(LoadedData::Missing(chunk)).await.is_ok(), Some(chunk_metadata) => { - let chunk_data = &chunk_metadata.serialized_data; - let result = match chunk_data.to_chunk(chunk) { - Ok(chunk) => LoadedData::Loaded(chunk), - Err(err) => LoadedData::Error((chunk, err)), - }; + let chunk_data = chunk_metadata.serialized_data.clone(); + let result = + match tokio::task::spawn_blocking(move || chunk_data.to_chunk(chunk)).await + { + Ok(Ok(chunk_res)) => LoadedData::Loaded(chunk_res), + Ok(Err(err)) => LoadedData::Error((chunk, err)), + Err(err) => LoadedData::Error(( + chunk, + ChunkReadingError::IoError(std::io::Error::other(err)), + )), + }; stream.send(result).await.is_ok() } diff --git a/crates/pumpkin-world/src/chunk/format/linear.rs b/crates/pumpkin-world/src/chunk/format/linear.rs index 0b190a115..f4ecae5f4 100644 --- a/crates/pumpkin-world/src/chunk/format/linear.rs +++ b/crates/pumpkin-world/src/chunk/format/linear.rs @@ -339,8 +339,12 @@ impl LinearV2File { } /// Build the decompressed byte stream for bucket `bucket_idx`. - fn serialise_bucket(&self, bucket_idx: usize) -> Vec { - let grid_size = self.grid_size; + fn serialise_bucket( + chunks_data: &[Option; CHUNK_COUNT], + timestamps: &[u64; CHUNK_COUNT], + bucket_idx: usize, + grid_size: u8, + ) -> Vec { let cpb = Self::chunks_per_bucket(grid_size); let mut buf = Vec::new(); @@ -348,8 +352,8 @@ impl LinearV2File { // Recover the global chunk index from bucket + local. let chunk_index = Self::global_chunk_index(bucket_idx, local, grid_size); let entry = BucketChunkEntry { - timestamp: self.timestamps[chunk_index], - data: self.chunks_data[chunk_index].clone(), + timestamp: timestamps[chunk_index], + data: chunks_data[chunk_index].clone(), }; entry.write_into(&mut buf); } @@ -371,13 +375,15 @@ impl LinearV2File { fn build_bitmap(&self) -> ChunkBitmap { let mut bitmap = ChunkBitmap::new(); for (i, chunk) in self.chunks_data.iter().enumerate() { - bitmap.set(i, chunk.is_some()); + if chunk.is_some() { + bitmap.set(i, true); + } } bitmap } } -impl ChunkSerializer for LinearV2File { +impl ChunkSerializer for LinearV2File { type Data = S; type WriteBackend = PathBuf; type ChunkConfig = (); @@ -398,23 +404,29 @@ impl ChunkSerializer for LinearV2File { let grid_size = self.grid_size; let bucket_count = Self::bucket_count(grid_size); + let chunks_data = self.chunks_data.clone(); + let timestamps = self.timestamps; - let mut compressed_buckets: Vec> = Vec::with_capacity(bucket_count); - let mut bucket_entries: Vec = Vec::with_capacity(bucket_count); + let (bucket_entries, compressed_buckets) = tokio::task::spawn_blocking(move || { + let mut compressed_buckets: Vec> = Vec::with_capacity(bucket_count); + let mut bucket_entries: Vec = Vec::with_capacity(bucket_count); - for bucket_idx in 0..bucket_count { - let raw = self.serialise_bucket(bucket_idx); - // TODO: ruzstd currently only supports Fastest level. - let compressed = - compress_to_vec(raw.as_slice(), CompressionLevel::Fastest).into_boxed_slice(); - let hash = xxh64(&compressed, 0); - bucket_entries.push(BucketSizeEntry { - size: compressed.len() as u32, - compression_level: 1, - xxhash: hash, - }); - compressed_buckets.push(compressed); - } + for bucket_idx in 0..bucket_count { + let raw = Self::serialise_bucket(&chunks_data, ×tamps, bucket_idx, grid_size); + let compressed = + compress_to_vec(raw.as_slice(), CompressionLevel::Fastest).into_boxed_slice(); + let hash = xxh64(&compressed, 0); + bucket_entries.push(BucketSizeEntry { + size: compressed.len() as u32, + compression_level: 1, + xxhash: hash, + }); + compressed_buckets.push(compressed); + } + (bucket_entries, compressed_buckets) + }) + .await + .map_err(std::io::Error::other)?; let newest_timestamp = self.timestamps.iter().copied().max().unwrap_or(0); @@ -583,15 +595,27 @@ impl ChunkSerializer for LinearV2File { for chunk in chunks { let index = Self::get_chunk_index(chunk.x, chunk.y); - let result = self.chunks_data[index].as_ref().map_or_else( - || LoadedData::Missing(chunk), - |data| match S::from_bytes(data, chunk) { - Ok(c) => LoadedData::Loaded(c), - Err(err) => LoadedData::Error((chunk, err)), - }, - ); + let is_ok = match &self.chunks_data[index] { + None => stream.send(LoadedData::Missing(chunk)).await.is_ok(), + Some(data) => { + let data = data.clone(); + let result = match tokio::task::spawn_blocking(move || { + S::from_bytes(&data, chunk) + }) + .await + { + Ok(Ok(c)) => LoadedData::Loaded(c), + Ok(Err(err)) => LoadedData::Error((chunk, err)), + Err(err) => LoadedData::Error(( + chunk, + ChunkReadingError::IoError(std::io::Error::other(err)), + )), + }; + stream.send(result).await.is_ok() + } + }; - if stream.send(result).await.is_err() { + if !is_ok { // Receiver dropped — stop early to avoid unnecessary work. return; } diff --git a/crates/pumpkin-world/src/chunk/format/pump.rs b/crates/pumpkin-world/src/chunk/format/pump.rs index 1b2a0bf22..ab4a99e19 100644 --- a/crates/pumpkin-world/src/chunk/format/pump.rs +++ b/crates/pumpkin-world/src/chunk/format/pump.rs @@ -34,7 +34,7 @@ impl Default for PumpFile { impl ChunkSerializer for PumpFile where - D: SingleChunkDataSerializer + Send + Sync + Sized, + D: SingleChunkDataSerializer + Send + Sync + Sized + 'static, { type Data = D; type WriteBackend = PathBuf; @@ -129,34 +129,30 @@ where let index = (rel_x + rel_z * 32) as usize; if let Some(chunk_bytes) = self.data.chunks.get(&index.to_string()) { - let mut decoder = match StreamingDecoder::new(&chunk_bytes[..]) { - Ok(d) => d, - Err(e) => { - let _ = stream - .send(LoadedData::Error(( - pos, - ChunkReadingError::IoError(std::io::Error::other(e.to_string())), - ))) - .await; - continue; - } - }; - let mut decompressed = Vec::new(); - if let Err(e) = std::io::Read::read_to_end(&mut decoder, &mut decompressed) { - let _ = stream - .send(LoadedData::Error((pos, ChunkReadingError::IoError(e)))) - .await; - continue; - } + let chunk_bytes = chunk_bytes.clone(); + let res = tokio::task::spawn_blocking(move || { + let mut decoder = StreamingDecoder::new(&chunk_bytes[..]).map_err(|e| { + ChunkReadingError::IoError(std::io::Error::other(e.to_string())) + })?; + let mut decompressed = Vec::new(); + std::io::Read::read_to_end(&mut decoder, &mut decompressed) + .map_err(ChunkReadingError::IoError)?; + let bytes = Bytes::from(decompressed); + D::from_bytes(&bytes, pos) + }) + .await; - let bytes = Bytes::from(decompressed); - match D::from_bytes(&bytes, pos) { - Ok(data) => { - let _ = stream.send(LoadedData::Loaded(data)).await; - } - Err(e) => { - let _ = stream.send(LoadedData::Error((pos, e))).await; - } + let data_res = match res { + Ok(Ok(data)) => LoadedData::Loaded(data), + Ok(Err(e)) => LoadedData::Error((pos, e)), + Err(e) => LoadedData::Error(( + pos, + ChunkReadingError::IoError(std::io::Error::other(e)), + )), + }; + + if stream.send(data_res).await.is_err() { + return; } } else { let _ = stream.send(LoadedData::Missing(pos)).await; diff --git a/crates/pumpkin-world/src/chunk/io/file_manager.rs b/crates/pumpkin-world/src/chunk/io/file_manager.rs index 02867cd01..671956c2b 100644 --- a/crates/pumpkin-world/src/chunk/io/file_manager.rs +++ b/crates/pumpkin-world/src/chunk/io/file_manager.rs @@ -58,7 +58,7 @@ struct ChunkSerializerLazyLoader> { internal: OnceCell>>, } -impl> ChunkSerializerLazyLoader { +impl + 'static> ChunkSerializerLazyLoader { fn new(path: PathBuf) -> Self { Self { path, @@ -101,7 +101,9 @@ impl> ChunkSerializerLazyLoader { match tokio::fs::read(&self.path).await { Ok(bytes) => { - let value = S::read(bytes.into())?; + let value = tokio::task::spawn_blocking(move || S::read(bytes.into())) + .await + .map_err(|e| ChunkReadingError::IoError(std::io::Error::other(e)))??; trace!("Successfully read file from disk: {}", self.path.display()); Ok(value) } @@ -428,3 +430,92 @@ where }) } } + +pub enum LevelFileIO +where + Linear: ChunkSerializer, + Anvil: ChunkSerializer, + Pump: ChunkSerializer, +{ + Linear(ChunkFileManager), + Anvil(ChunkFileManager), + Pump(ChunkFileManager), +} + +impl FileIO for LevelFileIO +where + P: PathFromLevelFolder + Send + Sync + Sized + Dirtiable + 'static, + Linear: ChunkSerializer, + Anvil: ChunkSerializer, + Pump: ChunkSerializer, + Linear::ChunkConfig: Send + Sync, + Anvil::ChunkConfig: Send + Sync, + Pump::ChunkConfig: Send + Sync, +{ + type Data = Arc

; + + fn fetch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunk_coords: &'a [Vector2], + stream: tokio::sync::mpsc::Sender>, + ) -> BoxFuture<'a, ()> { + match self { + Self::Linear(io) => io.fetch_chunks(folder, chunk_coords, stream), + Self::Anvil(io) => io.fetch_chunks(folder, chunk_coords, stream), + Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream), + } + } + + fn save_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks_data: Vec<(Vector2, Self::Data)>, + ) -> BoxFuture<'a, Result<(), ChunkWritingError>> { + match self { + Self::Linear(io) => io.save_chunks(folder, chunks_data), + Self::Anvil(io) => io.save_chunks(folder, chunks_data), + Self::Pump(io) => io.save_chunks(folder, chunks_data), + } + } + + fn watch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks: &'a [Vector2], + ) -> BoxFuture<'a, ()> { + match self { + Self::Linear(io) => io.watch_chunks(folder, chunks), + Self::Anvil(io) => io.watch_chunks(folder, chunks), + Self::Pump(io) => io.watch_chunks(folder, chunks), + } + } + + fn unwatch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks: &'a [Vector2], + ) -> BoxFuture<'a, ()> { + match self { + Self::Linear(io) => io.unwatch_chunks(folder, chunks), + Self::Anvil(io) => io.unwatch_chunks(folder, chunks), + Self::Pump(io) => io.unwatch_chunks(folder, chunks), + } + } + + fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { + match self { + Self::Linear(io) => io.clear_watched_chunks(), + Self::Anvil(io) => io.clear_watched_chunks(), + Self::Pump(io) => io.clear_watched_chunks(), + } + } + + fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> { + match self { + Self::Linear(io) => io.block_and_await_ongoing_tasks(), + Self::Anvil(io) => io.block_and_await_ongoing_tasks(), + Self::Pump(io) => io.block_and_await_ongoing_tasks(), + } + } +} diff --git a/crates/pumpkin-world/src/chunk/io/mod.rs b/crates/pumpkin-world/src/chunk/io/mod.rs index 75ebb94e3..743bd25b8 100644 --- a/crates/pumpkin-world/src/chunk/io/mod.rs +++ b/crates/pumpkin-world/src/chunk/io/mod.rs @@ -93,7 +93,7 @@ where /// /// The `Data` type is the type of the data that will be updated or serialized/deserialized /// like `ChunkData` or `EntityData` -pub trait ChunkSerializer: Send + Sync + Default { +pub trait ChunkSerializer: Send + Sync + Default + 'static { type Data: Send + Sync + Sized + Dirtiable; type WriteBackend; diff --git a/crates/pumpkin-world/src/chunk_system/schedule.rs b/crates/pumpkin-world/src/chunk_system/schedule.rs index a6c4d4323..b7cf41f06 100644 --- a/crates/pumpkin-world/src/chunk_system/schedule.rs +++ b/crates/pumpkin-world/src/chunk_system/schedule.rs @@ -211,19 +211,24 @@ impl GenerationSchedule { pos: ChunkPos, stage: StagedChunkEnum, ) -> i8 { - if last_high_priority.is_empty() { - return *last_level.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL) + (stage as i8); + let base_level = *last_level.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL); + if base_level == ChunkLoading::MAX_LEVEL { + return 127; } + if last_high_priority.is_empty() { + return base_level + (stage as i8); + } + let mut min_dst = i32::MAX; for i in last_high_priority { let dst = max((i.x - pos.x).abs(), (i.y - pos.y).abs()); + min_dst = min_dst.min(dst); if dst <= StagedChunkEnum::FULL_RADIUS && stage <= StagedChunkEnum::FULL_DEPENDENCIES[dst as usize] { - return *last_level.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL) + (stage as i8) - - 100; + return base_level + (stage as i8) - 100 + (dst as i8); } } - *last_level.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL) + (stage as i8) + base_level + (stage as i8) + (min_dst.min(60) as i8) } fn sort_queue(&mut self) { @@ -1143,6 +1148,20 @@ impl GenerationSchedule { continue; } + // Cancel/drop task if chunk is out of range or no longer needed by any target/dependency + let effective_target = self + .chunk_map + .get(&node.pos) + .map_or(StagedChunkEnum::None, |h| { + h.target_stage.max(h.dependency_stage) + }); + + if node.stage > effective_target { + self.waiting_for_chunks.remove(&task.1); + self.drop_node(task.1); + continue; + } + if node.stage == StagedChunkEnum::Empty { self.running_task_count += 1; let holder = self.chunk_map.get_mut(&node.pos).unwrap(); diff --git a/crates/pumpkin-world/src/chunk_system/worker_logic.rs b/crates/pumpkin-world/src/chunk_system/worker_logic.rs index b7e0a9781..83bc9768d 100644 --- a/crates/pumpkin-world/src/chunk_system/worker_logic.rs +++ b/crates/pumpkin-world/src/chunk_system/worker_logic.rs @@ -3,8 +3,8 @@ use super::generation_cache::Cache; use super::{ChunkPos, IOLock}; use crate::ProtoChunk; use crate::chunk::format::LightContainer; -use crate::chunk::io::LoadedData; use crate::chunk::io::LoadedData::Loaded; +use crate::chunk::io::{FileIO, LoadedData}; use crate::level::Level; use crossfire::compat::AsyncRx; use pumpkin_config::lighting::LightingEngineConfig; diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs b/crates/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs index 61f8eb147..0b871cbfb 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs @@ -26,16 +26,16 @@ impl MegaPineFoliagePlacer { ) -> Vec { let mut foliage_positions = Vec::new(); let pos = node.center; - let mut current = 0; - for y in pos.0.y - foliage_height + offset..pos.0.y + offset { + let mut current_radius = 0; + for y in (pos.0.y - foliage_height + offset)..=(pos.0.y + offset) { let delta = pos.0.y - y; - let rad = radius + let computed_radius = radius + node.foliage_radius - + (delta as f32 / foliage_height as f32 * 3.5).floor() as i32; - let radius = if delta > 0 && rad == current && (y & 1) == 0 { - radius + 1 + + ((delta as f32 / foliage_height as f32) * 3.5).floor() as i32; + let r = if delta > 0 && computed_radius == current_radius && (y & 1) == 0 { + computed_radius + 1 } else { - radius + computed_radius }; FoliagePlacer::generate_square( &mut foliage_positions, @@ -43,12 +43,12 @@ impl MegaPineFoliagePlacer { chunk, random, BlockPos::new(pos.0.x, y, pos.0.z), - radius, + r, 0, node.giant_trunk, foliage_provider, ); - current = rad; + current_radius = computed_radius; } foliage_positions } diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs b/crates/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs index 03b3c3253..7641af8d5 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs @@ -26,7 +26,7 @@ impl PineFoliagePlacer { ) -> Vec { let mut foliage_positions = Vec::new(); let mut radius = 0; - for y in (offset - foliage_height)..offset { + for y in (offset - foliage_height..=offset).rev() { FoliagePlacer::generate_square( &mut foliage_positions, self, diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs b/crates/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs index 91c8d0494..6717c819b 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs @@ -24,47 +24,51 @@ impl GiantTrunkPlacer { below_trunk_provider: &BlockStateProvider, trunk_block: &BlockState, ) -> (Vec, Vec) { - let pos = start_pos.down(); - TrunkPlacer::set_dirt(block_registry, chunk, random, &pos, below_trunk_provider); + let below = start_pos.down(); + TrunkPlacer::set_dirt(block_registry, chunk, random, &below, below_trunk_provider); TrunkPlacer::set_dirt( block_registry, chunk, random, - &pos.east(), + &below.east(), below_trunk_provider, ); TrunkPlacer::set_dirt( block_registry, chunk, random, - &pos.south(), + &below.south(), below_trunk_provider, ); TrunkPlacer::set_dirt( block_registry, chunk, random, - &pos.south().east(), + &below.south().east(), below_trunk_provider, ); let mut trunk_poses = Vec::new(); for y in 0..height { - if TrunkPlacer::try_place(chunk, &pos.up_height(y as i32), trunk_block) { - trunk_poses.push(pos.up_height(y as i32)); + let log_pos = start_pos.up_height(y as i32); + if TrunkPlacer::try_place(chunk, &log_pos, trunk_block) { + trunk_poses.push(log_pos); } if y >= height - 1 { continue; } - if TrunkPlacer::try_place(chunk, &pos.east().up_height(y as i32), trunk_block) { - trunk_poses.push(pos.east().up_height(y as i32)); + let log_pos_east = start_pos.east().up_height(y as i32); + if TrunkPlacer::try_place(chunk, &log_pos_east, trunk_block) { + trunk_poses.push(log_pos_east); } - if TrunkPlacer::try_place(chunk, &pos.east().south().up_height(y as i32), trunk_block) { - trunk_poses.push(pos.east().south().up_height(y as i32)); + let log_pos_se = start_pos.east().south().up_height(y as i32); + if TrunkPlacer::try_place(chunk, &log_pos_se, trunk_block) { + trunk_poses.push(log_pos_se); } - if TrunkPlacer::try_place(chunk, &pos.south().up_height(y as i32), trunk_block) { - trunk_poses.push(pos.south().up_height(y as i32)); + let log_pos_south = start_pos.south().up_height(y as i32); + if TrunkPlacer::try_place(chunk, &log_pos_south, trunk_block) { + trunk_poses.push(log_pos_south); } } ( diff --git a/crates/pumpkin-world/src/level.rs b/crates/pumpkin-world/src/level.rs index 62ede63a6..330f76624 100644 --- a/crates/pumpkin-world/src/level.rs +++ b/crates/pumpkin-world/src/level.rs @@ -7,7 +7,10 @@ use crate::{ chunk::{ ChunkData, ChunkEntityData, ChunkReadingError, format::anvil::AnvilChunkFile, - io::{Dirtiable, FileIO, LoadedData, file_manager::ChunkFileManager}, + io::{ + Dirtiable, FileIO, LoadedData, + file_manager::{ChunkFileManager, LevelFileIO}, + }, palette::has_random_ticking_fluid, }, generation::get_world_gen, @@ -47,6 +50,15 @@ use tokio_util::task::TaskTracker; pub type SyncChunk = Arc; pub type SyncEntityChunk = Arc; +pub type ChunkSaver = + LevelFileIO, AnvilChunkFile, PumpFile>; + +pub type EntitySaver = LevelFileIO< + LinearV2File, + AnvilChunkFile, + PumpFile, +>; + /// The `Level` module provides functionality for working with chunks within or outside a Minecraft world. /// /// Key features include: @@ -74,8 +86,8 @@ pub struct Level { chunk_watchers: Arc, usize>>, - pub chunk_saver: Arc>, - entity_saver: Arc>, + pub chunk_saver: Arc, + entity_saver: Arc, pub world_gen: Arc, @@ -216,21 +228,19 @@ impl Level { flat_biome, )); - let chunk_saver: Arc> = match &level_config.chunk { - ChunkConfig::Linear => Arc::new(ChunkFileManager::>::new(())), - ChunkConfig::Anvil(config) => Arc::new( - ChunkFileManager::>::new(config.clone()), - ), - ChunkConfig::Pump => Arc::new(ChunkFileManager::>::new(())), - }; - let entity_saver: Arc> = match &level_config.chunk { - ChunkConfig::Linear => { - Arc::new(ChunkFileManager::>::new(())) + let chunk_saver = match &level_config.chunk { + ChunkConfig::Linear => Arc::new(ChunkSaver::Linear(ChunkFileManager::new(()))), + ChunkConfig::Anvil(config) => { + Arc::new(ChunkSaver::Anvil(ChunkFileManager::new(config.clone()))) } - ChunkConfig::Anvil(config) => Arc::new(ChunkFileManager::< - AnvilChunkFile, - >::new(config.clone())), - ChunkConfig::Pump => Arc::new(ChunkFileManager::>::new(())), + ChunkConfig::Pump => Arc::new(ChunkSaver::Pump(ChunkFileManager::new(()))), + }; + let entity_saver = match &level_config.chunk { + ChunkConfig::Linear => Arc::new(EntitySaver::Linear(ChunkFileManager::new(()))), + ChunkConfig::Anvil(config) => { + Arc::new(EntitySaver::Anvil(ChunkFileManager::new(config.clone()))) + } + ChunkConfig::Pump => Arc::new(EntitySaver::Pump(ChunkFileManager::new(()))), }; let pending_entity_generations = Arc::new(DashMap::new()); diff --git a/crates/pumpkin/src/command/commands/pumpkin.rs b/crates/pumpkin/src/command/commands/pumpkin.rs index e0e55ba43..c804d4a1b 100644 --- a/crates/pumpkin/src/command/commands/pumpkin.rs +++ b/crates/pumpkin/src/command/commands/pumpkin.rs @@ -182,7 +182,9 @@ impl CommandExecutor for Executor { _args: &'a ConsumedArgs<'a>, ) -> CommandResult<'a> { Box::pin(async move { - let contributors = fetch_all_contributors(); + let contributors = tokio::task::spawn_blocking(fetch_all_contributors) + .await + .unwrap_or_default(); let contributor_names = contributors .iter() .map(|c| c.login.as_str()) @@ -282,7 +284,9 @@ impl CommandExecutor for Executor { msg = msg.add_child(TextComponent::text(" ")); - let donators_hover = fetch_donators_hover(); + let donators_hover = tokio::task::spawn_blocking(fetch_donators_hover) + .await + .unwrap_or_else(|_| TextComponent::text("Unable to load donators")); msg = msg.add_child( TextComponent::text("[Donate]") .click_event(ClickEvent::OpenUrl {