chore: more work

- fixed nether again, not the dead lock or whatever it is but the server crash
- further replace Notify with CancellationToken
- fixed chunk loading on inital spawn
- more stronghold work
This commit is contained in:
Alexander Medvedev 2026-01-21 23:08:56 +01:00
parent 274c7e195d
commit ac021d5d47
25 changed files with 880 additions and 843 deletions

View File

@ -411,7 +411,7 @@ pub(crate) fn build() -> TokenStream {
} }
impl ParameterRange { impl ParameterRange {
fn calc_distance(&self, noise: i64) -> i64 { pub fn calc_distance(&self, noise: i64) -> i64 {
if noise > self.max { if noise > self.max {
noise - self.max noise - self.max
} else if noise < self.min { } else if noise < self.min {
@ -441,65 +441,66 @@ pub(crate) fn build() -> TokenStream {
point_list: &[i64; 7], point_list: &[i64; 7],
previous_result_node: &mut Option<&'static BiomeTree>, previous_result_node: &mut Option<&'static BiomeTree>,
) -> &'static Biome { ) -> &'static Biome {
let result_node = self.get_resulting_node(point_list, *previous_result_node); // Initialize best distance from the previous result for spatial coherence
match result_node { let mut best_dist = previous_result_node
BiomeTree::Leaf { biome, .. } => {
*previous_result_node = Some(result_node);
biome
}
_ => unreachable!(),
}
}
fn get_resulting_node(
&'static self,
point_list: &[i64; 7],
previous_result_node: Option<&'static BiomeTree>,
) -> &'static BiomeTree {
match self {
Self::Leaf { .. } => self,
Self::Branch { nodes, .. } => {
let mut distance = previous_result_node
.map(|node| node.get_squared_distance(point_list)) .map(|node| node.get_squared_distance(point_list))
.unwrap_or(i64::MAX); .unwrap_or(i64::MAX);
let mut best_node = previous_result_node;
let mut best_node = previous_result_node.unwrap_or(self);
self.search(point_list, &mut best_dist, &mut best_node);
match best_node {
BiomeTree::Leaf { biome, .. } => {
*previous_result_node = Some(best_node);
biome
}
// Should not happen with valid tree data
_ => unreachable!("Biome search failed to find a leaf"),
}
}
fn search(
&'static self,
point: &[i64; 7],
best_dist: &mut i64,
best_node: &mut &'static BiomeTree,
) {
let dist = self.get_squared_distance(point);
// PRUNING: If this branch/leaf is further than our best, skip it and all children
if dist >= *best_dist {
return;
}
match self {
Self::Leaf { .. } => {
*best_dist = dist;
*best_node = self;
}
Self::Branch { nodes, .. } => {
for node in *nodes { for node in *nodes {
let node_distance = node.get_squared_distance(point_list); node.search(point, best_dist, best_node);
if distance > node_distance { }
let node2 = node.get_resulting_node(point_list, best_node);
let node2_distance = if node == node2 {
node_distance
} else {
node2.get_squared_distance(point_list)
};
if distance > node2_distance {
distance = node2_distance;
best_node = Some(node2);
} }
} }
} }
best_node.expect("This should be populated after traversing the tree") #[inline(always)]
} fn get_squared_distance(&self, p: &[i64; 7]) -> i64 {
} let params = match self {
}
fn get_squared_distance(&self, point_list: &[i64; 7]) -> i64 {
let parameters = match self {
Self::Leaf { parameters, .. } => parameters, Self::Leaf { parameters, .. } => parameters,
Self::Branch { parameters, .. } => parameters, Self::Branch { parameters, .. } => parameters,
}; };
parameters // Fully unrolled for 7 dimensions to maximize throughput
.iter() params[0].calc_distance(p[0]) +
.zip(point_list) params[1].calc_distance(p[1]) +
.map(|(bound, value)| { params[2].calc_distance(p[2]) +
let distance = bound.calc_distance(*value); params[3].calc_distance(p[3]) +
distance * distance params[4].calc_distance(p[4]) +
}) params[5].calc_distance(p[5]) +
.sum() params[6].calc_distance(p[6])
} }
} }

View File

@ -10,6 +10,7 @@ use pumpkin_data::{
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler; use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
pub mod end; pub mod end;
pub mod multi_noise; pub mod multi_noise;
pub mod position_finder;
thread_local! { thread_local! {
/// A shortcut; check if last used biome is what we should use /// A shortcut; check if last used biome is what we should use

View File

@ -0,0 +1,77 @@
use pumpkin_data::chunk::ParameterRange;
use pumpkin_util::math::vector2::Vector2;
pub struct FittestPositionFinderResult {
pub location: Vector2<i32>,
pub fitness: i64,
}
pub struct FittestPositionFinder;
impl FittestPositionFinder {
pub fn find_best_spawn_position(
target_noises: &[[ParameterRange; 7]],
sampler: &dyn Fn(i32, i32) -> [i64; 7],
) -> Vector2<i32> {
let mut best_result = Self::calculate_fitness(target_noises, sampler, 0, 0);
Self::find_fittest(target_noises, sampler, &mut best_result, 2048.0, 512.0);
Self::find_fittest(target_noises, sampler, &mut best_result, 512.0, 32.0);
best_result.location
}
fn find_fittest(
noises: &[[ParameterRange; 7]],
sampler: &dyn Fn(i32, i32) -> [i64; 7],
best_result: &mut FittestPositionFinderResult,
max_distance: f32,
step: f32,
) {
let mut angle = 0.0f32;
let mut distance = step;
let center = best_result.location;
while distance <= max_distance {
let x = center.x + (angle.sin() * distance) as i32;
let z = center.y + (angle.cos() * distance) as i32;
let result = Self::calculate_fitness(noises, sampler, x, z);
if result.fitness < best_result.fitness {
*best_result = result;
}
angle += step / distance;
if angle > std::f32::consts::TAU {
angle = 0.0;
distance += step;
}
}
}
fn calculate_fitness(
noises: &[[ParameterRange; 7]],
sampler: &dyn Fn(i32, i32) -> [i64; 7],
x: i32,
z: i32,
) -> FittestPositionFinderResult {
let sampled_noise = sampler(x, z);
let mut min_squared_dist = i64::MAX;
for noise_ranges in noises {
let mut current_dist = 0i64;
for i in 0..7 {
current_dist += noise_ranges[i].calc_distance(sampled_noise[i]);
}
min_squared_dist = min_squared_dist.min(current_dist);
}
let origin_dist_sq = (x as i64 * x as i64) + (z as i64 * z as i64);
let fitness = min_squared_dist * (2048 * 2048) + origin_dist_sq;
FittestPositionFinderResult {
location: Vector2::new(x, z),
fitness,
}
}
}

View File

@ -9,6 +9,7 @@ TODO
use crate::block::RawBlockState; use crate::block::RawBlockState;
use crate::chunk::io::LoadedData::Loaded; use crate::chunk::io::LoadedData::Loaded;
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk}; use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk};
use crate::generation::biome_coords;
use pumpkin_data::block_properties::is_air; use pumpkin_data::block_properties::is_air;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use std::default::Default; use std::default::Default;
@ -791,12 +792,12 @@ impl Chunk {
dimension.min_y, dimension.min_y,
); );
let proto_biome_height = (dimension.height / 4) as usize; let proto_biome_height = biome_coords::from_block(proto_chunk.height());
let biome_min_y = dimension.min_y / 4; let biome_min_y = biome_coords::from_block(dimension.min_y);
for y_offset in 0..proto_biome_height { for y_offset in 0..proto_biome_height {
let section_index = y_offset / 4; let section_index = y_offset as usize / 4;
let relative_biome_y = y_offset % 4; let relative_y = y_offset as usize % 4;
if let Some(section) = sections.sections.get_mut(section_index) { if let Some(section) = sections.sections.get_mut(section_index) {
let absolute_biome_y = biome_min_y + y_offset as i32; let absolute_biome_y = biome_min_y + y_offset as i32;
@ -804,7 +805,7 @@ impl Chunk {
for z in 0..4 { for z in 0..4 {
for x in 0..4 { for x in 0..4 {
let biome = proto_chunk.get_biome(x as i32, absolute_biome_y, z as i32); let biome = proto_chunk.get_biome(x as i32, absolute_biome_y, z as i32);
section.biomes.set(x, relative_biome_y, z, biome.id); section.biomes.set(x, relative_y, z, biome.id);
} }
} }
} }
@ -1988,15 +1989,11 @@ impl GenerationSchedule {
); );
// let mut clock = Instant::now(); // let mut clock = Instant::now();
loop { loop {
if level.should_unload.load(Relaxed) { if level.should_unload.swap(false, Relaxed) {
// log::debug!("unload chunk signal");
self.unload_chunk(); self.unload_chunk();
level.should_unload.store(false, Relaxed);
} }
if level.should_save.load(Relaxed) { if level.should_save.swap(false, Relaxed) {
// log::debug!("save all chunk signal");
self.save_all_chunk(false); self.save_all_chunk(false);
level.should_save.store(false, Relaxed);
} }
if level.shut_down_chunk_system.load(Relaxed) { if level.shut_down_chunk_system.load(Relaxed) {
// log::debug!("shut down signal"); // log::debug!("shut down signal");

View File

@ -7,9 +7,6 @@ use serde::Deserialize;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::{block::BlockStateCodec, world::BlockRegistryExt}; use crate::{block::BlockStateCodec, world::BlockRegistryExt};
#[derive(Deserialize)]
pub struct EmptyTODOStruct {}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum BlockPredicate { pub enum BlockPredicate {
@ -37,9 +34,9 @@ pub enum BlockPredicate {
Not(NotBlockPredicate), Not(NotBlockPredicate),
#[serde(rename = "minecraft:true")] #[serde(rename = "minecraft:true")]
AlwaysTrue, AlwaysTrue,
/// Not used // Not used
#[serde(rename = "minecraft:unobstructed")] // #[serde(rename = "minecraft:unobstructed")]
Unobstructed(EmptyTODOStruct), // Unobstructed(EmptyTODOStruct),
} }
impl BlockPredicate { impl BlockPredicate {
@ -62,7 +59,6 @@ impl BlockPredicate {
BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos), BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos),
BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos), BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos),
BlockPredicate::AlwaysTrue => true, BlockPredicate::AlwaysTrue => true,
BlockPredicate::Unobstructed(_predicate) => false,
} }
} }
} }

View File

@ -1,416 +0,0 @@
use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::{floor_div, floor_mod};
use crate::generation::section_coords;
use super::{
GlobalRandomConfig,
aquifer_sampler::{
AquiferSampler, AquiferSamplerImpl, FluidLevelSampler, SeaLevelAquiferSampler,
WorldAquiferSampler,
},
biome_coords,
noise::router::{
chunk_density_function::{
ChunkNoiseFunctionBuilderOptions, ChunkNoiseFunctionSampleOptions, SampleAction,
WrapperData,
},
chunk_noise_router::ChunkNoiseRouter,
density_function::{IndexToNoisePos, NoisePos, UnblendedNoisePos},
proto_noise_router::ProtoNoiseRouter,
surface_height_sampler::SurfaceHeightEstimateSampler,
},
ore_sampler::OreVeinSampler,
settings::GenerationShapeConfig,
};
pub const LAVA_BLOCK: Block = Block::LAVA;
pub const WATER_BLOCK: Block = Block::WATER;
pub const CHUNK_DIM: u8 = 16;
pub enum BlockStateSampler {
Aquifer(AquiferSampler),
Ore(OreVeinSampler),
}
impl BlockStateSampler {
pub fn sample(
&mut self,
router: &mut ChunkNoiseRouter,
pos: &impl NoisePos,
sample_options: &ChunkNoiseFunctionSampleOptions,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
match self {
Self::Aquifer(aquifer) => aquifer.apply(router, pos, sample_options, height_estimator),
Self::Ore(ore) => ore.sample(router, pos, sample_options),
}
}
}
pub struct ChainedBlockStateSampler {
pub(crate) samplers: Box<[BlockStateSampler]>,
}
impl ChainedBlockStateSampler {
pub fn new(samplers: Box<[BlockStateSampler]>) -> Self {
Self { samplers }
}
fn sample(
&mut self,
router: &mut ChunkNoiseRouter,
pos: &impl NoisePos,
sample_options: &ChunkNoiseFunctionSampleOptions,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
for sampler in self.samplers.iter_mut() {
if let Some(state) = sampler.sample(router, pos, sample_options, height_estimator) {
return Some(state);
}
}
None
}
}
struct InterpolationIndexMapper {
x: i32,
z: i32,
minimum_cell_y: i32,
vertical_cell_block_count: i32,
}
impl IndexToNoisePos for InterpolationIndexMapper {
fn at(
&self,
index: usize,
sample_data: Option<&mut ChunkNoiseFunctionSampleOptions>,
) -> impl NoisePos + 'static {
if let Some(sample_data) = sample_data {
sample_data.cache_result_unique_id += 1;
sample_data.fill_index = index;
}
let y = (index as i32 + self.minimum_cell_y) * self.vertical_cell_block_count;
// TODO: Change this when Blender is implemented
UnblendedNoisePos::new(self.x, y, self.z)
}
}
struct ChunkIndexMapper {
start_x: i32,
start_y: i32,
start_z: i32,
horizontal_cell_block_count: usize,
vertical_cell_block_count: usize,
}
impl IndexToNoisePos for ChunkIndexMapper {
fn at(
&self,
index: usize,
sample_options: Option<&mut ChunkNoiseFunctionSampleOptions>,
) -> impl NoisePos + 'static {
let cell_z_position = floor_mod(index, self.horizontal_cell_block_count);
let xy_portion = floor_div(index, self.horizontal_cell_block_count);
let cell_x_position = floor_mod(xy_portion, self.horizontal_cell_block_count);
let cell_y_position = self.vertical_cell_block_count
- 1
- floor_div(xy_portion, self.horizontal_cell_block_count);
if let Some(sample_options) = sample_options {
sample_options.fill_index = index;
if let SampleAction::CellCaches(wrapper_data) = &mut sample_options.action {
wrapper_data.update_position(cell_x_position, cell_y_position, cell_z_position);
}
}
// TODO: Change this when Blender is implemented
UnblendedNoisePos::new(
self.start_x + cell_x_position as i32,
self.start_y + cell_y_position as i32,
self.start_z + cell_z_position as i32,
)
}
}
pub struct ChunkNoiseGenerator<'a> {
pub state_sampler: ChainedBlockStateSampler,
generation_shape: &'a GenerationShapeConfig,
start_cell_pos_x: i32,
start_cell_pos_z: i32,
vertical_cell_count: usize,
minimum_cell_y: i32,
cache_fill_unique_id: u64,
cache_result_unique_id: u64,
pub router: ChunkNoiseRouter<'a>,
}
impl<'a> ChunkNoiseGenerator<'a> {
#[expect(clippy::too_many_arguments)]
pub fn new(
noise_router_base: &'a ProtoNoiseRouter,
random_config: &GlobalRandomConfig,
horizontal_cell_count: usize,
start_block_x: i32,
start_block_z: i32,
generation_shape: &'a GenerationShapeConfig,
level_sampler: FluidLevelSampler,
aquifers: bool,
ore_veins: bool,
) -> Self {
let start_cell_pos_x = floor_div(
start_block_x,
generation_shape.horizontal_cell_block_count() as i32,
);
let start_cell_pos_z = floor_div(
start_block_z,
generation_shape.horizontal_cell_block_count() as i32,
);
let horizontal_biome_end = biome_coords::from_block(
horizontal_cell_count * generation_shape.horizontal_cell_block_count() as usize,
);
let vertical_cell_count = floor_div(
generation_shape.height as usize,
generation_shape.vertical_cell_block_count() as usize,
);
let minimum_cell_y = floor_div(
generation_shape.min_y as i32,
generation_shape.vertical_cell_block_count() as i32,
);
let vertical_cell_block_count = generation_shape.vertical_cell_block_count();
let horizontal_cell_block_count = generation_shape.horizontal_cell_block_count();
let builder_options = ChunkNoiseFunctionBuilderOptions::new(
horizontal_cell_block_count as usize,
vertical_cell_block_count as usize,
vertical_cell_count,
horizontal_cell_count,
biome_coords::from_block(start_block_x),
biome_coords::from_block(start_block_z),
horizontal_biome_end,
);
let aquifer_sampler = if aquifers {
let section_x = section_coords::block_to_section(start_block_x);
let section_z = section_coords::block_to_section(start_block_z);
AquiferSampler::Aquifer(WorldAquiferSampler::new(
section_x,
section_z,
&random_config.aquifer_random_deriver,
generation_shape.min_y,
generation_shape.height,
level_sampler,
))
} else {
AquiferSampler::SeaLevel(SeaLevelAquiferSampler::new(level_sampler))
};
let samplers: Box<[BlockStateSampler]> = if ore_veins {
let ore_sampler = OreVeinSampler::new(random_config.ore_random_deriver.clone());
Box::new([
BlockStateSampler::Aquifer(aquifer_sampler),
BlockStateSampler::Ore(ore_sampler),
])
} else {
Box::new([BlockStateSampler::Aquifer(aquifer_sampler)])
};
let state_sampler = ChainedBlockStateSampler::new(samplers);
let router = ChunkNoiseRouter::generate(noise_router_base, &builder_options);
Self {
state_sampler,
generation_shape,
start_cell_pos_x,
start_cell_pos_z,
vertical_cell_count,
minimum_cell_y,
cache_fill_unique_id: 0,
cache_result_unique_id: 0,
router,
}
}
#[inline]
pub fn sample_start_density(&mut self) {
self.cache_result_unique_id = 0;
self.sample_density(true, self.start_cell_pos_x);
}
#[inline]
pub fn sample_end_density(&mut self, cell_x: i32) {
self.sample_density(false, self.start_cell_pos_x + cell_x + 1);
}
fn sample_density(&mut self, start: bool, current_x: i32) {
let x = current_x * self.horizontal_cell_block_count() as i32;
for cell_z in 0..=(16 / self.horizontal_cell_block_count()) {
let current_cell_z_pos = self.start_cell_pos_z + cell_z as i32;
let z = current_cell_z_pos * self.horizontal_cell_block_count() as i32;
self.cache_fill_unique_id += 1;
let mapper = InterpolationIndexMapper {
x,
z,
minimum_cell_y: self.minimum_cell_y,
vertical_cell_block_count: self.vertical_cell_block_count() as i32,
};
let mut options = ChunkNoiseFunctionSampleOptions::new(
false,
SampleAction::CellCaches(WrapperData::new(
0,
0,
0,
self.horizontal_cell_block_count() as usize,
self.vertical_cell_block_count() as usize,
)),
self.cache_result_unique_id,
self.cache_fill_unique_id,
0,
);
self.fill_interpolator_buffers(start, cell_z as usize, &mapper, &mut options);
self.cache_result_unique_id = options.cache_result_unique_id;
}
self.cache_fill_unique_id += 1;
}
#[inline]
fn fill_interpolator_buffers(
&mut self,
start: bool,
cell_z: usize,
mapper: &impl IndexToNoisePos,
sample_options: &mut ChunkNoiseFunctionSampleOptions,
) {
self.router
.fill_interpolator_buffers(start, cell_z, mapper, sample_options);
}
#[inline]
pub fn interpolate_x(&mut self, delta: f64) {
self.router.interpolate_x(delta);
}
#[inline]
pub fn interpolate_y(&mut self, delta: f64) {
self.router.interpolate_y(delta);
}
#[inline]
pub fn interpolate_z(&mut self, delta: f64) {
self.cache_result_unique_id += 1;
self.router.interpolate_z(delta);
}
#[inline]
pub fn swap_buffers(&mut self) {
self.router.swap_buffers();
}
pub fn on_sampled_cell_corners(&mut self, cell_x: i32, cell_y: i32, cell_z: i32) {
self.router
.on_sampled_cell_corners(cell_y as usize, cell_z as usize);
self.cache_fill_unique_id += 1;
let start_x = (self.start_cell_pos_x + cell_x) * self.horizontal_cell_block_count() as i32;
let start_y = (cell_y + self.minimum_cell_y) * self.vertical_cell_block_count() as i32;
let start_z = (self.start_cell_pos_z + cell_z) * self.horizontal_cell_block_count() as i32;
let mapper = ChunkIndexMapper {
start_x,
start_y,
start_z,
horizontal_cell_block_count: self.horizontal_cell_block_count() as usize,
vertical_cell_block_count: self.vertical_cell_block_count() as usize,
};
let mut sample_options = ChunkNoiseFunctionSampleOptions::new(
true,
SampleAction::CellCaches(WrapperData::new(
0,
0,
0,
self.horizontal_cell_block_count() as usize,
self.vertical_cell_block_count() as usize,
)),
self.cache_result_unique_id,
self.cache_fill_unique_id,
0,
);
self.router.fill_cell_caches(&mapper, &mut sample_options);
self.cache_fill_unique_id += 1;
}
#[expect(clippy::too_many_arguments)]
pub fn sample_block_state(
&mut self,
start_x: i32,
start_y: i32,
start_z: i32,
cell_x: i32,
cell_y: i32,
cell_z: i32,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
//TODO: Fix this when Blender is added
let pos = UnblendedNoisePos::new(start_x + cell_x, start_y + cell_y, start_z + cell_z);
let options = ChunkNoiseFunctionSampleOptions::new(
false,
SampleAction::CellCaches(WrapperData::new(
cell_x as usize,
cell_y as usize,
cell_z as usize,
self.horizontal_cell_block_count() as usize,
self.vertical_cell_block_count() as usize,
)),
self.cache_result_unique_id,
self.cache_fill_unique_id,
0,
);
self.state_sampler
.sample(&mut self.router, &pos, &options, height_estimator)
}
#[inline]
pub fn horizontal_cell_block_count(&self) -> u8 {
self.generation_shape.horizontal_cell_block_count()
}
#[inline]
pub fn vertical_cell_block_count(&self) -> u8 {
self.generation_shape.vertical_cell_block_count()
}
/// Aka bottom_y
#[inline]
pub fn min_y(&self) -> i8 {
self.generation_shape.min_y
}
#[inline]
pub fn height(&self) -> u16 {
self.generation_shape.height
}
}
#[cfg(test)]
mod test {
// TODO: Add test to verify the height estimator has no interpolators or cell caches
}

View File

@ -5,9 +5,7 @@ use pumpkin_util::{
}; };
use serde::Deserialize; use serde::Deserialize;
use crate::generation::chunk_noise::WATER_BLOCK; use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache};
use crate::generation::proto_chunk::GenerationCache;
// TODO: remove .to_state()
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DesertWellFeature; pub struct DesertWellFeature;

View File

@ -1,18 +1,15 @@
#![allow(dead_code)] #![allow(dead_code)]
pub mod aquifer_sampler;
mod biome; mod biome;
mod blender; mod blender;
mod block_predicate; mod block_predicate;
mod block_state_provider; mod block_state_provider;
pub mod carver; pub mod carver;
pub mod chunk_noise;
mod feature; mod feature;
pub mod generator; pub mod generator;
pub mod height_limit; pub mod height_limit;
pub mod height_provider; pub mod height_provider;
pub mod noise; pub mod noise;
pub mod ore_sampler;
pub mod positions; pub mod positions;
pub mod proto_chunk; pub mod proto_chunk;
pub mod rule; pub mod rule;

View File

@ -5,14 +5,16 @@ use pumpkin_util::{
random::{RandomDeriver, RandomDeriverImpl, RandomImpl}, random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
}; };
use super::{ use crate::generation::{
chunk_noise::{LAVA_BLOCK, WATER_BLOCK}, noise::{
noise::router::{ LAVA_BLOCK, WATER_BLOCK,
router::{
chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_density_function::ChunkNoiseFunctionSampleOptions,
chunk_noise_router::ChunkNoiseRouter, chunk_noise_router::ChunkNoiseRouter,
density_function::{NoisePos, UnblendedNoisePos}, density_function::{NoisePos, UnblendedNoisePos},
surface_height_sampler::SurfaceHeightEstimateSampler, surface_height_sampler::SurfaceHeightEstimateSampler,
}, },
},
positions::{MIN_HEIGHT_CELL, block_pos, chunk_pos}, positions::{MIN_HEIGHT_CELL, block_pos, chunk_pos},
proto_chunk::StandardChunkFluidLevelSampler, proto_chunk::StandardChunkFluidLevelSampler,
section_coords, section_coords,
@ -696,8 +698,9 @@ mod random_positions_and_hypot {
block::RawBlockState, block::RawBlockState,
generation::{ generation::{
GlobalRandomConfig, biome_coords, GlobalRandomConfig, biome_coords,
chunk_noise::{BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, noise::{
noise::router::{ BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK,
router::{
chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction}, chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction},
chunk_noise_router::ChunkNoiseRouter, chunk_noise_router::ChunkNoiseRouter,
density_function::UnblendedNoisePos, density_function::UnblendedNoisePos,
@ -706,6 +709,7 @@ mod random_positions_and_hypot {
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
}, },
}, },
},
positions::chunk_pos, positions::chunk_pos,
proto_chunk::StandardChunkFluidLevelSampler, proto_chunk::StandardChunkFluidLevelSampler,
settings::{GENERATION_SETTINGS, GeneratorSetting}, settings::{GENERATION_SETTINGS, GeneratorSetting},

View File

@ -1,2 +1,424 @@
pub mod aquifer_sampler;
pub mod ore_sampler;
pub mod perlin; pub mod perlin;
pub mod router; pub mod router;
use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::{floor_div, floor_mod};
use crate::generation::{
noise::{
aquifer_sampler::{
AquiferSampler, AquiferSamplerImpl, FluidLevelSampler, SeaLevelAquiferSampler,
WorldAquiferSampler,
},
ore_sampler::OreVeinSampler,
},
section_coords,
};
use super::{
GlobalRandomConfig, biome_coords,
noise::router::{
chunk_density_function::{
ChunkNoiseFunctionBuilderOptions, ChunkNoiseFunctionSampleOptions, SampleAction,
WrapperData,
},
chunk_noise_router::ChunkNoiseRouter,
density_function::{IndexToNoisePos, NoisePos, UnblendedNoisePos},
proto_noise_router::ProtoNoiseRouter,
surface_height_sampler::SurfaceHeightEstimateSampler,
},
settings::GenerationShapeConfig,
};
pub const LAVA_BLOCK: Block = Block::LAVA;
pub const WATER_BLOCK: Block = Block::WATER;
pub const CHUNK_DIM: u8 = 16;
pub enum BlockStateSampler {
Aquifer(AquiferSampler),
Ore(OreVeinSampler),
}
impl BlockStateSampler {
pub fn sample(
&mut self,
router: &mut ChunkNoiseRouter,
pos: &impl NoisePos,
sample_options: &ChunkNoiseFunctionSampleOptions,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
match self {
Self::Aquifer(aquifer) => aquifer.apply(router, pos, sample_options, height_estimator),
Self::Ore(ore) => ore.sample(router, pos, sample_options),
}
}
}
pub struct ChainedBlockStateSampler {
pub(crate) samplers: Box<[BlockStateSampler]>,
}
impl ChainedBlockStateSampler {
pub fn new(samplers: Box<[BlockStateSampler]>) -> Self {
Self { samplers }
}
fn sample(
&mut self,
router: &mut ChunkNoiseRouter,
pos: &impl NoisePos,
sample_options: &ChunkNoiseFunctionSampleOptions,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
for sampler in self.samplers.iter_mut() {
if let Some(state) = sampler.sample(router, pos, sample_options, height_estimator) {
return Some(state);
}
}
None
}
}
struct InterpolationIndexMapper {
x: i32,
z: i32,
minimum_cell_y: i32,
vertical_cell_block_count: i32,
}
impl IndexToNoisePos for InterpolationIndexMapper {
fn at(
&self,
index: usize,
sample_data: Option<&mut ChunkNoiseFunctionSampleOptions>,
) -> impl NoisePos + 'static {
if let Some(sample_data) = sample_data {
sample_data.cache_result_unique_id += 1;
sample_data.fill_index = index;
}
let y = (index as i32 + self.minimum_cell_y) * self.vertical_cell_block_count;
// TODO: Change this when Blender is implemented
UnblendedNoisePos::new(self.x, y, self.z)
}
}
struct ChunkIndexMapper {
start_x: i32,
start_y: i32,
start_z: i32,
horizontal_cell_block_count: usize,
vertical_cell_block_count: usize,
}
impl IndexToNoisePos for ChunkIndexMapper {
fn at(
&self,
index: usize,
sample_options: Option<&mut ChunkNoiseFunctionSampleOptions>,
) -> impl NoisePos + 'static {
let cell_z_position = floor_mod(index, self.horizontal_cell_block_count);
let xy_portion = floor_div(index, self.horizontal_cell_block_count);
let cell_x_position = floor_mod(xy_portion, self.horizontal_cell_block_count);
let cell_y_position = self.vertical_cell_block_count
- 1
- floor_div(xy_portion, self.horizontal_cell_block_count);
if let Some(sample_options) = sample_options {
sample_options.fill_index = index;
if let SampleAction::CellCaches(wrapper_data) = &mut sample_options.action {
wrapper_data.update_position(cell_x_position, cell_y_position, cell_z_position);
}
}
// TODO: Change this when Blender is implemented
UnblendedNoisePos::new(
self.start_x + cell_x_position as i32,
self.start_y + cell_y_position as i32,
self.start_z + cell_z_position as i32,
)
}
}
pub struct ChunkNoiseGenerator<'a> {
pub state_sampler: ChainedBlockStateSampler,
generation_shape: &'a GenerationShapeConfig,
start_cell_pos_x: i32,
start_cell_pos_z: i32,
vertical_cell_count: usize,
minimum_cell_y: i32,
cache_fill_unique_id: u64,
cache_result_unique_id: u64,
pub router: ChunkNoiseRouter<'a>,
}
impl<'a> ChunkNoiseGenerator<'a> {
#[expect(clippy::too_many_arguments)]
pub fn new(
noise_router_base: &'a ProtoNoiseRouter,
random_config: &GlobalRandomConfig,
horizontal_cell_count: usize,
start_block_x: i32,
start_block_z: i32,
generation_shape: &'a GenerationShapeConfig,
level_sampler: FluidLevelSampler,
aquifers: bool,
ore_veins: bool,
) -> Self {
let start_cell_pos_x = floor_div(
start_block_x,
generation_shape.horizontal_cell_block_count() as i32,
);
let start_cell_pos_z = floor_div(
start_block_z,
generation_shape.horizontal_cell_block_count() as i32,
);
let horizontal_biome_end = biome_coords::from_block(
horizontal_cell_count * generation_shape.horizontal_cell_block_count() as usize,
);
let vertical_cell_count = floor_div(
generation_shape.height as usize,
generation_shape.vertical_cell_block_count() as usize,
);
let minimum_cell_y = floor_div(
generation_shape.min_y as i32,
generation_shape.vertical_cell_block_count() as i32,
);
let vertical_cell_block_count = generation_shape.vertical_cell_block_count();
let horizontal_cell_block_count = generation_shape.horizontal_cell_block_count();
let builder_options = ChunkNoiseFunctionBuilderOptions::new(
horizontal_cell_block_count as usize,
vertical_cell_block_count as usize,
vertical_cell_count,
horizontal_cell_count,
biome_coords::from_block(start_block_x),
biome_coords::from_block(start_block_z),
horizontal_biome_end,
);
let aquifer_sampler = if aquifers {
let section_x = section_coords::block_to_section(start_block_x);
let section_z = section_coords::block_to_section(start_block_z);
AquiferSampler::Aquifer(WorldAquiferSampler::new(
section_x,
section_z,
&random_config.aquifer_random_deriver,
generation_shape.min_y,
generation_shape.height,
level_sampler,
))
} else {
AquiferSampler::SeaLevel(SeaLevelAquiferSampler::new(level_sampler))
};
let samplers: Box<[BlockStateSampler]> = if ore_veins {
let ore_sampler = OreVeinSampler::new(random_config.ore_random_deriver.clone());
Box::new([
BlockStateSampler::Aquifer(aquifer_sampler),
BlockStateSampler::Ore(ore_sampler),
])
} else {
Box::new([BlockStateSampler::Aquifer(aquifer_sampler)])
};
let state_sampler = ChainedBlockStateSampler::new(samplers);
let router = ChunkNoiseRouter::generate(noise_router_base, &builder_options);
Self {
state_sampler,
generation_shape,
start_cell_pos_x,
start_cell_pos_z,
vertical_cell_count,
minimum_cell_y,
cache_fill_unique_id: 0,
cache_result_unique_id: 0,
router,
}
}
#[inline]
pub fn sample_start_density(&mut self) {
self.cache_result_unique_id = 0;
self.sample_density(true, self.start_cell_pos_x);
}
#[inline]
pub fn sample_end_density(&mut self, cell_x: i32) {
self.sample_density(false, self.start_cell_pos_x + cell_x + 1);
}
fn sample_density(&mut self, start: bool, current_x: i32) {
let x = current_x * self.horizontal_cell_block_count() as i32;
for cell_z in 0..=(16 / self.horizontal_cell_block_count()) {
let current_cell_z_pos = self.start_cell_pos_z + cell_z as i32;
let z = current_cell_z_pos * self.horizontal_cell_block_count() as i32;
self.cache_fill_unique_id += 1;
let mapper = InterpolationIndexMapper {
x,
z,
minimum_cell_y: self.minimum_cell_y,
vertical_cell_block_count: self.vertical_cell_block_count() as i32,
};
let mut options = ChunkNoiseFunctionSampleOptions::new(
false,
SampleAction::CellCaches(WrapperData::new(
0,
0,
0,
self.horizontal_cell_block_count() as usize,
self.vertical_cell_block_count() as usize,
)),
self.cache_result_unique_id,
self.cache_fill_unique_id,
0,
);
self.fill_interpolator_buffers(start, cell_z as usize, &mapper, &mut options);
self.cache_result_unique_id = options.cache_result_unique_id;
}
self.cache_fill_unique_id += 1;
}
#[inline]
fn fill_interpolator_buffers(
&mut self,
start: bool,
cell_z: usize,
mapper: &impl IndexToNoisePos,
sample_options: &mut ChunkNoiseFunctionSampleOptions,
) {
self.router
.fill_interpolator_buffers(start, cell_z, mapper, sample_options);
}
#[inline]
pub fn interpolate_x(&mut self, delta: f64) {
self.router.interpolate_x(delta);
}
#[inline]
pub fn interpolate_y(&mut self, delta: f64) {
self.router.interpolate_y(delta);
}
#[inline]
pub fn interpolate_z(&mut self, delta: f64) {
self.cache_result_unique_id += 1;
self.router.interpolate_z(delta);
}
#[inline]
pub fn swap_buffers(&mut self) {
self.router.swap_buffers();
}
pub fn on_sampled_cell_corners(&mut self, cell_x: i32, cell_y: i32, cell_z: i32) {
self.router
.on_sampled_cell_corners(cell_y as usize, cell_z as usize);
self.cache_fill_unique_id += 1;
let start_x = (self.start_cell_pos_x + cell_x) * self.horizontal_cell_block_count() as i32;
let start_y = (cell_y + self.minimum_cell_y) * self.vertical_cell_block_count() as i32;
let start_z = (self.start_cell_pos_z + cell_z) * self.horizontal_cell_block_count() as i32;
let mapper = ChunkIndexMapper {
start_x,
start_y,
start_z,
horizontal_cell_block_count: self.horizontal_cell_block_count() as usize,
vertical_cell_block_count: self.vertical_cell_block_count() as usize,
};
let mut sample_options = ChunkNoiseFunctionSampleOptions::new(
true,
SampleAction::CellCaches(WrapperData::new(
0,
0,
0,
self.horizontal_cell_block_count() as usize,
self.vertical_cell_block_count() as usize,
)),
self.cache_result_unique_id,
self.cache_fill_unique_id,
0,
);
self.router.fill_cell_caches(&mapper, &mut sample_options);
self.cache_fill_unique_id += 1;
}
#[expect(clippy::too_many_arguments)]
pub fn sample_block_state(
&mut self,
start_x: i32,
start_y: i32,
start_z: i32,
cell_x: i32,
cell_y: i32,
cell_z: i32,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
//TODO: Fix this when Blender is added
let pos = UnblendedNoisePos::new(start_x + cell_x, start_y + cell_y, start_z + cell_z);
let options = ChunkNoiseFunctionSampleOptions::new(
false,
SampleAction::CellCaches(WrapperData::new(
cell_x as usize,
cell_y as usize,
cell_z as usize,
self.horizontal_cell_block_count() as usize,
self.vertical_cell_block_count() as usize,
)),
self.cache_result_unique_id,
self.cache_fill_unique_id,
0,
);
self.state_sampler
.sample(&mut self.router, &pos, &options, height_estimator)
}
#[inline]
pub fn horizontal_cell_block_count(&self) -> u8 {
self.generation_shape.horizontal_cell_block_count()
}
#[inline]
pub fn vertical_cell_block_count(&self) -> u8 {
self.generation_shape.vertical_cell_block_count()
}
/// Aka bottom_y
#[inline]
pub fn min_y(&self) -> i8 {
self.generation_shape.min_y
}
#[inline]
pub fn height(&self) -> u16 {
self.generation_shape.height
}
}
#[cfg(test)]
mod test {
// TODO: Add test to verify the height estimator has no interpolators or cell caches
}

View File

@ -4,10 +4,9 @@ use pumpkin_util::{
random::{RandomDeriver, RandomDeriverImpl, RandomImpl}, random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
}; };
use crate::generation::noise::router::chunk_noise_router::ChunkNoiseRouter; use crate::generation::noise::router::{
chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_noise_router::ChunkNoiseRouter,
use super::noise::router::{ density_function::NoisePos,
chunk_density_function::ChunkNoiseFunctionSampleOptions, density_function::NoisePos,
}; };
pub struct OreVeinSampler { pub struct OreVeinSampler {

View File

@ -62,23 +62,6 @@ impl SplinePoint {
} }
} }
/// Returns the smallest usize between min..max that does not match the predicate
fn binary_walk(min: usize, max: usize, pred: impl Fn(usize) -> bool) -> usize {
let mut i = max - min;
let mut min = min;
while i > 0 {
let j = i / 2;
let k = min + j;
if pred(k) {
i = j;
} else {
min = k + 1;
i -= j + 1;
}
}
min
}
pub enum Range { pub enum Range {
In(usize), In(usize),
Below, Below,
@ -166,16 +149,6 @@ impl Spline {
(min, max) (min, max)
} }
fn find_index_for_location(&self, loc: f32) -> Range {
let index_greater_than_x =
binary_walk(0, self.points.len(), |i| loc < self.points[i].location);
if index_greater_than_x == 0 {
Range::Below
} else {
Range::In(index_greater_than_x - 1)
}
}
fn sample( fn sample(
&self, &self,
pos: &impl NoisePos, pos: &impl NoisePos,
@ -188,50 +161,43 @@ impl Spline {
sample_options, sample_options,
) as f32; ) as f32;
match self.find_index_for_location(location) { let n = self.points.len();
Range::In(index) => { let index_greater_than_x = self.points.partition_point(|p| location >= p.location);
if index == self.points.len() - 1 {
let last_known_sample =
self.points[index]
.value
.sample(pos, component_stack, sample_options);
self.points[index].sample_outside_range(location, last_known_sample)
} else {
let lower_point = &self.points[index];
let upper_point = &self.points[index + 1];
let lower_value = if index_greater_than_x == 0 {
lower_point let point = &self.points[0];
let val = point.value.sample(pos, component_stack, sample_options);
return point.sample_outside_range(location, val);
}
if index_greater_than_x == n {
let point = &self.points[n - 1];
let val = point.value.sample(pos, component_stack, sample_options);
return point.sample_outside_range(location, val);
}
let lower_point = &self.points[index_greater_than_x - 1];
let upper_point = &self.points[index_greater_than_x];
let lower_value = lower_point
.value .value
.sample(pos, component_stack, sample_options); .sample(pos, component_stack, sample_options);
let upper_value = let upper_value = upper_point
upper_point
.value .value
.sample(pos, component_stack, sample_options); .sample(pos, component_stack, sample_options);
// Use linear interpolation (-ish cuz of derivatives) to derivate a point between two points let dist = upper_point.location - lower_point.location;
let x_scale = (location - lower_point.location) let x_scale = (location - lower_point.location) / dist;
/ (upper_point.location - lower_point.location);
let extrapolated_lower_value = lower_point.derivative
* (upper_point.location - lower_point.location)
- (upper_value - lower_value);
let extrapolated_upper_value = -upper_point.derivative
* (upper_point.location - lower_point.location)
+ (upper_value - lower_value);
(x_scale * (1f32 - x_scale)) let delta = upper_value - lower_value;
* lerp(x_scale, extrapolated_lower_value, extrapolated_upper_value) let extrapolated_lower = lower_point.derivative * dist - delta;
+ lerp(x_scale, lower_value, upper_value) let extrapolated_upper = -upper_point.derivative * dist + delta;
}
} let cubic_part =
Range::Below => { (x_scale * (1.0 - x_scale)) * lerp(x_scale, extrapolated_lower, extrapolated_upper);
let last_known_sample = let linear_part = lerp(x_scale, lower_value, upper_value);
self.points[0]
.value cubic_part + linear_part
.sample(pos, component_stack, sample_options);
self.points[0].sample_outside_range(location, last_known_sample)
}
}
} }
} }

View File

@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::pin::Pin; use std::pin::Pin;
use pumpkin_data::block_properties::is_air; use pumpkin_data::block_properties::is_air;
@ -16,10 +16,7 @@ use pumpkin_util::{
}; };
use super::{ use super::{
GlobalRandomConfig, GlobalRandomConfig, biome_coords,
aquifer_sampler::{FluidLevel, FluidLevelSamplerImpl},
biome_coords,
chunk_noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK},
feature::placed_features::PLACED_FEATURES, feature::placed_features::PLACED_FEATURES,
noise::router::{ noise::router::{
multi_noise_sampler::MultiNoiseSampler, proto_noise_router::DoublePerlinNoiseBuilder, multi_noise_sampler::MultiNoiseSampler, proto_noise_router::DoublePerlinNoiseBuilder,
@ -32,10 +29,13 @@ use super::{
}; };
use crate::chunk::{ChunkData, ChunkHeightmapType}; use crate::chunk::{ChunkData, ChunkHeightmapType};
use crate::chunk_system::StagedChunkEnum; use crate::chunk_system::StagedChunkEnum;
use crate::generation::aquifer_sampler::FluidLevelSampler;
use crate::generation::height_limit::HeightLimitView; use crate::generation::height_limit::HeightLimitView;
use crate::generation::noise::aquifer_sampler::{
FluidLevel, FluidLevelSampler, FluidLevelSamplerImpl,
};
use crate::generation::noise::perlin::DoublePerlinNoiseSampler; use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions; use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions;
use crate::generation::noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK};
use crate::generation::structure::placement::StructurePlacementCalculator; use crate::generation::structure::placement::StructurePlacementCalculator;
use crate::generation::structure::structures::StructureInstance; use crate::generation::structure::structures::StructureInstance;
use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry}; use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry};
@ -132,7 +132,7 @@ pub struct ProtoChunk {
biome_mixer_seed: i64, biome_mixer_seed: i64,
// These are local positions // These are local positions
flat_block_map: Box<[BlockStateId]>, flat_block_map: Box<[BlockStateId]>,
pub flat_biome_map: Box<[&'static Biome]>, pub flat_biome_map: Box<[u8]>,
/// HEIGHTMAPS /// HEIGHTMAPS
/// ///
/// Top block that is not air /// Top block that is not air
@ -186,7 +186,7 @@ impl ProtoChunk {
default_block, default_block,
flat_block_map: vec![0; CHUNK_AREA * height as usize].into_boxed_slice(), flat_block_map: vec![0; CHUNK_AREA * height as usize].into_boxed_slice(),
flat_biome_map: vec![ flat_biome_map: vec![
&Biome::PLAINS; Biome::PLAINS.id;
biome_coords::from_block(CHUNK_DIM as usize) biome_coords::from_block(CHUNK_DIM as usize)
* biome_coords::from_block(CHUNK_DIM as usize) * biome_coords::from_block(CHUNK_DIM as usize)
* biome_coords::from_block(height as usize) * biome_coords::from_block(height as usize)
@ -243,7 +243,6 @@ impl ProtoChunk {
for y in 0..4 { for y in 0..4 {
for z in 0..4 { for z in 0..4 {
let biome_id = section.biomes.get(x, y, z); let biome_id = section.biomes.get(x, y, z);
let biome = Biome::from_id(biome_id).unwrap();
let relative_y_block = section_base_y + (y as i32 * 4); let relative_y_block = section_base_y + (y as i32 * 4);
@ -255,7 +254,7 @@ impl ProtoChunk {
z as i32, z as i32,
); );
proto_chunk.flat_biome_map[index] = biome; proto_chunk.flat_biome_map[index] = biome_id;
} }
} }
} }
@ -481,6 +480,11 @@ impl ProtoChunk {
#[inline] #[inline]
pub fn get_biome(&self, x: i32, y: i32, z: i32) -> &'static Biome { pub fn get_biome(&self, x: i32, y: i32, z: i32) -> &'static Biome {
Biome::from_id(self.get_biome_id(x, y, z)).unwrap()
}
#[inline]
pub fn get_biome_id(&self, x: i32, y: i32, z: i32) -> u8 {
let index = self.local_biome_pos_to_biome_index( let index = self.local_biome_pos_to_biome_index(
x & biome_coords::from_block(15), x & biome_coords::from_block(15),
y - biome_coords::from_block(self.bottom_y() as i32), y - biome_coords::from_block(self.bottom_y() as i32),
@ -640,7 +644,7 @@ impl ProtoChunk {
z, z,
); );
self.flat_biome_map[index] = biome; self.flat_biome_map[index] = biome.id;
} }
} }
} }
@ -882,37 +886,86 @@ impl ProtoChunk {
block_registry: &dyn BlockRegistryExt, block_registry: &dyn BlockRegistryExt,
random_config: &GlobalRandomConfig, random_config: &GlobalRandomConfig,
) { ) {
// 1. Efficiently collect unique biomes from the flat map
let (center_x, center_z, min_y, height, biomes_in_chunk) = { let (center_x, center_z, min_y, height, biomes_in_chunk) = {
let chunk = cache.get_center_chunk(); let chunk = cache.get_center_chunk();
let mut biomes = HashSet::new(); let mut unique_biomes = Vec::with_capacity(4); // Usually few biomes per chunk
// bad for &biome_id in chunk.flat_biome_map.iter() {
for biome in chunk.flat_biome_map.clone() { if !unique_biomes.contains(&biome_id) {
biomes.insert(biome); unique_biomes.push(biome_id);
} }
}
(chunk.x, chunk.z, chunk.bottom_y(), chunk.height(), biomes) (
chunk.x,
chunk.z,
chunk.bottom_y() as i32,
chunk.height() as i32,
unique_biomes,
)
}; };
let block_pos = BlockPos::new( let start_block_x = chunk_pos::start_block_x(center_x);
section_coords::section_to_block(center_x), let start_block_z = chunk_pos::start_block_z(center_z);
section_coords::block_to_section(min_y) as i32, let origin_pos = BlockPos::new(start_block_x, min_y, start_block_z);
section_coords::section_to_block(center_z),
);
let population_seed = let population_seed =
Xoroshiro::get_population_seed(random_config.seed, block_pos.0.x, block_pos.0.z); Xoroshiro::get_population_seed(random_config.seed, start_block_x, start_block_z);
const MAX_STEPS: usize = 11; // Vanilla has 11 steps (0..10)
for step in 0..11 {
// --- STEP A: STRUCTURE PIECES ---
// Structures are handled first because features (like trees) need to avoid clipping into them
Self::generate_structure_step(cache, step, population_seed, random_config.seed as i64);
for step in 0..MAX_STEPS { // --- STEP B: PLACED FEATURES ---
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64); let mut features_to_run = Vec::new();
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed)); for biome_id in &biomes_in_chunk {
if let Some(biome) = Biome::from_id(*biome_id)
&& let Some(features_at_step) = biome.features.get(step)
{
for &feature_id in *features_at_step {
features_to_run
.push(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
}
}
}
// CRITICAL: Determinism. Sort features so the index 'p' always refers to the same feature.
features_to_run.sort_unstable();
features_to_run.dedup();
for (p, feature_id) in features_to_run.into_iter().enumerate() {
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
let mut random =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
feature.generate(
cache,
block_registry,
min_y as i8,
height as u16,
feature_id,
&mut random,
origin_pos,
);
}
}
}
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
}
fn generate_structure_step<T: GenerationCache>(
cache: &mut T,
step: usize,
population_seed: u64,
world_seed: i64,
) {
let mut tasks = Vec::new(); let mut tasks = Vec::new();
{ {
let center_chunk = cache.get_center_chunk(); let center_chunk = cache.get_center_chunk();
for (id, instance) in &center_chunk.structure_starts { for (id, instance) in &center_chunk.structure_starts {
// Check if this structure belongs to the current step
if let Some(s) = STRUCTURES.get(id) if let Some(s) = STRUCTURES.get(id)
&& s.step.ordinal() != step && s.step.ordinal() != step
{ {
@ -920,13 +973,10 @@ impl ProtoChunk {
} }
match instance { match instance {
StructureInstance::Start(pos) => { StructureInstance::Start(pos) => tasks.push(pos.collector.clone()),
tasks.push(pos.collector.clone()); StructureInstance::Reference(origin_block_pos) => {
} let origin_chunk_x = origin_block_pos.0.x >> 4;
StructureInstance::Reference(origin_pos) => { let origin_chunk_z = origin_block_pos.0.z >> 4;
let origin_chunk_x = origin_pos.0.x >> 4;
let origin_chunk_z = origin_pos.0.z >> 4;
if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z) if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z)
&& let Some(StructureInstance::Start(pos)) = && let Some(StructureInstance::Start(pos)) =
neighbor.structure_starts.get(id) neighbor.structure_starts.get(id)
@ -938,44 +988,14 @@ impl ProtoChunk {
} }
} }
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
let chunk = cache.get_center_chunk_mut(); let chunk = cache.get_center_chunk_mut();
for collector_arc in tasks { for collector_arc in tasks {
let mut collector = collector_arc.lock().unwrap(); let mut collector = collector_arc.lock().unwrap();
collector.generate_in_chunk(chunk, &mut random, random_config.seed as i64); collector.generate_in_chunk(chunk, &mut random, world_seed);
} }
let mut features_to_run = HashSet::new();
for biome in &biomes_in_chunk {
if let Some(features_at_step) = biome.features.get(step) {
for feature_id in *features_at_step {
features_to_run
.insert(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
}
}
}
// let mut sorted_features: Vec<_> = features_to_run.into_iter().collect();
// sorted_features.sort_by_key(|f| f.registry_index());
for (p, feature_id) in features_to_run.into_iter().enumerate() {
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
let mut random =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
feature.generate(
cache,
block_registry,
min_y,
height,
feature_id,
&mut random,
block_pos,
);
}
}
}
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
} }
pub fn set_structure_starts( pub fn set_structure_starts(
@ -1054,34 +1074,36 @@ impl ProtoChunk {
} }
pub fn set_structure_references<T: GenerationCache>(cache: &mut T) { pub fn set_structure_references<T: GenerationCache>(cache: &mut T) {
let (center_x, center_z) = { let (center_x, center_z, start_x, start_z) = {
let chunk = cache.get_center_chunk(); let chunk = cache.get_center_chunk();
(chunk.x, chunk.z) (
chunk.x,
chunk.z,
chunk_pos::start_block_x(chunk.x),
chunk_pos::start_block_z(chunk.z),
)
}; };
let start_block_x = chunk_pos::start_block_x(center_x); let end_x = start_x + 15;
let start_block_z = chunk_pos::start_block_z(center_z); let end_z = start_z + 15;
let end_block_x = start_block_x + 15;
let end_block_z = start_block_z + 15;
let radius = 8; let radius = 8;
let mut references_to_add = Vec::new();
let mut references = Vec::new();
for x in (center_x - radius)..=(center_x + radius) { for x in (center_x - radius)..=(center_x + radius) {
for z in (center_z - radius)..=(center_z + radius) { for z in (center_z - radius)..=(center_z + radius) {
if let Some(neighbor_chunk) = cache.get_chunk(x, z) { if x == center_x && z == center_z {
for (structure_key, instance) in &neighbor_chunk.structure_starts { continue;
if let StructureInstance::Start(start_data) = instance {
let bbox = start_data.get_bounding_box();
if bbox.intersects_raw_xz(
start_block_x,
start_block_z,
end_block_x,
end_block_z,
) {
references_to_add
.push((structure_key.clone(), start_data.start_pos));
} }
if let Some(neighbor) = cache.get_chunk(x, z) {
for (key, instance) in &neighbor.structure_starts {
if let StructureInstance::Start(start_data) = instance
&& start_data
.get_bounding_box()
.intersects_raw_xz(start_x, start_z, end_x, end_z)
{
references.push((key.clone(), start_data.start_pos));
} }
} }
} }
@ -1089,11 +1111,11 @@ impl ProtoChunk {
} }
let center_chunk = cache.get_center_chunk_mut(); let center_chunk = cache.get_center_chunk_mut();
for (key, pos) in references_to_add { for (key, pos) in references {
center_chunk center_chunk
.structure_starts .structure_starts
.entry(key) .entry(key)
.or_insert(StructureInstance::Reference(pos)); .or_insert_with(|| StructureInstance::Reference(pos));
} }
center_chunk.stage = StagedChunkEnum::StructureReferences; center_chunk.stage = StagedChunkEnum::StructureReferences;

View File

@ -142,30 +142,31 @@ impl ConcentricRingsStructurePlacement {
pub fn is_start_chunk( pub fn is_start_chunk(
&self, &self,
_calculator: &StructurePlacementCalculator, _calculator: &StructurePlacementCalculator,
chunk_x: i32, _chunk_x: i32,
chunk_z: i32, _chunk_z: i32,
salt: u32, _salt: u32,
) -> bool { ) -> bool {
// TODO: this is entirely wrong, but the original logic is too complex for now // TODO: this is entirely wrong, but the original logic is too complex for now
let x = (chunk_x << 4) as f64; // let x = (chunk_x << 4) as f64;
let z = (chunk_z << 4) as f64; // let z = (chunk_z << 4) as f64;
let distance_sq = x * x + z * z; // let distance_sq = x * x + z * z;
let distance = distance_sq.sqrt(); // let distance = distance_sq.sqrt();
let ring_width = 1000.0; // let ring_width = 1000.0;
let ring_gap = 2000.0; // let ring_gap = 2000.0;
let in_ring = (distance % (ring_width + ring_gap)) < ring_width; // let in_ring = (distance % (ring_width + ring_gap)) < ring_width;
if !in_ring { // if !in_ring {
return false; // return false;
} // }
let seed = (chunk_x as i64).wrapping_mul(341873128712) // let seed = (chunk_x as i64).wrapping_mul(341873128712)
^ (chunk_z as i64).wrapping_mul(132897987541) // ^ (chunk_z as i64).wrapping_mul(132897987541)
^ (salt as i64); // ^ (salt as i64);
(seed.abs() % 400) == 0 // (seed.abs() % 400) == 0
rand::random_bool(1.0 / 100.0)
} }
} }

View File

@ -401,6 +401,7 @@ impl StructurePiecesCollector {
pub fn add_piece(&mut self, piece: Box<dyn StructurePieceBase>) { pub fn add_piece(&mut self, piece: Box<dyn StructurePieceBase>) {
self.pieces.push(piece); self.pieces.push(piece);
self.cached_box = None;
} }
#[expect(clippy::borrowed_box)] #[expect(clippy::borrowed_box)]

View File

@ -12,7 +12,7 @@ use serde::Deserialize;
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
generation::{ generation::{
positions::chunk_pos::{get_offset_x, get_offset_z}, section_coords,
structure::{ structure::{
piece::StructurePieceType, piece::StructurePieceType,
structures::{ structures::{
@ -57,33 +57,32 @@ impl StructureGenerator for StrongholdGenerator {
&self, &self,
context: StructureGeneratorContext, context: StructureGeneratorContext,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let mut _i = 0; let mut retry_count: i32 = 0;
// The collector must be shared/mutable during generation
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
let mut random = context.random; let mut random = context.random;
// Loop until we have a valid stronghold (Vanilla loop logic)
loop { loop {
collector.clear(); collector.clear();
_i += 1;
let start_x = get_offset_x(context.chunk_x, 2); // let attempt_seed = get_carver_seed(
let start_z = get_offset_z(context.chunk_z, 2); // &mut context.random,
// context.seed as u64,
// context.chunk_x,
// context.chunk_z,
// );
let start_x = section_coords::section_to_block(context.chunk_x) + 2;
let start_z = section_coords::section_to_block(context.chunk_z) + 2;
// Create the Start Piece (SpiralStaircase)
// Note: new_start must return a concrete struct that implements StructurePieceBase
let start_piece = SpiralStaircasePiece::new_start(&mut random, start_x, start_z); let start_piece = SpiralStaircasePiece::new_start(&mut random, start_x, start_z);
// We need to Box the start piece to treat it uniformly in the list
let start_box: Box<dyn StructurePieceBase> = Box::new(start_piece.clone()); let start_box: Box<dyn StructurePieceBase> = Box::new(start_piece.clone());
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = // In Vanilla, pieces_to_process is 'start.pieces'
vec![start_box.clone_box()]; let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = Vec::new();
collector.add_piece(start_box); collector.add_piece(start_box);
// 1. Initial Fill // Initial Fill
// We cast strictly to SpiralStaircasePiece here because the start piece is special
start_piece.fill_openings( start_piece.fill_openings(
&start_piece.piece.piece, &start_piece.piece.piece,
&mut random, &mut random,
@ -91,12 +90,10 @@ impl StructureGenerator for StrongholdGenerator {
&mut pieces_to_process, &mut pieces_to_process,
); );
// 2. The Growth Loop // Growth Loop
while !pieces_to_process.is_empty() { while !pieces_to_process.is_empty() {
// Pick a random piece from the "to process" list
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize; let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
let piece = pieces_to_process.remove(idx); let piece = pieces_to_process.remove(idx);
piece.fill_openings( piece.fill_openings(
&start_piece.piece.piece, &start_piece.piece.piece,
&mut random, &mut random,
@ -105,19 +102,29 @@ impl StructureGenerator for StrongholdGenerator {
); );
} }
// Shift height
collector.shift_into(context.sea_level, context.min_y, &mut random, 10); collector.shift_into(context.sea_level, context.min_y, &mut random, 10);
let has_portal_room = collector.pieces.iter().any(|p| { let has_portal_room = collector.pieces.iter().any(|p| {
p.get_structure_piece().r#type == StructurePieceType::StrongholdPortalRoom p.get_structure_piece().r#type == StructurePieceType::StrongholdPortalRoom
}); });
if !collector.is_empty() && has_portal_room || collector.pieces.len() >= 100 { if !collector.is_empty() && has_portal_room {
break; break;
} }
retry_count += 1;
if retry_count > 1000 {
return None;
}
} }
Some(StructurePosition { Some(StructurePosition {
start_pos: BlockPos::new(context.chunk_x, 0, context.chunk_z), start_pos: BlockPos::new(
section_coords::section_to_block(context.chunk_x),
0,
section_coords::section_to_block(context.chunk_z),
),
collector: Arc::new(Mutex::new(collector)), collector: Arc::new(Mutex::new(collector)),
}) })
} }
@ -572,20 +579,20 @@ impl StrongholdPiece {
chain_length: u32, chain_length: u32,
piece_type: Option<StrongholdPieceType>, piece_type: Option<StrongholdPieceType>,
) -> Option<Box<dyn StructurePieceBase>> { ) -> Option<Box<dyn StructurePieceBase>> {
// 1. Hard limits // Vanilla limit is 50
if chain_length > 50 { if chain_length > 50 {
return None; return None;
} }
// Get bounds from the underlying StructurePiece of the start wrapper // Distance check from start (112 blocks)
let start_box = start.bounding_box; let start_box = start.bounding_box;
if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 { if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 {
return None; return None;
} }
if let Some(piece_type) = piece_type { let next_piece = if let Some(p_type) = piece_type {
let piece = Self::create_piece( Self::create_piece(
&piece_type, &p_type,
collector, collector,
random, random,
x, x,
@ -593,22 +600,17 @@ impl StrongholdPiece {
z, z,
orientation, orientation,
chain_length, chain_length,
); )
if let Some(piece) = piece { } else {
collector.add_piece(piece.clone_box()); Self::pick_piece(collector, random, x, y, z, orientation, chain_length)
return Some(piece); };
}
}
// 2. Pick the piece if let Some(p) = next_piece {
let picked_piece =
Self::pick_piece(collector, random, x, y, z, orientation, chain_length + 1);
if let Some(ref p) = picked_piece {
collector.add_piece(p.clone_box()); collector.add_piece(p.clone_box());
return Some(p);
} }
picked_piece None
} }
fn pick_piece( fn pick_piece(

View File

@ -129,7 +129,11 @@ impl StructurePieceBase for SpiralStaircasePiece {
1, 1,
1, 1,
pieces_to_process, pieces_to_process,
Some(StrongholdPieceType::FiveWayCrossing), if self.is_structure_start {
Some(StrongholdPieceType::FiveWayCrossing)
} else {
None
},
); );
} }

View File

@ -7,9 +7,9 @@ use pumpkin_util::{
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
block::RawBlockState, block::RawBlockState,
generation::{ generation::noise::{
chunk_noise::WATER_BLOCK, noise::perlin::DoublePerlinNoiseSampler, WATER_BLOCK, perlin::DoublePerlinNoiseSampler,
noise::router::proto_noise_router::DoublePerlinNoiseBuilder, router::proto_noise_router::DoublePerlinNoiseBuilder,
}, },
}; };
@ -152,12 +152,12 @@ impl SurfaceTerrainBuilder {
if surface_y <= elevation_y { if surface_y <= elevation_y {
for y in (chunk.bottom_y() as i32..=elevation_y).rev() { for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
let pos = Vector3::new(global_x, y, global_z); let pos = Vector3::new(global_x, y, global_z);
let block_state = chunk.get_block_state(&pos).to_block_id(); let block_id = chunk.get_block_state(&pos).to_block_id();
if block_state == Block::get_raw_id_from_state_id(chunk.default_block.id) { if block_id == Block::get_raw_id_from_state_id(chunk.default_block.id) {
break; break;
} }
if block_state == WATER_BLOCK { if block_id == Block::WATER {
return; return;
} }
} }

View File

@ -24,6 +24,7 @@ use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use pumpkin_util::world_seed::Seed; use pumpkin_util::world_seed::Seed;
use rand::{Rng, SeedableRng, rngs::SmallRng}; use rand::{Rng, SeedableRng, rngs::SmallRng};
use std::sync::Mutex; use std::sync::Mutex;
use tokio_util::sync::CancellationToken;
// use std::time::Duration; // use std::time::Duration;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -38,7 +39,7 @@ use std::{
use tokio::{ use tokio::{
select, select,
sync::{ sync::{
Notify, RwLock, RwLock,
mpsc::{self, UnboundedReceiver}, mpsc::{self, UnboundedReceiver},
oneshot, oneshot,
}, },
@ -83,8 +84,7 @@ pub struct Level {
tasks: TaskTracker, tasks: TaskTracker,
pub chunk_system_tasks: TaskTracker, pub chunk_system_tasks: TaskTracker,
/// Notification that interrupts tasks for shutdown /// Notification that interrupts tasks for shutdown
pub shutdown_notifier: Notify, pub cancel_token: CancellationToken,
pub is_shutting_down: AtomicBool,
pub shut_down_chunk_system: AtomicBool, pub shut_down_chunk_system: AtomicBool,
pub should_save: AtomicBool, pub should_save: AtomicBool,
@ -185,8 +185,7 @@ impl Level {
chunk_watchers: Arc::new(DashMap::new()), chunk_watchers: Arc::new(DashMap::new()),
tasks: TaskTracker::new(), tasks: TaskTracker::new(),
chunk_system_tasks: TaskTracker::new(), chunk_system_tasks: TaskTracker::new(),
shutdown_notifier: Notify::new(), cancel_token: CancellationToken::new(),
is_shutting_down: AtomicBool::new(false),
shut_down_chunk_system: AtomicBool::new(false), shut_down_chunk_system: AtomicBool::new(false),
should_save: AtomicBool::new(false), should_save: AtomicBool::new(false),
should_unload: AtomicBool::new(false), should_unload: AtomicBool::new(false),
@ -219,7 +218,7 @@ impl Level {
builder builder
.spawn(move || { .spawn(move || {
while let Ok(pos) = rx.recv() { while let Ok(pos) = rx.recv() {
if level_clone.is_shutting_down.load(Ordering::Relaxed) { if level_clone.cancel_token.is_cancelled() {
break; break;
} }
@ -259,58 +258,34 @@ impl Level {
pub async fn shutdown(&self) { pub async fn shutdown(&self) {
log::info!("Saving level..."); log::info!("Saving level...");
self.cancel_token.cancel();
self.is_shutting_down.store(true, Ordering::Relaxed);
self.shutdown_notifier.notify_waiters();
self.tasks.close();
log::debug!("Awaiting level tasks");
#[cfg(feature = "tokio_taskdump")]
match tokio::time::timeout(std::time::Duration::from_secs(30), self.tasks.wait()).await {
Ok(guard) => guard,
Err(_) => {
dump().await;
panic!("Timeout Awaiting level tasks");
}
};
self.tasks.wait().await;
log::debug!("Done awaiting level chunk tasks");
self.shut_down_chunk_system.store(true, Ordering::Relaxed); self.shut_down_chunk_system.store(true, Ordering::Relaxed);
self.level_channel.notify(); self.level_channel.notify();
let handles: Vec<_> = { self.tasks.close();
let mut lock = self.thread_tracker.lock().unwrap();
log::info!("Shutting down {} jobs", lock.len());
lock.drain(..).collect()
};
for handle in handles {
log::debug!(
"Waiting for thread {:?} ({}) to stop",
handle.thread().id(),
handle.thread().name().unwrap_or("unknown")
);
if let Err(e) = handle.join() {
log::error!("Thread panicked during execution: {:?}", e);
}
}
log::info!("Wait chunk system tasks stop");
self.chunk_system_tasks.close(); self.chunk_system_tasks.close();
#[cfg(feature = "tokio_taskdump")]
match tokio::time::timeout(std::time::Duration::from_secs(30), self.tasks.wait()).await { let handles = {
Ok(guard) => guard, let mut lock = self.thread_tracker.lock().unwrap();
Err(_) => { lock.drain(..).collect::<Vec<_>>()
dump().await;
panic!("Timeout Awaiting chunk_system_tasks tasks");
}
}; };
log::info!("Joining {} synchronous threads...", handles.len());
tokio::task::spawn_blocking(move || {
for handle in handles {
let _ = handle.join();
}
})
.await
.unwrap();
log::debug!("Awaiting remaining async tasks...");
self.tasks.wait().await;
self.chunk_system_tasks.wait().await; self.chunk_system_tasks.wait().await;
// wait for chunks currently saving in other
log::info!("Wait chunk saver to stop"); log::info!("Flushing savers to disk...");
self.chunk_saver.block_and_await_ongoing_tasks().await; self.chunk_saver.block_and_await_ongoing_tasks().await;
self.entity_saver.block_and_await_ongoing_tasks().await;
// save all chunks currently in memory // save all chunks currently in memory
// let chunks_to_write = self // let chunks_to_write = self
@ -324,11 +299,6 @@ impl Level {
// self.chunk_saver.clear_watched_chunks().await; // self.chunk_saver.clear_watched_chunks().await;
// self.write_chunks(chunks_to_write).await; // self.write_chunks(chunks_to_write).await;
log::debug!("Done awaiting level entity tasks");
// wait for chunks currently saving in other threads
self.entity_saver.block_and_await_ongoing_tasks().await;
// save all chunks currently in memory // save all chunks currently in memory
let chunks_to_write = self let chunks_to_write = self
.loaded_entity_chunks .loaded_entity_chunks
@ -622,7 +592,7 @@ impl Level {
let level = self.clone(); let level = self.clone();
self.spawn_task(async move { self.spawn_task(async move {
let cancel_notifier = level.shutdown_notifier.notified(); let cancel_notifier = level.cancel_token.cancelled();
let fetch_task = async { let fetch_task = async {
let mut to_fetch = Vec::new(); let mut to_fetch = Vec::new();

View File

@ -45,7 +45,16 @@ pub use generation::{
proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting, proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting,
}; };
use crate::generation::{chunk_noise::CHUNK_DIM, proto_chunk::TerrainCache}; use crate::generation::{
biome_coords,
noise::{
CHUNK_DIM, ChunkNoiseGenerator,
aquifer_sampler::{FluidLevel, FluidLevelSampler},
},
positions::chunk_pos,
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,
@ -54,16 +63,10 @@ pub fn bench_create_and_populate_noise(
default_block: &'static BlockState, default_block: &'static BlockState,
) { ) {
use crate::biome::hash_seed; use crate::biome::hash_seed;
use crate::generation::chunk_noise::ChunkNoiseGenerator;
use crate::generation::noise::router::surface_height_sampler::{ use crate::generation::noise::router::surface_height_sampler::{
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
}; };
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler; use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
use crate::generation::{
aquifer_sampler::{FluidLevel, FluidLevelSampler},
biome_coords,
positions::chunk_pos,
};
let biome_mixer_seed = hash_seed(random_config.seed); let biome_mixer_seed = hash_seed(random_config.seed);
let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed); let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed);
@ -160,7 +163,6 @@ pub fn bench_create_and_populate_noise_with_surface(
default_block: &'static BlockState, default_block: &'static BlockState,
) { ) {
use crate::biome::hash_seed; use crate::biome::hash_seed;
use crate::generation::chunk_noise::ChunkNoiseGenerator;
use crate::generation::noise::router::{ use crate::generation::noise::router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
surface_height_sampler::{ surface_height_sampler::{
@ -168,11 +170,6 @@ pub fn bench_create_and_populate_noise_with_surface(
}, },
}; };
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler; use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
use crate::generation::{
aquifer_sampler::{FluidLevel, FluidLevelSampler},
biome_coords,
positions::chunk_pos,
};
let biome_mixer_seed = hash_seed(random_config.seed); let biome_mixer_seed = hash_seed(random_config.seed);
let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed); let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed);

View File

@ -253,6 +253,11 @@ impl LevelData {
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION, level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
} }
} }
pub fn set_pos(&mut self, x: i32, z: i32) {
self.spawn_x = x;
self.spawn_z = z;
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]

View File

@ -47,11 +47,11 @@ use pumpkin_protocol::{
use std::net::SocketAddr; use std::net::SocketAddr;
use tokio::{ use tokio::{
net::UdpSocket, net::UdpSocket,
sync::Mutex,
sync::mpsc::{Receiver, Sender}, sync::mpsc::{Receiver, Sender},
sync::{Mutex, Notify},
task::JoinHandle, task::JoinHandle,
}; };
use tokio_util::task::TaskTracker; use tokio_util::{sync::CancellationToken, task::TaskTracker};
pub mod connection; pub mod connection;
pub mod login; pub mod login;
@ -84,13 +84,8 @@ pub struct BedrockClient {
output_split_number: AtomicU16, output_split_number: AtomicU16,
output_sequenced_index: AtomicU32, output_sequenced_index: AtomicU32,
output_ordered_index: AtomicU32, output_ordered_index: AtomicU32,
/// An notifier that is triggered when this client is closed. /// An notifier that is triggered when this client is closed.
close_interrupt: Arc<Notify>, close_token: CancellationToken,
/// Indicates if the client connection is closed.
pub closed: Arc<AtomicBool>,
/// Store Fragments until the packet is complete /// Store Fragments until the packet is complete
compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>, compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>,
//input_sequence_number: AtomicU32, //input_sequence_number: AtomicU32,
@ -121,23 +116,21 @@ impl BedrockClient {
output_sequenced_index: AtomicU32::new(0), output_sequenced_index: AtomicU32::new(0),
output_ordered_index: AtomicU32::new(0), output_ordered_index: AtomicU32::new(0),
compounds: Arc::new(Mutex::new(HashMap::new())), compounds: Arc::new(Mutex::new(HashMap::new())),
closed: Arc::new(AtomicBool::new(false)), close_token: CancellationToken::new(),
close_interrupt: Arc::new(Notify::new()),
//input_sequence_number: AtomicU32::new(0), //input_sequence_number: AtomicU32::new(0),
} }
} }
pub fn start_outgoing_packet_task(&mut self) { pub fn start_outgoing_packet_task(&mut self) {
let mut packet_receiver = self.outgoing_packet_queue_recv.take().unwrap(); let mut packet_receiver = self.outgoing_packet_queue_recv.take().unwrap();
let close_interrupt = self.close_interrupt.clone(); let close_token = self.close_token.clone();
let closed = self.closed.clone();
let writer = self.network_writer.clone(); let writer = self.network_writer.clone();
let addr = self.address; let addr = self.address;
let socket = self.socket.clone(); let socket = self.socket.clone();
self.spawn_task(async move { self.spawn_task(async move {
while !closed.load(Ordering::Relaxed) { while !close_token.is_cancelled() {
let recv_result = tokio::select! { let recv_result = tokio::select! {
() = close_interrupt.notified() => { () = close_token.cancelled() => {
None None
}, },
recv_result = packet_receiver.recv() => { recv_result = packet_receiver.recv() => {
@ -156,12 +149,11 @@ impl BedrockClient {
.await .await
{ {
// It is expected that the packet will fail if we are closed // It is expected that the packet will fail if we are closed
if !closed.load(Ordering::Relaxed) { if !close_token.is_cancelled() {
log::warn!("Failed to send packet to client: {err}",); log::warn!("Failed to send packet to client: {err}",);
// We now need to close the connection to the client since the stream is in an // We now need to close the connection to the client since the stream is in an
// unknown state // unknown state
close_interrupt.notify_waiters(); close_token.cancel();
closed.store(true, Ordering::Relaxed);
break; break;
} }
} }
@ -208,7 +200,7 @@ impl BedrockClient {
pub async fn enqueue_packet_data(&self, packet_data: Bytes) { pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await { if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
// This is expected to fail if we are closed // This is expected to fail if we are closed
if !self.closed.load(Ordering::Relaxed) { if !self.is_closed() {
log::error!("Failed to add packet to the outgoing packet queue for client: {err}"); log::error!("Failed to add packet to the outgoing packet queue for client: {err}");
} }
} }
@ -362,18 +354,17 @@ impl BedrockClient {
.await .await
{ {
// It is expected that the packet will fail if we are closed // It is expected that the packet will fail if we are closed
if !self.closed.load(Ordering::Relaxed) { if !self.is_closed() {
log::warn!("Failed to send packet to client: {err}"); log::warn!("Failed to send packet to client: {err}");
// We now need to close the connection to the client since the stream is in an // We now need to close the connection to the client since the stream is in an
// unknown state // unknown state
self.closed.store(true, Ordering::Relaxed); self.close_token.cancel();
} }
} }
} }
pub async fn close(&self) { pub async fn close(&self) {
self.close_interrupt.notify_waiters(); self.close_token.cancel();
self.closed.store(true, Ordering::Relaxed);
self.tasks.close(); self.tasks.close();
self.tasks.wait().await; self.tasks.wait().await;
self.be_clients.lock().await.remove(&self.address); self.be_clients.lock().await.remove(&self.address);
@ -383,6 +374,10 @@ impl BedrockClient {
} }
} }
pub fn is_closed(&self) -> bool {
self.close_token.is_cancelled()
}
pub async fn send_ack(&self, ack: &Ack) { pub async fn send_ack(&self, ack: &Ack) {
let mut packet_buf = Vec::new(); let mut packet_buf = Vec::new();
ack.write(&mut packet_buf).unwrap(); ack.write(&mut packet_buf).unwrap();
@ -640,7 +635,7 @@ impl BedrockClient {
} }
pub async fn await_close_interrupt(&self) { pub async fn await_close_interrupt(&self) {
self.close_interrupt.notified().await; self.close_token.cancelled().await;
} }
pub async fn get_packet_payload(&self, packet: Cursor<Vec<u8>>) -> Option<Bytes> { pub async fn get_packet_payload(&self, packet: Cursor<Vec<u8>>) -> Option<Bytes> {
@ -671,7 +666,7 @@ impl BedrockClient {
F: Future + Send + 'static, F: Future + Send + 'static,
F::Output: Send + 'static, F::Output: Send + 'static,
{ {
if self.closed.load(Ordering::Relaxed) { if self.close_token.is_cancelled() {
None None
} else { } else {
Some(self.tasks.spawn(task)) Some(self.tasks.spawn(task))

View File

@ -1,5 +1,4 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{io::Write, sync::Arc}; use std::{io::Write, sync::Arc};
use bytes::Bytes; use bytes::Bytes;
@ -72,8 +71,6 @@ pub struct JavaClient {
pub server_address: Mutex<String>, pub server_address: Mutex<String>,
/// The current connection state of the client (e.g., Handshaking, Status, Play). /// The current connection state of the client (e.g., Handshaking, Status, Play).
pub connection_state: AtomicCell<ConnectionState>, pub connection_state: AtomicCell<ConnectionState>,
/// Indicates if the client connection is closed.
pub closed: Arc<AtomicBool>,
/// The client's IP address. /// The client's IP address.
pub address: Mutex<SocketAddr>, pub address: Mutex<SocketAddr>,
/// The client's brand or modpack information, Optional. /// The client's brand or modpack information, Optional.
@ -105,7 +102,6 @@ impl JavaClient {
server_address: Mutex::new(String::new()), server_address: Mutex::new(String::new()),
address: Mutex::new(address), address: Mutex::new(address),
connection_state: AtomicCell::new(ConnectionState::HandShake), connection_state: AtomicCell::new(ConnectionState::HandShake),
closed: Arc::new(AtomicBool::new(false)),
close_token: CancellationToken::new(), close_token: CancellationToken::new(),
tasks: TaskTracker::new(), tasks: TaskTracker::new(),
outgoing_packet_queue_send: send, outgoing_packet_queue_send: send,
@ -187,7 +183,7 @@ impl JavaClient {
F: Future + Send + 'static, F: Future + Send + 'static,
F::Output: Send + 'static, F::Output: Send + 'static,
{ {
if self.closed.load(Ordering::Relaxed) { if self.close_token.is_cancelled() {
None None
} else { } else {
Some(self.tasks.spawn(task)) Some(self.tasks.spawn(task))
@ -210,7 +206,7 @@ impl JavaClient {
pub async fn enqueue_packet_data(&self, packet_data: Bytes) { pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await { if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
// This is expected to fail if we are closed // This is expected to fail if we are closed
if !self.closed.load(Ordering::Relaxed) { if !self.close_token.is_cancelled() {
log::error!( log::error!(
"Failed to add packet to the outgoing packet queue for client {}: {}", "Failed to add packet to the outgoing packet queue for client {}: {}",
self.id, self.id,
@ -283,7 +279,7 @@ impl JavaClient {
.await .await
{ {
// It is expected that the packet will fail if we are closed // It is expected that the packet will fail if we are closed
if !self.closed.load(Ordering::Relaxed) { if !self.close_token.is_cancelled() {
log::warn!("Failed to send packet to client {}: {}", self.id, err); log::warn!("Failed to send packet to client {}: {}", self.id, err);
// We now need to close the connection to the client since the stream is in an // We now need to close the connection to the client since the stream is in an
// unknown state // unknown state
@ -406,14 +402,13 @@ impl JavaClient {
.outgoing_packet_queue_recv .outgoing_packet_queue_recv
.take() .take()
.expect("This was set in the new fn"); .expect("This was set in the new fn");
let close_interrupt = self.close_token.clone(); let close_token = self.close_token.clone();
let closed = self.closed.clone();
let writer = self.network_writer.clone(); let writer = self.network_writer.clone();
let id = self.id; let id = self.id;
self.spawn_task(async move { self.spawn_task(async move {
while !closed.load(Ordering::Relaxed) { while !close_token.is_cancelled() {
let recv_result = tokio::select! { let recv_result = tokio::select! {
() = close_interrupt.cancelled() => None, () = close_token.cancelled() => None,
res = packet_receiver.recv() => res, res = packet_receiver.recv() => res,
}; };
@ -423,12 +418,11 @@ impl JavaClient {
if let Err(err) = writer.lock().await.write_packet(packet_data).await { if let Err(err) = writer.lock().await.write_packet(packet_data).await {
// It is expected that the packet will fail if we are closed // It is expected that the packet will fail if we are closed
if !closed.load(Ordering::Relaxed) { if !close_token.is_cancelled() {
log::warn!("Failed to send packet to client {id}: {err}",); log::warn!("Failed to send packet to client {id}: {err}",);
// We now need to close the connection to the client since the stream is in an // We now need to close the connection to the client since the stream is in an
// unknown state // unknown state
close_interrupt.cancel(); close_token.cancel();
closed.store(true, Ordering::Relaxed);
break; break;
} }
} }
@ -447,7 +441,10 @@ impl JavaClient {
/// This function does not attempt to send any disconnect packets to the client. /// This function does not attempt to send any disconnect packets to the client.
pub fn close(&self) { pub fn close(&self) {
self.close_token.cancel(); self.close_token.cancel();
self.closed.store(true, Ordering::Relaxed); }
pub fn is_closed(&self) -> bool {
self.close_token.is_cancelled()
} }
async fn handle_login_packet( async fn handle_login_packet(

View File

@ -129,8 +129,8 @@ impl ClientPlatform {
#[must_use] #[must_use]
pub fn closed(&self) -> bool { pub fn closed(&self) -> bool {
match self { match self {
Self::Java(java) => java.closed.load(Ordering::Relaxed), Self::Java(java) => java.is_closed(),
Self::Bedrock(bedrock) => bedrock.closed.load(Ordering::Relaxed), Self::Bedrock(bedrock) => bedrock.is_closed(),
} }
} }

View File

@ -25,9 +25,10 @@ pub async fn update_position(player: &Arc<Player>) {
let new_chunk_center = entity.chunk_pos.load(); let new_chunk_center = entity.chunk_pos.load();
let old_cylindrical = player.watched_section.load(); let old_cylindrical = player.watched_section.load();
if old_cylindrical.center == new_chunk_center { // This does break when a new player spawns
return; // if old_cylindrical.center == new_chunk_center {
} // return;
// }
let view_distance = get_view_distance(player).await; let view_distance = get_view_distance(player).await;
let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance); let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance);