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::{ use pumpkin_world::{
GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoNoiseRouters, GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoNoiseRouters,
bench_create_and_populate_biome, bench_create_and_populate_noise, 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) { fn bench_terrain_gen(c: &mut Criterion) {
@ -13,13 +13,31 @@ fn bench_terrain_gen(c: &mut Criterion) {
let surface_config = GENERATION_SETTINGS let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld) .get(&GeneratorSetting::Overworld)
.unwrap(); .unwrap();
let terrain_cache = TerrainCache::from_random(&random_config);
let default_block = surface_config.default_block.get_state();
c.bench_function("overworld biome", |b| { 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| { 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| { c.bench_function("overworld surface", |b| {
@ -28,6 +46,8 @@ fn bench_terrain_gen(c: &mut Criterion) {
&base_router, &base_router,
&random_config, &random_config,
surface_config, surface_config,
&terrain_cache,
default_block,
) )
}); });
}); });

View File

@ -64,9 +64,12 @@ mod test {
GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk, GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk,
chunk::palette::BIOME_NETWORK_MAX_BITS, chunk::palette::BIOME_NETWORK_MAX_BITS,
dimension::Dimension, dimension::Dimension,
generation::noise_router::{ generation::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, noise_router::{
proto_noise_router::ProtoNoiseRouters, multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
proto_noise_router::ProtoNoiseRouters,
},
proto_chunk::TerrainCache,
}, },
}; };
@ -108,11 +111,19 @@ mod test {
let surface_settings = GENERATION_SETTINGS let surface_settings = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld) .get(&GeneratorSetting::Overworld)
.unwrap(); .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() { for data in expected_data.into_iter() {
let chunk_pos = Vector2::new(data.x, data.z); let chunk_pos = Vector2::new(data.x, data.z);
let mut chunk = let mut chunk = ProtoChunk::new(
ProtoChunk::new(chunk_pos, &noise_router, &random_config, surface_settings); chunk_pos,
&noise_router,
&random_config,
surface_settings,
&terrain_cache,
default_block,
);
chunk.populate_biomes(Dimension::Overworld); chunk.populate_biomes(Dimension::Overworld);
for (biome_x, biome_y, biome_z, biome_id) in data.data { 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, GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk,
biome::{BiomeSupplier, MultiNoiseBiomeSupplier}, biome::{BiomeSupplier, MultiNoiseBiomeSupplier},
dimension::Dimension, dimension::Dimension,
generation::noise_router::{ generation::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, noise_router::{
proto_noise_router::ProtoNoiseRouters, multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
proto_noise_router::ProtoNoiseRouters,
},
proto_chunk::TerrainCache,
}, },
}; };
@ -61,8 +64,15 @@ mod test {
let surface_config = GENERATION_SETTINGS let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld) .get(&GeneratorSetting::Overworld)
.unwrap(); .unwrap();
let terrain_cache = TerrainCache::from_random(&random_config);
let mut chunk = ProtoChunk::new(chunk_pos, &noise_rounter, &random_config, surface_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() { 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); let point = chunk.multi_noise_sampler.sample(x, y, z);

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ use std::{
use tokio::{ use tokio::{
select, select,
sync::{ sync::{
Mutex, Notify, RwLock, Notify, RwLock,
mpsc::{self, UnboundedReceiver}, mpsc::{self, UnboundedReceiver},
}, },
task::JoinHandle, task::JoinHandle,
@ -73,7 +73,7 @@ pub struct Level {
/// Semaphore to limit concurrent chunk generation tasks /// Semaphore to limit concurrent chunk generation tasks
//chunk_generation_semaphore: Arc<Semaphore>, //chunk_generation_semaphore: Arc<Semaphore>,
/// Map to deduplicate chunk generation and avoid DashMap write lock /// 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 /// Tracks tasks associated with this world instance
tasks: TaskTracker, tasks: TaskTracker,
/// Notification that interrupts tasks for shutdown /// Notification that interrupts tasks for shutdown
@ -155,7 +155,7 @@ impl Level {
shutdown_notifier: Notify::new(), shutdown_notifier: Notify::new(),
// Limits concurrent chunk generation tasks to 2x the number of CPUs // Limits concurrent chunk generation tasks to 2x the number of CPUs
//chunk_generation_semaphore: Arc::new(Semaphore::new(num_cpus::get())), //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( world_gen_pool: Arc::new(
ThreadPoolBuilder::new() ThreadPoolBuilder::new()
//.num_threads((num_cpus::get() - 1).min(1)) //.num_threads((num_cpus::get() - 1).min(1))
@ -785,83 +785,81 @@ impl Level {
let block_registry = block_registry.clone(); let block_registry = block_registry.clone();
let self_clone = self_clone.clone(); let self_clone = self_clone.clone();
let notify = { // let notify = {
let mut locks = self_clone.chunk_generation_locks.lock().await; // let mut locks = self_clone.chunk_generation_locks.lock().await;
if let Some(existing) = locks.get(&pos) { // if let Some(existing) = locks.get(&pos) {
Some(existing.clone()) // Some(existing.clone())
} else { // } else {
let notify = Arc::new(Notify::new()); // let notify = Arc::new(Notify::new());
locks.insert(pos, notify.clone()); // locks.insert(pos, notify.clone());
None // 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 { let result = {
// Wait for the chunk to be generated by another task // Deduplicate chunk generation using chunk_generation_locks
notify.notified().await;
// After being notified, the chunk should be in loaded_chunks // We are responsible for generating the chunk
// However, it might have been unloaded between notification and access let mut generated_chunk =
if let Some(chunk) = loaded_chunks.get(&pos) { world_gen.generate_chunk(&self_clone, block_registry.as_ref(), &pos);
let chunk = chunk.clone(); generated_chunk.heightmap = generated_chunk.calculate_heightmap();
if !send_chunk(true, chunk, &channel) { let arc_chunk = Arc::new(RwLock::new(generated_chunk));
// Stop any additional queued generations loaded_chunks.insert(pos, arc_chunk.clone());
cloned_continue_to_generate.store(false, Ordering::Relaxed);
} // Store the notify for later removal
} else { (arc_chunk, pos)
// 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"); // 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 load_channel = channel.clone();
let loaded_chunks = self.loaded_entity_chunks.clone(); let loaded_chunks = self.loaded_entity_chunks.clone();
let world_gen_pool = self.world_gen_pool.clone();
let handle_load = async move { let handle_load = async move {
while let Some(data) = load_bridge_recv.recv().await { while let Some(data) = load_bridge_recv.recv().await {
let is_ok = match data { let is_ok = match data {
@ -977,7 +976,7 @@ impl Level {
let cloned_continue_to_generate = continue_to_generate.clone(); let cloned_continue_to_generate = continue_to_generate.clone();
//let semaphore = chunk_generation_semaphore.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 // Acquire a permit from the semaphore to limit concurrent generation
//let _permit = semaphore.acquire().await.expect("Semaphore closed"); //let _permit = semaphore.acquire().await.expect("Semaphore closed");

View File

@ -1,5 +1,6 @@
use dimension::Dimension; use dimension::Dimension;
use generation::settings::GenerationSettings; use generation::settings::GenerationSettings;
use pumpkin_data::BlockState;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
pub mod biome; pub mod biome;
@ -43,12 +44,23 @@ pub use generation::{
GlobalRandomConfig, noise_router::proto_noise_router::ProtoNoiseRouters, GlobalRandomConfig, noise_router::proto_noise_router::ProtoNoiseRouters,
proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting, proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting,
}; };
use crate::generation::proto_chunk::TerrainCache;
pub fn bench_create_and_populate_noise( pub fn bench_create_and_populate_noise(
base_router: &ProtoNoiseRouters, base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig, random_config: &GlobalRandomConfig,
settings: &GenerationSettings, 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(); chunk.populate_noise();
} }
@ -56,8 +68,17 @@ pub fn bench_create_and_populate_biome(
base_router: &ProtoNoiseRouters, base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig, random_config: &GlobalRandomConfig,
settings: &GenerationSettings, 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_biomes(Dimension::Overworld);
} }
@ -65,8 +86,17 @@ pub fn bench_create_and_populate_noise_with_surface(
base_router: &ProtoNoiseRouters, base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig, random_config: &GlobalRandomConfig,
settings: &GenerationSettings, 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_biomes(Dimension::Overworld);
chunk.populate_noise(); chunk.populate_noise();
chunk.build_surface(); chunk.build_surface();