diff --git a/pumpkin-data/build/biome.rs b/pumpkin-data/build/biome.rs index 7e1b93815..e04e9a9bf 100644 --- a/pumpkin-data/build/biome.rs +++ b/pumpkin-data/build/biome.rs @@ -411,7 +411,7 @@ pub(crate) fn build() -> TokenStream { } impl ParameterRange { - fn calc_distance(&self, noise: i64) -> i64 { + pub fn calc_distance(&self, noise: i64) -> i64 { if noise > self.max { noise - self.max } else if noise < self.min { @@ -436,70 +436,71 @@ pub(crate) fn build() -> TokenStream { impl BiomeTree { - pub fn get( + pub fn get( &'static self, point_list: &[i64; 7], previous_result_node: &mut Option<&'static BiomeTree>, ) -> &'static Biome { - let result_node = self.get_resulting_node(point_list, *previous_result_node); - match result_node { + // Initialize best distance from the previous result for spatial coherence + let mut best_dist = previous_result_node + .map(|node| node.get_squared_distance(point_list)) + .unwrap_or(i64::MAX); + + 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(result_node); + *previous_result_node = Some(best_node); biome } - _ => unreachable!(), + // Should not happen with valid tree data + _ => unreachable!("Biome search failed to find a leaf"), } } - fn get_resulting_node( + fn search( &'static self, - point_list: &[i64; 7], - previous_result_node: Option<&'static BiomeTree>, - ) -> &'static BiomeTree { + 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 { .. } => self, + Self::Leaf { .. } => { + *best_dist = dist; + *best_node = self; + } Self::Branch { nodes, .. } => { - let mut distance = previous_result_node - .map(|node| node.get_squared_distance(point_list)) - .unwrap_or(i64::MAX); - let mut best_node = previous_result_node; - for node in *nodes { - let node_distance = node.get_squared_distance(point_list); - 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); - } - } + node.search(point, best_dist, best_node); } - - best_node.expect("This should be populated after traversing the tree") } } } - fn get_squared_distance(&self, point_list: &[i64; 7]) -> i64 { - let parameters = match self { + #[inline(always)] + fn get_squared_distance(&self, p: &[i64; 7]) -> i64 { + let params = match self { Self::Leaf { parameters, .. } => parameters, Self::Branch { parameters, .. } => parameters, }; - parameters - .iter() - .zip(point_list) - .map(|(bound, value)| { - let distance = bound.calc_distance(*value); - distance * distance - }) - .sum() + // Fully unrolled for 7 dimensions to maximize throughput + params[0].calc_distance(p[0]) + + params[1].calc_distance(p[1]) + + params[2].calc_distance(p[2]) + + params[3].calc_distance(p[3]) + + params[4].calc_distance(p[4]) + + params[5].calc_distance(p[5]) + + params[6].calc_distance(p[6]) } } diff --git a/pumpkin-world/src/biome/mod.rs b/pumpkin-world/src/biome/mod.rs index 03942790d..5a093ae68 100644 --- a/pumpkin-world/src/biome/mod.rs +++ b/pumpkin-world/src/biome/mod.rs @@ -10,6 +10,7 @@ use pumpkin_data::{ use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler; pub mod end; pub mod multi_noise; +pub mod position_finder; thread_local! { /// A shortcut; check if last used biome is what we should use diff --git a/pumpkin-world/src/biome/position_finder.rs b/pumpkin-world/src/biome/position_finder.rs new file mode 100644 index 000000000..383fdb818 --- /dev/null +++ b/pumpkin-world/src/biome/position_finder.rs @@ -0,0 +1,77 @@ +use pumpkin_data::chunk::ParameterRange; +use pumpkin_util::math::vector2::Vector2; + +pub struct FittestPositionFinderResult { + pub location: Vector2, + 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 { + 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, + } + } +} diff --git a/pumpkin-world/src/chunk_system.rs b/pumpkin-world/src/chunk_system.rs index 8da3ad8ca..caa1b7d37 100644 --- a/pumpkin-world/src/chunk_system.rs +++ b/pumpkin-world/src/chunk_system.rs @@ -9,6 +9,7 @@ TODO use crate::block::RawBlockState; use crate::chunk::io::LoadedData::Loaded; use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk}; +use crate::generation::biome_coords; use pumpkin_data::block_properties::is_air; use pumpkin_data::dimension::Dimension; use std::default::Default; @@ -791,12 +792,12 @@ impl Chunk { dimension.min_y, ); - let proto_biome_height = (dimension.height / 4) as usize; - let biome_min_y = dimension.min_y / 4; + let proto_biome_height = biome_coords::from_block(proto_chunk.height()); + let biome_min_y = biome_coords::from_block(dimension.min_y); for y_offset in 0..proto_biome_height { - let section_index = y_offset / 4; - let relative_biome_y = y_offset % 4; + let section_index = y_offset as usize / 4; + let relative_y = y_offset as usize % 4; if let Some(section) = sections.sections.get_mut(section_index) { let absolute_biome_y = biome_min_y + y_offset as i32; @@ -804,7 +805,7 @@ impl Chunk { for z in 0..4 { for x in 0..4 { 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(); loop { - if level.should_unload.load(Relaxed) { - // log::debug!("unload chunk signal"); + if level.should_unload.swap(false, Relaxed) { self.unload_chunk(); - level.should_unload.store(false, Relaxed); } - if level.should_save.load(Relaxed) { - // log::debug!("save all chunk signal"); + if level.should_save.swap(false, Relaxed) { self.save_all_chunk(false); - level.should_save.store(false, Relaxed); } if level.shut_down_chunk_system.load(Relaxed) { // log::debug!("shut down signal"); diff --git a/pumpkin-world/src/generation/block_predicate.rs b/pumpkin-world/src/generation/block_predicate.rs index 777972109..c0e039f7e 100644 --- a/pumpkin-world/src/generation/block_predicate.rs +++ b/pumpkin-world/src/generation/block_predicate.rs @@ -7,9 +7,6 @@ use serde::Deserialize; use crate::generation::proto_chunk::GenerationCache; use crate::{block::BlockStateCodec, world::BlockRegistryExt}; -#[derive(Deserialize)] -pub struct EmptyTODOStruct {} - #[derive(Deserialize)] #[serde(tag = "type")] pub enum BlockPredicate { @@ -37,9 +34,9 @@ pub enum BlockPredicate { Not(NotBlockPredicate), #[serde(rename = "minecraft:true")] AlwaysTrue, - /// Not used - #[serde(rename = "minecraft:unobstructed")] - Unobstructed(EmptyTODOStruct), + // Not used + // #[serde(rename = "minecraft:unobstructed")] + // Unobstructed(EmptyTODOStruct), } impl BlockPredicate { @@ -62,7 +59,6 @@ impl BlockPredicate { BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos), BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos), BlockPredicate::AlwaysTrue => true, - BlockPredicate::Unobstructed(_predicate) => false, } } } diff --git a/pumpkin-world/src/generation/chunk_noise.rs b/pumpkin-world/src/generation/chunk_noise.rs deleted file mode 100644 index 7ba027544..000000000 --- a/pumpkin-world/src/generation/chunk_noise.rs +++ /dev/null @@ -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 -} diff --git a/pumpkin-world/src/generation/feature/features/desert_well.rs b/pumpkin-world/src/generation/feature/features/desert_well.rs index 220d38d81..f1449ef34 100644 --- a/pumpkin-world/src/generation/feature/features/desert_well.rs +++ b/pumpkin-world/src/generation/feature/features/desert_well.rs @@ -5,9 +5,7 @@ use pumpkin_util::{ }; use serde::Deserialize; -use crate::generation::chunk_noise::WATER_BLOCK; -use crate::generation::proto_chunk::GenerationCache; -// TODO: remove .to_state() +use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache}; #[derive(Deserialize)] pub struct DesertWellFeature; diff --git a/pumpkin-world/src/generation/mod.rs b/pumpkin-world/src/generation/mod.rs index 7a5a38780..3436b279b 100644 --- a/pumpkin-world/src/generation/mod.rs +++ b/pumpkin-world/src/generation/mod.rs @@ -1,18 +1,15 @@ #![allow(dead_code)] -pub mod aquifer_sampler; mod biome; mod blender; mod block_predicate; mod block_state_provider; pub mod carver; -pub mod chunk_noise; mod feature; pub mod generator; pub mod height_limit; pub mod height_provider; pub mod noise; -pub mod ore_sampler; pub mod positions; pub mod proto_chunk; pub mod rule; diff --git a/pumpkin-world/src/generation/aquifer_sampler.rs b/pumpkin-world/src/generation/noise/aquifer_sampler.rs similarity index 99% rename from pumpkin-world/src/generation/aquifer_sampler.rs rename to pumpkin-world/src/generation/noise/aquifer_sampler.rs index c9c211126..b9bad9e9f 100644 --- a/pumpkin-world/src/generation/aquifer_sampler.rs +++ b/pumpkin-world/src/generation/noise/aquifer_sampler.rs @@ -5,13 +5,15 @@ use pumpkin_util::{ random::{RandomDeriver, RandomDeriverImpl, RandomImpl}, }; -use super::{ - chunk_noise::{LAVA_BLOCK, WATER_BLOCK}, - noise::router::{ - chunk_density_function::ChunkNoiseFunctionSampleOptions, - chunk_noise_router::ChunkNoiseRouter, - density_function::{NoisePos, UnblendedNoisePos}, - surface_height_sampler::SurfaceHeightEstimateSampler, +use crate::generation::{ + noise::{ + LAVA_BLOCK, WATER_BLOCK, + router::{ + chunk_density_function::ChunkNoiseFunctionSampleOptions, + chunk_noise_router::ChunkNoiseRouter, + density_function::{NoisePos, UnblendedNoisePos}, + surface_height_sampler::SurfaceHeightEstimateSampler, + }, }, positions::{MIN_HEIGHT_CELL, block_pos, chunk_pos}, proto_chunk::StandardChunkFluidLevelSampler, @@ -696,14 +698,16 @@ mod random_positions_and_hypot { block::RawBlockState, generation::{ GlobalRandomConfig, biome_coords, - chunk_noise::{BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, - noise::router::{ - chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction}, - chunk_noise_router::ChunkNoiseRouter, - density_function::UnblendedNoisePos, - proto_noise_router::ProtoNoiseRouters, - surface_height_sampler::{ - SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, + noise::{ + BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK, + router::{ + chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction}, + chunk_noise_router::ChunkNoiseRouter, + density_function::UnblendedNoisePos, + proto_noise_router::ProtoNoiseRouters, + surface_height_sampler::{ + SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, + }, }, }, positions::chunk_pos, diff --git a/pumpkin-world/src/generation/noise/mod.rs b/pumpkin-world/src/generation/noise/mod.rs index 38edf2401..ce14d77f6 100644 --- a/pumpkin-world/src/generation/noise/mod.rs +++ b/pumpkin-world/src/generation/noise/mod.rs @@ -1,2 +1,424 @@ +pub mod aquifer_sampler; +pub mod ore_sampler; pub mod perlin; 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 +} diff --git a/pumpkin-world/src/generation/ore_sampler.rs b/pumpkin-world/src/generation/noise/ore_sampler.rs similarity index 94% rename from pumpkin-world/src/generation/ore_sampler.rs rename to pumpkin-world/src/generation/noise/ore_sampler.rs index 5fbeef0d1..7c41a5c6c 100644 --- a/pumpkin-world/src/generation/ore_sampler.rs +++ b/pumpkin-world/src/generation/noise/ore_sampler.rs @@ -4,10 +4,9 @@ use pumpkin_util::{ random::{RandomDeriver, RandomDeriverImpl, RandomImpl}, }; -use crate::generation::noise::router::chunk_noise_router::ChunkNoiseRouter; - -use super::noise::router::{ - chunk_density_function::ChunkNoiseFunctionSampleOptions, density_function::NoisePos, +use crate::generation::noise::router::{ + chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_noise_router::ChunkNoiseRouter, + density_function::NoisePos, }; pub struct OreVeinSampler { diff --git a/pumpkin-world/src/generation/noise/router/density_function/spline.rs b/pumpkin-world/src/generation/noise/router/density_function/spline.rs index cbad455ca..af843175c 100644 --- a/pumpkin-world/src/generation/noise/router/density_function/spline.rs +++ b/pumpkin-world/src/generation/noise/router/density_function/spline.rs @@ -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 { In(usize), Below, @@ -166,16 +149,6 @@ impl Spline { (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( &self, pos: &impl NoisePos, @@ -188,50 +161,43 @@ impl Spline { sample_options, ) as f32; - match self.find_index_for_location(location) { - Range::In(index) => { - 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 n = self.points.len(); + let index_greater_than_x = self.points.partition_point(|p| location >= p.location); - let lower_value = - lower_point - .value - .sample(pos, component_stack, sample_options); - let upper_value = - upper_point - .value - .sample(pos, component_stack, sample_options); - - // Use linear interpolation (-ish cuz of derivatives) to derivate a point between two points - let x_scale = (location - lower_point.location) - / (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)) - * lerp(x_scale, extrapolated_lower_value, extrapolated_upper_value) - + lerp(x_scale, lower_value, upper_value) - } - } - Range::Below => { - let last_known_sample = - self.points[0] - .value - .sample(pos, component_stack, sample_options); - self.points[0].sample_outside_range(location, last_known_sample) - } + if index_greater_than_x == 0 { + 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 + .sample(pos, component_stack, sample_options); + let upper_value = upper_point + .value + .sample(pos, component_stack, sample_options); + + let dist = upper_point.location - lower_point.location; + let x_scale = (location - lower_point.location) / dist; + + let delta = upper_value - lower_value; + let extrapolated_lower = lower_point.derivative * dist - delta; + let extrapolated_upper = -upper_point.derivative * dist + delta; + + let cubic_part = + (x_scale * (1.0 - x_scale)) * lerp(x_scale, extrapolated_lower, extrapolated_upper); + let linear_part = lerp(x_scale, lower_value, upper_value); + + cubic_part + linear_part } } diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 8472b8859..1925e282c 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::pin::Pin; use pumpkin_data::block_properties::is_air; @@ -16,10 +16,7 @@ use pumpkin_util::{ }; use super::{ - GlobalRandomConfig, - aquifer_sampler::{FluidLevel, FluidLevelSamplerImpl}, - biome_coords, - chunk_noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, + GlobalRandomConfig, biome_coords, feature::placed_features::PLACED_FEATURES, noise::router::{ multi_noise_sampler::MultiNoiseSampler, proto_noise_router::DoublePerlinNoiseBuilder, @@ -32,10 +29,13 @@ use super::{ }; use crate::chunk::{ChunkData, ChunkHeightmapType}; use crate::chunk_system::StagedChunkEnum; -use crate::generation::aquifer_sampler::FluidLevelSampler; 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::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::structures::StructureInstance; use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry}; @@ -132,7 +132,7 @@ pub struct ProtoChunk { biome_mixer_seed: i64, // These are local positions flat_block_map: Box<[BlockStateId]>, - pub flat_biome_map: Box<[&'static Biome]>, + pub flat_biome_map: Box<[u8]>, /// HEIGHTMAPS /// /// Top block that is not air @@ -186,7 +186,7 @@ impl ProtoChunk { default_block, flat_block_map: vec![0; CHUNK_AREA * height as usize].into_boxed_slice(), 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(height as usize) @@ -243,7 +243,6 @@ impl ProtoChunk { for y in 0..4 { for z in 0..4 { 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); @@ -255,7 +254,7 @@ impl ProtoChunk { 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] 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( x & biome_coords::from_block(15), y - biome_coords::from_block(self.bottom_y() as i32), @@ -640,7 +644,7 @@ impl ProtoChunk { z, ); - self.flat_biome_map[index] = biome; + self.flat_biome_map[index] = biome.id; } } } @@ -882,79 +886,53 @@ impl ProtoChunk { block_registry: &dyn BlockRegistryExt, random_config: &GlobalRandomConfig, ) { + // 1. Efficiently collect unique biomes from the flat map let (center_x, center_z, min_y, height, biomes_in_chunk) = { let chunk = cache.get_center_chunk(); - let mut biomes = HashSet::new(); - // bad - for biome in chunk.flat_biome_map.clone() { - biomes.insert(biome); + let mut unique_biomes = Vec::with_capacity(4); // Usually few biomes per chunk + for &biome_id in chunk.flat_biome_map.iter() { + if !unique_biomes.contains(&biome_id) { + 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( - section_coords::section_to_block(center_x), - section_coords::block_to_section(min_y) as i32, - section_coords::section_to_block(center_z), - ); + let start_block_x = chunk_pos::start_block_x(center_x); + let start_block_z = chunk_pos::start_block_z(center_z); + let origin_pos = BlockPos::new(start_block_x, min_y, start_block_z); + 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 { - let decorator_seed = get_decorator_seed(population_seed, 0, step as u64); - let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed)); - - let mut tasks = Vec::new(); - - { - let center_chunk = cache.get_center_chunk(); - for (id, instance) in ¢er_chunk.structure_starts { - // Check if this structure belongs to the current step - if let Some(s) = STRUCTURES.get(id) - && s.step.ordinal() != step - { - continue; - } - - match instance { - StructureInstance::Start(pos) => { - tasks.push(pos.collector.clone()); - } - StructureInstance::Reference(origin_pos) => { - 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) - && let Some(StructureInstance::Start(pos)) = - neighbor.structure_starts.get(id) - { - tasks.push(pos.collector.clone()); - } - } - } - } - } - - let chunk = cache.get_center_chunk_mut(); - for collector_arc in tasks { - let mut collector = collector_arc.lock().unwrap(); - collector.generate_in_chunk(chunk, &mut random, random_config.seed as i64); - } - - 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 { + // --- STEP B: PLACED FEATURES --- + let mut features_to_run = Vec::new(); + 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 - .insert(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id)); + .push(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()); + + // 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) { @@ -965,11 +943,11 @@ impl ProtoChunk { feature.generate( cache, block_registry, - min_y, - height, + min_y as i8, + height as u16, feature_id, &mut random, - block_pos, + origin_pos, ); } } @@ -978,6 +956,48 @@ impl ProtoChunk { cache.get_center_chunk_mut().stage = StagedChunkEnum::Features; } + fn generate_structure_step( + cache: &mut T, + step: usize, + population_seed: u64, + world_seed: i64, + ) { + let mut tasks = Vec::new(); + { + let center_chunk = cache.get_center_chunk(); + for (id, instance) in ¢er_chunk.structure_starts { + if let Some(s) = STRUCTURES.get(id) + && s.step.ordinal() != step + { + continue; + } + + match instance { + StructureInstance::Start(pos) => tasks.push(pos.collector.clone()), + StructureInstance::Reference(origin_block_pos) => { + let origin_chunk_x = origin_block_pos.0.x >> 4; + let origin_chunk_z = origin_block_pos.0.z >> 4; + if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z) + && let Some(StructureInstance::Start(pos)) = + neighbor.structure_starts.get(id) + { + tasks.push(pos.collector.clone()); + } + } + } + } + } + + 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(); + for collector_arc in tasks { + let mut collector = collector_arc.lock().unwrap(); + collector.generate_in_chunk(chunk, &mut random, world_seed); + } + } + pub fn set_structure_starts( &mut self, random_config: &GlobalRandomConfig, @@ -1054,34 +1074,36 @@ impl ProtoChunk { } pub fn set_structure_references(cache: &mut T) { - let (center_x, center_z) = { + let (center_x, center_z, start_x, start_z) = { 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 start_block_z = chunk_pos::start_block_z(center_z); - let end_block_x = start_block_x + 15; - let end_block_z = start_block_z + 15; - + let end_x = start_x + 15; + let end_z = start_z + 15; 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 z in (center_z - radius)..=(center_z + radius) { - if let Some(neighbor_chunk) = cache.get_chunk(x, z) { - for (structure_key, instance) in &neighbor_chunk.structure_starts { - 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 x == center_x && z == center_z { + continue; + } + + 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(); - for (key, pos) in references_to_add { + for (key, pos) in references { center_chunk .structure_starts .entry(key) - .or_insert(StructureInstance::Reference(pos)); + .or_insert_with(|| StructureInstance::Reference(pos)); } center_chunk.stage = StagedChunkEnum::StructureReferences; diff --git a/pumpkin-world/src/generation/structure/placement.rs b/pumpkin-world/src/generation/structure/placement.rs index b68874bf3..b8485c4d6 100644 --- a/pumpkin-world/src/generation/structure/placement.rs +++ b/pumpkin-world/src/generation/structure/placement.rs @@ -142,30 +142,31 @@ impl ConcentricRingsStructurePlacement { pub fn is_start_chunk( &self, _calculator: &StructurePlacementCalculator, - chunk_x: i32, - chunk_z: i32, - salt: u32, + _chunk_x: i32, + _chunk_z: i32, + _salt: u32, ) -> bool { // TODO: this is entirely wrong, but the original logic is too complex for now - let x = (chunk_x << 4) as f64; - let z = (chunk_z << 4) as f64; - let distance_sq = x * x + z * z; - let distance = distance_sq.sqrt(); + // let x = (chunk_x << 4) as f64; + // let z = (chunk_z << 4) as f64; + // let distance_sq = x * x + z * z; + // let distance = distance_sq.sqrt(); - let ring_width = 1000.0; - let ring_gap = 2000.0; + // let ring_width = 1000.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 { - return false; - } + // if !in_ring { + // return false; + // } - let seed = (chunk_x as i64).wrapping_mul(341873128712) - ^ (chunk_z as i64).wrapping_mul(132897987541) - ^ (salt as i64); + // let seed = (chunk_x as i64).wrapping_mul(341873128712) + // ^ (chunk_z as i64).wrapping_mul(132897987541) + // ^ (salt as i64); - (seed.abs() % 400) == 0 + // (seed.abs() % 400) == 0 + rand::random_bool(1.0 / 100.0) } } diff --git a/pumpkin-world/src/generation/structure/structures/mod.rs b/pumpkin-world/src/generation/structure/structures/mod.rs index 78b895b65..7267aa4a7 100644 --- a/pumpkin-world/src/generation/structure/structures/mod.rs +++ b/pumpkin-world/src/generation/structure/structures/mod.rs @@ -401,6 +401,7 @@ impl StructurePiecesCollector { pub fn add_piece(&mut self, piece: Box) { self.pieces.push(piece); + self.cached_box = None; } #[expect(clippy::borrowed_box)] diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs b/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs index e40fa2b30..4e74a980a 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use crate::{ ProtoChunk, generation::{ - positions::chunk_pos::{get_offset_x, get_offset_z}, + section_coords, structure::{ piece::StructurePieceType, structures::{ @@ -57,33 +57,32 @@ impl StructureGenerator for StrongholdGenerator { &self, context: StructureGeneratorContext, ) -> Option { - let mut _i = 0; - // The collector must be shared/mutable during generation + let mut retry_count: i32 = 0; let mut collector = StructurePiecesCollector::default(); let mut random = context.random; - // Loop until we have a valid stronghold (Vanilla loop logic) loop { collector.clear(); - _i += 1; - let start_x = get_offset_x(context.chunk_x, 2); - let start_z = get_offset_z(context.chunk_z, 2); + // let attempt_seed = get_carver_seed( + // &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); - - // We need to Box the start piece to treat it uniformly in the list let start_box: Box = Box::new(start_piece.clone()); - let mut pieces_to_process: Vec> = - vec![start_box.clone_box()]; + // In Vanilla, pieces_to_process is 'start.pieces' + let mut pieces_to_process: Vec> = Vec::new(); collector.add_piece(start_box); - // 1. Initial Fill - // We cast strictly to SpiralStaircasePiece here because the start piece is special + // Initial Fill start_piece.fill_openings( &start_piece.piece.piece, &mut random, @@ -91,12 +90,10 @@ impl StructureGenerator for StrongholdGenerator { &mut pieces_to_process, ); - // 2. The Growth Loop + // Growth Loop 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 piece = pieces_to_process.remove(idx); - piece.fill_openings( &start_piece.piece.piece, &mut random, @@ -105,19 +102,29 @@ impl StructureGenerator for StrongholdGenerator { ); } + // Shift height collector.shift_into(context.sea_level, context.min_y, &mut random, 10); let has_portal_room = collector.pieces.iter().any(|p| { 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; } + + retry_count += 1; + if retry_count > 1000 { + return None; + } } 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)), }) } @@ -572,20 +579,20 @@ impl StrongholdPiece { chain_length: u32, piece_type: Option, ) -> Option> { - // 1. Hard limits + // Vanilla limit is 50 if chain_length > 50 { return None; } - // Get bounds from the underlying StructurePiece of the start wrapper + // Distance check from start (112 blocks) let start_box = start.bounding_box; if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 { return None; } - if let Some(piece_type) = piece_type { - let piece = Self::create_piece( - &piece_type, + let next_piece = if let Some(p_type) = piece_type { + Self::create_piece( + &p_type, collector, random, x, @@ -593,22 +600,17 @@ impl StrongholdPiece { z, orientation, chain_length, - ); - if let Some(piece) = piece { - collector.add_piece(piece.clone_box()); - return Some(piece); - } - } + ) + } else { + Self::pick_piece(collector, random, x, y, z, orientation, chain_length) + }; - // 2. Pick the piece - let picked_piece = - Self::pick_piece(collector, random, x, y, z, orientation, chain_length + 1); - - if let Some(ref p) = picked_piece { + if let Some(p) = next_piece { collector.add_piece(p.clone_box()); + return Some(p); } - picked_piece + None } fn pick_piece( diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs b/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs index 961c5d3db..5e8b8f258 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs @@ -129,7 +129,11 @@ impl StructurePieceBase for SpiralStaircasePiece { 1, 1, pieces_to_process, - Some(StrongholdPieceType::FiveWayCrossing), + if self.is_structure_start { + Some(StrongholdPieceType::FiveWayCrossing) + } else { + None + }, ); } diff --git a/pumpkin-world/src/generation/surface/terrain.rs b/pumpkin-world/src/generation/surface/terrain.rs index 339075351..4549dab2f 100644 --- a/pumpkin-world/src/generation/surface/terrain.rs +++ b/pumpkin-world/src/generation/surface/terrain.rs @@ -7,9 +7,9 @@ use pumpkin_util::{ use crate::{ ProtoChunk, block::RawBlockState, - generation::{ - chunk_noise::WATER_BLOCK, noise::perlin::DoublePerlinNoiseSampler, - noise::router::proto_noise_router::DoublePerlinNoiseBuilder, + generation::noise::{ + WATER_BLOCK, perlin::DoublePerlinNoiseSampler, + router::proto_noise_router::DoublePerlinNoiseBuilder, }, }; @@ -152,12 +152,12 @@ impl SurfaceTerrainBuilder { if surface_y <= elevation_y { for y in (chunk.bottom_y() as i32..=elevation_y).rev() { let pos = Vector3::new(global_x, y, global_z); - let block_state = chunk.get_block_state(&pos).to_block_id(); - if block_state == Block::get_raw_id_from_state_id(chunk.default_block.id) { + let block_id = chunk.get_block_state(&pos).to_block_id(); + if block_id == Block::get_raw_id_from_state_id(chunk.default_block.id) { break; } - if block_state == WATER_BLOCK { + if block_id == Block::WATER { return; } } diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 60370944d..d7159944a 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -24,6 +24,7 @@ use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use pumpkin_util::world_seed::Seed; use rand::{Rng, SeedableRng, rngs::SmallRng}; use std::sync::Mutex; +use tokio_util::sync::CancellationToken; // use std::time::Duration; use std::{ collections::HashMap, @@ -38,7 +39,7 @@ use std::{ use tokio::{ select, sync::{ - Notify, RwLock, + RwLock, mpsc::{self, UnboundedReceiver}, oneshot, }, @@ -83,8 +84,7 @@ pub struct Level { tasks: TaskTracker, pub chunk_system_tasks: TaskTracker, /// Notification that interrupts tasks for shutdown - pub shutdown_notifier: Notify, - pub is_shutting_down: AtomicBool, + pub cancel_token: CancellationToken, pub shut_down_chunk_system: AtomicBool, pub should_save: AtomicBool, @@ -185,8 +185,7 @@ impl Level { chunk_watchers: Arc::new(DashMap::new()), tasks: TaskTracker::new(), chunk_system_tasks: TaskTracker::new(), - shutdown_notifier: Notify::new(), - is_shutting_down: AtomicBool::new(false), + cancel_token: CancellationToken::new(), shut_down_chunk_system: AtomicBool::new(false), should_save: AtomicBool::new(false), should_unload: AtomicBool::new(false), @@ -219,7 +218,7 @@ impl Level { builder .spawn(move || { while let Ok(pos) = rx.recv() { - if level_clone.is_shutting_down.load(Ordering::Relaxed) { + if level_clone.cancel_token.is_cancelled() { break; } @@ -259,58 +258,34 @@ impl Level { pub async fn shutdown(&self) { log::info!("Saving level..."); - - 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.cancel_token.cancel(); self.shut_down_chunk_system.store(true, Ordering::Relaxed); self.level_channel.notify(); - let handles: Vec<_> = { - 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.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 { - Ok(guard) => guard, - Err(_) => { - dump().await; - panic!("Timeout Awaiting chunk_system_tasks tasks"); - } + + let handles = { + let mut lock = self.thread_tracker.lock().unwrap(); + lock.drain(..).collect::>() }; + + 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; - // 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.entity_saver.block_and_await_ongoing_tasks().await; // save all chunks currently in memory // let chunks_to_write = self @@ -324,11 +299,6 @@ impl Level { // self.chunk_saver.clear_watched_chunks().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 let chunks_to_write = self .loaded_entity_chunks @@ -622,7 +592,7 @@ impl Level { let level = self.clone(); self.spawn_task(async move { - let cancel_notifier = level.shutdown_notifier.notified(); + let cancel_notifier = level.cancel_token.cancelled(); let fetch_task = async { let mut to_fetch = Vec::new(); diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index b5715d2fd..cb21436a1 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -45,7 +45,16 @@ pub use generation::{ 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( base_router: &ProtoNoiseRouters, random_config: &GlobalRandomConfig, @@ -54,16 +63,10 @@ pub fn bench_create_and_populate_noise( default_block: &'static BlockState, ) { use crate::biome::hash_seed; - use crate::generation::chunk_noise::ChunkNoiseGenerator; use crate::generation::noise::router::surface_height_sampler::{ SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, }; 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 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, ) { use crate::biome::hash_seed; - use crate::generation::chunk_noise::ChunkNoiseGenerator; use crate::generation::noise::router::{ multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, 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::{ - aquifer_sampler::{FluidLevel, FluidLevelSampler}, - biome_coords, - positions::chunk_pos, - }; let biome_mixer_seed = hash_seed(random_config.seed); let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed); diff --git a/pumpkin-world/src/world_info/mod.rs b/pumpkin-world/src/world_info/mod.rs index dc3ba7e6a..5b3221ed5 100644 --- a/pumpkin-world/src/world_info/mod.rs +++ b/pumpkin-world/src/world_info/mod.rs @@ -253,6 +253,11 @@ impl LevelData { 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)] diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index d0d96dbd3..4c66bd737 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -47,11 +47,11 @@ use pumpkin_protocol::{ use std::net::SocketAddr; use tokio::{ net::UdpSocket, + sync::Mutex, sync::mpsc::{Receiver, Sender}, - sync::{Mutex, Notify}, task::JoinHandle, }; -use tokio_util::task::TaskTracker; +use tokio_util::{sync::CancellationToken, task::TaskTracker}; pub mod connection; pub mod login; @@ -84,13 +84,8 @@ pub struct BedrockClient { output_split_number: AtomicU16, output_sequenced_index: AtomicU32, output_ordered_index: AtomicU32, - /// An notifier that is triggered when this client is closed. - close_interrupt: Arc, - - /// Indicates if the client connection is closed. - pub closed: Arc, - + close_token: CancellationToken, /// Store Fragments until the packet is complete compounds: Arc>>>>, //input_sequence_number: AtomicU32, @@ -121,23 +116,21 @@ impl BedrockClient { output_sequenced_index: AtomicU32::new(0), output_ordered_index: AtomicU32::new(0), compounds: Arc::new(Mutex::new(HashMap::new())), - closed: Arc::new(AtomicBool::new(false)), - close_interrupt: Arc::new(Notify::new()), + close_token: CancellationToken::new(), //input_sequence_number: AtomicU32::new(0), } } pub fn start_outgoing_packet_task(&mut self) { let mut packet_receiver = self.outgoing_packet_queue_recv.take().unwrap(); - let close_interrupt = self.close_interrupt.clone(); - let closed = self.closed.clone(); + let close_token = self.close_token.clone(); let writer = self.network_writer.clone(); let addr = self.address; let socket = self.socket.clone(); self.spawn_task(async move { - while !closed.load(Ordering::Relaxed) { + while !close_token.is_cancelled() { let recv_result = tokio::select! { - () = close_interrupt.notified() => { + () = close_token.cancelled() => { None }, recv_result = packet_receiver.recv() => { @@ -156,12 +149,11 @@ impl BedrockClient { .await { // 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}",); // We now need to close the connection to the client since the stream is in an // unknown state - close_interrupt.notify_waiters(); - closed.store(true, Ordering::Relaxed); + close_token.cancel(); break; } } @@ -208,7 +200,7 @@ impl BedrockClient { pub async fn enqueue_packet_data(&self, packet_data: Bytes) { if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await { // 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}"); } } @@ -362,18 +354,17 @@ impl BedrockClient { .await { // 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}"); // We now need to close the connection to the client since the stream is in an // unknown state - self.closed.store(true, Ordering::Relaxed); + self.close_token.cancel(); } } } pub async fn close(&self) { - self.close_interrupt.notify_waiters(); - self.closed.store(true, Ordering::Relaxed); + self.close_token.cancel(); self.tasks.close(); self.tasks.wait().await; 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) { let mut packet_buf = Vec::new(); ack.write(&mut packet_buf).unwrap(); @@ -640,7 +635,7 @@ impl BedrockClient { } 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>) -> Option { @@ -671,7 +666,7 @@ impl BedrockClient { F: Future + Send + 'static, F::Output: Send + 'static, { - if self.closed.load(Ordering::Relaxed) { + if self.close_token.is_cancelled() { None } else { Some(self.tasks.spawn(task)) diff --git a/pumpkin/src/net/java/mod.rs b/pumpkin/src/net/java/mod.rs index aa691c312..be8695861 100644 --- a/pumpkin/src/net/java/mod.rs +++ b/pumpkin/src/net/java/mod.rs @@ -1,5 +1,4 @@ use std::net::SocketAddr; -use std::sync::atomic::{AtomicBool, Ordering}; use std::{io::Write, sync::Arc}; use bytes::Bytes; @@ -72,8 +71,6 @@ pub struct JavaClient { pub server_address: Mutex, /// The current connection state of the client (e.g., Handshaking, Status, Play). pub connection_state: AtomicCell, - /// Indicates if the client connection is closed. - pub closed: Arc, /// The client's IP address. pub address: Mutex, /// The client's brand or modpack information, Optional. @@ -105,7 +102,6 @@ impl JavaClient { server_address: Mutex::new(String::new()), address: Mutex::new(address), connection_state: AtomicCell::new(ConnectionState::HandShake), - closed: Arc::new(AtomicBool::new(false)), close_token: CancellationToken::new(), tasks: TaskTracker::new(), outgoing_packet_queue_send: send, @@ -187,7 +183,7 @@ impl JavaClient { F: Future + Send + 'static, F::Output: Send + 'static, { - if self.closed.load(Ordering::Relaxed) { + if self.close_token.is_cancelled() { None } else { Some(self.tasks.spawn(task)) @@ -210,7 +206,7 @@ impl JavaClient { pub async fn enqueue_packet_data(&self, packet_data: Bytes) { if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await { // This is expected to fail if we are closed - if !self.closed.load(Ordering::Relaxed) { + if !self.close_token.is_cancelled() { log::error!( "Failed to add packet to the outgoing packet queue for client {}: {}", self.id, @@ -283,7 +279,7 @@ impl JavaClient { .await { // 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); // We now need to close the connection to the client since the stream is in an // unknown state @@ -406,14 +402,13 @@ impl JavaClient { .outgoing_packet_queue_recv .take() .expect("This was set in the new fn"); - let close_interrupt = self.close_token.clone(); - let closed = self.closed.clone(); + let close_token = self.close_token.clone(); let writer = self.network_writer.clone(); let id = self.id; self.spawn_task(async move { - while !closed.load(Ordering::Relaxed) { + while !close_token.is_cancelled() { let recv_result = tokio::select! { - () = close_interrupt.cancelled() => None, + () = close_token.cancelled() => None, res = packet_receiver.recv() => res, }; @@ -423,12 +418,11 @@ impl JavaClient { if let Err(err) = writer.lock().await.write_packet(packet_data).await { // 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}",); // We now need to close the connection to the client since the stream is in an // unknown state - close_interrupt.cancel(); - closed.store(true, Ordering::Relaxed); + close_token.cancel(); break; } } @@ -447,7 +441,10 @@ impl JavaClient { /// This function does not attempt to send any disconnect packets to the client. pub fn close(&self) { 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( diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index b8aa59950..e2c25d408 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -129,8 +129,8 @@ impl ClientPlatform { #[must_use] pub fn closed(&self) -> bool { match self { - Self::Java(java) => java.closed.load(Ordering::Relaxed), - Self::Bedrock(bedrock) => bedrock.closed.load(Ordering::Relaxed), + Self::Java(java) => java.is_closed(), + Self::Bedrock(bedrock) => bedrock.is_closed(), } } diff --git a/pumpkin/src/world/chunker.rs b/pumpkin/src/world/chunker.rs index 05bd844e2..c49d1c8ba 100644 --- a/pumpkin/src/world/chunker.rs +++ b/pumpkin/src/world/chunker.rs @@ -25,9 +25,10 @@ pub async fn update_position(player: &Arc) { let new_chunk_center = entity.chunk_pos.load(); let old_cylindrical = player.watched_section.load(); - if old_cylindrical.center == new_chunk_center { - return; - } + // This does break when a new player spawns + // if old_cylindrical.center == new_chunk_center { + // return; + // } let view_distance = get_view_distance(player).await; let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance);