Made Chunks faster

Moreeee Speeeedddddd
This commit is contained in:
Alexander Medvedev
2025-08-04 15:43:47 +02:00
parent 43e8d9b6d9
commit 9f94ce1d0d
8 changed files with 298 additions and 150 deletions

View File

@@ -3,7 +3,7 @@ use pumpkin_data::noise_router::OVERWORLD_BASE_NOISE_ROUTER;
use pumpkin_world::{
GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoNoiseRouters,
bench_create_and_populate_biome, bench_create_and_populate_noise,
bench_create_and_populate_noise_with_surface,
bench_create_and_populate_noise_with_surface, generation::proto_chunk::TerrainCache,
};
fn bench_terrain_gen(c: &mut Criterion) {
@@ -13,13 +13,31 @@ fn bench_terrain_gen(c: &mut Criterion) {
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&random_config);
let default_block = surface_config.default_block.get_state();
c.bench_function("overworld biome", |b| {
b.iter(|| bench_create_and_populate_biome(&base_router, &random_config, surface_config));
b.iter(|| {
bench_create_and_populate_biome(
&base_router,
&random_config,
surface_config,
&terrain_cache,
default_block,
)
});
});
c.bench_function("overworld noise", |b| {
b.iter(|| bench_create_and_populate_noise(&base_router, &random_config, surface_config));
b.iter(|| {
bench_create_and_populate_noise(
&base_router,
&random_config,
surface_config,
&terrain_cache,
default_block,
)
});
});
c.bench_function("overworld surface", |b| {
@@ -28,6 +46,8 @@ fn bench_terrain_gen(c: &mut Criterion) {
&base_router,
&random_config,
surface_config,
&terrain_cache,
default_block,
)
});
});

View File

@@ -64,9 +64,12 @@ mod test {
GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk,
chunk::palette::BIOME_NETWORK_MAX_BITS,
dimension::Dimension,
generation::noise_router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
proto_noise_router::ProtoNoiseRouters,
generation::{
noise_router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
proto_noise_router::ProtoNoiseRouters,
},
proto_chunk::TerrainCache,
},
};
@@ -108,11 +111,19 @@ mod test {
let surface_settings = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&random_config);
let default_block = surface_settings.default_block.get_state();
for data in expected_data.into_iter() {
let chunk_pos = Vector2::new(data.x, data.z);
let mut chunk =
ProtoChunk::new(chunk_pos, &noise_router, &random_config, surface_settings);
let mut chunk = ProtoChunk::new(
chunk_pos,
&noise_router,
&random_config,
surface_settings,
&terrain_cache,
default_block,
);
chunk.populate_biomes(Dimension::Overworld);
for (biome_x, biome_y, biome_z, biome_id) in data.data {

View File

@@ -40,9 +40,12 @@ mod test {
GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk,
biome::{BiomeSupplier, MultiNoiseBiomeSupplier},
dimension::Dimension,
generation::noise_router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
proto_noise_router::ProtoNoiseRouters,
generation::{
noise_router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
proto_noise_router::ProtoNoiseRouters,
},
proto_chunk::TerrainCache,
},
};
@@ -61,8 +64,15 @@ mod test {
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(chunk_pos, &noise_rounter, &random_config, surface_config);
let terrain_cache = TerrainCache::from_random(&random_config);
let mut chunk = ProtoChunk::new(
chunk_pos,
&noise_rounter,
&random_config,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
for (x, y, z, tem, hum, con, ero, dep, wei) in expected_data.into_iter() {
let point = chunk.multi_noise_sampler.sample(x, y, z);

View File

@@ -1,6 +1,7 @@
use std::sync::Arc;
use async_trait::async_trait;
use pumpkin_data::BlockState;
use pumpkin_data::noise_router::{
END_BASE_NOISE_ROUTER, NETHER_BASE_NOISE_ROUTER, OVERWORLD_BASE_NOISE_ROUTER,
};
@@ -11,6 +12,7 @@ use super::{
settings::gen_settings_from_dimension,
};
use crate::chunk::format::LightContainer;
use crate::generation::proto_chunk::TerrainCache;
use crate::level::Level;
use crate::world::BlockRegistryExt;
use crate::{chunk::ChunkLight, dimension::Dimension};
@@ -40,11 +42,16 @@ pub struct VanillaGenerator {
random_config: GlobalRandomConfig,
base_router: ProtoNoiseRouters,
dimension: Dimension,
terrain_cache: TerrainCache,
default_block: &'static BlockState,
}
impl GeneratorInit for VanillaGenerator {
fn new(seed: Seed, dimension: Dimension) -> Self {
let random_config = GlobalRandomConfig::new(seed.0, false);
// TODO: The generation settings contains (part of?) the noise routers too; do we keep the separate or
// use only the generation settings?
let base = match dimension {
@@ -52,11 +59,17 @@ impl GeneratorInit for VanillaGenerator {
Dimension::Nether => NETHER_BASE_NOISE_ROUTER,
Dimension::End => END_BASE_NOISE_ROUTER,
};
let terrain_cache = TerrainCache::from_random(&random_config);
let generation_settings = gen_settings_from_dimension(&dimension);
let default_block = generation_settings.default_block.get_state();
let base_router = ProtoNoiseRouters::generate(&base, &random_config);
Self {
random_config,
base_router,
dimension,
terrain_cache,
default_block,
}
}
}
@@ -83,6 +96,8 @@ impl WorldGenerator for VanillaGenerator {
&self.base_router,
&self.random_config,
generation_settings,
&self.terrain_cache,
self.default_block,
);
proto_chunk.populate_biomes(self.dimension);
proto_chunk.populate_noise();

View File

@@ -8,9 +8,10 @@ use pumpkin_data::{
use pumpkin_util::{
HeightMap,
math::{position::BlockPos, vector2::Vector2, vector3::Vector3},
random::{RandomGenerator, RandomImpl, get_decorator_seed, xoroshiro128::Xoroshiro},
random::{RandomGenerator, get_decorator_seed, xoroshiro128::Xoroshiro},
};
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::{
BlockStateId,
biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier, hash_seed},
@@ -102,6 +103,7 @@ impl FluidLevelSamplerImpl for StandardChunkFluidLevelSampler {
pub struct ProtoChunk<'a> {
pub chunk_pos: Vector2<i32>,
pub noise_sampler: ChunkNoiseGenerator<'a>,
pub terrain_cache: &'a TerrainCache,
// TODO: These can technically go to an even higher level and we can reuse them across chunks
pub multi_noise_sampler: MultiNoiseSampler<'a>,
pub surface_height_estimate_sampler: SurfaceHeightEstimateSampler<'a>,
@@ -122,12 +124,35 @@ pub struct ProtoChunk<'a> {
// may want to use chunk status
}
pub struct TerrainCache {
pub terrain_builder: SurfaceTerrainBuilder,
pub surface_noise: DoublePerlinNoiseSampler,
pub secondary_noise: DoublePerlinNoiseSampler,
}
impl TerrainCache {
pub fn from_random(random_config: &GlobalRandomConfig) -> Self {
let random = &random_config.base_random_deriver;
let mut noise_builder = DoublePerlinNoiseBuilder::new(random_config);
let terrain_builder = SurfaceTerrainBuilder::new(&mut noise_builder, random);
let surface_noise = noise_builder.get_noise_sampler_for_id("surface");
let secondary_noise = noise_builder.get_noise_sampler_for_id("surface_secondary");
Self {
terrain_builder,
surface_noise,
secondary_noise,
}
}
}
impl<'a> ProtoChunk<'a> {
pub fn new(
chunk_pos: Vector2<i32>,
base_router: &'a ProtoNoiseRouters,
random_config: &'a GlobalRandomConfig,
settings: &'a GenerationSettings,
terrain_cache: &'a TerrainCache,
default_block: &'static BlockState,
) -> Self {
let generation_shape = &settings.shape;
@@ -184,11 +209,11 @@ impl<'a> ProtoChunk<'a> {
let surface_height_estimate_sampler =
SurfaceHeightEstimateSampler::generate(&base_router.surface_estimator, &surface_config);
let default_block = settings.default_block.get_state();
let default_heightmap = vec![i16::MIN; CHUNK_AREA].into_boxed_slice();
Self {
chunk_pos,
settings,
terrain_cache,
default_block,
random_config,
noise_sampler: sampler,
@@ -463,61 +488,68 @@ impl<'a> ProtoChunk<'a> {
pub fn populate_noise(&mut self) {
let horizontal_cell_block_count = self.noise_sampler.horizontal_cell_block_count();
let vertical_cell_block_count = self.noise_sampler.vertical_cell_block_count();
let horizontal_cells = CHUNK_DIM / horizontal_cell_block_count;
let min_y = self.noise_sampler.min_y();
let minimum_cell_y = min_y / vertical_cell_block_count as i8;
let cell_height = self.noise_sampler.height() / vertical_cell_block_count as u16;
let start_block_x = self.start_block_x();
let start_block_z = self.start_block_z();
let start_cell_x = self.start_cell_x();
let start_cell_z = self.start_cell_z();
// TODO: Block state updates when we implement those
self.noise_sampler.sample_start_density();
for cell_x in 0..horizontal_cells {
self.noise_sampler.sample_end_density(cell_x);
let sample_start_x =
(self.start_cell_x() + cell_x as i32) * horizontal_cell_block_count as i32;
(start_cell_x + cell_x as i32) * horizontal_cell_block_count as i32;
for cell_z in 0..horizontal_cells {
let sample_start_z =
(start_cell_z + cell_z as i32) * horizontal_cell_block_count as i32;
for cell_y in (0..cell_height).rev() {
self.noise_sampler
.on_sampled_cell_corners(cell_x, cell_y, cell_z);
let sample_start_y =
(minimum_cell_y as i32 + cell_y as i32) * vertical_cell_block_count as i32;
let sample_start_z =
(self.start_cell_z() + cell_z as i32) * horizontal_cell_block_count as i32;
let block_y_base = sample_start_y;
let delta_y_step = 1.0 / vertical_cell_block_count as f64;
for local_y in (0..vertical_cell_block_count).rev() {
let block_y = (minimum_cell_y as i32 + cell_y as i32)
* vertical_cell_block_count as i32
+ local_y as i32;
let delta_y = local_y as f64 / vertical_cell_block_count as f64;
let block_y = block_y_base + local_y as i32;
let delta_y = local_y as f64 * delta_y_step;
self.noise_sampler.interpolate_y(delta_y);
let block_x_base =
start_block_x + cell_x as i32 * horizontal_cell_block_count as i32;
let delta_x_step = 1.0 / horizontal_cell_block_count as f64;
for local_x in 0..horizontal_cell_block_count {
let block_x = self.start_block_x()
+ cell_x as i32 * horizontal_cell_block_count as i32
+ local_x as i32;
let delta_x = local_x as f64 / horizontal_cell_block_count as f64;
let block_x = block_x_base + local_x as i32;
let delta_x = local_x as f64 * delta_x_step;
self.noise_sampler.interpolate_x(delta_x);
let block_z_base =
start_block_z + cell_z as i32 * horizontal_cell_block_count as i32;
let delta_z_step = 1.0 / horizontal_cell_block_count as f64;
for local_z in 0..horizontal_cell_block_count {
let block_z = self.start_block_z()
+ cell_z as i32 * horizontal_cell_block_count as i32
+ local_z as i32;
let delta_z = local_z as f64 / horizontal_cell_block_count as f64;
let block_z = block_z_base + local_z as i32;
let delta_z = local_z as f64 * delta_z_step;
self.noise_sampler.interpolate_z(delta_z);
// TODO: Can the math here be simplified? Do the above values come
// to the same results?
let cell_offset_x = block_x - sample_start_x;
// The `cell_offset` calculations are still a good idea for clarity and correctness
// but let's confirm the values.
// block_x = start_block_x + cell_x*H + local_x
// sample_start_x = start_cell_x*H + cell_x*H = (start_cell_x+cell_x)*H
// These can be simplified.
let cell_offset_x = local_x as i32;
let cell_offset_y = block_y - sample_start_y;
let cell_offset_z = block_z - sample_start_z;
#[cfg(debug_assertions)]
{
assert!(cell_offset_x >= 0);
assert!(cell_offset_y >= 0);
assert!(cell_offset_z >= 0);
}
let cell_offset_z = local_z as i32;
let block_state = self
.noise_sampler
@@ -540,27 +572,10 @@ impl<'a> ProtoChunk<'a> {
}
}
}
self.noise_sampler.swap_buffers();
}
}
pub fn generate_entities(&self) {
let start_x = self.start_block_x();
let start_z = self.start_block_z();
let population_seed =
Xoroshiro::get_population_seed(self.random_config.seed, start_x, start_z);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(population_seed));
let biome = self.get_biome(&Vector3::new(
start_x,
self.bottom_section_coord() as i32 + self.height() as i32 - 1,
start_z,
));
while random.next_f32() < biome.creature_spawn_probability {}
todo!()
}
pub fn get_biome_for_terrain_gen(&self, global_block_pos: &Vector3<i32>) -> &'static Biome {
let seed_biome_pos = biome::get_biome_blend(
self.bottom_y(),
@@ -582,14 +597,15 @@ impl<'a> ProtoChunk<'a> {
let min_y = self.bottom_y();
let random = &self.random_config.base_random_deriver;
let mut noise_builder = DoublePerlinNoiseBuilder::new(self.random_config);
let terrain_builder = SurfaceTerrainBuilder::new(&mut noise_builder, random);
let noise_builder = DoublePerlinNoiseBuilder::new(self.random_config);
let mut context = MaterialRuleContext::new(
min_y,
self.height(),
noise_builder,
random,
&terrain_builder,
&self.terrain_cache.terrain_builder,
&self.terrain_cache.surface_noise,
&self.terrain_cache.secondary_noise,
);
for local_x in 0..16 {
for local_z in 0..16 {
@@ -607,7 +623,9 @@ impl<'a> ProtoChunk<'a> {
let this_biome = self.get_biome_for_terrain_gen(&Vector3::new(x, biome_y, z));
if this_biome == &Biome::ERODED_BADLANDS {
terrain_builder.place_badlands_pillar(self, x, z, top_block);
self.terrain_cache
.terrain_builder
.place_badlands_pillar(self, x, z, top_block);
// Get the top block again if we placed a pillar!
top_block = self.top_block_height_exclusive(&Vector2::new(local_x, local_z));
@@ -678,7 +696,7 @@ impl<'a> ProtoChunk<'a> {
&mut self.surface_height_estimate_sampler,
);
terrain_builder.place_iceberg(
self.terrain_cache.terrain_builder.place_iceberg(
self,
this_biome,
x,
@@ -787,6 +805,7 @@ mod test {
density_function::{NoiseFunctionComponentRange, PassThrough},
proto_noise_router::{ProtoNoiseFunctionComponent, ProtoNoiseRouters},
},
proto_chunk::TerrainCache,
settings::{GENERATION_SETTINGS, GeneratorSetting},
},
};
@@ -796,6 +815,8 @@ mod test {
const SEED: u64 = 0;
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
LazyLock::new(|| GlobalRandomConfig::new(SEED, false)); // TODO: use legacy when needed
static TERRAIN_CACHE: LazyLock<TerrainCache> =
LazyLock::new(|| TerrainCache::from_random(&RANDOM_CONFIG));
static BASE_NOISE_ROUTER: LazyLock<ProtoNoiseRouters> =
LazyLock::new(|| ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG));
@@ -847,6 +868,8 @@ mod test {
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -904,6 +927,8 @@ mod test {
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -961,6 +986,8 @@ mod test {
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1018,6 +1045,8 @@ mod test {
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1075,6 +1104,8 @@ mod test {
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1101,6 +1132,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1122,6 +1155,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1143,6 +1178,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1169,6 +1206,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1195,6 +1234,8 @@ mod test {
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1221,6 +1262,8 @@ mod test {
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
@@ -1247,6 +1290,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
@@ -1276,6 +1321,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
@@ -1300,11 +1347,14 @@ mod test {
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-6, 11),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
@@ -1329,11 +1379,15 @@ mod test {
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-7, 9),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
@@ -1358,11 +1412,15 @@ mod test {
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-2, 15),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
@@ -1393,6 +1451,8 @@ mod test {
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);

View File

@@ -44,8 +44,8 @@ pub struct MaterialRuleContext<'a> {
last_est_heiht_unique_horizontal_pos_value: i64,
unique_horizontal_pos_value: i64,
surface_min_y: i32,
pub surface_noise: DoublePerlinNoiseSampler,
pub secondary_noise: DoublePerlinNoiseSampler,
pub surface_noise: &'a DoublePerlinNoiseSampler,
pub secondary_noise: &'a DoublePerlinNoiseSampler,
pub stone_depth_below: i32,
pub stone_depth_above: i32,
pub terrain_builder: &'a SurfaceTerrainBuilder,
@@ -55,9 +55,11 @@ impl<'a> MaterialRuleContext<'a> {
pub fn new(
min_y: i8,
height: u16,
mut noise_builder: DoublePerlinNoiseBuilder<'a>,
noise_builder: DoublePerlinNoiseBuilder<'a>,
random_deriver: &'a RandomDeriver,
terrain_builder: &'a SurfaceTerrainBuilder,
surface_noise: &'a DoublePerlinNoiseSampler,
secondary_noise: &'a DoublePerlinNoiseSampler,
) -> Self {
const HORIZONTAL_POS: i64 = -i64::MAX; // Vanilla
Self {
@@ -76,8 +78,8 @@ impl<'a> MaterialRuleContext<'a> {
biome: &Biome::PLAINS,
run_depth: 0,
secondary_depth: 0.0,
surface_noise: noise_builder.get_noise_sampler_for_id("surface"),
secondary_noise: noise_builder.get_noise_sampler_for_id("surface_secondary"),
surface_noise,
secondary_noise,
noise_builder,
stone_depth_below: 0,
stone_depth_above: 0,
@@ -325,6 +327,7 @@ pub struct NoiseThresholdMaterialCondition {
impl NoiseThresholdMaterialCondition {
pub fn test(&self, context: &mut MaterialRuleContext) -> bool {
// TODO: we want to cache these
let sampler = context
.noise_builder
.get_noise_sampler_for_id(self.noise.strip_prefix("minecraft:").unwrap());

View File

@@ -31,7 +31,7 @@ use std::{
use tokio::{
select,
sync::{
Mutex, Notify, RwLock,
Notify, RwLock,
mpsc::{self, UnboundedReceiver},
},
task::JoinHandle,
@@ -73,7 +73,7 @@ pub struct Level {
/// Semaphore to limit concurrent chunk generation tasks
//chunk_generation_semaphore: Arc<Semaphore>,
/// Map to deduplicate chunk generation and avoid DashMap write lock
chunk_generation_locks: Arc<Mutex<HashMap<Vector2<i32>, Arc<Notify>>>>,
//chunk_generation_locks: Arc<Mutex<HashMap<Vector2<i32>, Arc<Notify>>>>,
/// Tracks tasks associated with this world instance
tasks: TaskTracker,
/// Notification that interrupts tasks for shutdown
@@ -155,7 +155,7 @@ impl Level {
shutdown_notifier: Notify::new(),
// Limits concurrent chunk generation tasks to 2x the number of CPUs
//chunk_generation_semaphore: Arc::new(Semaphore::new(num_cpus::get())),
chunk_generation_locks: Arc::new(Mutex::new(HashMap::new())),
//chunk_generation_locks: Arc::new(Mutex::new(HashMap::new())),
world_gen_pool: Arc::new(
ThreadPoolBuilder::new()
//.num_threads((num_cpus::get() - 1).min(1))
@@ -785,83 +785,81 @@ impl Level {
let block_registry = block_registry.clone();
let self_clone = self_clone.clone();
let notify = {
let mut locks = self_clone.chunk_generation_locks.lock().await;
if let Some(existing) = locks.get(&pos) {
Some(existing.clone())
} else {
let notify = Arc::new(Notify::new());
locks.insert(pos, notify.clone());
None
// let notify = {
// let mut locks = self_clone.chunk_generation_locks.lock().await;
// if let Some(existing) = locks.get(&pos) {
// Some(existing.clone())
// } else {
// let notify = Arc::new(Notify::new());
// locks.insert(pos, notify.clone());
// None
// }
// };
// if let Some(notify) = notify {
// // Wait for the chunk to be generated by another task
// notify.notified().await;
// // After being notified, the chunk should be in loaded_chunks
// // However, it might have been unloaded between notification and access
// if let Some(chunk) = loaded_chunks.get(&pos) {
// let chunk = chunk.clone();
// if !send_chunk(true, chunk, &channel) {
// // Stop any additional queued generations
// cloned_continue_to_generate.store(false, Ordering::Relaxed);
// }
// } else {
// // Chunk was unloaded after notification, skip this iteration
// // The chunk generation will be retried if needed
// log::info!("Chunk at {pos:?} was unloaded after generation notification");
// }
// } else {
//let _permit = chunk_generation_semaphore
// .acquire()
// .await
// .expect("Semaphore closed");
// let handle = tokio::runtime::Handle::current();
world_gen_pool.spawn(move || {
// Acquire a permit from the semaphore to limit concurrent generation
// Rayon tasks are queued, so also check it here
if !cloned_continue_to_generate.load(Ordering::Relaxed) {
return;
}
};
if let Some(notify) = notify {
// Wait for the chunk to be generated by another task
notify.notified().await;
// After being notified, the chunk should be in loaded_chunks
// However, it might have been unloaded between notification and access
if let Some(chunk) = loaded_chunks.get(&pos) {
let chunk = chunk.clone();
if !send_chunk(true, chunk, &channel) {
// Stop any additional queued generations
cloned_continue_to_generate.store(false, Ordering::Relaxed);
}
} else {
// Chunk was unloaded after notification, skip this iteration
// The chunk generation will be retried if needed
log::info!("Chunk at {pos:?} was unloaded after generation notification");
let result = {
// Deduplicate chunk generation using chunk_generation_locks
// We are responsible for generating the chunk
let mut generated_chunk =
world_gen.generate_chunk(&self_clone, block_registry.as_ref(), &pos);
generated_chunk.heightmap = generated_chunk.calculate_heightmap();
let arc_chunk = Arc::new(RwLock::new(generated_chunk));
loaded_chunks.insert(pos, arc_chunk.clone());
// Store the notify for later removal
(arc_chunk, pos)
};
// TODO: this is slow and causes dead locks
// Remove the notify and wake up any waiters
// Do this outside the rayon thread to avoid deadlock
// let (arc_chunk, pos) = result;
// {
// let self_clone = self_clone.clone();
// handle.spawn(async move {
// let mut locks = self_clone.chunk_generation_locks.lock().await;
// if let Some(notify) = locks.remove(&pos) {
// notify.notify_waiters();
// }
// });
// }
if !send_chunk(true, result.0, &channel) {
// Stop any additional queued generations
cloned_continue_to_generate.store(false, Ordering::Relaxed);
}
} else {
//let _permit = chunk_generation_semaphore
// .acquire()
// .await
// .expect("Semaphore closed");
let handle = tokio::runtime::Handle::current();
world_gen_pool.spawn(move || {
// Acquire a permit from the semaphore to limit concurrent generation
// Rayon tasks are queued, so also check it here
if !cloned_continue_to_generate.load(Ordering::Relaxed) {
return;
}
let result = {
// Deduplicate chunk generation using chunk_generation_locks
// We are responsible for generating the chunk
let mut generated_chunk = world_gen.generate_chunk(
&self_clone,
block_registry.as_ref(),
&pos,
);
generated_chunk.heightmap = generated_chunk.calculate_heightmap();
let arc_chunk = Arc::new(RwLock::new(generated_chunk));
loaded_chunks.insert(pos, arc_chunk.clone());
// Store the notify for later removal
(arc_chunk, pos)
};
// Remove the notify and wake up any waiters
// Do this outside the rayon thread to avoid deadlock
let (arc_chunk, pos) = result;
{
let self_clone = self_clone.clone();
handle.spawn(async move {
let mut locks = self_clone.chunk_generation_locks.lock().await;
if let Some(notify) = locks.remove(&pos) {
notify.notify_waiters();
}
});
}
if !send_chunk(true, arc_chunk, &channel) {
// Stop any additional queued generations
cloned_continue_to_generate.store(false, Ordering::Relaxed);
}
});
}
});
//}
}
};
@@ -922,6 +920,7 @@ impl Level {
let load_channel = channel.clone();
let loaded_chunks = self.loaded_entity_chunks.clone();
let world_gen_pool = self.world_gen_pool.clone();
let handle_load = async move {
while let Some(data) = load_bridge_recv.recv().await {
let is_ok = match data {
@@ -977,7 +976,7 @@ impl Level {
let cloned_continue_to_generate = continue_to_generate.clone();
//let semaphore = chunk_generation_semaphore.clone();
tokio::spawn(async move {
world_gen_pool.spawn(move || {
// Acquire a permit from the semaphore to limit concurrent generation
//let _permit = semaphore.acquire().await.expect("Semaphore closed");

View File

@@ -1,5 +1,6 @@
use dimension::Dimension;
use generation::settings::GenerationSettings;
use pumpkin_data::BlockState;
use pumpkin_util::math::vector2::Vector2;
pub mod biome;
@@ -43,12 +44,23 @@ pub use generation::{
GlobalRandomConfig, noise_router::proto_noise_router::ProtoNoiseRouters,
proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting,
};
use crate::generation::proto_chunk::TerrainCache;
pub fn bench_create_and_populate_noise(
base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig,
settings: &GenerationSettings,
terrain_cache: &TerrainCache,
default_block: &'static BlockState,
) {
let mut chunk = ProtoChunk::new(Vector2::new(0, 0), base_router, random_config, settings);
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
base_router,
random_config,
settings,
terrain_cache,
default_block,
);
chunk.populate_noise();
}
@@ -56,8 +68,17 @@ pub fn bench_create_and_populate_biome(
base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig,
settings: &GenerationSettings,
terrain_cache: &TerrainCache,
default_block: &'static BlockState,
) {
let mut chunk = ProtoChunk::new(Vector2::new(0, 0), base_router, random_config, settings);
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
base_router,
random_config,
settings,
terrain_cache,
default_block,
);
chunk.populate_biomes(Dimension::Overworld);
}
@@ -65,8 +86,17 @@ pub fn bench_create_and_populate_noise_with_surface(
base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig,
settings: &GenerationSettings,
terrain_cache: &TerrainCache,
default_block: &'static BlockState,
) {
let mut chunk = ProtoChunk::new(Vector2::new(0, 0), base_router, random_config, settings);
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
base_router,
random_config,
settings,
terrain_cache,
default_block,
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();