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:
parent
274c7e195d
commit
ac021d5d47
@ -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 {
|
||||
@ -441,65 +441,66 @@ pub(crate) fn build() -> TokenStream {
|
||||
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 {
|
||||
BiomeTree::Leaf { biome, .. } => {
|
||||
*previous_result_node = Some(result_node);
|
||||
biome
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_resulting_node(
|
||||
&'static self,
|
||||
point_list: &[i64; 7],
|
||||
previous_result_node: Option<&'static BiomeTree>,
|
||||
) -> &'static BiomeTree {
|
||||
match self {
|
||||
Self::Leaf { .. } => self,
|
||||
Self::Branch { nodes, .. } => {
|
||||
let mut distance = previous_result_node
|
||||
// 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;
|
||||
|
||||
let mut best_node = previous_result_node.unwrap_or(self);
|
||||
|
||||
self.search(point_list, &mut best_dist, &mut best_node);
|
||||
|
||||
match best_node {
|
||||
BiomeTree::Leaf { biome, .. } => {
|
||||
*previous_result_node = Some(best_node);
|
||||
biome
|
||||
}
|
||||
// Should not happen with valid tree data
|
||||
_ => unreachable!("Biome search failed to find a leaf"),
|
||||
}
|
||||
}
|
||||
|
||||
fn search(
|
||||
&'static self,
|
||||
point: &[i64; 7],
|
||||
best_dist: &mut i64,
|
||||
best_node: &mut &'static BiomeTree,
|
||||
) {
|
||||
let dist = self.get_squared_distance(point);
|
||||
|
||||
// PRUNING: If this branch/leaf is further than our best, skip it and all children
|
||||
if dist >= *best_dist {
|
||||
return;
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Leaf { .. } => {
|
||||
*best_dist = dist;
|
||||
*best_node = self;
|
||||
}
|
||||
Self::Branch { nodes, .. } => {
|
||||
for node in *nodes {
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
77
pumpkin-world/src/biome/position_finder.rs
Normal file
77
pumpkin-world/src/biome/position_finder.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -5,14 +5,16 @@ use pumpkin_util::{
|
||||
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
||||
};
|
||||
|
||||
use super::{
|
||||
chunk_noise::{LAVA_BLOCK, WATER_BLOCK},
|
||||
noise::router::{
|
||||
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,
|
||||
section_coords,
|
||||
@ -696,8 +698,9 @@ mod random_positions_and_hypot {
|
||||
block::RawBlockState,
|
||||
generation::{
|
||||
GlobalRandomConfig, biome_coords,
|
||||
chunk_noise::{BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK},
|
||||
noise::router::{
|
||||
noise::{
|
||||
BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK,
|
||||
router::{
|
||||
chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction},
|
||||
chunk_noise_router::ChunkNoiseRouter,
|
||||
density_function::UnblendedNoisePos,
|
||||
@ -706,6 +709,7 @@ mod random_positions_and_hypot {
|
||||
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
positions::chunk_pos,
|
||||
proto_chunk::StandardChunkFluidLevelSampler,
|
||||
settings::{GENERATION_SETTINGS, GeneratorSetting},
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
@ -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
|
||||
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
|
||||
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);
|
||||
let dist = upper_point.location - lower_point.location;
|
||||
let x_scale = (location - lower_point.location) / dist;
|
||||
|
||||
(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)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,37 +886,86 @@ 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));
|
||||
// --- 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
|
||||
.push(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Determinism. Sort features so the index 'p' always refers to the same feature.
|
||||
features_to_run.sort_unstable();
|
||||
features_to_run.dedup();
|
||||
|
||||
for (p, feature_id) in features_to_run.into_iter().enumerate() {
|
||||
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
|
||||
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
|
||||
let mut random =
|
||||
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||
|
||||
feature.generate(
|
||||
cache,
|
||||
block_registry,
|
||||
min_y as i8,
|
||||
height as u16,
|
||||
feature_id,
|
||||
&mut random,
|
||||
origin_pos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
|
||||
}
|
||||
|
||||
fn generate_structure_step<T: GenerationCache>(
|
||||
cache: &mut T,
|
||||
step: usize,
|
||||
population_seed: u64,
|
||||
world_seed: i64,
|
||||
) {
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
{
|
||||
let 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
|
||||
{
|
||||
@ -920,13 +973,10 @@ impl ProtoChunk {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
@ -938,44 +988,14 @@ impl ProtoChunk {
|
||||
}
|
||||
}
|
||||
|
||||
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
|
||||
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||
|
||||
let chunk = cache.get_center_chunk_mut();
|
||||
for collector_arc in tasks {
|
||||
let mut collector = collector_arc.lock().unwrap();
|
||||
collector.generate_in_chunk(chunk, &mut random, random_config.seed as i64);
|
||||
collector.generate_in_chunk(chunk, &mut random, world_seed);
|
||||
}
|
||||
|
||||
let mut features_to_run = HashSet::new();
|
||||
for biome in &biomes_in_chunk {
|
||||
if let Some(features_at_step) = biome.features.get(step) {
|
||||
for feature_id in *features_at_step {
|
||||
features_to_run
|
||||
.insert(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
// let mut sorted_features: Vec<_> = features_to_run.into_iter().collect();
|
||||
// sorted_features.sort_by_key(|f| f.registry_index());
|
||||
|
||||
for (p, feature_id) in features_to_run.into_iter().enumerate() {
|
||||
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
|
||||
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
|
||||
let mut random =
|
||||
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||
|
||||
feature.generate(
|
||||
cache,
|
||||
block_registry,
|
||||
min_y,
|
||||
height,
|
||||
feature_id,
|
||||
&mut random,
|
||||
block_pos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
|
||||
}
|
||||
|
||||
pub fn set_structure_starts(
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user