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 {
|
impl ParameterRange {
|
||||||
fn calc_distance(&self, noise: i64) -> i64 {
|
pub fn calc_distance(&self, noise: i64) -> i64 {
|
||||||
if noise > self.max {
|
if noise > self.max {
|
||||||
noise - self.max
|
noise - self.max
|
||||||
} else if noise < self.min {
|
} else if noise < self.min {
|
||||||
@ -441,65 +441,66 @@ pub(crate) fn build() -> TokenStream {
|
|||||||
point_list: &[i64; 7],
|
point_list: &[i64; 7],
|
||||||
previous_result_node: &mut Option<&'static BiomeTree>,
|
previous_result_node: &mut Option<&'static BiomeTree>,
|
||||||
) -> &'static Biome {
|
) -> &'static Biome {
|
||||||
let result_node = self.get_resulting_node(point_list, *previous_result_node);
|
// Initialize best distance from the previous result for spatial coherence
|
||||||
match result_node {
|
let mut best_dist = previous_result_node
|
||||||
BiomeTree::Leaf { biome, .. } => {
|
|
||||||
*previous_result_node = Some(result_node);
|
|
||||||
biome
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_resulting_node(
|
|
||||||
&'static self,
|
|
||||||
point_list: &[i64; 7],
|
|
||||||
previous_result_node: Option<&'static BiomeTree>,
|
|
||||||
) -> &'static BiomeTree {
|
|
||||||
match self {
|
|
||||||
Self::Leaf { .. } => self,
|
|
||||||
Self::Branch { nodes, .. } => {
|
|
||||||
let mut distance = previous_result_node
|
|
||||||
.map(|node| node.get_squared_distance(point_list))
|
.map(|node| node.get_squared_distance(point_list))
|
||||||
.unwrap_or(i64::MAX);
|
.unwrap_or(i64::MAX);
|
||||||
let mut best_node = previous_result_node;
|
|
||||||
|
|
||||||
|
let mut best_node = previous_result_node.unwrap_or(self);
|
||||||
|
|
||||||
|
self.search(point_list, &mut best_dist, &mut best_node);
|
||||||
|
|
||||||
|
match best_node {
|
||||||
|
BiomeTree::Leaf { biome, .. } => {
|
||||||
|
*previous_result_node = Some(best_node);
|
||||||
|
biome
|
||||||
|
}
|
||||||
|
// Should not happen with valid tree data
|
||||||
|
_ => unreachable!("Biome search failed to find a leaf"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(
|
||||||
|
&'static self,
|
||||||
|
point: &[i64; 7],
|
||||||
|
best_dist: &mut i64,
|
||||||
|
best_node: &mut &'static BiomeTree,
|
||||||
|
) {
|
||||||
|
let dist = self.get_squared_distance(point);
|
||||||
|
|
||||||
|
// PRUNING: If this branch/leaf is further than our best, skip it and all children
|
||||||
|
if dist >= *best_dist {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Leaf { .. } => {
|
||||||
|
*best_dist = dist;
|
||||||
|
*best_node = self;
|
||||||
|
}
|
||||||
|
Self::Branch { nodes, .. } => {
|
||||||
for node in *nodes {
|
for node in *nodes {
|
||||||
let node_distance = node.get_squared_distance(point_list);
|
node.search(point, best_dist, best_node);
|
||||||
if distance > node_distance {
|
}
|
||||||
let node2 = node.get_resulting_node(point_list, best_node);
|
|
||||||
let node2_distance = if node == node2 {
|
|
||||||
node_distance
|
|
||||||
} else {
|
|
||||||
node2.get_squared_distance(point_list)
|
|
||||||
};
|
|
||||||
|
|
||||||
if distance > node2_distance {
|
|
||||||
distance = node2_distance;
|
|
||||||
best_node = Some(node2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
best_node.expect("This should be populated after traversing the tree")
|
#[inline(always)]
|
||||||
}
|
fn get_squared_distance(&self, p: &[i64; 7]) -> i64 {
|
||||||
}
|
let params = match self {
|
||||||
}
|
|
||||||
|
|
||||||
fn get_squared_distance(&self, point_list: &[i64; 7]) -> i64 {
|
|
||||||
let parameters = match self {
|
|
||||||
Self::Leaf { parameters, .. } => parameters,
|
Self::Leaf { parameters, .. } => parameters,
|
||||||
Self::Branch { parameters, .. } => parameters,
|
Self::Branch { parameters, .. } => parameters,
|
||||||
};
|
};
|
||||||
|
|
||||||
parameters
|
// Fully unrolled for 7 dimensions to maximize throughput
|
||||||
.iter()
|
params[0].calc_distance(p[0]) +
|
||||||
.zip(point_list)
|
params[1].calc_distance(p[1]) +
|
||||||
.map(|(bound, value)| {
|
params[2].calc_distance(p[2]) +
|
||||||
let distance = bound.calc_distance(*value);
|
params[3].calc_distance(p[3]) +
|
||||||
distance * distance
|
params[4].calc_distance(p[4]) +
|
||||||
})
|
params[5].calc_distance(p[5]) +
|
||||||
.sum()
|
params[6].calc_distance(p[6])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use pumpkin_data::{
|
|||||||
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
|
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
|
||||||
pub mod end;
|
pub mod end;
|
||||||
pub mod multi_noise;
|
pub mod multi_noise;
|
||||||
|
pub mod position_finder;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
/// A shortcut; check if last used biome is what we should use
|
/// A shortcut; check if last used biome is what we should use
|
||||||
|
|||||||
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::block::RawBlockState;
|
||||||
use crate::chunk::io::LoadedData::Loaded;
|
use crate::chunk::io::LoadedData::Loaded;
|
||||||
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk};
|
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk};
|
||||||
|
use crate::generation::biome_coords;
|
||||||
use pumpkin_data::block_properties::is_air;
|
use pumpkin_data::block_properties::is_air;
|
||||||
use pumpkin_data::dimension::Dimension;
|
use pumpkin_data::dimension::Dimension;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
@ -791,12 +792,12 @@ impl Chunk {
|
|||||||
dimension.min_y,
|
dimension.min_y,
|
||||||
);
|
);
|
||||||
|
|
||||||
let proto_biome_height = (dimension.height / 4) as usize;
|
let proto_biome_height = biome_coords::from_block(proto_chunk.height());
|
||||||
let biome_min_y = dimension.min_y / 4;
|
let biome_min_y = biome_coords::from_block(dimension.min_y);
|
||||||
|
|
||||||
for y_offset in 0..proto_biome_height {
|
for y_offset in 0..proto_biome_height {
|
||||||
let section_index = y_offset / 4;
|
let section_index = y_offset as usize / 4;
|
||||||
let relative_biome_y = y_offset % 4;
|
let relative_y = y_offset as usize % 4;
|
||||||
|
|
||||||
if let Some(section) = sections.sections.get_mut(section_index) {
|
if let Some(section) = sections.sections.get_mut(section_index) {
|
||||||
let absolute_biome_y = biome_min_y + y_offset as i32;
|
let absolute_biome_y = biome_min_y + y_offset as i32;
|
||||||
@ -804,7 +805,7 @@ impl Chunk {
|
|||||||
for z in 0..4 {
|
for z in 0..4 {
|
||||||
for x in 0..4 {
|
for x in 0..4 {
|
||||||
let biome = proto_chunk.get_biome(x as i32, absolute_biome_y, z as i32);
|
let biome = proto_chunk.get_biome(x as i32, absolute_biome_y, z as i32);
|
||||||
section.biomes.set(x, relative_biome_y, z, biome.id);
|
section.biomes.set(x, relative_y, z, biome.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1988,15 +1989,11 @@ impl GenerationSchedule {
|
|||||||
);
|
);
|
||||||
// let mut clock = Instant::now();
|
// let mut clock = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
if level.should_unload.load(Relaxed) {
|
if level.should_unload.swap(false, Relaxed) {
|
||||||
// log::debug!("unload chunk signal");
|
|
||||||
self.unload_chunk();
|
self.unload_chunk();
|
||||||
level.should_unload.store(false, Relaxed);
|
|
||||||
}
|
}
|
||||||
if level.should_save.load(Relaxed) {
|
if level.should_save.swap(false, Relaxed) {
|
||||||
// log::debug!("save all chunk signal");
|
|
||||||
self.save_all_chunk(false);
|
self.save_all_chunk(false);
|
||||||
level.should_save.store(false, Relaxed);
|
|
||||||
}
|
}
|
||||||
if level.shut_down_chunk_system.load(Relaxed) {
|
if level.shut_down_chunk_system.load(Relaxed) {
|
||||||
// log::debug!("shut down signal");
|
// log::debug!("shut down signal");
|
||||||
|
|||||||
@ -7,9 +7,6 @@ use serde::Deserialize;
|
|||||||
use crate::generation::proto_chunk::GenerationCache;
|
use crate::generation::proto_chunk::GenerationCache;
|
||||||
use crate::{block::BlockStateCodec, world::BlockRegistryExt};
|
use crate::{block::BlockStateCodec, world::BlockRegistryExt};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct EmptyTODOStruct {}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum BlockPredicate {
|
pub enum BlockPredicate {
|
||||||
@ -37,9 +34,9 @@ pub enum BlockPredicate {
|
|||||||
Not(NotBlockPredicate),
|
Not(NotBlockPredicate),
|
||||||
#[serde(rename = "minecraft:true")]
|
#[serde(rename = "minecraft:true")]
|
||||||
AlwaysTrue,
|
AlwaysTrue,
|
||||||
/// Not used
|
// Not used
|
||||||
#[serde(rename = "minecraft:unobstructed")]
|
// #[serde(rename = "minecraft:unobstructed")]
|
||||||
Unobstructed(EmptyTODOStruct),
|
// Unobstructed(EmptyTODOStruct),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockPredicate {
|
impl BlockPredicate {
|
||||||
@ -62,7 +59,6 @@ impl BlockPredicate {
|
|||||||
BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos),
|
BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos),
|
||||||
BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos),
|
BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos),
|
||||||
BlockPredicate::AlwaysTrue => true,
|
BlockPredicate::AlwaysTrue => true,
|
||||||
BlockPredicate::Unobstructed(_predicate) => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 serde::Deserialize;
|
||||||
|
|
||||||
use crate::generation::chunk_noise::WATER_BLOCK;
|
use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache};
|
||||||
use crate::generation::proto_chunk::GenerationCache;
|
|
||||||
// TODO: remove .to_state()
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct DesertWellFeature;
|
pub struct DesertWellFeature;
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod aquifer_sampler;
|
|
||||||
mod biome;
|
mod biome;
|
||||||
mod blender;
|
mod blender;
|
||||||
mod block_predicate;
|
mod block_predicate;
|
||||||
mod block_state_provider;
|
mod block_state_provider;
|
||||||
pub mod carver;
|
pub mod carver;
|
||||||
pub mod chunk_noise;
|
|
||||||
mod feature;
|
mod feature;
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
pub mod height_limit;
|
pub mod height_limit;
|
||||||
pub mod height_provider;
|
pub mod height_provider;
|
||||||
pub mod noise;
|
pub mod noise;
|
||||||
pub mod ore_sampler;
|
|
||||||
pub mod positions;
|
pub mod positions;
|
||||||
pub mod proto_chunk;
|
pub mod proto_chunk;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
|
|||||||
@ -5,14 +5,16 @@ use pumpkin_util::{
|
|||||||
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use crate::generation::{
|
||||||
chunk_noise::{LAVA_BLOCK, WATER_BLOCK},
|
noise::{
|
||||||
noise::router::{
|
LAVA_BLOCK, WATER_BLOCK,
|
||||||
|
router::{
|
||||||
chunk_density_function::ChunkNoiseFunctionSampleOptions,
|
chunk_density_function::ChunkNoiseFunctionSampleOptions,
|
||||||
chunk_noise_router::ChunkNoiseRouter,
|
chunk_noise_router::ChunkNoiseRouter,
|
||||||
density_function::{NoisePos, UnblendedNoisePos},
|
density_function::{NoisePos, UnblendedNoisePos},
|
||||||
surface_height_sampler::SurfaceHeightEstimateSampler,
|
surface_height_sampler::SurfaceHeightEstimateSampler,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
positions::{MIN_HEIGHT_CELL, block_pos, chunk_pos},
|
positions::{MIN_HEIGHT_CELL, block_pos, chunk_pos},
|
||||||
proto_chunk::StandardChunkFluidLevelSampler,
|
proto_chunk::StandardChunkFluidLevelSampler,
|
||||||
section_coords,
|
section_coords,
|
||||||
@ -696,8 +698,9 @@ mod random_positions_and_hypot {
|
|||||||
block::RawBlockState,
|
block::RawBlockState,
|
||||||
generation::{
|
generation::{
|
||||||
GlobalRandomConfig, biome_coords,
|
GlobalRandomConfig, biome_coords,
|
||||||
chunk_noise::{BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK},
|
noise::{
|
||||||
noise::router::{
|
BlockStateSampler, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK,
|
||||||
|
router::{
|
||||||
chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction},
|
chunk_density_function::{ChunkNoiseFunctionSampleOptions, SampleAction},
|
||||||
chunk_noise_router::ChunkNoiseRouter,
|
chunk_noise_router::ChunkNoiseRouter,
|
||||||
density_function::UnblendedNoisePos,
|
density_function::UnblendedNoisePos,
|
||||||
@ -706,6 +709,7 @@ mod random_positions_and_hypot {
|
|||||||
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
positions::chunk_pos,
|
positions::chunk_pos,
|
||||||
proto_chunk::StandardChunkFluidLevelSampler,
|
proto_chunk::StandardChunkFluidLevelSampler,
|
||||||
settings::{GENERATION_SETTINGS, GeneratorSetting},
|
settings::{GENERATION_SETTINGS, GeneratorSetting},
|
||||||
@ -1,2 +1,424 @@
|
|||||||
|
pub mod aquifer_sampler;
|
||||||
|
pub mod ore_sampler;
|
||||||
pub mod perlin;
|
pub mod perlin;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
|
|
||||||
|
use pumpkin_data::{Block, BlockState};
|
||||||
|
use pumpkin_util::math::{floor_div, floor_mod};
|
||||||
|
|
||||||
|
use crate::generation::{
|
||||||
|
noise::{
|
||||||
|
aquifer_sampler::{
|
||||||
|
AquiferSampler, AquiferSamplerImpl, FluidLevelSampler, SeaLevelAquiferSampler,
|
||||||
|
WorldAquiferSampler,
|
||||||
|
},
|
||||||
|
ore_sampler::OreVeinSampler,
|
||||||
|
},
|
||||||
|
section_coords,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
GlobalRandomConfig, biome_coords,
|
||||||
|
noise::router::{
|
||||||
|
chunk_density_function::{
|
||||||
|
ChunkNoiseFunctionBuilderOptions, ChunkNoiseFunctionSampleOptions, SampleAction,
|
||||||
|
WrapperData,
|
||||||
|
},
|
||||||
|
chunk_noise_router::ChunkNoiseRouter,
|
||||||
|
density_function::{IndexToNoisePos, NoisePos, UnblendedNoisePos},
|
||||||
|
proto_noise_router::ProtoNoiseRouter,
|
||||||
|
surface_height_sampler::SurfaceHeightEstimateSampler,
|
||||||
|
},
|
||||||
|
settings::GenerationShapeConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LAVA_BLOCK: Block = Block::LAVA;
|
||||||
|
pub const WATER_BLOCK: Block = Block::WATER;
|
||||||
|
|
||||||
|
pub const CHUNK_DIM: u8 = 16;
|
||||||
|
|
||||||
|
pub enum BlockStateSampler {
|
||||||
|
Aquifer(AquiferSampler),
|
||||||
|
Ore(OreVeinSampler),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockStateSampler {
|
||||||
|
pub fn sample(
|
||||||
|
&mut self,
|
||||||
|
router: &mut ChunkNoiseRouter,
|
||||||
|
pos: &impl NoisePos,
|
||||||
|
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||||
|
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||||
|
) -> Option<&'static BlockState> {
|
||||||
|
match self {
|
||||||
|
Self::Aquifer(aquifer) => aquifer.apply(router, pos, sample_options, height_estimator),
|
||||||
|
Self::Ore(ore) => ore.sample(router, pos, sample_options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChainedBlockStateSampler {
|
||||||
|
pub(crate) samplers: Box<[BlockStateSampler]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChainedBlockStateSampler {
|
||||||
|
pub fn new(samplers: Box<[BlockStateSampler]>) -> Self {
|
||||||
|
Self { samplers }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(
|
||||||
|
&mut self,
|
||||||
|
router: &mut ChunkNoiseRouter,
|
||||||
|
pos: &impl NoisePos,
|
||||||
|
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||||
|
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||||
|
) -> Option<&'static BlockState> {
|
||||||
|
for sampler in self.samplers.iter_mut() {
|
||||||
|
if let Some(state) = sampler.sample(router, pos, sample_options, height_estimator) {
|
||||||
|
return Some(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InterpolationIndexMapper {
|
||||||
|
x: i32,
|
||||||
|
z: i32,
|
||||||
|
|
||||||
|
minimum_cell_y: i32,
|
||||||
|
vertical_cell_block_count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexToNoisePos for InterpolationIndexMapper {
|
||||||
|
fn at(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
sample_data: Option<&mut ChunkNoiseFunctionSampleOptions>,
|
||||||
|
) -> impl NoisePos + 'static {
|
||||||
|
if let Some(sample_data) = sample_data {
|
||||||
|
sample_data.cache_result_unique_id += 1;
|
||||||
|
sample_data.fill_index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = (index as i32 + self.minimum_cell_y) * self.vertical_cell_block_count;
|
||||||
|
|
||||||
|
// TODO: Change this when Blender is implemented
|
||||||
|
UnblendedNoisePos::new(self.x, y, self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChunkIndexMapper {
|
||||||
|
start_x: i32,
|
||||||
|
start_y: i32,
|
||||||
|
start_z: i32,
|
||||||
|
|
||||||
|
horizontal_cell_block_count: usize,
|
||||||
|
vertical_cell_block_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexToNoisePos for ChunkIndexMapper {
|
||||||
|
fn at(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
sample_options: Option<&mut ChunkNoiseFunctionSampleOptions>,
|
||||||
|
) -> impl NoisePos + 'static {
|
||||||
|
let cell_z_position = floor_mod(index, self.horizontal_cell_block_count);
|
||||||
|
let xy_portion = floor_div(index, self.horizontal_cell_block_count);
|
||||||
|
let cell_x_position = floor_mod(xy_portion, self.horizontal_cell_block_count);
|
||||||
|
let cell_y_position = self.vertical_cell_block_count
|
||||||
|
- 1
|
||||||
|
- floor_div(xy_portion, self.horizontal_cell_block_count);
|
||||||
|
|
||||||
|
if let Some(sample_options) = sample_options {
|
||||||
|
sample_options.fill_index = index;
|
||||||
|
if let SampleAction::CellCaches(wrapper_data) = &mut sample_options.action {
|
||||||
|
wrapper_data.update_position(cell_x_position, cell_y_position, cell_z_position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change this when Blender is implemented
|
||||||
|
UnblendedNoisePos::new(
|
||||||
|
self.start_x + cell_x_position as i32,
|
||||||
|
self.start_y + cell_y_position as i32,
|
||||||
|
self.start_z + cell_z_position as i32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChunkNoiseGenerator<'a> {
|
||||||
|
pub state_sampler: ChainedBlockStateSampler,
|
||||||
|
generation_shape: &'a GenerationShapeConfig,
|
||||||
|
start_cell_pos_x: i32,
|
||||||
|
start_cell_pos_z: i32,
|
||||||
|
|
||||||
|
vertical_cell_count: usize,
|
||||||
|
minimum_cell_y: i32,
|
||||||
|
|
||||||
|
cache_fill_unique_id: u64,
|
||||||
|
cache_result_unique_id: u64,
|
||||||
|
|
||||||
|
pub router: ChunkNoiseRouter<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ChunkNoiseGenerator<'a> {
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
|
noise_router_base: &'a ProtoNoiseRouter,
|
||||||
|
random_config: &GlobalRandomConfig,
|
||||||
|
horizontal_cell_count: usize,
|
||||||
|
start_block_x: i32,
|
||||||
|
start_block_z: i32,
|
||||||
|
generation_shape: &'a GenerationShapeConfig,
|
||||||
|
level_sampler: FluidLevelSampler,
|
||||||
|
aquifers: bool,
|
||||||
|
ore_veins: bool,
|
||||||
|
) -> Self {
|
||||||
|
let start_cell_pos_x = floor_div(
|
||||||
|
start_block_x,
|
||||||
|
generation_shape.horizontal_cell_block_count() as i32,
|
||||||
|
);
|
||||||
|
let start_cell_pos_z = floor_div(
|
||||||
|
start_block_z,
|
||||||
|
generation_shape.horizontal_cell_block_count() as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
let horizontal_biome_end = biome_coords::from_block(
|
||||||
|
horizontal_cell_count * generation_shape.horizontal_cell_block_count() as usize,
|
||||||
|
);
|
||||||
|
let vertical_cell_count = floor_div(
|
||||||
|
generation_shape.height as usize,
|
||||||
|
generation_shape.vertical_cell_block_count() as usize,
|
||||||
|
);
|
||||||
|
let minimum_cell_y = floor_div(
|
||||||
|
generation_shape.min_y as i32,
|
||||||
|
generation_shape.vertical_cell_block_count() as i32,
|
||||||
|
);
|
||||||
|
let vertical_cell_block_count = generation_shape.vertical_cell_block_count();
|
||||||
|
let horizontal_cell_block_count = generation_shape.horizontal_cell_block_count();
|
||||||
|
|
||||||
|
let builder_options = ChunkNoiseFunctionBuilderOptions::new(
|
||||||
|
horizontal_cell_block_count as usize,
|
||||||
|
vertical_cell_block_count as usize,
|
||||||
|
vertical_cell_count,
|
||||||
|
horizontal_cell_count,
|
||||||
|
biome_coords::from_block(start_block_x),
|
||||||
|
biome_coords::from_block(start_block_z),
|
||||||
|
horizontal_biome_end,
|
||||||
|
);
|
||||||
|
|
||||||
|
let aquifer_sampler = if aquifers {
|
||||||
|
let section_x = section_coords::block_to_section(start_block_x);
|
||||||
|
let section_z = section_coords::block_to_section(start_block_z);
|
||||||
|
AquiferSampler::Aquifer(WorldAquiferSampler::new(
|
||||||
|
section_x,
|
||||||
|
section_z,
|
||||||
|
&random_config.aquifer_random_deriver,
|
||||||
|
generation_shape.min_y,
|
||||||
|
generation_shape.height,
|
||||||
|
level_sampler,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
AquiferSampler::SeaLevel(SeaLevelAquiferSampler::new(level_sampler))
|
||||||
|
};
|
||||||
|
|
||||||
|
let samplers: Box<[BlockStateSampler]> = if ore_veins {
|
||||||
|
let ore_sampler = OreVeinSampler::new(random_config.ore_random_deriver.clone());
|
||||||
|
Box::new([
|
||||||
|
BlockStateSampler::Aquifer(aquifer_sampler),
|
||||||
|
BlockStateSampler::Ore(ore_sampler),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
Box::new([BlockStateSampler::Aquifer(aquifer_sampler)])
|
||||||
|
};
|
||||||
|
let state_sampler = ChainedBlockStateSampler::new(samplers);
|
||||||
|
|
||||||
|
let router = ChunkNoiseRouter::generate(noise_router_base, &builder_options);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
state_sampler,
|
||||||
|
generation_shape,
|
||||||
|
start_cell_pos_x,
|
||||||
|
start_cell_pos_z,
|
||||||
|
vertical_cell_count,
|
||||||
|
minimum_cell_y,
|
||||||
|
|
||||||
|
cache_fill_unique_id: 0,
|
||||||
|
cache_result_unique_id: 0,
|
||||||
|
|
||||||
|
router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sample_start_density(&mut self) {
|
||||||
|
self.cache_result_unique_id = 0;
|
||||||
|
self.sample_density(true, self.start_cell_pos_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sample_end_density(&mut self, cell_x: i32) {
|
||||||
|
self.sample_density(false, self.start_cell_pos_x + cell_x + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_density(&mut self, start: bool, current_x: i32) {
|
||||||
|
let x = current_x * self.horizontal_cell_block_count() as i32;
|
||||||
|
|
||||||
|
for cell_z in 0..=(16 / self.horizontal_cell_block_count()) {
|
||||||
|
let current_cell_z_pos = self.start_cell_pos_z + cell_z as i32;
|
||||||
|
let z = current_cell_z_pos * self.horizontal_cell_block_count() as i32;
|
||||||
|
self.cache_fill_unique_id += 1;
|
||||||
|
|
||||||
|
let mapper = InterpolationIndexMapper {
|
||||||
|
x,
|
||||||
|
z,
|
||||||
|
minimum_cell_y: self.minimum_cell_y,
|
||||||
|
vertical_cell_block_count: self.vertical_cell_block_count() as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut options = ChunkNoiseFunctionSampleOptions::new(
|
||||||
|
false,
|
||||||
|
SampleAction::CellCaches(WrapperData::new(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
self.horizontal_cell_block_count() as usize,
|
||||||
|
self.vertical_cell_block_count() as usize,
|
||||||
|
)),
|
||||||
|
self.cache_result_unique_id,
|
||||||
|
self.cache_fill_unique_id,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.fill_interpolator_buffers(start, cell_z as usize, &mapper, &mut options);
|
||||||
|
self.cache_result_unique_id = options.cache_result_unique_id;
|
||||||
|
}
|
||||||
|
self.cache_fill_unique_id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn fill_interpolator_buffers(
|
||||||
|
&mut self,
|
||||||
|
start: bool,
|
||||||
|
cell_z: usize,
|
||||||
|
mapper: &impl IndexToNoisePos,
|
||||||
|
sample_options: &mut ChunkNoiseFunctionSampleOptions,
|
||||||
|
) {
|
||||||
|
self.router
|
||||||
|
.fill_interpolator_buffers(start, cell_z, mapper, sample_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn interpolate_x(&mut self, delta: f64) {
|
||||||
|
self.router.interpolate_x(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn interpolate_y(&mut self, delta: f64) {
|
||||||
|
self.router.interpolate_y(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn interpolate_z(&mut self, delta: f64) {
|
||||||
|
self.cache_result_unique_id += 1;
|
||||||
|
self.router.interpolate_z(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn swap_buffers(&mut self) {
|
||||||
|
self.router.swap_buffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_sampled_cell_corners(&mut self, cell_x: i32, cell_y: i32, cell_z: i32) {
|
||||||
|
self.router
|
||||||
|
.on_sampled_cell_corners(cell_y as usize, cell_z as usize);
|
||||||
|
self.cache_fill_unique_id += 1;
|
||||||
|
|
||||||
|
let start_x = (self.start_cell_pos_x + cell_x) * self.horizontal_cell_block_count() as i32;
|
||||||
|
let start_y = (cell_y + self.minimum_cell_y) * self.vertical_cell_block_count() as i32;
|
||||||
|
let start_z = (self.start_cell_pos_z + cell_z) * self.horizontal_cell_block_count() as i32;
|
||||||
|
|
||||||
|
let mapper = ChunkIndexMapper {
|
||||||
|
start_x,
|
||||||
|
start_y,
|
||||||
|
start_z,
|
||||||
|
horizontal_cell_block_count: self.horizontal_cell_block_count() as usize,
|
||||||
|
vertical_cell_block_count: self.vertical_cell_block_count() as usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sample_options = ChunkNoiseFunctionSampleOptions::new(
|
||||||
|
true,
|
||||||
|
SampleAction::CellCaches(WrapperData::new(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
self.horizontal_cell_block_count() as usize,
|
||||||
|
self.vertical_cell_block_count() as usize,
|
||||||
|
)),
|
||||||
|
self.cache_result_unique_id,
|
||||||
|
self.cache_fill_unique_id,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.router.fill_cell_caches(&mapper, &mut sample_options);
|
||||||
|
self.cache_fill_unique_id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
|
pub fn sample_block_state(
|
||||||
|
&mut self,
|
||||||
|
start_x: i32,
|
||||||
|
start_y: i32,
|
||||||
|
start_z: i32,
|
||||||
|
cell_x: i32,
|
||||||
|
cell_y: i32,
|
||||||
|
cell_z: i32,
|
||||||
|
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||||
|
) -> Option<&'static BlockState> {
|
||||||
|
//TODO: Fix this when Blender is added
|
||||||
|
let pos = UnblendedNoisePos::new(start_x + cell_x, start_y + cell_y, start_z + cell_z);
|
||||||
|
|
||||||
|
let options = ChunkNoiseFunctionSampleOptions::new(
|
||||||
|
false,
|
||||||
|
SampleAction::CellCaches(WrapperData::new(
|
||||||
|
cell_x as usize,
|
||||||
|
cell_y as usize,
|
||||||
|
cell_z as usize,
|
||||||
|
self.horizontal_cell_block_count() as usize,
|
||||||
|
self.vertical_cell_block_count() as usize,
|
||||||
|
)),
|
||||||
|
self.cache_result_unique_id,
|
||||||
|
self.cache_fill_unique_id,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.state_sampler
|
||||||
|
.sample(&mut self.router, &pos, &options, height_estimator)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn horizontal_cell_block_count(&self) -> u8 {
|
||||||
|
self.generation_shape.horizontal_cell_block_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn vertical_cell_block_count(&self) -> u8 {
|
||||||
|
self.generation_shape.vertical_cell_block_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Aka bottom_y
|
||||||
|
#[inline]
|
||||||
|
pub fn min_y(&self) -> i8 {
|
||||||
|
self.generation_shape.min_y
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn height(&self) -> u16 {
|
||||||
|
self.generation_shape.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
// TODO: Add test to verify the height estimator has no interpolators or cell caches
|
||||||
|
}
|
||||||
|
|||||||
@ -4,10 +4,9 @@ use pumpkin_util::{
|
|||||||
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generation::noise::router::chunk_noise_router::ChunkNoiseRouter;
|
use crate::generation::noise::router::{
|
||||||
|
chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_noise_router::ChunkNoiseRouter,
|
||||||
use super::noise::router::{
|
density_function::NoisePos,
|
||||||
chunk_density_function::ChunkNoiseFunctionSampleOptions, density_function::NoisePos,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct OreVeinSampler {
|
pub struct OreVeinSampler {
|
||||||
@ -62,23 +62,6 @@ impl SplinePoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the smallest usize between min..max that does not match the predicate
|
|
||||||
fn binary_walk(min: usize, max: usize, pred: impl Fn(usize) -> bool) -> usize {
|
|
||||||
let mut i = max - min;
|
|
||||||
let mut min = min;
|
|
||||||
while i > 0 {
|
|
||||||
let j = i / 2;
|
|
||||||
let k = min + j;
|
|
||||||
if pred(k) {
|
|
||||||
i = j;
|
|
||||||
} else {
|
|
||||||
min = k + 1;
|
|
||||||
i -= j + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
min
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Range {
|
pub enum Range {
|
||||||
In(usize),
|
In(usize),
|
||||||
Below,
|
Below,
|
||||||
@ -166,16 +149,6 @@ impl Spline {
|
|||||||
(min, max)
|
(min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_index_for_location(&self, loc: f32) -> Range {
|
|
||||||
let index_greater_than_x =
|
|
||||||
binary_walk(0, self.points.len(), |i| loc < self.points[i].location);
|
|
||||||
if index_greater_than_x == 0 {
|
|
||||||
Range::Below
|
|
||||||
} else {
|
|
||||||
Range::In(index_greater_than_x - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample(
|
fn sample(
|
||||||
&self,
|
&self,
|
||||||
pos: &impl NoisePos,
|
pos: &impl NoisePos,
|
||||||
@ -188,50 +161,43 @@ impl Spline {
|
|||||||
sample_options,
|
sample_options,
|
||||||
) as f32;
|
) as f32;
|
||||||
|
|
||||||
match self.find_index_for_location(location) {
|
let n = self.points.len();
|
||||||
Range::In(index) => {
|
let index_greater_than_x = self.points.partition_point(|p| location >= p.location);
|
||||||
if index == self.points.len() - 1 {
|
|
||||||
let last_known_sample =
|
|
||||||
self.points[index]
|
|
||||||
.value
|
|
||||||
.sample(pos, component_stack, sample_options);
|
|
||||||
self.points[index].sample_outside_range(location, last_known_sample)
|
|
||||||
} else {
|
|
||||||
let lower_point = &self.points[index];
|
|
||||||
let upper_point = &self.points[index + 1];
|
|
||||||
|
|
||||||
let lower_value =
|
if index_greater_than_x == 0 {
|
||||||
lower_point
|
let point = &self.points[0];
|
||||||
|
let val = point.value.sample(pos, component_stack, sample_options);
|
||||||
|
return point.sample_outside_range(location, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if index_greater_than_x == n {
|
||||||
|
let point = &self.points[n - 1];
|
||||||
|
let val = point.value.sample(pos, component_stack, sample_options);
|
||||||
|
return point.sample_outside_range(location, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lower_point = &self.points[index_greater_than_x - 1];
|
||||||
|
let upper_point = &self.points[index_greater_than_x];
|
||||||
|
|
||||||
|
let lower_value = lower_point
|
||||||
.value
|
.value
|
||||||
.sample(pos, component_stack, sample_options);
|
.sample(pos, component_stack, sample_options);
|
||||||
let upper_value =
|
let upper_value = upper_point
|
||||||
upper_point
|
|
||||||
.value
|
.value
|
||||||
.sample(pos, component_stack, sample_options);
|
.sample(pos, component_stack, sample_options);
|
||||||
|
|
||||||
// Use linear interpolation (-ish cuz of derivatives) to derivate a point between two points
|
let dist = upper_point.location - lower_point.location;
|
||||||
let x_scale = (location - lower_point.location)
|
let x_scale = (location - lower_point.location) / dist;
|
||||||
/ (upper_point.location - lower_point.location);
|
|
||||||
let extrapolated_lower_value = lower_point.derivative
|
|
||||||
* (upper_point.location - lower_point.location)
|
|
||||||
- (upper_value - lower_value);
|
|
||||||
let extrapolated_upper_value = -upper_point.derivative
|
|
||||||
* (upper_point.location - lower_point.location)
|
|
||||||
+ (upper_value - lower_value);
|
|
||||||
|
|
||||||
(x_scale * (1f32 - x_scale))
|
let delta = upper_value - lower_value;
|
||||||
* lerp(x_scale, extrapolated_lower_value, extrapolated_upper_value)
|
let extrapolated_lower = lower_point.derivative * dist - delta;
|
||||||
+ lerp(x_scale, lower_value, upper_value)
|
let extrapolated_upper = -upper_point.derivative * dist + delta;
|
||||||
}
|
|
||||||
}
|
let cubic_part =
|
||||||
Range::Below => {
|
(x_scale * (1.0 - x_scale)) * lerp(x_scale, extrapolated_lower, extrapolated_upper);
|
||||||
let last_known_sample =
|
let linear_part = lerp(x_scale, lower_value, upper_value);
|
||||||
self.points[0]
|
|
||||||
.value
|
cubic_part + linear_part
|
||||||
.sample(pos, component_stack, sample_options);
|
|
||||||
self.points[0].sample_outside_range(location, last_known_sample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashMap;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use pumpkin_data::block_properties::is_air;
|
use pumpkin_data::block_properties::is_air;
|
||||||
@ -16,10 +16,7 @@ use pumpkin_util::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
GlobalRandomConfig,
|
GlobalRandomConfig, biome_coords,
|
||||||
aquifer_sampler::{FluidLevel, FluidLevelSamplerImpl},
|
|
||||||
biome_coords,
|
|
||||||
chunk_noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK},
|
|
||||||
feature::placed_features::PLACED_FEATURES,
|
feature::placed_features::PLACED_FEATURES,
|
||||||
noise::router::{
|
noise::router::{
|
||||||
multi_noise_sampler::MultiNoiseSampler, proto_noise_router::DoublePerlinNoiseBuilder,
|
multi_noise_sampler::MultiNoiseSampler, proto_noise_router::DoublePerlinNoiseBuilder,
|
||||||
@ -32,10 +29,13 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::chunk::{ChunkData, ChunkHeightmapType};
|
use crate::chunk::{ChunkData, ChunkHeightmapType};
|
||||||
use crate::chunk_system::StagedChunkEnum;
|
use crate::chunk_system::StagedChunkEnum;
|
||||||
use crate::generation::aquifer_sampler::FluidLevelSampler;
|
|
||||||
use crate::generation::height_limit::HeightLimitView;
|
use crate::generation::height_limit::HeightLimitView;
|
||||||
|
use crate::generation::noise::aquifer_sampler::{
|
||||||
|
FluidLevel, FluidLevelSampler, FluidLevelSamplerImpl,
|
||||||
|
};
|
||||||
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
|
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
|
||||||
use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions;
|
use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions;
|
||||||
|
use crate::generation::noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK};
|
||||||
use crate::generation::structure::placement::StructurePlacementCalculator;
|
use crate::generation::structure::placement::StructurePlacementCalculator;
|
||||||
use crate::generation::structure::structures::StructureInstance;
|
use crate::generation::structure::structures::StructureInstance;
|
||||||
use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry};
|
use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry};
|
||||||
@ -132,7 +132,7 @@ pub struct ProtoChunk {
|
|||||||
biome_mixer_seed: i64,
|
biome_mixer_seed: i64,
|
||||||
// These are local positions
|
// These are local positions
|
||||||
flat_block_map: Box<[BlockStateId]>,
|
flat_block_map: Box<[BlockStateId]>,
|
||||||
pub flat_biome_map: Box<[&'static Biome]>,
|
pub flat_biome_map: Box<[u8]>,
|
||||||
/// HEIGHTMAPS
|
/// HEIGHTMAPS
|
||||||
///
|
///
|
||||||
/// Top block that is not air
|
/// Top block that is not air
|
||||||
@ -186,7 +186,7 @@ impl ProtoChunk {
|
|||||||
default_block,
|
default_block,
|
||||||
flat_block_map: vec![0; CHUNK_AREA * height as usize].into_boxed_slice(),
|
flat_block_map: vec![0; CHUNK_AREA * height as usize].into_boxed_slice(),
|
||||||
flat_biome_map: vec![
|
flat_biome_map: vec![
|
||||||
&Biome::PLAINS;
|
Biome::PLAINS.id;
|
||||||
biome_coords::from_block(CHUNK_DIM as usize)
|
biome_coords::from_block(CHUNK_DIM as usize)
|
||||||
* biome_coords::from_block(CHUNK_DIM as usize)
|
* biome_coords::from_block(CHUNK_DIM as usize)
|
||||||
* biome_coords::from_block(height as usize)
|
* biome_coords::from_block(height as usize)
|
||||||
@ -243,7 +243,6 @@ impl ProtoChunk {
|
|||||||
for y in 0..4 {
|
for y in 0..4 {
|
||||||
for z in 0..4 {
|
for z in 0..4 {
|
||||||
let biome_id = section.biomes.get(x, y, z);
|
let biome_id = section.biomes.get(x, y, z);
|
||||||
let biome = Biome::from_id(biome_id).unwrap();
|
|
||||||
|
|
||||||
let relative_y_block = section_base_y + (y as i32 * 4);
|
let relative_y_block = section_base_y + (y as i32 * 4);
|
||||||
|
|
||||||
@ -255,7 +254,7 @@ impl ProtoChunk {
|
|||||||
z as i32,
|
z as i32,
|
||||||
);
|
);
|
||||||
|
|
||||||
proto_chunk.flat_biome_map[index] = biome;
|
proto_chunk.flat_biome_map[index] = biome_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,6 +480,11 @@ impl ProtoChunk {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_biome(&self, x: i32, y: i32, z: i32) -> &'static Biome {
|
pub fn get_biome(&self, x: i32, y: i32, z: i32) -> &'static Biome {
|
||||||
|
Biome::from_id(self.get_biome_id(x, y, z)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_biome_id(&self, x: i32, y: i32, z: i32) -> u8 {
|
||||||
let index = self.local_biome_pos_to_biome_index(
|
let index = self.local_biome_pos_to_biome_index(
|
||||||
x & biome_coords::from_block(15),
|
x & biome_coords::from_block(15),
|
||||||
y - biome_coords::from_block(self.bottom_y() as i32),
|
y - biome_coords::from_block(self.bottom_y() as i32),
|
||||||
@ -640,7 +644,7 @@ impl ProtoChunk {
|
|||||||
z,
|
z,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.flat_biome_map[index] = biome;
|
self.flat_biome_map[index] = biome.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -882,37 +886,86 @@ impl ProtoChunk {
|
|||||||
block_registry: &dyn BlockRegistryExt,
|
block_registry: &dyn BlockRegistryExt,
|
||||||
random_config: &GlobalRandomConfig,
|
random_config: &GlobalRandomConfig,
|
||||||
) {
|
) {
|
||||||
|
// 1. Efficiently collect unique biomes from the flat map
|
||||||
let (center_x, center_z, min_y, height, biomes_in_chunk) = {
|
let (center_x, center_z, min_y, height, biomes_in_chunk) = {
|
||||||
let chunk = cache.get_center_chunk();
|
let chunk = cache.get_center_chunk();
|
||||||
let mut biomes = HashSet::new();
|
let mut unique_biomes = Vec::with_capacity(4); // Usually few biomes per chunk
|
||||||
// bad
|
for &biome_id in chunk.flat_biome_map.iter() {
|
||||||
for biome in chunk.flat_biome_map.clone() {
|
if !unique_biomes.contains(&biome_id) {
|
||||||
biomes.insert(biome);
|
unique_biomes.push(biome_id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
(chunk.x, chunk.z, chunk.bottom_y(), chunk.height(), biomes)
|
(
|
||||||
|
chunk.x,
|
||||||
|
chunk.z,
|
||||||
|
chunk.bottom_y() as i32,
|
||||||
|
chunk.height() as i32,
|
||||||
|
unique_biomes,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_pos = BlockPos::new(
|
let start_block_x = chunk_pos::start_block_x(center_x);
|
||||||
section_coords::section_to_block(center_x),
|
let start_block_z = chunk_pos::start_block_z(center_z);
|
||||||
section_coords::block_to_section(min_y) as i32,
|
let origin_pos = BlockPos::new(start_block_x, min_y, start_block_z);
|
||||||
section_coords::section_to_block(center_z),
|
|
||||||
);
|
|
||||||
let population_seed =
|
let population_seed =
|
||||||
Xoroshiro::get_population_seed(random_config.seed, block_pos.0.x, block_pos.0.z);
|
Xoroshiro::get_population_seed(random_config.seed, start_block_x, start_block_z);
|
||||||
|
|
||||||
const MAX_STEPS: usize = 11;
|
// Vanilla has 11 steps (0..10)
|
||||||
|
for step in 0..11 {
|
||||||
|
// --- STEP A: STRUCTURE PIECES ---
|
||||||
|
// Structures are handled first because features (like trees) need to avoid clipping into them
|
||||||
|
Self::generate_structure_step(cache, step, population_seed, random_config.seed as i64);
|
||||||
|
|
||||||
for step in 0..MAX_STEPS {
|
// --- STEP B: PLACED FEATURES ---
|
||||||
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
|
let mut features_to_run = Vec::new();
|
||||||
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
for biome_id in &biomes_in_chunk {
|
||||||
|
if let Some(biome) = Biome::from_id(*biome_id)
|
||||||
|
&& let Some(features_at_step) = biome.features.get(step)
|
||||||
|
{
|
||||||
|
for &feature_id in *features_at_step {
|
||||||
|
features_to_run
|
||||||
|
.push(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Determinism. Sort features so the index 'p' always refers to the same feature.
|
||||||
|
features_to_run.sort_unstable();
|
||||||
|
features_to_run.dedup();
|
||||||
|
|
||||||
|
for (p, feature_id) in features_to_run.into_iter().enumerate() {
|
||||||
|
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
|
||||||
|
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
|
||||||
|
let mut random =
|
||||||
|
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||||
|
|
||||||
|
feature.generate(
|
||||||
|
cache,
|
||||||
|
block_registry,
|
||||||
|
min_y as i8,
|
||||||
|
height as u16,
|
||||||
|
feature_id,
|
||||||
|
&mut random,
|
||||||
|
origin_pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_structure_step<T: GenerationCache>(
|
||||||
|
cache: &mut T,
|
||||||
|
step: usize,
|
||||||
|
population_seed: u64,
|
||||||
|
world_seed: i64,
|
||||||
|
) {
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let center_chunk = cache.get_center_chunk();
|
let center_chunk = cache.get_center_chunk();
|
||||||
for (id, instance) in ¢er_chunk.structure_starts {
|
for (id, instance) in ¢er_chunk.structure_starts {
|
||||||
// Check if this structure belongs to the current step
|
|
||||||
if let Some(s) = STRUCTURES.get(id)
|
if let Some(s) = STRUCTURES.get(id)
|
||||||
&& s.step.ordinal() != step
|
&& s.step.ordinal() != step
|
||||||
{
|
{
|
||||||
@ -920,13 +973,10 @@ impl ProtoChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match instance {
|
match instance {
|
||||||
StructureInstance::Start(pos) => {
|
StructureInstance::Start(pos) => tasks.push(pos.collector.clone()),
|
||||||
tasks.push(pos.collector.clone());
|
StructureInstance::Reference(origin_block_pos) => {
|
||||||
}
|
let origin_chunk_x = origin_block_pos.0.x >> 4;
|
||||||
StructureInstance::Reference(origin_pos) => {
|
let origin_chunk_z = origin_block_pos.0.z >> 4;
|
||||||
let origin_chunk_x = origin_pos.0.x >> 4;
|
|
||||||
let origin_chunk_z = origin_pos.0.z >> 4;
|
|
||||||
|
|
||||||
if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z)
|
if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z)
|
||||||
&& let Some(StructureInstance::Start(pos)) =
|
&& let Some(StructureInstance::Start(pos)) =
|
||||||
neighbor.structure_starts.get(id)
|
neighbor.structure_starts.get(id)
|
||||||
@ -938,44 +988,14 @@ impl ProtoChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
|
||||||
|
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||||
|
|
||||||
let chunk = cache.get_center_chunk_mut();
|
let chunk = cache.get_center_chunk_mut();
|
||||||
for collector_arc in tasks {
|
for collector_arc in tasks {
|
||||||
let mut collector = collector_arc.lock().unwrap();
|
let mut collector = collector_arc.lock().unwrap();
|
||||||
collector.generate_in_chunk(chunk, &mut random, random_config.seed as i64);
|
collector.generate_in_chunk(chunk, &mut random, world_seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut features_to_run = HashSet::new();
|
|
||||||
for biome in &biomes_in_chunk {
|
|
||||||
if let Some(features_at_step) = biome.features.get(step) {
|
|
||||||
for feature_id in *features_at_step {
|
|
||||||
features_to_run
|
|
||||||
.insert(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// let mut sorted_features: Vec<_> = features_to_run.into_iter().collect();
|
|
||||||
// sorted_features.sort_by_key(|f| f.registry_index());
|
|
||||||
|
|
||||||
for (p, feature_id) in features_to_run.into_iter().enumerate() {
|
|
||||||
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
|
|
||||||
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
|
|
||||||
let mut random =
|
|
||||||
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
|
||||||
|
|
||||||
feature.generate(
|
|
||||||
cache,
|
|
||||||
block_registry,
|
|
||||||
min_y,
|
|
||||||
height,
|
|
||||||
feature_id,
|
|
||||||
&mut random,
|
|
||||||
block_pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_structure_starts(
|
pub fn set_structure_starts(
|
||||||
@ -1054,34 +1074,36 @@ impl ProtoChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_structure_references<T: GenerationCache>(cache: &mut T) {
|
pub fn set_structure_references<T: GenerationCache>(cache: &mut T) {
|
||||||
let (center_x, center_z) = {
|
let (center_x, center_z, start_x, start_z) = {
|
||||||
let chunk = cache.get_center_chunk();
|
let chunk = cache.get_center_chunk();
|
||||||
(chunk.x, chunk.z)
|
(
|
||||||
|
chunk.x,
|
||||||
|
chunk.z,
|
||||||
|
chunk_pos::start_block_x(chunk.x),
|
||||||
|
chunk_pos::start_block_z(chunk.z),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let start_block_x = chunk_pos::start_block_x(center_x);
|
let end_x = start_x + 15;
|
||||||
let start_block_z = chunk_pos::start_block_z(center_z);
|
let end_z = start_z + 15;
|
||||||
let end_block_x = start_block_x + 15;
|
|
||||||
let end_block_z = start_block_z + 15;
|
|
||||||
|
|
||||||
let radius = 8;
|
let radius = 8;
|
||||||
let mut references_to_add = Vec::new();
|
|
||||||
|
let mut references = Vec::new();
|
||||||
|
|
||||||
for x in (center_x - radius)..=(center_x + radius) {
|
for x in (center_x - radius)..=(center_x + radius) {
|
||||||
for z in (center_z - radius)..=(center_z + radius) {
|
for z in (center_z - radius)..=(center_z + radius) {
|
||||||
if let Some(neighbor_chunk) = cache.get_chunk(x, z) {
|
if x == center_x && z == center_z {
|
||||||
for (structure_key, instance) in &neighbor_chunk.structure_starts {
|
continue;
|
||||||
if let StructureInstance::Start(start_data) = instance {
|
|
||||||
let bbox = start_data.get_bounding_box();
|
|
||||||
if bbox.intersects_raw_xz(
|
|
||||||
start_block_x,
|
|
||||||
start_block_z,
|
|
||||||
end_block_x,
|
|
||||||
end_block_z,
|
|
||||||
) {
|
|
||||||
references_to_add
|
|
||||||
.push((structure_key.clone(), start_data.start_pos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(neighbor) = cache.get_chunk(x, z) {
|
||||||
|
for (key, instance) in &neighbor.structure_starts {
|
||||||
|
if let StructureInstance::Start(start_data) = instance
|
||||||
|
&& start_data
|
||||||
|
.get_bounding_box()
|
||||||
|
.intersects_raw_xz(start_x, start_z, end_x, end_z)
|
||||||
|
{
|
||||||
|
references.push((key.clone(), start_data.start_pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1089,11 +1111,11 @@ impl ProtoChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let center_chunk = cache.get_center_chunk_mut();
|
let center_chunk = cache.get_center_chunk_mut();
|
||||||
for (key, pos) in references_to_add {
|
for (key, pos) in references {
|
||||||
center_chunk
|
center_chunk
|
||||||
.structure_starts
|
.structure_starts
|
||||||
.entry(key)
|
.entry(key)
|
||||||
.or_insert(StructureInstance::Reference(pos));
|
.or_insert_with(|| StructureInstance::Reference(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
center_chunk.stage = StagedChunkEnum::StructureReferences;
|
center_chunk.stage = StagedChunkEnum::StructureReferences;
|
||||||
|
|||||||
@ -142,30 +142,31 @@ impl ConcentricRingsStructurePlacement {
|
|||||||
pub fn is_start_chunk(
|
pub fn is_start_chunk(
|
||||||
&self,
|
&self,
|
||||||
_calculator: &StructurePlacementCalculator,
|
_calculator: &StructurePlacementCalculator,
|
||||||
chunk_x: i32,
|
_chunk_x: i32,
|
||||||
chunk_z: i32,
|
_chunk_z: i32,
|
||||||
salt: u32,
|
_salt: u32,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// TODO: this is entirely wrong, but the original logic is too complex for now
|
// TODO: this is entirely wrong, but the original logic is too complex for now
|
||||||
let x = (chunk_x << 4) as f64;
|
// let x = (chunk_x << 4) as f64;
|
||||||
let z = (chunk_z << 4) as f64;
|
// let z = (chunk_z << 4) as f64;
|
||||||
let distance_sq = x * x + z * z;
|
// let distance_sq = x * x + z * z;
|
||||||
let distance = distance_sq.sqrt();
|
// let distance = distance_sq.sqrt();
|
||||||
|
|
||||||
let ring_width = 1000.0;
|
// let ring_width = 1000.0;
|
||||||
let ring_gap = 2000.0;
|
// let ring_gap = 2000.0;
|
||||||
|
|
||||||
let in_ring = (distance % (ring_width + ring_gap)) < ring_width;
|
// let in_ring = (distance % (ring_width + ring_gap)) < ring_width;
|
||||||
|
|
||||||
if !in_ring {
|
// if !in_ring {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
let seed = (chunk_x as i64).wrapping_mul(341873128712)
|
// let seed = (chunk_x as i64).wrapping_mul(341873128712)
|
||||||
^ (chunk_z as i64).wrapping_mul(132897987541)
|
// ^ (chunk_z as i64).wrapping_mul(132897987541)
|
||||||
^ (salt as i64);
|
// ^ (salt as i64);
|
||||||
|
|
||||||
(seed.abs() % 400) == 0
|
// (seed.abs() % 400) == 0
|
||||||
|
rand::random_bool(1.0 / 100.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -401,6 +401,7 @@ impl StructurePiecesCollector {
|
|||||||
|
|
||||||
pub fn add_piece(&mut self, piece: Box<dyn StructurePieceBase>) {
|
pub fn add_piece(&mut self, piece: Box<dyn StructurePieceBase>) {
|
||||||
self.pieces.push(piece);
|
self.pieces.push(piece);
|
||||||
|
self.cached_box = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::borrowed_box)]
|
#[expect(clippy::borrowed_box)]
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use serde::Deserialize;
|
|||||||
use crate::{
|
use crate::{
|
||||||
ProtoChunk,
|
ProtoChunk,
|
||||||
generation::{
|
generation::{
|
||||||
positions::chunk_pos::{get_offset_x, get_offset_z},
|
section_coords,
|
||||||
structure::{
|
structure::{
|
||||||
piece::StructurePieceType,
|
piece::StructurePieceType,
|
||||||
structures::{
|
structures::{
|
||||||
@ -57,33 +57,32 @@ impl StructureGenerator for StrongholdGenerator {
|
|||||||
&self,
|
&self,
|
||||||
context: StructureGeneratorContext,
|
context: StructureGeneratorContext,
|
||||||
) -> Option<StructurePosition> {
|
) -> Option<StructurePosition> {
|
||||||
let mut _i = 0;
|
let mut retry_count: i32 = 0;
|
||||||
// The collector must be shared/mutable during generation
|
|
||||||
let mut collector = StructurePiecesCollector::default();
|
let mut collector = StructurePiecesCollector::default();
|
||||||
let mut random = context.random;
|
let mut random = context.random;
|
||||||
|
|
||||||
// Loop until we have a valid stronghold (Vanilla loop logic)
|
|
||||||
loop {
|
loop {
|
||||||
collector.clear();
|
collector.clear();
|
||||||
_i += 1;
|
|
||||||
|
|
||||||
let start_x = get_offset_x(context.chunk_x, 2);
|
// let attempt_seed = get_carver_seed(
|
||||||
let start_z = get_offset_z(context.chunk_z, 2);
|
// &mut context.random,
|
||||||
|
// context.seed as u64,
|
||||||
|
// context.chunk_x,
|
||||||
|
// context.chunk_z,
|
||||||
|
// );
|
||||||
|
|
||||||
|
let start_x = section_coords::section_to_block(context.chunk_x) + 2;
|
||||||
|
let start_z = section_coords::section_to_block(context.chunk_z) + 2;
|
||||||
|
|
||||||
// Create the Start Piece (SpiralStaircase)
|
|
||||||
// Note: new_start must return a concrete struct that implements StructurePieceBase
|
|
||||||
let start_piece = SpiralStaircasePiece::new_start(&mut random, start_x, start_z);
|
let start_piece = SpiralStaircasePiece::new_start(&mut random, start_x, start_z);
|
||||||
|
|
||||||
// We need to Box the start piece to treat it uniformly in the list
|
|
||||||
let start_box: Box<dyn StructurePieceBase> = Box::new(start_piece.clone());
|
let start_box: Box<dyn StructurePieceBase> = Box::new(start_piece.clone());
|
||||||
|
|
||||||
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> =
|
// In Vanilla, pieces_to_process is 'start.pieces'
|
||||||
vec![start_box.clone_box()];
|
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = Vec::new();
|
||||||
|
|
||||||
collector.add_piece(start_box);
|
collector.add_piece(start_box);
|
||||||
|
|
||||||
// 1. Initial Fill
|
// Initial Fill
|
||||||
// We cast strictly to SpiralStaircasePiece here because the start piece is special
|
|
||||||
start_piece.fill_openings(
|
start_piece.fill_openings(
|
||||||
&start_piece.piece.piece,
|
&start_piece.piece.piece,
|
||||||
&mut random,
|
&mut random,
|
||||||
@ -91,12 +90,10 @@ impl StructureGenerator for StrongholdGenerator {
|
|||||||
&mut pieces_to_process,
|
&mut pieces_to_process,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. The Growth Loop
|
// Growth Loop
|
||||||
while !pieces_to_process.is_empty() {
|
while !pieces_to_process.is_empty() {
|
||||||
// Pick a random piece from the "to process" list
|
|
||||||
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
|
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
|
||||||
let piece = pieces_to_process.remove(idx);
|
let piece = pieces_to_process.remove(idx);
|
||||||
|
|
||||||
piece.fill_openings(
|
piece.fill_openings(
|
||||||
&start_piece.piece.piece,
|
&start_piece.piece.piece,
|
||||||
&mut random,
|
&mut random,
|
||||||
@ -105,19 +102,29 @@ impl StructureGenerator for StrongholdGenerator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shift height
|
||||||
collector.shift_into(context.sea_level, context.min_y, &mut random, 10);
|
collector.shift_into(context.sea_level, context.min_y, &mut random, 10);
|
||||||
|
|
||||||
let has_portal_room = collector.pieces.iter().any(|p| {
|
let has_portal_room = collector.pieces.iter().any(|p| {
|
||||||
p.get_structure_piece().r#type == StructurePieceType::StrongholdPortalRoom
|
p.get_structure_piece().r#type == StructurePieceType::StrongholdPortalRoom
|
||||||
});
|
});
|
||||||
|
|
||||||
if !collector.is_empty() && has_portal_room || collector.pieces.len() >= 100 {
|
if !collector.is_empty() && has_portal_room {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retry_count += 1;
|
||||||
|
if retry_count > 1000 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(StructurePosition {
|
Some(StructurePosition {
|
||||||
start_pos: BlockPos::new(context.chunk_x, 0, context.chunk_z),
|
start_pos: BlockPos::new(
|
||||||
|
section_coords::section_to_block(context.chunk_x),
|
||||||
|
0,
|
||||||
|
section_coords::section_to_block(context.chunk_z),
|
||||||
|
),
|
||||||
collector: Arc::new(Mutex::new(collector)),
|
collector: Arc::new(Mutex::new(collector)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -572,20 +579,20 @@ impl StrongholdPiece {
|
|||||||
chain_length: u32,
|
chain_length: u32,
|
||||||
piece_type: Option<StrongholdPieceType>,
|
piece_type: Option<StrongholdPieceType>,
|
||||||
) -> Option<Box<dyn StructurePieceBase>> {
|
) -> Option<Box<dyn StructurePieceBase>> {
|
||||||
// 1. Hard limits
|
// Vanilla limit is 50
|
||||||
if chain_length > 50 {
|
if chain_length > 50 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get bounds from the underlying StructurePiece of the start wrapper
|
// Distance check from start (112 blocks)
|
||||||
let start_box = start.bounding_box;
|
let start_box = start.bounding_box;
|
||||||
if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 {
|
if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(piece_type) = piece_type {
|
let next_piece = if let Some(p_type) = piece_type {
|
||||||
let piece = Self::create_piece(
|
Self::create_piece(
|
||||||
&piece_type,
|
&p_type,
|
||||||
collector,
|
collector,
|
||||||
random,
|
random,
|
||||||
x,
|
x,
|
||||||
@ -593,22 +600,17 @@ impl StrongholdPiece {
|
|||||||
z,
|
z,
|
||||||
orientation,
|
orientation,
|
||||||
chain_length,
|
chain_length,
|
||||||
);
|
)
|
||||||
if let Some(piece) = piece {
|
} else {
|
||||||
collector.add_piece(piece.clone_box());
|
Self::pick_piece(collector, random, x, y, z, orientation, chain_length)
|
||||||
return Some(piece);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Pick the piece
|
if let Some(p) = next_piece {
|
||||||
let picked_piece =
|
|
||||||
Self::pick_piece(collector, random, x, y, z, orientation, chain_length + 1);
|
|
||||||
|
|
||||||
if let Some(ref p) = picked_piece {
|
|
||||||
collector.add_piece(p.clone_box());
|
collector.add_piece(p.clone_box());
|
||||||
|
return Some(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
picked_piece
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pick_piece(
|
fn pick_piece(
|
||||||
|
|||||||
@ -129,7 +129,11 @@ impl StructurePieceBase for SpiralStaircasePiece {
|
|||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
pieces_to_process,
|
pieces_to_process,
|
||||||
Some(StrongholdPieceType::FiveWayCrossing),
|
if self.is_structure_start {
|
||||||
|
Some(StrongholdPieceType::FiveWayCrossing)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@ use pumpkin_util::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
ProtoChunk,
|
ProtoChunk,
|
||||||
block::RawBlockState,
|
block::RawBlockState,
|
||||||
generation::{
|
generation::noise::{
|
||||||
chunk_noise::WATER_BLOCK, noise::perlin::DoublePerlinNoiseSampler,
|
WATER_BLOCK, perlin::DoublePerlinNoiseSampler,
|
||||||
noise::router::proto_noise_router::DoublePerlinNoiseBuilder,
|
router::proto_noise_router::DoublePerlinNoiseBuilder,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -152,12 +152,12 @@ impl SurfaceTerrainBuilder {
|
|||||||
if surface_y <= elevation_y {
|
if surface_y <= elevation_y {
|
||||||
for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
|
for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
|
||||||
let pos = Vector3::new(global_x, y, global_z);
|
let pos = Vector3::new(global_x, y, global_z);
|
||||||
let block_state = chunk.get_block_state(&pos).to_block_id();
|
let block_id = chunk.get_block_state(&pos).to_block_id();
|
||||||
if block_state == Block::get_raw_id_from_state_id(chunk.default_block.id) {
|
if block_id == Block::get_raw_id_from_state_id(chunk.default_block.id) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if block_state == WATER_BLOCK {
|
if block_id == Block::WATER {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
|||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
// use std::time::Duration;
|
// use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@ -38,7 +39,7 @@ use std::{
|
|||||||
use tokio::{
|
use tokio::{
|
||||||
select,
|
select,
|
||||||
sync::{
|
sync::{
|
||||||
Notify, RwLock,
|
RwLock,
|
||||||
mpsc::{self, UnboundedReceiver},
|
mpsc::{self, UnboundedReceiver},
|
||||||
oneshot,
|
oneshot,
|
||||||
},
|
},
|
||||||
@ -83,8 +84,7 @@ pub struct Level {
|
|||||||
tasks: TaskTracker,
|
tasks: TaskTracker,
|
||||||
pub chunk_system_tasks: TaskTracker,
|
pub chunk_system_tasks: TaskTracker,
|
||||||
/// Notification that interrupts tasks for shutdown
|
/// Notification that interrupts tasks for shutdown
|
||||||
pub shutdown_notifier: Notify,
|
pub cancel_token: CancellationToken,
|
||||||
pub is_shutting_down: AtomicBool,
|
|
||||||
|
|
||||||
pub shut_down_chunk_system: AtomicBool,
|
pub shut_down_chunk_system: AtomicBool,
|
||||||
pub should_save: AtomicBool,
|
pub should_save: AtomicBool,
|
||||||
@ -185,8 +185,7 @@ impl Level {
|
|||||||
chunk_watchers: Arc::new(DashMap::new()),
|
chunk_watchers: Arc::new(DashMap::new()),
|
||||||
tasks: TaskTracker::new(),
|
tasks: TaskTracker::new(),
|
||||||
chunk_system_tasks: TaskTracker::new(),
|
chunk_system_tasks: TaskTracker::new(),
|
||||||
shutdown_notifier: Notify::new(),
|
cancel_token: CancellationToken::new(),
|
||||||
is_shutting_down: AtomicBool::new(false),
|
|
||||||
shut_down_chunk_system: AtomicBool::new(false),
|
shut_down_chunk_system: AtomicBool::new(false),
|
||||||
should_save: AtomicBool::new(false),
|
should_save: AtomicBool::new(false),
|
||||||
should_unload: AtomicBool::new(false),
|
should_unload: AtomicBool::new(false),
|
||||||
@ -219,7 +218,7 @@ impl Level {
|
|||||||
builder
|
builder
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
while let Ok(pos) = rx.recv() {
|
while let Ok(pos) = rx.recv() {
|
||||||
if level_clone.is_shutting_down.load(Ordering::Relaxed) {
|
if level_clone.cancel_token.is_cancelled() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,58 +258,34 @@ impl Level {
|
|||||||
|
|
||||||
pub async fn shutdown(&self) {
|
pub async fn shutdown(&self) {
|
||||||
log::info!("Saving level...");
|
log::info!("Saving level...");
|
||||||
|
self.cancel_token.cancel();
|
||||||
self.is_shutting_down.store(true, Ordering::Relaxed);
|
|
||||||
self.shutdown_notifier.notify_waiters();
|
|
||||||
|
|
||||||
self.tasks.close();
|
|
||||||
log::debug!("Awaiting level tasks");
|
|
||||||
#[cfg(feature = "tokio_taskdump")]
|
|
||||||
match tokio::time::timeout(std::time::Duration::from_secs(30), self.tasks.wait()).await {
|
|
||||||
Ok(guard) => guard,
|
|
||||||
Err(_) => {
|
|
||||||
dump().await;
|
|
||||||
panic!("Timeout Awaiting level tasks");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.tasks.wait().await;
|
|
||||||
log::debug!("Done awaiting level chunk tasks");
|
|
||||||
|
|
||||||
self.shut_down_chunk_system.store(true, Ordering::Relaxed);
|
self.shut_down_chunk_system.store(true, Ordering::Relaxed);
|
||||||
self.level_channel.notify();
|
self.level_channel.notify();
|
||||||
|
|
||||||
let handles: Vec<_> = {
|
self.tasks.close();
|
||||||
let mut lock = self.thread_tracker.lock().unwrap();
|
|
||||||
log::info!("Shutting down {} jobs", lock.len());
|
|
||||||
lock.drain(..).collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
for handle in handles {
|
|
||||||
log::debug!(
|
|
||||||
"Waiting for thread {:?} ({}) to stop",
|
|
||||||
handle.thread().id(),
|
|
||||||
handle.thread().name().unwrap_or("unknown")
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(e) = handle.join() {
|
|
||||||
log::error!("Thread panicked during execution: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Wait chunk system tasks stop");
|
|
||||||
self.chunk_system_tasks.close();
|
self.chunk_system_tasks.close();
|
||||||
#[cfg(feature = "tokio_taskdump")]
|
|
||||||
match tokio::time::timeout(std::time::Duration::from_secs(30), self.tasks.wait()).await {
|
let handles = {
|
||||||
Ok(guard) => guard,
|
let mut lock = self.thread_tracker.lock().unwrap();
|
||||||
Err(_) => {
|
lock.drain(..).collect::<Vec<_>>()
|
||||||
dump().await;
|
|
||||||
panic!("Timeout Awaiting chunk_system_tasks tasks");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::info!("Joining {} synchronous threads...", handles.len());
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
for handle in handles {
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
log::debug!("Awaiting remaining async tasks...");
|
||||||
|
self.tasks.wait().await;
|
||||||
self.chunk_system_tasks.wait().await;
|
self.chunk_system_tasks.wait().await;
|
||||||
// wait for chunks currently saving in other
|
|
||||||
log::info!("Wait chunk saver to stop");
|
log::info!("Flushing savers to disk...");
|
||||||
self.chunk_saver.block_and_await_ongoing_tasks().await;
|
self.chunk_saver.block_and_await_ongoing_tasks().await;
|
||||||
|
self.entity_saver.block_and_await_ongoing_tasks().await;
|
||||||
|
|
||||||
// save all chunks currently in memory
|
// save all chunks currently in memory
|
||||||
// let chunks_to_write = self
|
// let chunks_to_write = self
|
||||||
@ -324,11 +299,6 @@ impl Level {
|
|||||||
// self.chunk_saver.clear_watched_chunks().await;
|
// self.chunk_saver.clear_watched_chunks().await;
|
||||||
// self.write_chunks(chunks_to_write).await;
|
// self.write_chunks(chunks_to_write).await;
|
||||||
|
|
||||||
log::debug!("Done awaiting level entity tasks");
|
|
||||||
|
|
||||||
// wait for chunks currently saving in other threads
|
|
||||||
self.entity_saver.block_and_await_ongoing_tasks().await;
|
|
||||||
|
|
||||||
// save all chunks currently in memory
|
// save all chunks currently in memory
|
||||||
let chunks_to_write = self
|
let chunks_to_write = self
|
||||||
.loaded_entity_chunks
|
.loaded_entity_chunks
|
||||||
@ -622,7 +592,7 @@ impl Level {
|
|||||||
let level = self.clone();
|
let level = self.clone();
|
||||||
|
|
||||||
self.spawn_task(async move {
|
self.spawn_task(async move {
|
||||||
let cancel_notifier = level.shutdown_notifier.notified();
|
let cancel_notifier = level.cancel_token.cancelled();
|
||||||
|
|
||||||
let fetch_task = async {
|
let fetch_task = async {
|
||||||
let mut to_fetch = Vec::new();
|
let mut to_fetch = Vec::new();
|
||||||
|
|||||||
@ -45,7 +45,16 @@ pub use generation::{
|
|||||||
proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting,
|
proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generation::{chunk_noise::CHUNK_DIM, proto_chunk::TerrainCache};
|
use crate::generation::{
|
||||||
|
biome_coords,
|
||||||
|
noise::{
|
||||||
|
CHUNK_DIM, ChunkNoiseGenerator,
|
||||||
|
aquifer_sampler::{FluidLevel, FluidLevelSampler},
|
||||||
|
},
|
||||||
|
positions::chunk_pos,
|
||||||
|
proto_chunk::TerrainCache,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn bench_create_and_populate_noise(
|
pub fn bench_create_and_populate_noise(
|
||||||
base_router: &ProtoNoiseRouters,
|
base_router: &ProtoNoiseRouters,
|
||||||
random_config: &GlobalRandomConfig,
|
random_config: &GlobalRandomConfig,
|
||||||
@ -54,16 +63,10 @@ pub fn bench_create_and_populate_noise(
|
|||||||
default_block: &'static BlockState,
|
default_block: &'static BlockState,
|
||||||
) {
|
) {
|
||||||
use crate::biome::hash_seed;
|
use crate::biome::hash_seed;
|
||||||
use crate::generation::chunk_noise::ChunkNoiseGenerator;
|
|
||||||
use crate::generation::noise::router::surface_height_sampler::{
|
use crate::generation::noise::router::surface_height_sampler::{
|
||||||
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
||||||
};
|
};
|
||||||
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
||||||
use crate::generation::{
|
|
||||||
aquifer_sampler::{FluidLevel, FluidLevelSampler},
|
|
||||||
biome_coords,
|
|
||||||
positions::chunk_pos,
|
|
||||||
};
|
|
||||||
|
|
||||||
let biome_mixer_seed = hash_seed(random_config.seed);
|
let biome_mixer_seed = hash_seed(random_config.seed);
|
||||||
let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed);
|
let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed);
|
||||||
@ -160,7 +163,6 @@ pub fn bench_create_and_populate_noise_with_surface(
|
|||||||
default_block: &'static BlockState,
|
default_block: &'static BlockState,
|
||||||
) {
|
) {
|
||||||
use crate::biome::hash_seed;
|
use crate::biome::hash_seed;
|
||||||
use crate::generation::chunk_noise::ChunkNoiseGenerator;
|
|
||||||
use crate::generation::noise::router::{
|
use crate::generation::noise::router::{
|
||||||
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
|
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
|
||||||
surface_height_sampler::{
|
surface_height_sampler::{
|
||||||
@ -168,11 +170,6 @@ pub fn bench_create_and_populate_noise_with_surface(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
||||||
use crate::generation::{
|
|
||||||
aquifer_sampler::{FluidLevel, FluidLevelSampler},
|
|
||||||
biome_coords,
|
|
||||||
positions::chunk_pos,
|
|
||||||
};
|
|
||||||
|
|
||||||
let biome_mixer_seed = hash_seed(random_config.seed);
|
let biome_mixer_seed = hash_seed(random_config.seed);
|
||||||
let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed);
|
let mut chunk = ProtoChunk::new(0, 0, &Dimension::OVERWORLD, default_block, biome_mixer_seed);
|
||||||
|
|||||||
@ -253,6 +253,11 @@ impl LevelData {
|
|||||||
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
|
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_pos(&mut self, x: i32, z: i32) {
|
||||||
|
self.spawn_x = x;
|
||||||
|
self.spawn_z = z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|||||||
@ -47,11 +47,11 @@ use pumpkin_protocol::{
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
|
sync::Mutex,
|
||||||
sync::mpsc::{Receiver, Sender},
|
sync::mpsc::{Receiver, Sender},
|
||||||
sync::{Mutex, Notify},
|
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tokio_util::task::TaskTracker;
|
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||||
|
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
@ -84,13 +84,8 @@ pub struct BedrockClient {
|
|||||||
output_split_number: AtomicU16,
|
output_split_number: AtomicU16,
|
||||||
output_sequenced_index: AtomicU32,
|
output_sequenced_index: AtomicU32,
|
||||||
output_ordered_index: AtomicU32,
|
output_ordered_index: AtomicU32,
|
||||||
|
|
||||||
/// An notifier that is triggered when this client is closed.
|
/// An notifier that is triggered when this client is closed.
|
||||||
close_interrupt: Arc<Notify>,
|
close_token: CancellationToken,
|
||||||
|
|
||||||
/// Indicates if the client connection is closed.
|
|
||||||
pub closed: Arc<AtomicBool>,
|
|
||||||
|
|
||||||
/// Store Fragments until the packet is complete
|
/// Store Fragments until the packet is complete
|
||||||
compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>,
|
compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>,
|
||||||
//input_sequence_number: AtomicU32,
|
//input_sequence_number: AtomicU32,
|
||||||
@ -121,23 +116,21 @@ impl BedrockClient {
|
|||||||
output_sequenced_index: AtomicU32::new(0),
|
output_sequenced_index: AtomicU32::new(0),
|
||||||
output_ordered_index: AtomicU32::new(0),
|
output_ordered_index: AtomicU32::new(0),
|
||||||
compounds: Arc::new(Mutex::new(HashMap::new())),
|
compounds: Arc::new(Mutex::new(HashMap::new())),
|
||||||
closed: Arc::new(AtomicBool::new(false)),
|
close_token: CancellationToken::new(),
|
||||||
close_interrupt: Arc::new(Notify::new()),
|
|
||||||
//input_sequence_number: AtomicU32::new(0),
|
//input_sequence_number: AtomicU32::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_outgoing_packet_task(&mut self) {
|
pub fn start_outgoing_packet_task(&mut self) {
|
||||||
let mut packet_receiver = self.outgoing_packet_queue_recv.take().unwrap();
|
let mut packet_receiver = self.outgoing_packet_queue_recv.take().unwrap();
|
||||||
let close_interrupt = self.close_interrupt.clone();
|
let close_token = self.close_token.clone();
|
||||||
let closed = self.closed.clone();
|
|
||||||
let writer = self.network_writer.clone();
|
let writer = self.network_writer.clone();
|
||||||
let addr = self.address;
|
let addr = self.address;
|
||||||
let socket = self.socket.clone();
|
let socket = self.socket.clone();
|
||||||
self.spawn_task(async move {
|
self.spawn_task(async move {
|
||||||
while !closed.load(Ordering::Relaxed) {
|
while !close_token.is_cancelled() {
|
||||||
let recv_result = tokio::select! {
|
let recv_result = tokio::select! {
|
||||||
() = close_interrupt.notified() => {
|
() = close_token.cancelled() => {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
recv_result = packet_receiver.recv() => {
|
recv_result = packet_receiver.recv() => {
|
||||||
@ -156,12 +149,11 @@ impl BedrockClient {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// It is expected that the packet will fail if we are closed
|
// It is expected that the packet will fail if we are closed
|
||||||
if !closed.load(Ordering::Relaxed) {
|
if !close_token.is_cancelled() {
|
||||||
log::warn!("Failed to send packet to client: {err}",);
|
log::warn!("Failed to send packet to client: {err}",);
|
||||||
// We now need to close the connection to the client since the stream is in an
|
// We now need to close the connection to the client since the stream is in an
|
||||||
// unknown state
|
// unknown state
|
||||||
close_interrupt.notify_waiters();
|
close_token.cancel();
|
||||||
closed.store(true, Ordering::Relaxed);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +200,7 @@ impl BedrockClient {
|
|||||||
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
||||||
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
|
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
|
||||||
// This is expected to fail if we are closed
|
// This is expected to fail if we are closed
|
||||||
if !self.closed.load(Ordering::Relaxed) {
|
if !self.is_closed() {
|
||||||
log::error!("Failed to add packet to the outgoing packet queue for client: {err}");
|
log::error!("Failed to add packet to the outgoing packet queue for client: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,18 +354,17 @@ impl BedrockClient {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// It is expected that the packet will fail if we are closed
|
// It is expected that the packet will fail if we are closed
|
||||||
if !self.closed.load(Ordering::Relaxed) {
|
if !self.is_closed() {
|
||||||
log::warn!("Failed to send packet to client: {err}");
|
log::warn!("Failed to send packet to client: {err}");
|
||||||
// We now need to close the connection to the client since the stream is in an
|
// We now need to close the connection to the client since the stream is in an
|
||||||
// unknown state
|
// unknown state
|
||||||
self.closed.store(true, Ordering::Relaxed);
|
self.close_token.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn close(&self) {
|
pub async fn close(&self) {
|
||||||
self.close_interrupt.notify_waiters();
|
self.close_token.cancel();
|
||||||
self.closed.store(true, Ordering::Relaxed);
|
|
||||||
self.tasks.close();
|
self.tasks.close();
|
||||||
self.tasks.wait().await;
|
self.tasks.wait().await;
|
||||||
self.be_clients.lock().await.remove(&self.address);
|
self.be_clients.lock().await.remove(&self.address);
|
||||||
@ -383,6 +374,10 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_closed(&self) -> bool {
|
||||||
|
self.close_token.is_cancelled()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_ack(&self, ack: &Ack) {
|
pub async fn send_ack(&self, ack: &Ack) {
|
||||||
let mut packet_buf = Vec::new();
|
let mut packet_buf = Vec::new();
|
||||||
ack.write(&mut packet_buf).unwrap();
|
ack.write(&mut packet_buf).unwrap();
|
||||||
@ -640,7 +635,7 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn await_close_interrupt(&self) {
|
pub async fn await_close_interrupt(&self) {
|
||||||
self.close_interrupt.notified().await;
|
self.close_token.cancelled().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_packet_payload(&self, packet: Cursor<Vec<u8>>) -> Option<Bytes> {
|
pub async fn get_packet_payload(&self, packet: Cursor<Vec<u8>>) -> Option<Bytes> {
|
||||||
@ -671,7 +666,7 @@ impl BedrockClient {
|
|||||||
F: Future + Send + 'static,
|
F: Future + Send + 'static,
|
||||||
F::Output: Send + 'static,
|
F::Output: Send + 'static,
|
||||||
{
|
{
|
||||||
if self.closed.load(Ordering::Relaxed) {
|
if self.close_token.is_cancelled() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(self.tasks.spawn(task))
|
Some(self.tasks.spawn(task))
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::{io::Write, sync::Arc};
|
use std::{io::Write, sync::Arc};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -72,8 +71,6 @@ pub struct JavaClient {
|
|||||||
pub server_address: Mutex<String>,
|
pub server_address: Mutex<String>,
|
||||||
/// The current connection state of the client (e.g., Handshaking, Status, Play).
|
/// The current connection state of the client (e.g., Handshaking, Status, Play).
|
||||||
pub connection_state: AtomicCell<ConnectionState>,
|
pub connection_state: AtomicCell<ConnectionState>,
|
||||||
/// Indicates if the client connection is closed.
|
|
||||||
pub closed: Arc<AtomicBool>,
|
|
||||||
/// The client's IP address.
|
/// The client's IP address.
|
||||||
pub address: Mutex<SocketAddr>,
|
pub address: Mutex<SocketAddr>,
|
||||||
/// The client's brand or modpack information, Optional.
|
/// The client's brand or modpack information, Optional.
|
||||||
@ -105,7 +102,6 @@ impl JavaClient {
|
|||||||
server_address: Mutex::new(String::new()),
|
server_address: Mutex::new(String::new()),
|
||||||
address: Mutex::new(address),
|
address: Mutex::new(address),
|
||||||
connection_state: AtomicCell::new(ConnectionState::HandShake),
|
connection_state: AtomicCell::new(ConnectionState::HandShake),
|
||||||
closed: Arc::new(AtomicBool::new(false)),
|
|
||||||
close_token: CancellationToken::new(),
|
close_token: CancellationToken::new(),
|
||||||
tasks: TaskTracker::new(),
|
tasks: TaskTracker::new(),
|
||||||
outgoing_packet_queue_send: send,
|
outgoing_packet_queue_send: send,
|
||||||
@ -187,7 +183,7 @@ impl JavaClient {
|
|||||||
F: Future + Send + 'static,
|
F: Future + Send + 'static,
|
||||||
F::Output: Send + 'static,
|
F::Output: Send + 'static,
|
||||||
{
|
{
|
||||||
if self.closed.load(Ordering::Relaxed) {
|
if self.close_token.is_cancelled() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(self.tasks.spawn(task))
|
Some(self.tasks.spawn(task))
|
||||||
@ -210,7 +206,7 @@ impl JavaClient {
|
|||||||
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
||||||
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
|
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
|
||||||
// This is expected to fail if we are closed
|
// This is expected to fail if we are closed
|
||||||
if !self.closed.load(Ordering::Relaxed) {
|
if !self.close_token.is_cancelled() {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Failed to add packet to the outgoing packet queue for client {}: {}",
|
"Failed to add packet to the outgoing packet queue for client {}: {}",
|
||||||
self.id,
|
self.id,
|
||||||
@ -283,7 +279,7 @@ impl JavaClient {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// It is expected that the packet will fail if we are closed
|
// It is expected that the packet will fail if we are closed
|
||||||
if !self.closed.load(Ordering::Relaxed) {
|
if !self.close_token.is_cancelled() {
|
||||||
log::warn!("Failed to send packet to client {}: {}", self.id, err);
|
log::warn!("Failed to send packet to client {}: {}", self.id, err);
|
||||||
// We now need to close the connection to the client since the stream is in an
|
// We now need to close the connection to the client since the stream is in an
|
||||||
// unknown state
|
// unknown state
|
||||||
@ -406,14 +402,13 @@ impl JavaClient {
|
|||||||
.outgoing_packet_queue_recv
|
.outgoing_packet_queue_recv
|
||||||
.take()
|
.take()
|
||||||
.expect("This was set in the new fn");
|
.expect("This was set in the new fn");
|
||||||
let close_interrupt = self.close_token.clone();
|
let close_token = self.close_token.clone();
|
||||||
let closed = self.closed.clone();
|
|
||||||
let writer = self.network_writer.clone();
|
let writer = self.network_writer.clone();
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
self.spawn_task(async move {
|
self.spawn_task(async move {
|
||||||
while !closed.load(Ordering::Relaxed) {
|
while !close_token.is_cancelled() {
|
||||||
let recv_result = tokio::select! {
|
let recv_result = tokio::select! {
|
||||||
() = close_interrupt.cancelled() => None,
|
() = close_token.cancelled() => None,
|
||||||
res = packet_receiver.recv() => res,
|
res = packet_receiver.recv() => res,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -423,12 +418,11 @@ impl JavaClient {
|
|||||||
|
|
||||||
if let Err(err) = writer.lock().await.write_packet(packet_data).await {
|
if let Err(err) = writer.lock().await.write_packet(packet_data).await {
|
||||||
// It is expected that the packet will fail if we are closed
|
// It is expected that the packet will fail if we are closed
|
||||||
if !closed.load(Ordering::Relaxed) {
|
if !close_token.is_cancelled() {
|
||||||
log::warn!("Failed to send packet to client {id}: {err}",);
|
log::warn!("Failed to send packet to client {id}: {err}",);
|
||||||
// We now need to close the connection to the client since the stream is in an
|
// We now need to close the connection to the client since the stream is in an
|
||||||
// unknown state
|
// unknown state
|
||||||
close_interrupt.cancel();
|
close_token.cancel();
|
||||||
closed.store(true, Ordering::Relaxed);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,7 +441,10 @@ impl JavaClient {
|
|||||||
/// This function does not attempt to send any disconnect packets to the client.
|
/// This function does not attempt to send any disconnect packets to the client.
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
self.close_token.cancel();
|
self.close_token.cancel();
|
||||||
self.closed.store(true, Ordering::Relaxed);
|
}
|
||||||
|
|
||||||
|
pub fn is_closed(&self) -> bool {
|
||||||
|
self.close_token.is_cancelled()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_login_packet(
|
async fn handle_login_packet(
|
||||||
|
|||||||
@ -129,8 +129,8 @@ impl ClientPlatform {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn closed(&self) -> bool {
|
pub fn closed(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Java(java) => java.closed.load(Ordering::Relaxed),
|
Self::Java(java) => java.is_closed(),
|
||||||
Self::Bedrock(bedrock) => bedrock.closed.load(Ordering::Relaxed),
|
Self::Bedrock(bedrock) => bedrock.is_closed(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,9 +25,10 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
let new_chunk_center = entity.chunk_pos.load();
|
let new_chunk_center = entity.chunk_pos.load();
|
||||||
let old_cylindrical = player.watched_section.load();
|
let old_cylindrical = player.watched_section.load();
|
||||||
|
|
||||||
if old_cylindrical.center == new_chunk_center {
|
// This does break when a new player spawns
|
||||||
return;
|
// if old_cylindrical.center == new_chunk_center {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
let view_distance = get_view_distance(player).await;
|
let view_distance = get_view_distance(player).await;
|
||||||
let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance);
|
let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user