mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Made Chunks faster
Moreeee Speeeedddddd
This commit is contained in:
@@ -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,
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user