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 {
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])
}
}

View File

@ -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

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::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");

View File

@ -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,
}
}
}

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 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;

View File

@ -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;

View File

@ -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,

View File

@ -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
}

View File

@ -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 {

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 {
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
}
}

View File

@ -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 &center_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<T: GenerationCache>(
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 &center_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<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();
(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;

View File

@ -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)
}
}

View File

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

View File

@ -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<StructurePosition> {
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<dyn StructurePieceBase> = Box::new(start_piece.clone());
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> =
vec![start_box.clone_box()];
// In Vanilla, pieces_to_process is 'start.pieces'
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = 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<StrongholdPieceType>,
) -> Option<Box<dyn StructurePieceBase>> {
// 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(

View File

@ -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
},
);
}

View File

@ -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;
}
}

View File

@ -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::<Vec<_>>()
};
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();

View File

@ -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);

View File

@ -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)]

View File

@ -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<Notify>,
/// Indicates if the client connection is closed.
pub closed: Arc<AtomicBool>,
close_token: CancellationToken,
/// Store Fragments until the packet is complete
compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>,
//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<Vec<u8>>) -> Option<Bytes> {
@ -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))

View File

@ -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<String>,
/// The current connection state of the client (e.g., Handshaking, Status, Play).
pub connection_state: AtomicCell<ConnectionState>,
/// Indicates if the client connection is closed.
pub closed: Arc<AtomicBool>,
/// The client's IP address.
pub address: Mutex<SocketAddr>,
/// 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(

View File

@ -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(),
}
}

View File

@ -25,9 +25,10 @@ pub async fn update_position(player: &Arc<Player>) {
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);