fix: structures
This commit is contained in:
parent
3392aa164f
commit
d41e5b9b98
@ -477,7 +477,7 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
assert_eq_delta,
|
assert_eq_delta,
|
||||||
noise::perlin::{OctavePerlinNoiseSampler, PerlinNoiseSampler},
|
noise::perlin::{OctavePerlinNoiseSampler, PerlinNoiseSampler},
|
||||||
random::{RandomDeriverImpl, RandomImpl, legacy_rand::LegacyRand, xoroshiro128::Xoroshiro},
|
random::{RandomImpl, legacy_rand::LegacyRand, xoroshiro128::Xoroshiro},
|
||||||
read_data_from_file,
|
read_data_from_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -126,6 +126,13 @@ impl Xoroshiro {
|
|||||||
self.hi = m.rotate_left(28);
|
self.hi = m.rotate_left(28);
|
||||||
n
|
n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn next_splitter(&mut self) -> XoroshiroSplitter {
|
||||||
|
XoroshiroSplitter {
|
||||||
|
lo: self.next_random(),
|
||||||
|
hi: self.next_random(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GaussianGenerator for Xoroshiro {
|
impl GaussianGenerator for Xoroshiro {
|
||||||
@ -160,10 +167,7 @@ impl RandomImpl for Xoroshiro {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn next_splitter(&mut self) -> RandomDeriver {
|
fn next_splitter(&mut self) -> RandomDeriver {
|
||||||
RandomDeriver::Xoroshiro(XoroshiroSplitter {
|
RandomDeriver::Xoroshiro(self.next_splitter())
|
||||||
lo: self.next_random(),
|
|
||||||
hi: self.next_random(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_i32(&mut self) -> i32 {
|
fn next_i32(&mut self) -> i32 {
|
||||||
@ -220,25 +224,36 @@ pub struct XoroshiroSplitter {
|
|||||||
hi: u64,
|
hi: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RandomDeriverImpl for XoroshiroSplitter {
|
impl XoroshiroSplitter {
|
||||||
fn split_string(&self, seed: &str) -> RandomGenerator {
|
#[must_use]
|
||||||
|
pub fn split_string(&self, seed: &str) -> Xoroshiro {
|
||||||
let bytes = md5::compute(seed.as_bytes());
|
let bytes = md5::compute(seed.as_bytes());
|
||||||
let l = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length"));
|
let l = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length"));
|
||||||
let m = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length"));
|
let m = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length"));
|
||||||
|
|
||||||
RandomGenerator::Xoroshiro(Xoroshiro::new(l ^ self.lo, m ^ self.hi))
|
Xoroshiro::new(l ^ self.lo, m ^ self.hi)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn split_pos(&self, x: i32, y: i32, z: i32) -> Xoroshiro {
|
||||||
|
let l = hash_block_pos(x, y, z) as u64;
|
||||||
|
let m = l ^ self.lo;
|
||||||
|
|
||||||
|
Xoroshiro::new(m, self.hi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RandomDeriverImpl for XoroshiroSplitter {
|
||||||
|
fn split_string(&self, seed: &str) -> RandomGenerator {
|
||||||
|
RandomGenerator::Xoroshiro(self.split_string(seed))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_u64(&self, seed: u64) -> RandomGenerator {
|
fn split_u64(&self, seed: u64) -> RandomGenerator {
|
||||||
RandomGenerator::Xoroshiro(Xoroshiro::new(seed ^ self.lo, seed ^ self.hi))
|
RandomGenerator::Xoroshiro(Xoroshiro::new(seed ^ self.lo, seed ^ self.hi))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::many_single_char_names)]
|
|
||||||
fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator {
|
fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator {
|
||||||
let l = hash_block_pos(x, y, z) as u64;
|
RandomGenerator::Xoroshiro(self.split_pos(x, y, z))
|
||||||
let m = l ^ self.lo;
|
|
||||||
|
|
||||||
RandomGenerator::Xoroshiro(Xoroshiro::new(m, self.hi))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use pumpkin_world::{
|
|||||||
|
|
||||||
fn bench_terrain_gen(c: &mut Criterion) {
|
fn bench_terrain_gen(c: &mut Criterion) {
|
||||||
let seed = 0;
|
let seed = 0;
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
let base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
let surface_config = GenerationSettings::from_dimension(&Dimension::OVERWORLD);
|
let surface_config = GenerationSettings::from_dimension(&Dimension::OVERWORLD);
|
||||||
let terrain_cache = TerrainCache::from_random(&random_config);
|
let terrain_cache = TerrainCache::from_random(&random_config);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use std::hint::black_box;
|
|||||||
|
|
||||||
fn bench_noise_router_creation(c: &mut Criterion) {
|
fn bench_noise_router_creation(c: &mut Criterion) {
|
||||||
let base_routers = &OVERWORLD_BASE_NOISE_ROUTER;
|
let base_routers = &OVERWORLD_BASE_NOISE_ROUTER;
|
||||||
let random_config = GlobalRandomConfig::new(0, false);
|
let random_config = GlobalRandomConfig::new(0);
|
||||||
|
|
||||||
let proto_routers = ProtoNoiseRouters::generate(base_routers, &random_config);
|
let proto_routers = ProtoNoiseRouters::generate(base_routers, &random_config);
|
||||||
let proto_noise_router = proto_routers.noise;
|
let proto_noise_router = proto_routers.noise;
|
||||||
|
|||||||
@ -73,7 +73,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn biome_desert() {
|
fn biome_desert() {
|
||||||
let seed = 13579;
|
let seed = 13579;
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let noise_router =
|
let noise_router =
|
||||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(1, 1, 1);
|
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(1, 1, 1);
|
||||||
@ -101,7 +101,7 @@ mod test {
|
|||||||
read_data_from_file!("../../assets/biome_no_blend_no_beard_0.json");
|
read_data_from_file!("../../assets/biome_no_blend_no_beard_0.json");
|
||||||
|
|
||||||
let seed = 0;
|
let seed = 0;
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let noise_router =
|
let noise_router =
|
||||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
let surface_settings = GenerationSettings::from_dimension(&Dimension::OVERWORLD);
|
let surface_settings = GenerationSettings::from_dimension(&Dimension::OVERWORLD);
|
||||||
|
|||||||
@ -66,7 +66,7 @@ mod test {
|
|||||||
let chunk_x = 0;
|
let chunk_x = 0;
|
||||||
let chunk_z = 0;
|
let chunk_z = 0;
|
||||||
|
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let noise_router =
|
let noise_router =
|
||||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ mod test {
|
|||||||
read_data_from_file!("../../assets/multi_noise_biome_source_test.json");
|
read_data_from_file!("../../assets/multi_noise_biome_source_test.json");
|
||||||
|
|
||||||
let seed = 0;
|
let seed = 0;
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let noise_router =
|
let noise_router =
|
||||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,11 @@ use slotmap::Key;
|
|||||||
|
|
||||||
pub struct ChunkHolder {
|
pub struct ChunkHolder {
|
||||||
pub target_stage: StagedChunkEnum,
|
pub target_stage: StagedChunkEnum,
|
||||||
|
/// Minimum stage required by generation dependencies (e.g. StructureReferences neighbors).
|
||||||
|
/// May exceed `target_stage` when this chunk is needed by a neighbor's generation task
|
||||||
|
/// but isn't in the player's view radius. Tasks up to this stage are scheduled but the
|
||||||
|
/// chunk is not made public and is unloaded once the dependency is satisfied.
|
||||||
|
pub dependency_stage: StagedChunkEnum,
|
||||||
pub current_stage: StagedChunkEnum,
|
pub current_stage: StagedChunkEnum,
|
||||||
pub chunk: Option<Chunk>,
|
pub chunk: Option<Chunk>,
|
||||||
pub occupied: NodeKey,
|
pub occupied: NodeKey,
|
||||||
@ -16,6 +21,7 @@ impl Default for ChunkHolder {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
target_stage: StagedChunkEnum::None,
|
target_stage: StagedChunkEnum::None,
|
||||||
|
dependency_stage: StagedChunkEnum::None,
|
||||||
current_stage: StagedChunkEnum::None,
|
current_stage: StagedChunkEnum::None,
|
||||||
chunk: None,
|
chunk: None,
|
||||||
occupied: NodeKey::null(),
|
occupied: NodeKey::null(),
|
||||||
|
|||||||
@ -115,7 +115,7 @@ impl StagedChunkEnum {
|
|||||||
match self {
|
match self {
|
||||||
Self::Empty => 0,
|
Self::Empty => 0,
|
||||||
Self::StructureStart => 0,
|
Self::StructureStart => 0,
|
||||||
Self::StructureReferences => 0,
|
Self::StructureReferences => 8,
|
||||||
Self::Biomes => 0,
|
Self::Biomes => 0,
|
||||||
Self::Noise => 0,
|
Self::Noise => 0,
|
||||||
Self::Surface => 0,
|
Self::Surface => 0,
|
||||||
@ -131,7 +131,7 @@ impl StagedChunkEnum {
|
|||||||
match self {
|
match self {
|
||||||
Self::Empty => 0,
|
Self::Empty => 0,
|
||||||
Self::StructureStart => 0,
|
Self::StructureStart => 0,
|
||||||
Self::StructureReferences => 0,
|
Self::StructureReferences => 8,
|
||||||
Self::Biomes => 0,
|
Self::Biomes => 0,
|
||||||
Self::Noise => 0,
|
Self::Noise => 0,
|
||||||
Self::Surface => 0,
|
Self::Surface => 0,
|
||||||
@ -148,7 +148,17 @@ impl StagedChunkEnum {
|
|||||||
// the Biome Step, this should be more efficient
|
// the Biome Step, this should be more efficient
|
||||||
Self::Biomes => &[Self::Empty],
|
Self::Biomes => &[Self::Empty],
|
||||||
Self::StructureStart => &[Self::Biomes],
|
Self::StructureStart => &[Self::Biomes],
|
||||||
Self::StructureReferences => &[Self::StructureStart],
|
Self::StructureReferences => &[
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
Self::StructureStart,
|
||||||
|
],
|
||||||
Self::Noise => &[Self::StructureReferences],
|
Self::Noise => &[Self::StructureReferences],
|
||||||
Self::Surface => &[Self::Noise],
|
Self::Surface => &[Self::Noise],
|
||||||
Self::Features => &[Self::Surface, Self::Surface],
|
Self::Features => &[Self::Surface, Self::Surface],
|
||||||
|
|||||||
@ -59,6 +59,11 @@ pub struct GenerationSchedule {
|
|||||||
chunk_map: HashMap<ChunkPos, ChunkHolder>,
|
chunk_map: HashMap<ChunkPos, ChunkHolder>,
|
||||||
unload_chunks: HashSetType<ChunkPos>,
|
unload_chunks: HashSetType<ChunkPos>,
|
||||||
|
|
||||||
|
/// Tasks that are graph-ready (in_degree == 0) but cannot yet run because
|
||||||
|
/// one or more of their required neighbor chunks haven't been delivered yet.
|
||||||
|
/// Parked here and re-queued by `check_waiting_tasks()` as chunk data arrives.
|
||||||
|
waiting_for_chunks: HashSetType<NodeKey>,
|
||||||
|
|
||||||
io_lock: IOLock,
|
io_lock: IOLock,
|
||||||
running_task_count: u16,
|
running_task_count: u16,
|
||||||
recv_chunk: crossfire::compat::MRx<(ChunkPos, RecvChunk)>,
|
recv_chunk: crossfire::compat::MRx<(ChunkPos, RecvChunk)>,
|
||||||
@ -83,9 +88,6 @@ impl GenerationSchedule {
|
|||||||
let (send_read_io, recv_read_io) =
|
let (send_read_io, recv_read_io) =
|
||||||
crossfire::compat::mpmc::bounded_tx_blocking_rx_async(io_read_thread_count + 5);
|
crossfire::compat::mpmc::bounded_tx_blocking_rx_async(io_read_thread_count + 5);
|
||||||
|
|
||||||
// Use a bounded single-producer/single-consumer channel to prevent unbounded memory growth
|
|
||||||
// when IO can't keep up. If the queue reaches capacity, producers will block until
|
|
||||||
// the disk catches up.
|
|
||||||
let (send_write_io, recv_write_io) =
|
let (send_write_io, recv_write_io) =
|
||||||
crossfire::compat::spsc::bounded_tx_blocking_rx_async(500);
|
crossfire::compat::spsc::bounded_tx_blocking_rx_async(500);
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ impl GenerationSchedule {
|
|||||||
let level_clone = level.clone();
|
let level_clone = level.clone();
|
||||||
|
|
||||||
let handle = thread::Builder::new()
|
let handle = thread::Builder::new()
|
||||||
.name(format!("Gen-{i}")) // Identifying dim helps debugging
|
.name(format!("Gen-{i}"))
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
generation_work(recv_gen, send_chunk, level_clone);
|
generation_work(recv_gen, send_chunk, level_clone);
|
||||||
})
|
})
|
||||||
@ -136,6 +138,7 @@ impl GenerationSchedule {
|
|||||||
send_level: level_channel,
|
send_level: level_channel,
|
||||||
public_chunk_map: level_sched.loaded_chunks.clone(),
|
public_chunk_map: level_sched.loaded_chunks.clone(),
|
||||||
unload_chunks: HashSetType::default(),
|
unload_chunks: HashSetType::default(),
|
||||||
|
waiting_for_chunks: HashSetType::default(),
|
||||||
io_lock,
|
io_lock,
|
||||||
running_task_count: 0,
|
running_task_count: 0,
|
||||||
recv_chunk,
|
recv_chunk,
|
||||||
@ -157,26 +160,22 @@ impl GenerationSchedule {
|
|||||||
match self.lighting_config {
|
match self.lighting_config {
|
||||||
LightingEngineConfig::Full => {
|
LightingEngineConfig::Full => {
|
||||||
let mut engine = chunk.light_engine.lock().unwrap();
|
let mut engine = chunk.light_engine.lock().unwrap();
|
||||||
|
|
||||||
for section in &mut engine.block_light {
|
for section in &mut engine.block_light {
|
||||||
section.fill(15);
|
section.fill(15);
|
||||||
}
|
}
|
||||||
for section in &mut engine.sky_light {
|
for section in &mut engine.sky_light {
|
||||||
section.fill(15);
|
section.fill(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.dirty.store(true, Relaxed);
|
chunk.dirty.store(true, Relaxed);
|
||||||
}
|
}
|
||||||
LightingEngineConfig::Dark => {
|
LightingEngineConfig::Dark => {
|
||||||
let mut engine = chunk.light_engine.lock().unwrap();
|
let mut engine = chunk.light_engine.lock().unwrap();
|
||||||
|
|
||||||
for section in &mut engine.block_light {
|
for section in &mut engine.block_light {
|
||||||
section.fill(0);
|
section.fill(0);
|
||||||
}
|
}
|
||||||
for section in &mut engine.sky_light {
|
for section in &mut engine.sky_light {
|
||||||
section.fill(0);
|
section.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.dirty.store(true, Relaxed);
|
chunk.dirty.store(true, Relaxed);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -222,8 +221,13 @@ impl GenerationSchedule {
|
|||||||
self.queue = new_queue;
|
self.queue = new_queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the dependency chain for `req_stage` exists on `holder` (for chunk at
|
/// Ensure that the dependency chain for `req_stage` exists on `holder` (for chunk at
|
||||||
// `chunk_pos`) and wire it to depend on `dependency_task`.
|
/// `chunk_pos`) and wire it to depend on `dependency_task`.
|
||||||
|
///
|
||||||
|
/// Bumps `holder.dependency_stage` (NOT `target_stage`) to at least `req_stage` so
|
||||||
|
/// that neighbor chunks pulled in as generation dependencies are not discarded before
|
||||||
|
/// their dependency is satisfied. `target_stage` is left alone so the level-change
|
||||||
|
/// bookkeeping invariant (`old_stage == holder.target_stage`) is never violated.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn ensure_dependency_chain(
|
pub(crate) fn ensure_dependency_chain(
|
||||||
graph: &mut DAG,
|
graph: &mut DAG,
|
||||||
@ -245,13 +249,24 @@ impl GenerationSchedule {
|
|||||||
graph.add_edge(holder.occupied, dependency_task);
|
graph.add_edge(holder.occupied, dependency_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
if holder.current_stage >= req_stage {
|
// Bump dependency_stage so this chunk's IO/generation tasks are scheduled and
|
||||||
return;
|
// kept alive even if target_stage is None (outside player view radius).
|
||||||
|
// We deliberately do NOT touch target_stage — that field is owned by resort_work
|
||||||
|
// and must match the level-change bookkeeping or the debug_assert will fire.
|
||||||
|
if holder.dependency_stage < req_stage {
|
||||||
|
holder.dependency_stage = req_stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Effective target is the max of what the player wants and what dependencies need.
|
||||||
|
let effective_target = holder.target_stage.max(holder.dependency_stage);
|
||||||
|
|
||||||
|
// Create any missing tasks from current_stage+1 up to effective_target.
|
||||||
|
// We do this even when current_stage >= req_stage, because dependency_stage may
|
||||||
|
// require tasks beyond req_stage that haven't been created yet.
|
||||||
|
if holder.current_stage < effective_target {
|
||||||
let empty = StagedChunkEnum::Empty as usize;
|
let empty = StagedChunkEnum::Empty as usize;
|
||||||
let start = (holder.current_stage as usize + 1).max(empty);
|
let start = (holder.current_stage as usize + 1).max(empty);
|
||||||
let end = req_stage as u8 as usize;
|
let end = effective_target as u8 as usize;
|
||||||
let mut newly_created = [false; StagedChunkEnum::COUNT];
|
let mut newly_created = [false; StagedChunkEnum::COUNT];
|
||||||
|
|
||||||
for (i, flag) in newly_created[start..=end].iter_mut().enumerate() {
|
for (i, flag) in newly_created[start..=end].iter_mut().enumerate() {
|
||||||
@ -306,29 +321,85 @@ impl GenerationSchedule {
|
|||||||
entry_task,
|
entry_task,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ano_task = holder.tasks[end];
|
// If req_stage is already satisfied, dependency_task doesn't need to wait —
|
||||||
|
// it was only blocked on `occupied` (handled above) and the stage itself is done.
|
||||||
|
// Do NOT add an edge here: tasks[req_stage] is null (completed and dropped).
|
||||||
|
if holder.current_stage >= req_stage {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire req_stage task → dependency_task so dependency_task can't run until
|
||||||
|
// this chunk reaches req_stage. tasks[req_stage] is guaranteed non-null here:
|
||||||
|
// effective_target >= req_stage (we just set dependency_stage = req_stage) and
|
||||||
|
// current_stage < req_stage, so the task was created in the loop above (or
|
||||||
|
// already existed).
|
||||||
|
let req_end = req_stage as u8 as usize;
|
||||||
|
let ano_task = holder.tasks[req_end];
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!ano_task.is_null(),
|
!ano_task.is_null(),
|
||||||
"holder.tasks[end] must not be null before adding edge"
|
"holder.tasks[req_stage] must not be null before adding edge"
|
||||||
);
|
);
|
||||||
graph.add_edge(ano_task, dependency_task);
|
graph.add_edge(ano_task, dependency_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if any tasks parked in `waiting_for_chunks` now have all their neighbor
|
||||||
|
/// chunk data available, and re-queue them if so.
|
||||||
|
/// Must be called after every `receive_chunk` call.
|
||||||
|
fn check_waiting_tasks(&mut self) {
|
||||||
|
if self.waiting_for_chunks.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut now_ready: Vec<NodeKey> = Vec::new();
|
||||||
|
|
||||||
|
self.waiting_for_chunks.retain(|&node_key| {
|
||||||
|
let Some(node) = self.graph.nodes.get(node_key) else {
|
||||||
|
return false; // node was dropped, discard silently
|
||||||
|
};
|
||||||
|
let write_radius = node.stage.get_write_radius();
|
||||||
|
let pos = node.pos;
|
||||||
|
let all_ready = (-write_radius..=write_radius).all(|dx| {
|
||||||
|
(-write_radius..=write_radius).all(|dy| {
|
||||||
|
self.chunk_map
|
||||||
|
.get(&pos.add_raw(dx, dy))
|
||||||
|
.is_some_and(|h| h.chunk.is_some())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if all_ready {
|
||||||
|
now_ready.push(node_key);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for node_key in now_ready {
|
||||||
|
if let Some(n) = self.graph.nodes.get_mut(node_key)
|
||||||
|
&& n.in_degree == 0
|
||||||
|
&& !n.in_queue
|
||||||
|
{
|
||||||
|
n.in_queue = true;
|
||||||
|
let priority =
|
||||||
|
Self::calc_priority(&self.last_level, &self.last_high_priority, n.pos, n.stage);
|
||||||
|
self.queue.push(TaskHeapNode(priority, node_key));
|
||||||
|
}
|
||||||
|
// If in_degree > 0, drop_node will re-queue when unblocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resort_work(&mut self, new_data: (Option<LevelChange>, Option<Vec<ChunkPos>>)) -> bool {
|
fn resort_work(&mut self, new_data: (Option<LevelChange>, Option<Vec<ChunkPos>>)) -> bool {
|
||||||
// true -> updated | false -> not update
|
|
||||||
if new_data.0.is_none() && new_data.1.is_none() {
|
if new_data.0.is_none() && new_data.1.is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if let Some(high_priority) = new_data.1 {
|
if let Some(high_priority) = new_data.1 {
|
||||||
// debug!("receive new priority");
|
|
||||||
self.last_high_priority = high_priority;
|
self.last_high_priority = high_priority;
|
||||||
}
|
}
|
||||||
let Some(new_level) = new_data.0 else {
|
let Some(new_level) = new_data.0 else {
|
||||||
self.sort_queue();
|
self.sort_queue();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
// debug!("receive new level");
|
|
||||||
for (pos, (old_stage, new_stage)) in new_level.0 {
|
for (pos, (old_stage, new_stage)) in new_level.0 {
|
||||||
debug_assert_ne!(old_stage, new_stage);
|
debug_assert_ne!(old_stage, new_stage);
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
@ -340,15 +411,25 @@ impl GenerationSchedule {
|
|||||||
let mut holder = self.chunk_map.remove(&pos).unwrap_or_default();
|
let mut holder = self.chunk_map.remove(&pos).unwrap_or_default();
|
||||||
debug_assert_eq!(holder.target_stage, old_stage);
|
debug_assert_eq!(holder.target_stage, old_stage);
|
||||||
holder.target_stage = new_stage;
|
holder.target_stage = new_stage;
|
||||||
if old_stage > new_stage {
|
|
||||||
for i in (new_stage.max(holder.current_stage) as usize + 1)..=(old_stage as usize) {
|
// Effective target is what we actually need to schedule tasks up to.
|
||||||
|
let effective_old = old_stage.max(holder.dependency_stage);
|
||||||
|
let effective_new = new_stage.max(holder.dependency_stage);
|
||||||
|
|
||||||
|
if effective_old > effective_new {
|
||||||
|
for i in (effective_new.max(holder.current_stage) as usize + 1)
|
||||||
|
..=(effective_old as usize)
|
||||||
|
{
|
||||||
let task = &mut holder.tasks[i];
|
let task = &mut holder.tasks[i];
|
||||||
if !task.is_null() {
|
if !task.is_null() {
|
||||||
self.drop_node(*task); // Properly handles incoming/outgoing edges and in_degree
|
self.waiting_for_chunks.remove(task);
|
||||||
|
self.drop_node(*task);
|
||||||
*task = NodeKey::null();
|
*task = NodeKey::null();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if new_stage == StagedChunkEnum::None {
|
if new_stage == StagedChunkEnum::None
|
||||||
|
&& holder.dependency_stage == StagedChunkEnum::None
|
||||||
|
{
|
||||||
self.unload_chunks.insert(pos);
|
self.unload_chunks.insert(pos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -366,7 +447,8 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i in (old_stage.max(holder.current_stage) as u8 + 1)..=(new_stage as u8) {
|
for i in (effective_old.max(holder.current_stage) as u8 + 1)..=(effective_new as u8)
|
||||||
|
{
|
||||||
let task = &mut holder.tasks[i as usize];
|
let task = &mut holder.tasks[i as usize];
|
||||||
if task.is_null() {
|
if task.is_null() {
|
||||||
*task = self.graph.nodes.insert(Node::new(pos, i.into()));
|
*task = self.graph.nodes.insert(Node::new(pos, i.into()));
|
||||||
@ -412,7 +494,7 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let node = self.graph.nodes.get_mut(task).unwrap();
|
let node = self.graph.nodes.get_mut(task).unwrap();
|
||||||
if node.in_degree == 0 {
|
if node.in_degree == 0 && !node.in_queue {
|
||||||
node.in_queue = true;
|
node.in_queue = true;
|
||||||
self.queue.push(TaskHeapNode(0, task));
|
self.queue.push(TaskHeapNode(0, task));
|
||||||
}
|
}
|
||||||
@ -446,7 +528,6 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
let sc = Arc::strong_count(&chunk);
|
let sc = Arc::strong_count(&chunk);
|
||||||
if sc == 1 {
|
if sc == 1 {
|
||||||
// debug!("unload chunk {pos:?} to file");
|
|
||||||
chunks.push((pos, Chunk::Level(chunk)));
|
chunks.push((pos, Chunk::Level(chunk)));
|
||||||
self.chunk_map.remove(&pos);
|
self.chunk_map.remove(&pos);
|
||||||
} else {
|
} else {
|
||||||
@ -460,14 +541,12 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
Chunk::Proto(chunk) => {
|
Chunk::Proto(chunk) => {
|
||||||
debug_assert!(!holder.public);
|
debug_assert!(!holder.public);
|
||||||
// debug!("unload proto chunk {pos:?} to file");
|
|
||||||
chunks.push((pos, Chunk::Proto(chunk)));
|
chunks.push((pos, Chunk::Proto(chunk)));
|
||||||
self.chunk_map.remove(&pos);
|
self.chunk_map.remove(&pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// debug!("send {} unloaded chunks to io write", chunks.len());
|
|
||||||
if chunks.is_empty() {
|
if chunks.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -490,7 +569,6 @@ impl GenerationSchedule {
|
|||||||
if let Some(chunk) = &holder.chunk {
|
if let Some(chunk) = &holder.chunk {
|
||||||
match chunk {
|
match chunk {
|
||||||
Chunk::Level(sync_chunk) => {
|
Chunk::Level(sync_chunk) => {
|
||||||
// Only save level chunks that are marked dirty
|
|
||||||
if sync_chunk.is_dirty() {
|
if sync_chunk.is_dirty() {
|
||||||
chunks.push((*pos, Chunk::Level(sync_chunk.clone())));
|
chunks.push((*pos, Chunk::Level(sync_chunk.clone())));
|
||||||
}
|
}
|
||||||
@ -528,7 +606,6 @@ impl GenerationSchedule {
|
|||||||
let Some(old) = self.graph.nodes.remove(node) else {
|
let Some(old) = self.graph.nodes.remove(node) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// debug!("drop node {node:?}");
|
|
||||||
let mut edge = old.edge;
|
let mut edge = old.edge;
|
||||||
while !edge.is_null() {
|
while !edge.is_null() {
|
||||||
let cur = self.graph.edges.remove(edge).unwrap();
|
let cur = self.graph.edges.remove(edge).unwrap();
|
||||||
@ -536,6 +613,9 @@ impl GenerationSchedule {
|
|||||||
debug_assert!(node.in_degree >= 1);
|
debug_assert!(node.in_degree >= 1);
|
||||||
node.in_degree -= 1;
|
node.in_degree -= 1;
|
||||||
if node.in_degree == 0 && !node.in_queue {
|
if node.in_degree == 0 && !node.in_queue {
|
||||||
|
// Don't queue if parked in waiting_for_chunks — check_waiting_tasks()
|
||||||
|
// will re-queue it once chunk data arrives.
|
||||||
|
if !self.waiting_for_chunks.contains(&cur.to) {
|
||||||
self.queue.push(TaskHeapNode(
|
self.queue.push(TaskHeapNode(
|
||||||
Self::calc_priority(
|
Self::calc_priority(
|
||||||
&self.last_level,
|
&self.last_level,
|
||||||
@ -548,6 +628,7 @@ impl GenerationSchedule {
|
|||||||
node.in_queue = true;
|
node.in_queue = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
edge = cur.next;
|
edge = cur.next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -555,7 +636,6 @@ impl GenerationSchedule {
|
|||||||
fn receive_chunk(&mut self, pos: ChunkPos, data: RecvChunk) {
|
fn receive_chunk(&mut self, pos: ChunkPos, data: RecvChunk) {
|
||||||
match data {
|
match data {
|
||||||
RecvChunk::IO(chunk) => {
|
RecvChunk::IO(chunk) => {
|
||||||
// debug!("receive io chunk pos {pos:?}");
|
|
||||||
let mut holder = self.chunk_map.remove(&pos).unwrap();
|
let mut holder = self.chunk_map.remove(&pos).unwrap();
|
||||||
if holder.chunk.is_some() {
|
if holder.chunk.is_some() {
|
||||||
warn!(
|
warn!(
|
||||||
@ -576,7 +656,6 @@ impl GenerationSchedule {
|
|||||||
|
|
||||||
match &chunk {
|
match &chunk {
|
||||||
Chunk::Level(data) => {
|
Chunk::Level(data) => {
|
||||||
// Full chunk from IO - mark as public and notify
|
|
||||||
self.apply_lighting_override(data);
|
self.apply_lighting_override(data);
|
||||||
let result = self.public_chunk_map.insert(pos, data.clone());
|
let result = self.public_chunk_map.insert(pos, data.clone());
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
@ -593,8 +672,6 @@ impl GenerationSchedule {
|
|||||||
self.listener.process_new_chunk(pos, data);
|
self.listener.process_new_chunk(pos, data);
|
||||||
}
|
}
|
||||||
Chunk::Proto(_) => {
|
Chunk::Proto(_) => {
|
||||||
// Proto chunk from IO (downgraded for relighting) - mark as non-public
|
|
||||||
// so players get notified when it finishes generation
|
|
||||||
if holder.public {
|
if holder.public {
|
||||||
debug!(
|
debug!(
|
||||||
"Chunk {:?} downgraded to Proto for relighting, marking as non-public",
|
"Chunk {:?} downgraded to Proto for relighting, marking as non-public",
|
||||||
@ -607,9 +684,11 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
holder.chunk = Some(chunk);
|
holder.chunk = Some(chunk);
|
||||||
self.chunk_map.insert(pos, holder);
|
self.chunk_map.insert(pos, holder);
|
||||||
|
|
||||||
|
// A new chunk arrived — unblock any waiting generation tasks
|
||||||
|
self.check_waiting_tasks();
|
||||||
}
|
}
|
||||||
RecvChunk::Generation(data) => {
|
RecvChunk::Generation(data) => {
|
||||||
// debug!("receive gen chunk pos {pos:?}");
|
|
||||||
let mut dx = 0;
|
let mut dx = 0;
|
||||||
let mut dy = 0;
|
let mut dy = 0;
|
||||||
for chunk in data.chunks {
|
for chunk in data.chunks {
|
||||||
@ -618,8 +697,6 @@ impl GenerationSchedule {
|
|||||||
Chunk::Level(chunk) => {
|
Chunk::Level(chunk) => {
|
||||||
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
|
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
|
||||||
if new_pos == pos {
|
if new_pos == pos {
|
||||||
// Expect the holder to be one stage before Full (Lighting). If not,
|
|
||||||
// log and align so we don't panic in production/debug runs.
|
|
||||||
if holder.current_stage != StagedChunkEnum::Lighting {
|
if holder.current_stage != StagedChunkEnum::Lighting {
|
||||||
warn!(
|
warn!(
|
||||||
"receive_chunk(Level): holder at {:?} for pos {:?} expected {:?}; aligning",
|
"receive_chunk(Level): holder at {:?} for pos {:?} expected {:?}; aligning",
|
||||||
@ -636,12 +713,9 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
holder.current_stage = StagedChunkEnum::Full;
|
holder.current_stage = StagedChunkEnum::Full;
|
||||||
|
|
||||||
// Check if this is the first time becoming public
|
|
||||||
let was_public = holder.public;
|
let was_public = holder.public;
|
||||||
|
|
||||||
if was_public {
|
if was_public {
|
||||||
// Was already public but chunk has been regenerated (e.g., after relighting)
|
|
||||||
// Update the chunk data and re-notify players
|
|
||||||
self.apply_lighting_override(&chunk);
|
self.apply_lighting_override(&chunk);
|
||||||
holder.chunk = Some(Chunk::Level(chunk.clone()));
|
holder.chunk = Some(Chunk::Level(chunk.clone()));
|
||||||
self.public_chunk_map.insert(new_pos, chunk.clone());
|
self.public_chunk_map.insert(new_pos, chunk.clone());
|
||||||
@ -652,7 +726,6 @@ impl GenerationSchedule {
|
|||||||
self.listener.process_new_chunk(new_pos, &chunk);
|
self.listener.process_new_chunk(new_pos, &chunk);
|
||||||
} else {
|
} else {
|
||||||
self.apply_lighting_override(&chunk);
|
self.apply_lighting_override(&chunk);
|
||||||
// Clone once for public_chunk_map (needed for player access)
|
|
||||||
let public_chunk = chunk.clone();
|
let public_chunk = chunk.clone();
|
||||||
holder.chunk = Some(Chunk::Level(chunk));
|
holder.chunk = Some(Chunk::Level(chunk));
|
||||||
let result =
|
let result =
|
||||||
@ -663,7 +736,6 @@ impl GenerationSchedule {
|
|||||||
"public_chunk_map.insert returned existing chunk for {new_pos:?}"
|
"public_chunk_map.insert returned existing chunk for {new_pos:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Notify players about the new chunk
|
|
||||||
if let Some(pc) = self.public_chunk_map.get(&new_pos) {
|
if let Some(pc) = self.public_chunk_map.get(&new_pos) {
|
||||||
trace!(
|
trace!(
|
||||||
"Notifying players: new chunk at {:?} (generation complete)",
|
"Notifying players: new chunk at {:?} (generation complete)",
|
||||||
@ -678,23 +750,31 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Non-center chunk... just restore it to holder without cloning
|
|
||||||
holder.chunk = Some(Chunk::Level(chunk));
|
holder.chunk = Some(Chunk::Level(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always drop the occupied node before nulling it
|
|
||||||
if !holder.occupied.is_null()
|
if !holder.occupied.is_null()
|
||||||
&& self.graph.nodes.contains_key(holder.occupied)
|
&& self.graph.nodes.contains_key(holder.occupied)
|
||||||
{
|
{
|
||||||
self.drop_node(holder.occupied);
|
self.drop_node(holder.occupied);
|
||||||
}
|
}
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
|
|
||||||
|
// If this neighbor chunk was only loaded for a dependency and
|
||||||
|
// is no longer needed, clear dependency_stage and queue unload.
|
||||||
|
if holder.target_stage == StagedChunkEnum::None
|
||||||
|
&& new_pos != pos
|
||||||
|
&& holder.current_stage >= holder.dependency_stage
|
||||||
|
{
|
||||||
|
holder.dependency_stage = StagedChunkEnum::None;
|
||||||
|
self.unload_chunks.insert(new_pos);
|
||||||
|
}
|
||||||
|
|
||||||
self.chunk_map.insert(new_pos, holder);
|
self.chunk_map.insert(new_pos, holder);
|
||||||
}
|
}
|
||||||
Chunk::Proto(chunk) => {
|
Chunk::Proto(chunk) => {
|
||||||
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
|
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
|
||||||
|
|
||||||
// Clean up the task node for the stage that just finished.
|
|
||||||
let stage = chunk.stage_id();
|
let stage = chunk.stage_id();
|
||||||
if stage < holder.tasks.len() as u8 {
|
if stage < holder.tasks.len() as u8 {
|
||||||
let task_idx = stage as usize;
|
let task_idx = stage as usize;
|
||||||
@ -705,23 +785,28 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if new_pos == pos {
|
if new_pos == pos {
|
||||||
// Center Chunk Logic
|
|
||||||
debug_assert_ne!(holder.current_stage, StagedChunkEnum::None);
|
debug_assert_ne!(holder.current_stage, StagedChunkEnum::None);
|
||||||
if self.graph.nodes.contains_key(holder.occupied) {
|
if self.graph.nodes.contains_key(holder.occupied) {
|
||||||
self.drop_node(holder.occupied);
|
self.drop_node(holder.occupied);
|
||||||
}
|
}
|
||||||
holder.current_stage = StagedChunkEnum::from(stage);
|
holder.current_stage = StagedChunkEnum::from(stage);
|
||||||
} else {
|
} else {
|
||||||
// Neighbor Logic
|
|
||||||
if holder.current_stage < StagedChunkEnum::from(stage) {
|
if holder.current_stage < StagedChunkEnum::from(stage) {
|
||||||
holder.current_stage = StagedChunkEnum::from(stage);
|
holder.current_stage = StagedChunkEnum::from(stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !holder.occupied.is_null()
|
if !holder.occupied.is_null()
|
||||||
&& self.graph.nodes.contains_key(holder.occupied)
|
&& self.graph.nodes.contains_key(holder.occupied)
|
||||||
{
|
{
|
||||||
self.drop_node(holder.occupied);
|
self.drop_node(holder.occupied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear dependency_stage and queue unload if no longer needed
|
||||||
|
if holder.target_stage == StagedChunkEnum::None
|
||||||
|
&& holder.current_stage >= holder.dependency_stage
|
||||||
|
{
|
||||||
|
holder.dependency_stage = StagedChunkEnum::None;
|
||||||
|
self.unload_chunks.insert(new_pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
@ -735,6 +820,9 @@ impl GenerationSchedule {
|
|||||||
dx += 1;
|
dx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Neighbor chunks returned to holders — unblock waiting tasks
|
||||||
|
self.check_waiting_tasks();
|
||||||
}
|
}
|
||||||
RecvChunk::GenerationFailure {
|
RecvChunk::GenerationFailure {
|
||||||
pos: fail_pos,
|
pos: fail_pos,
|
||||||
@ -746,11 +834,9 @@ impl GenerationSchedule {
|
|||||||
fail_pos, stage, error
|
fail_pos, stage, error
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up the holder
|
|
||||||
if let Some(mut holder) = self.chunk_map.remove(&pos) {
|
if let Some(mut holder) = self.chunk_map.remove(&pos) {
|
||||||
let target_stage = holder.target_stage;
|
let target_stage = holder.target_stage;
|
||||||
|
|
||||||
// Clean up occupied node
|
|
||||||
if !holder.occupied.is_null() {
|
if !holder.occupied.is_null() {
|
||||||
if self.graph.nodes.contains_key(holder.occupied) {
|
if self.graph.nodes.contains_key(holder.occupied) {
|
||||||
self.drop_node(holder.occupied);
|
self.drop_node(holder.occupied);
|
||||||
@ -758,31 +844,28 @@ impl GenerationSchedule {
|
|||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up all task nodes
|
|
||||||
for i in 0..holder.tasks.len() {
|
for i in 0..holder.tasks.len() {
|
||||||
if !holder.tasks[i].is_null() {
|
if !holder.tasks[i].is_null() {
|
||||||
|
self.waiting_for_chunks.remove(&holder.tasks[i]);
|
||||||
self.drop_node(holder.tasks[i]);
|
self.drop_node(holder.tasks[i]);
|
||||||
holder.tasks[i] = NodeKey::null();
|
holder.tasks[i] = NodeKey::null();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset chunk to None so it will be regenerated
|
|
||||||
holder.current_stage = StagedChunkEnum::None;
|
holder.current_stage = StagedChunkEnum::None;
|
||||||
|
holder.dependency_stage = StagedChunkEnum::None;
|
||||||
holder.chunk = None;
|
holder.chunk = None;
|
||||||
|
|
||||||
// Recreate tasks from None to target_stage
|
|
||||||
for i in (StagedChunkEnum::None as usize + 1)..=(target_stage as usize) {
|
for i in (StagedChunkEnum::None as usize + 1)..=(target_stage as usize) {
|
||||||
let stage_enum = StagedChunkEnum::from(i as u8);
|
let stage_enum = StagedChunkEnum::from(i as u8);
|
||||||
let task_node = Node::new(pos, stage_enum);
|
let task_node = Node::new(pos, stage_enum);
|
||||||
holder.tasks[i] = self.graph.nodes.insert(task_node);
|
holder.tasks[i] = self.graph.nodes.insert(task_node);
|
||||||
|
|
||||||
// Add dependencies
|
|
||||||
if i > (StagedChunkEnum::None as usize + 1) {
|
if i > (StagedChunkEnum::None as usize + 1) {
|
||||||
self.graph.add_edge(holder.tasks[i - 1], holder.tasks[i]);
|
self.graph.add_edge(holder.tasks[i - 1], holder.tasks[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add first task to queue with high priority (use a boost to retry faster)
|
|
||||||
if target_stage > StagedChunkEnum::None {
|
if target_stage > StagedChunkEnum::None {
|
||||||
let first_task = holder.tasks[StagedChunkEnum::None as usize + 1];
|
let first_task = holder.tasks[StagedChunkEnum::None as usize + 1];
|
||||||
if let Some(node) = self.graph.nodes.get_mut(first_task) {
|
if let Some(node) = self.graph.nodes.get_mut(first_task) {
|
||||||
@ -794,7 +877,7 @@ impl GenerationSchedule {
|
|||||||
&self.last_high_priority,
|
&self.last_high_priority,
|
||||||
pos,
|
pos,
|
||||||
StagedChunkEnum::from(1),
|
StagedChunkEnum::from(1),
|
||||||
) - 50, // Priority boost for retry
|
) - 50,
|
||||||
first_task,
|
first_task,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -819,7 +902,6 @@ impl GenerationSchedule {
|
|||||||
thread::current().id(),
|
thread::current().id(),
|
||||||
thread::current().name().unwrap_or("unknown")
|
thread::current().name().unwrap_or("unknown")
|
||||||
);
|
);
|
||||||
// let mut clock = Instant::now();
|
|
||||||
loop {
|
loop {
|
||||||
if level.should_unload.swap(false, Relaxed) {
|
if level.should_unload.swap(false, Relaxed) {
|
||||||
self.unload_chunk();
|
self.unload_chunk();
|
||||||
@ -828,16 +910,13 @@ impl GenerationSchedule {
|
|||||||
self.save_all_chunk(false);
|
self.save_all_chunk(false);
|
||||||
}
|
}
|
||||||
if level.shut_down_chunk_system.load(Relaxed) {
|
if level.shut_down_chunk_system.load(Relaxed) {
|
||||||
// Save all chunks BEFORE breaking the loop to ensure IO write thread processes them
|
|
||||||
info!("Saving chunks before shutdown...");
|
info!("Saving chunks before shutdown...");
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
'out2: while let Some(task) = self.queue.pop() {
|
'out2: while let Some(task) = self.queue.pop() {
|
||||||
// Check shutdown flag again before processing tasks to avoid IO errors
|
|
||||||
if level.shut_down_chunk_system.load(Relaxed) {
|
if level.shut_down_chunk_system.load(Relaxed) {
|
||||||
// Don't process any more tasks, just break to save chunks
|
|
||||||
self.queue.push(task);
|
self.queue.push(task);
|
||||||
info!("Shutdown detected during task processing, saving chunks...");
|
info!("Shutdown detected during task processing, saving chunks...");
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
@ -866,31 +945,60 @@ impl GenerationSchedule {
|
|||||||
ChunkPos::new(i32::MAX, i32::MAX),
|
ChunkPos::new(i32::MAX, i32::MAX),
|
||||||
StagedChunkEnum::None,
|
StagedChunkEnum::None,
|
||||||
));
|
));
|
||||||
for i in
|
let effective_target = holder.target_stage.max(holder.dependency_stage);
|
||||||
(holder.current_stage as usize + 1)..=(holder.target_stage as usize)
|
for i in (holder.current_stage as usize + 1)..=(effective_target as usize) {
|
||||||
{
|
|
||||||
self.graph.add_edge(occupy, holder.tasks[i]);
|
self.graph.add_edge(occupy, holder.tasks[i]);
|
||||||
}
|
}
|
||||||
holder.occupied = occupy;
|
holder.occupied = occupy;
|
||||||
|
|
||||||
// debug!("send task {:?} {node:?}", task.1);
|
|
||||||
|
|
||||||
if self.io_read.send(node.pos).is_err() {
|
if self.io_read.send(node.pos).is_err() {
|
||||||
// IO thread closed (likely due to shutdown), save and exit cleanly
|
|
||||||
info!("IO read thread closed, saving remaining chunks...");
|
info!("IO read thread closed, saving remaining chunks...");
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
break 'out2;
|
break 'out2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let write_radius = node.stage.get_write_radius();
|
let write_radius = node.stage.get_write_radius();
|
||||||
|
|
||||||
|
// Pre-validate that every chunk in the write area (including the
|
||||||
|
// center for write_radius==0 stages like Biomes, StructureStart,
|
||||||
|
// Noise, Surface) has its data present before we swap anything out.
|
||||||
|
//
|
||||||
|
// The dependency graph ensures predecessor *tasks* are complete, but
|
||||||
|
// there is a brief window between a task completing on a generation
|
||||||
|
// thread and its chunk data being placed back into the holder. Any
|
||||||
|
// stage whose write area overlaps with a currently-running task will
|
||||||
|
// see chunk==None in that window. We park here and let
|
||||||
|
// check_waiting_tasks() re-queue once all data has arrived.
|
||||||
|
{
|
||||||
|
let all_ready = (-write_radius..=write_radius).all(|dx| {
|
||||||
|
(-write_radius..=write_radius).all(|dy| {
|
||||||
|
self.chunk_map
|
||||||
|
.get(&node.pos.add_raw(dx, dy))
|
||||||
|
.is_some_and(|h| h.chunk.is_some())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if !all_ready {
|
||||||
|
if let Some(n) = self.graph.nodes.get_mut(task.1) {
|
||||||
|
n.in_queue = false;
|
||||||
|
}
|
||||||
|
self.waiting_for_chunks.insert(task.1);
|
||||||
|
// Close the TOCTOU window: the chunk we're waiting for may
|
||||||
|
// have arrived in the recv_chunk drain that happened earlier
|
||||||
|
// in this same loop iteration, before this task was parked.
|
||||||
|
// If so, check_waiting_tasks() will immediately re-queue it
|
||||||
|
// so it isn't stranded with running_task_count==0.
|
||||||
|
self.check_waiting_tasks();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut cache = Cache::new(
|
let mut cache = Cache::new(
|
||||||
node.pos.x - write_radius,
|
node.pos.x - write_radius,
|
||||||
node.pos.y - write_radius,
|
node.pos.y - write_radius,
|
||||||
write_radius << 1 | 1,
|
write_radius << 1 | 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only create the occupy node after we know all chunks are available
|
|
||||||
// and we're actually proceeding with the task
|
|
||||||
let occupy = self.graph.nodes.insert(Node::new(
|
let occupy = self.graph.nodes.insert(Node::new(
|
||||||
ChunkPos::new(i32::MAX, i32::MAX),
|
ChunkPos::new(i32::MAX, i32::MAX),
|
||||||
StagedChunkEnum::None,
|
StagedChunkEnum::None,
|
||||||
@ -911,8 +1019,6 @@ impl GenerationSchedule {
|
|||||||
};
|
};
|
||||||
match tmp {
|
match tmp {
|
||||||
Chunk::Level(chunk) => {
|
Chunk::Level(chunk) => {
|
||||||
// Don't clone Level chunks. Move them into cache to avoid
|
|
||||||
// extra Arc references that prevent unloading.
|
|
||||||
cache.chunks.push(Chunk::Level(chunk));
|
cache.chunks.push(Chunk::Level(chunk));
|
||||||
}
|
}
|
||||||
Chunk::Proto(chunk) => {
|
Chunk::Proto(chunk) => {
|
||||||
@ -951,11 +1057,8 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug!("send task {:?} {node:?}", task.1);
|
|
||||||
|
|
||||||
self.running_task_count += 1;
|
self.running_task_count += 1;
|
||||||
if self.generate.send((node.pos, cache, node.stage)).is_err() {
|
if self.generate.send((node.pos, cache, node.stage)).is_err() {
|
||||||
// revert running task count increment and exit cleanly with save
|
|
||||||
self.running_task_count = self.running_task_count.saturating_sub(1);
|
self.running_task_count = self.running_task_count.saturating_sub(1);
|
||||||
info!("Generation thread closed, saving remaining chunks...");
|
info!("Generation thread closed, saving remaining chunks...");
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
@ -966,8 +1069,10 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.queue.is_empty() {
|
if self.queue.is_empty() {
|
||||||
// debug!("the queue is empty. thread sleep");
|
// Wait while there are in-flight tasks OR tasks parked waiting for chunk data.
|
||||||
while self.running_task_count > 0 && self.queue.is_empty() {
|
while (self.running_task_count > 0 || !self.waiting_for_chunks.is_empty())
|
||||||
|
&& self.queue.is_empty()
|
||||||
|
{
|
||||||
if let Ok((pos, data)) = self.recv_chunk.try_recv() {
|
if let Ok((pos, data)) = self.recv_chunk.try_recv() {
|
||||||
self.receive_chunk(pos, data);
|
self.receive_chunk(pos, data);
|
||||||
self.resort_work(self.send_level.get());
|
self.resort_work(self.send_level.get());
|
||||||
@ -978,8 +1083,7 @@ impl GenerationSchedule {
|
|||||||
thread::sleep(Duration::from_millis(50));
|
thread::sleep(Duration::from_millis(50));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.queue.is_empty() {
|
if self.queue.is_empty() && self.waiting_for_chunks.is_empty() {
|
||||||
// debug!("no work to do. thread sleep");
|
|
||||||
debug_assert!(self.debug_check());
|
debug_assert!(self.debug_check());
|
||||||
debug_assert_eq!(self.running_task_count, 0);
|
debug_assert_eq!(self.running_task_count, 0);
|
||||||
self.resort_work(self.send_level.wait_and_get(&level));
|
self.resort_work(self.send_level.wait_and_get(&level));
|
||||||
@ -995,7 +1099,7 @@ impl GenerationSchedule {
|
|||||||
while self.running_task_count > 0 && wait_iterations < max_wait_iterations {
|
while self.running_task_count > 0 && wait_iterations < max_wait_iterations {
|
||||||
if let Ok((pos, data)) = self.recv_chunk.try_recv() {
|
if let Ok((pos, data)) = self.recv_chunk.try_recv() {
|
||||||
self.receive_chunk(pos, data);
|
self.receive_chunk(pos, data);
|
||||||
wait_iterations = 0; // Reset counter when we receive data
|
wait_iterations = 0;
|
||||||
} else {
|
} else {
|
||||||
wait_iterations += 1;
|
wait_iterations += 1;
|
||||||
if wait_iterations % 20 == 0 {
|
if wait_iterations % 20 == 0 {
|
||||||
@ -1014,14 +1118,12 @@ impl GenerationSchedule {
|
|||||||
"Cancelling {} in-flight generation tasks",
|
"Cancelling {} in-flight generation tasks",
|
||||||
self.running_task_count
|
self.running_task_count
|
||||||
);
|
);
|
||||||
// Collect nodes to drop
|
|
||||||
let mut nodes_to_drop = Vec::new();
|
let mut nodes_to_drop = Vec::new();
|
||||||
|
|
||||||
// Clear occupy nodes for cancelled tasks
|
|
||||||
for holder in self.chunk_map.values_mut() {
|
for holder in self.chunk_map.values_mut() {
|
||||||
// Collect any task nodes associated with this holder to ensure full cleanup
|
|
||||||
for task in &mut holder.tasks {
|
for task in &mut holder.tasks {
|
||||||
if !task.is_null() {
|
if !task.is_null() {
|
||||||
|
self.waiting_for_chunks.remove(task);
|
||||||
nodes_to_drop.push(*task);
|
nodes_to_drop.push(*task);
|
||||||
*task = NodeKey::null();
|
*task = NodeKey::null();
|
||||||
}
|
}
|
||||||
@ -1032,13 +1134,11 @@ impl GenerationSchedule {
|
|||||||
&& node.pos.x == i32::MAX
|
&& node.pos.x == i32::MAX
|
||||||
&& node.pos.y == i32::MAX
|
&& node.pos.y == i32::MAX
|
||||||
{
|
{
|
||||||
// This is an occupy node for an in-flight task
|
|
||||||
nodes_to_drop.push(holder.occupied);
|
nodes_to_drop.push(holder.occupied);
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safely drop them (using `drop_node`, which decrements in_degree on targets)
|
|
||||||
for node_key in nodes_to_drop {
|
for node_key in nodes_to_drop {
|
||||||
self.drop_node(node_key);
|
self.drop_node(node_key);
|
||||||
}
|
}
|
||||||
@ -1046,11 +1146,8 @@ impl GenerationSchedule {
|
|||||||
self.running_task_count = 0;
|
self.running_task_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop the io_write sender to signal the write thread to exit
|
|
||||||
// Chunks were already saved during loop exit above
|
|
||||||
drop(self.io_write);
|
drop(self.io_write);
|
||||||
|
|
||||||
// Clean up any remaining graph structures
|
|
||||||
let unreleased_count = self.graph.nodes.len();
|
let unreleased_count = self.graph.nodes.len();
|
||||||
if unreleased_count > 0 {
|
if unreleased_count > 0 {
|
||||||
warn!(
|
warn!(
|
||||||
@ -1063,7 +1160,6 @@ impl GenerationSchedule {
|
|||||||
|
|
||||||
fn debug_check(&self) -> bool {
|
fn debug_check(&self) -> bool {
|
||||||
if !self.graph.nodes.is_empty() {
|
if !self.graph.nodes.is_empty() {
|
||||||
// error!("nodes: {:?}", self.graph.nodes);
|
|
||||||
for (key, value) in &self.graph.nodes {
|
for (key, value) in &self.graph.nodes {
|
||||||
error!("unrelease node {key:?}: {value:?}");
|
error!("unrelease node {key:?}: {value:?}");
|
||||||
}
|
}
|
||||||
@ -1079,7 +1175,8 @@ impl GenerationSchedule {
|
|||||||
*self.last_level.get(pos).unwrap_or(&ChunkLoading::MAX_LEVEL)
|
*self.last_level.get(pos).unwrap_or(&ChunkLoading::MAX_LEVEL)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
debug_assert!(holder.current_stage >= holder.target_stage);
|
let effective = holder.target_stage.max(holder.dependency_stage);
|
||||||
|
debug_assert!(holder.current_stage >= effective);
|
||||||
debug_assert!(holder.occupied.is_null());
|
debug_assert!(holder.occupied.is_null());
|
||||||
if holder.current_stage != StagedChunkEnum::None {
|
if holder.current_stage != StagedChunkEnum::None {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
|
|||||||
@ -26,7 +26,7 @@ pub struct VanillaGenerator {
|
|||||||
|
|
||||||
impl GeneratorInit for VanillaGenerator {
|
impl GeneratorInit for VanillaGenerator {
|
||||||
fn new(seed: Seed, dimension: Dimension) -> Self {
|
fn new(seed: Seed, dimension: Dimension) -> Self {
|
||||||
let random_config = GlobalRandomConfig::new(seed.0, false);
|
let random_config = GlobalRandomConfig::new(seed.0);
|
||||||
|
|
||||||
// TODO: The generation settings contains (part of?) the noise routers too; do we keep the separate or
|
// TODO: The generation settings contains (part of?) the noise routers too; do we keep the separate or
|
||||||
// use only the generation settings?
|
// use only the generation settings?
|
||||||
|
|||||||
@ -21,10 +21,7 @@ mod surface;
|
|||||||
use generator::{GeneratorInit, VanillaGenerator};
|
use generator::{GeneratorInit, VanillaGenerator};
|
||||||
use pumpkin_data::dimension::Dimension;
|
use pumpkin_data::dimension::Dimension;
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
random::{
|
random::xoroshiro128::{Xoroshiro, XoroshiroSplitter},
|
||||||
RandomDeriver, RandomDeriverImpl, RandomImpl, legacy_rand::LegacyRand,
|
|
||||||
xoroshiro128::Xoroshiro,
|
|
||||||
},
|
|
||||||
world_seed::Seed,
|
world_seed::Seed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -36,19 +33,15 @@ pub fn get_world_gen(seed: Seed, dimension: Dimension) -> Box<VanillaGenerator>
|
|||||||
|
|
||||||
pub struct GlobalRandomConfig {
|
pub struct GlobalRandomConfig {
|
||||||
pub seed: u64,
|
pub seed: u64,
|
||||||
base_random_deriver: RandomDeriver,
|
base_random_deriver: XoroshiroSplitter,
|
||||||
aquifer_random_deriver: RandomDeriver,
|
aquifer_random_deriver: XoroshiroSplitter,
|
||||||
ore_random_deriver: RandomDeriver,
|
pub ore_random_deriver: XoroshiroSplitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalRandomConfig {
|
impl GlobalRandomConfig {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(seed: u64, legacy: bool) -> Self {
|
pub fn new(seed: u64) -> Self {
|
||||||
let random_deriver = if legacy {
|
let random_deriver = Xoroshiro::from_seed(seed).next_splitter();
|
||||||
LegacyRand::from_seed(seed).next_splitter()
|
|
||||||
} else {
|
|
||||||
Xoroshiro::from_seed(seed).next_splitter()
|
|
||||||
};
|
|
||||||
|
|
||||||
let aquifer_deriver = random_deriver
|
let aquifer_deriver = random_deriver
|
||||||
.split_string("minecraft:aquifer")
|
.split_string("minecraft:aquifer")
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use enum_dispatch::enum_dispatch;
|
|||||||
use pumpkin_data::{Block, BlockState};
|
use pumpkin_data::{Block, BlockState};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{clamped_map, floor_div, vector3::Vector3},
|
math::{clamped_map, floor_div, vector3::Vector3},
|
||||||
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
random::{RandomImpl, xoroshiro128::XoroshiroSplitter},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generation::{
|
use crate::generation::{
|
||||||
@ -139,7 +139,7 @@ impl WorldAquiferSampler {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
chunk_x: i32,
|
chunk_x: i32,
|
||||||
chunk_z: i32,
|
chunk_z: i32,
|
||||||
random_deriver: &RandomDeriver,
|
random_deriver: &XoroshiroSplitter,
|
||||||
minimum_y: i8,
|
minimum_y: i8,
|
||||||
height: u16,
|
height: u16,
|
||||||
fluid_level: FluidLevelSampler,
|
fluid_level: FluidLevelSampler,
|
||||||
@ -725,7 +725,7 @@ mod random_positions_and_hypot {
|
|||||||
|
|
||||||
const SEED: u64 = 0;
|
const SEED: u64 = 0;
|
||||||
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
|
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
|
||||||
LazyLock::new(|| GlobalRandomConfig::new(SEED, false));
|
LazyLock::new(|| GlobalRandomConfig::new(SEED));
|
||||||
static PROTO_ROUTER: LazyLock<ProtoNoiseRouters> = LazyLock::new(|| {
|
static PROTO_ROUTER: LazyLock<ProtoNoiseRouters> = LazyLock::new(|| {
|
||||||
let router_ast = &OVERWORLD_BASE_NOISE_ROUTER;
|
let router_ast = &OVERWORLD_BASE_NOISE_ROUTER;
|
||||||
ProtoNoiseRouters::generate(router_ast, &RANDOM_CONFIG)
|
ProtoNoiseRouters::generate(router_ast, &RANDOM_CONFIG)
|
||||||
|
|||||||
@ -4,7 +4,10 @@ pub mod perlin;
|
|||||||
pub mod router;
|
pub mod router;
|
||||||
|
|
||||||
use pumpkin_data::{Block, BlockState, chunk_gen_settings::GenerationShapeConfig};
|
use pumpkin_data::{Block, BlockState, chunk_gen_settings::GenerationShapeConfig};
|
||||||
use pumpkin_util::math::{floor_div, floor_mod, vector3::Vector3};
|
use pumpkin_util::{
|
||||||
|
math::{floor_div, floor_mod, vector3::Vector3},
|
||||||
|
random::xoroshiro128::XoroshiroSplitter,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::generation::{
|
use crate::generation::{
|
||||||
noise::{
|
noise::{
|
||||||
@ -45,14 +48,14 @@ impl BlockStateSampler {
|
|||||||
pub fn sample(
|
pub fn sample(
|
||||||
&mut self,
|
&mut self,
|
||||||
router: &mut ChunkNoiseRouter,
|
router: &mut ChunkNoiseRouter,
|
||||||
random_config: &GlobalRandomConfig,
|
ore_random_deriver: &XoroshiroSplitter,
|
||||||
pos: &Vector3<i32>,
|
pos: &Vector3<i32>,
|
||||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||||
) -> Option<&'static BlockState> {
|
) -> Option<&'static BlockState> {
|
||||||
match self {
|
match self {
|
||||||
Self::Aquifer(aquifer) => aquifer.apply(router, pos, sample_options, height_estimator),
|
Self::Aquifer(aquifer) => aquifer.apply(router, pos, sample_options, height_estimator),
|
||||||
Self::Ore(ore) => ore.sample(router, random_config, pos, sample_options),
|
Self::Ore(ore) => ore.sample(router, ore_random_deriver, pos, sample_options),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,15 +73,19 @@ impl ChainedBlockStateSampler {
|
|||||||
fn sample(
|
fn sample(
|
||||||
&mut self,
|
&mut self,
|
||||||
router: &mut ChunkNoiseRouter,
|
router: &mut ChunkNoiseRouter,
|
||||||
random_config: &GlobalRandomConfig,
|
ore_random_deriver: &XoroshiroSplitter,
|
||||||
pos: &Vector3<i32>,
|
pos: &Vector3<i32>,
|
||||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||||
) -> Option<&'static BlockState> {
|
) -> Option<&'static BlockState> {
|
||||||
for sampler in &mut self.samplers {
|
for sampler in &mut self.samplers {
|
||||||
if let Some(state) =
|
if let Some(state) = sampler.sample(
|
||||||
sampler.sample(router, random_config, pos, sample_options, height_estimator)
|
router,
|
||||||
{
|
ore_random_deriver,
|
||||||
|
pos,
|
||||||
|
sample_options,
|
||||||
|
height_estimator,
|
||||||
|
) {
|
||||||
return Some(state);
|
return Some(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,7 +378,7 @@ impl<'a> ChunkNoiseGenerator<'a> {
|
|||||||
#[expect(clippy::too_many_arguments)]
|
#[expect(clippy::too_many_arguments)]
|
||||||
pub fn sample_block_state(
|
pub fn sample_block_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
random_config: &GlobalRandomConfig,
|
ore_random_deriver: &XoroshiroSplitter,
|
||||||
start_x: i32,
|
start_x: i32,
|
||||||
start_y: i32,
|
start_y: i32,
|
||||||
start_z: i32,
|
start_z: i32,
|
||||||
@ -399,7 +406,7 @@ impl<'a> ChunkNoiseGenerator<'a> {
|
|||||||
|
|
||||||
self.state_sampler.sample(
|
self.state_sampler.sample(
|
||||||
&mut self.router,
|
&mut self.router,
|
||||||
random_config,
|
ore_random_deriver,
|
||||||
&pos,
|
&pos,
|
||||||
&options,
|
&options,
|
||||||
height_estimator,
|
height_estimator,
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
use pumpkin_data::{Block, BlockState};
|
use pumpkin_data::{Block, BlockState};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{clamped_map, vector3::Vector3},
|
math::{clamped_map, vector3::Vector3},
|
||||||
random::{RandomDeriverImpl, RandomImpl},
|
random::{RandomImpl, xoroshiro128::XoroshiroSplitter},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::generation::noise::router::{
|
||||||
GlobalRandomConfig,
|
chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_noise_router::ChunkNoiseRouter,
|
||||||
generation::noise::router::{
|
|
||||||
chunk_density_function::ChunkNoiseFunctionSampleOptions,
|
|
||||||
chunk_noise_router::ChunkNoiseRouter,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct OreVeinSampler;
|
pub struct OreVeinSampler;
|
||||||
@ -18,7 +14,7 @@ impl OreVeinSampler {
|
|||||||
pub fn sample(
|
pub fn sample(
|
||||||
&self,
|
&self,
|
||||||
router: &mut ChunkNoiseRouter,
|
router: &mut ChunkNoiseRouter,
|
||||||
random_config: &GlobalRandomConfig,
|
ore_random_deriver: &XoroshiroSplitter,
|
||||||
pos: &Vector3<i32>,
|
pos: &Vector3<i32>,
|
||||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||||
) -> Option<&'static BlockState> {
|
) -> Option<&'static BlockState> {
|
||||||
@ -37,9 +33,7 @@ impl OreVeinSampler {
|
|||||||
let mapped_diff = clamped_map(closest_to_bound as f64, 0f64, 20f64, -0.2f64, 0f64);
|
let mapped_diff = clamped_map(closest_to_bound as f64, 0f64, 20f64, -0.2f64, 0f64);
|
||||||
let abs_sample = vein_toggle.abs();
|
let abs_sample = vein_toggle.abs();
|
||||||
if abs_sample + mapped_diff >= 0.4f32 as f64 {
|
if abs_sample + mapped_diff >= 0.4f32 as f64 {
|
||||||
let mut random = random_config
|
let mut random = ore_random_deriver.split_pos(pos.x, block_y, pos.z);
|
||||||
.ore_random_deriver
|
|
||||||
.split_pos(pos.x, block_y, pos.z);
|
|
||||||
|
|
||||||
let vein_ridged_sample = router.vein_ridged(pos, sample_options);
|
let vein_ridged_sample = router.vein_ridged(pos, sample_options);
|
||||||
if random.next_f32() <= 0.7f32 && vein_ridged_sample < 0f64 {
|
if random.next_f32() <= 0.7f32 && vein_ridged_sample < 0f64 {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use pumpkin_data::chunk::DoublePerlinNoiseParameters;
|
use pumpkin_data::chunk::DoublePerlinNoiseParameters;
|
||||||
use pumpkin_util::{noise::perlin::OctavePerlinNoiseSampler, random::RandomGenerator};
|
use pumpkin_util::{noise::perlin::OctavePerlinNoiseSampler, random::RandomImpl};
|
||||||
|
|
||||||
pub struct DoublePerlinNoiseSampler {
|
pub struct DoublePerlinNoiseSampler {
|
||||||
first_sampler: OctavePerlinNoiseSampler,
|
first_sampler: OctavePerlinNoiseSampler,
|
||||||
@ -9,7 +9,7 @@ pub struct DoublePerlinNoiseSampler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DoublePerlinNoiseSampler {
|
impl DoublePerlinNoiseSampler {
|
||||||
fn create_amplitude(octaves: i32) -> f64 {
|
const fn create_amplitude(octaves: i32) -> f64 {
|
||||||
0.1f64 * (1f64 + 1f64 / (octaves + 1) as f64)
|
0.1f64 * (1f64 + 1f64 / (octaves + 1) as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ impl DoublePerlinNoiseSampler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_params(
|
pub fn from_params(
|
||||||
rand: &mut RandomGenerator,
|
rand: &mut impl RandomImpl,
|
||||||
parameters: &DoublePerlinNoiseParameters,
|
parameters: &DoublePerlinNoiseParameters,
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -27,7 +27,7 @@ impl DoublePerlinNoiseSampler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
rand: &mut RandomGenerator,
|
rand: &mut impl RandomImpl,
|
||||||
first_octave: i32,
|
first_octave: i32,
|
||||||
amplitudes: &[f64],
|
amplitudes: &[f64],
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
|
|||||||
@ -58,7 +58,7 @@ pub struct WrapperData {
|
|||||||
|
|
||||||
impl WrapperData {
|
impl WrapperData {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub const fn new(
|
||||||
cell_x_block_position: usize,
|
cell_x_block_position: usize,
|
||||||
cell_y_block_position: usize,
|
cell_y_block_position: usize,
|
||||||
cell_z_block_position: usize,
|
cell_z_block_position: usize,
|
||||||
@ -77,7 +77,7 @@ impl WrapperData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_position(
|
pub const fn update_position(
|
||||||
&mut self,
|
&mut self,
|
||||||
cell_x_block_position: usize,
|
cell_x_block_position: usize,
|
||||||
cell_y_block_position: usize,
|
cell_y_block_position: usize,
|
||||||
@ -246,7 +246,7 @@ impl DensityInterpolator {
|
|||||||
cell_z_position * (self.vertical_cell_count + 1) + cell_y_position
|
cell_z_position * (self.vertical_cell_count + 1) + cell_y_position
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn on_sampled_cell_corners(
|
pub(crate) const fn on_sampled_cell_corners(
|
||||||
&mut self,
|
&mut self,
|
||||||
cell_y_position: usize,
|
cell_y_position: usize,
|
||||||
cell_z_position: usize,
|
cell_z_position: usize,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use pumpkin_data::noise_router::{InterpolatedNoiseSamplerData, NoiseData, Shifte
|
|||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{clamped_lerp, vector3::Vector3},
|
math::{clamped_lerp, vector3::Vector3},
|
||||||
noise::perlin::OctavePerlinNoiseSampler,
|
noise::perlin::OctavePerlinNoiseSampler,
|
||||||
random::RandomGenerator,
|
random::xoroshiro128::Xoroshiro,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generation::{
|
use crate::generation::{
|
||||||
@ -190,7 +190,7 @@ pub struct InterpolatedNoiseSampler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InterpolatedNoiseSampler {
|
impl InterpolatedNoiseSampler {
|
||||||
pub fn new(data: &'static InterpolatedNoiseSamplerData, random: &mut RandomGenerator) -> Self {
|
pub fn new(data: &'static InterpolatedNoiseSamplerData, random: &mut Xoroshiro) -> Self {
|
||||||
let big_start = -15;
|
let big_start = -15;
|
||||||
let big_amplitudes = [1.0; 16];
|
let big_amplitudes = [1.0; 16];
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ static TEST_OPTIONS: ChunkNoiseFunctionBuilderOptions =
|
|||||||
ChunkNoiseFunctionBuilderOptions::new(0, 0, 0, 0, 0, 0, 0);
|
ChunkNoiseFunctionBuilderOptions::new(0, 0, 0, 0, 0, 0, 0);
|
||||||
const SEED: u64 = 0;
|
const SEED: u64 = 0;
|
||||||
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
|
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
|
||||||
LazyLock::new(|| GlobalRandomConfig::new(SEED, false));
|
LazyLock::new(|| GlobalRandomConfig::new(SEED));
|
||||||
|
|
||||||
macro_rules! build_function_stack {
|
macro_rules! build_function_stack {
|
||||||
($stack:expr) => {{
|
($stack:expr) => {{
|
||||||
|
|||||||
@ -238,7 +238,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn sample() {
|
fn sample() {
|
||||||
let seed = 123;
|
let seed = 123;
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let noise_router =
|
let noise_router =
|
||||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(1, 1, 1);
|
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(1, 1, 1);
|
||||||
@ -259,7 +259,7 @@ mod test {
|
|||||||
fn sample_2() {
|
fn sample_2() {
|
||||||
// we use a different seed
|
// we use a different seed
|
||||||
let seed = 13579;
|
let seed = 13579;
|
||||||
let random_config = GlobalRandomConfig::new(seed, false);
|
let random_config = GlobalRandomConfig::new(seed);
|
||||||
let noise_router =
|
let noise_router =
|
||||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||||
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(1, 1, 1);
|
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(1, 1, 1);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use pumpkin_data::{
|
|||||||
UnaryOperation,
|
UnaryOperation,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use pumpkin_util::random::RandomDeriverImpl;
|
use pumpkin_util::random::xoroshiro128::XoroshiroSplitter;
|
||||||
|
|
||||||
use crate::{GlobalRandomConfig, generation::noise::perlin::DoublePerlinNoiseSampler};
|
use crate::{GlobalRandomConfig, generation::noise::perlin::DoublePerlinNoiseSampler};
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ pub enum ProtoNoiseFunctionComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct DoublePerlinNoiseBuilder<'a> {
|
pub struct DoublePerlinNoiseBuilder<'a> {
|
||||||
random_config: &'a GlobalRandomConfig,
|
base_random_deriver: &'a XoroshiroSplitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DoublePerlinNoiseBuilder<'a> {
|
impl<'a> DoublePerlinNoiseBuilder<'a> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(rand: &'a GlobalRandomConfig) -> Self {
|
pub const fn new(base_random_deriver: &'a XoroshiroSplitter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
random_config: rand,
|
base_random_deriver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,10 +76,7 @@ impl<'a> DoublePerlinNoiseBuilder<'a> {
|
|||||||
.unwrap_or_else(|| panic!("Unknown noise id: {id}"));
|
.unwrap_or_else(|| panic!("Unknown noise id: {id}"));
|
||||||
|
|
||||||
// Note that the parameters' id is different than `id`
|
// Note that the parameters' id is different than `id`
|
||||||
let mut random = self
|
let mut random = self.base_random_deriver.split_string(parameters.id());
|
||||||
.random_config
|
|
||||||
.base_random_deriver
|
|
||||||
.split_string(parameters.id());
|
|
||||||
DoublePerlinNoiseSampler::from_params(&mut random, parameters, false)
|
DoublePerlinNoiseSampler::from_params(&mut random, parameters, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +141,8 @@ impl ProtoNoiseRouters {
|
|||||||
base_stack: &[BaseNoiseFunctionComponent],
|
base_stack: &[BaseNoiseFunctionComponent],
|
||||||
random_config: &GlobalRandomConfig,
|
random_config: &GlobalRandomConfig,
|
||||||
) -> Box<[ProtoNoiseFunctionComponent]> {
|
) -> Box<[ProtoNoiseFunctionComponent]> {
|
||||||
let perlin_noise_builder = DoublePerlinNoiseBuilder::new(random_config);
|
let perlin_noise_builder =
|
||||||
|
DoublePerlinNoiseBuilder::new(&random_config.base_random_deriver);
|
||||||
|
|
||||||
// Contiguous memory for our function components
|
// Contiguous memory for our function components
|
||||||
let mut stack = Vec::<ProtoNoiseFunctionComponent>::with_capacity(base_stack.len());
|
let mut stack = Vec::<ProtoNoiseFunctionComponent>::with_capacity(base_stack.len());
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use pumpkin_data::structures::{
|
|||||||
};
|
};
|
||||||
use pumpkin_data::tag;
|
use pumpkin_data::tag;
|
||||||
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
|
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
|
||||||
|
use pumpkin_util::random::xoroshiro128::XoroshiroSplitter;
|
||||||
use pumpkin_util::random::{RandomImpl, get_carver_seed};
|
use pumpkin_util::random::{RandomImpl, get_carver_seed};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
HeightMap,
|
HeightMap,
|
||||||
@ -171,7 +172,7 @@ impl TerrainCache {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_random(random_config: &GlobalRandomConfig) -> Self {
|
pub fn from_random(random_config: &GlobalRandomConfig) -> Self {
|
||||||
let random = &random_config.base_random_deriver;
|
let random = &random_config.base_random_deriver;
|
||||||
let noise_builder = DoublePerlinNoiseBuilder::new(random_config);
|
let noise_builder = DoublePerlinNoiseBuilder::new(&random_config.base_random_deriver);
|
||||||
let terrain_builder = SurfaceTerrainBuilder::new(&noise_builder, random);
|
let terrain_builder = SurfaceTerrainBuilder::new(&noise_builder, random);
|
||||||
let surface_noise = noise_builder.get_noise_sampler_for_id("surface");
|
let surface_noise = noise_builder.get_noise_sampler_for_id("surface");
|
||||||
let secondary_noise = noise_builder.get_noise_sampler_for_id("surface_secondary");
|
let secondary_noise = noise_builder.get_noise_sampler_for_id("surface_secondary");
|
||||||
@ -595,7 +596,7 @@ impl ProtoChunk {
|
|||||||
);
|
);
|
||||||
self.populate_noise(
|
self.populate_noise(
|
||||||
&mut noise_sampler,
|
&mut noise_sampler,
|
||||||
random_config,
|
&random_config.ore_random_deriver,
|
||||||
&mut surface_height_estimate_sampler,
|
&mut surface_height_estimate_sampler,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -698,7 +699,7 @@ impl ProtoChunk {
|
|||||||
pub fn populate_noise(
|
pub fn populate_noise(
|
||||||
&mut self,
|
&mut self,
|
||||||
noise_sampler: &mut ChunkNoiseGenerator,
|
noise_sampler: &mut ChunkNoiseGenerator,
|
||||||
random_config: &GlobalRandomConfig,
|
ore_random_deriver: &XoroshiroSplitter,
|
||||||
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
|
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
|
||||||
) {
|
) {
|
||||||
let h_count = noise_sampler.horizontal_cell_block_count() as i32;
|
let h_count = noise_sampler.horizontal_cell_block_count() as i32;
|
||||||
@ -750,7 +751,7 @@ impl ProtoChunk {
|
|||||||
|
|
||||||
let block_state = noise_sampler
|
let block_state = noise_sampler
|
||||||
.sample_block_state(
|
.sample_block_state(
|
||||||
random_config,
|
ore_random_deriver,
|
||||||
sample_start_x,
|
sample_start_x,
|
||||||
sample_start_y,
|
sample_start_y,
|
||||||
sample_start_z,
|
sample_start_z,
|
||||||
@ -805,7 +806,7 @@ impl ProtoChunk {
|
|||||||
let min_y = self.bottom_y();
|
let min_y = self.bottom_y();
|
||||||
|
|
||||||
let random = &random_config.base_random_deriver;
|
let random = &random_config.base_random_deriver;
|
||||||
let noise_builder = DoublePerlinNoiseBuilder::new(random_config);
|
let noise_builder = DoublePerlinNoiseBuilder::new(&random_config.base_random_deriver);
|
||||||
let mut context = MaterialRuleContext::new(
|
let mut context = MaterialRuleContext::new(
|
||||||
min_y,
|
min_y,
|
||||||
self.height(),
|
self.height(),
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use pumpkin_data::{
|
|||||||
};
|
};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{lerp2, vertical_surface_type::VerticalSurfaceType},
|
math::{lerp2, vertical_surface_type::VerticalSurfaceType},
|
||||||
random::{RandomDeriver, RandomDeriverImpl, RandomImpl},
|
random::{RandomImpl, xoroshiro128::XoroshiroSplitter},
|
||||||
};
|
};
|
||||||
|
|
||||||
use terrain::SurfaceTerrainBuilder;
|
use terrain::SurfaceTerrainBuilder;
|
||||||
@ -32,7 +32,7 @@ pub mod terrain;
|
|||||||
pub struct MaterialRuleContext<'a> {
|
pub struct MaterialRuleContext<'a> {
|
||||||
pub min_y: i8,
|
pub min_y: i8,
|
||||||
pub height: u16,
|
pub height: u16,
|
||||||
pub random_deriver: &'a RandomDeriver,
|
pub random_deriver: &'a XoroshiroSplitter,
|
||||||
fluid_height: i32,
|
fluid_height: i32,
|
||||||
pub block_pos_x: i32,
|
pub block_pos_x: i32,
|
||||||
pub block_pos_y: i32,
|
pub block_pos_y: i32,
|
||||||
@ -61,7 +61,7 @@ impl<'a> MaterialRuleContext<'a> {
|
|||||||
min_y: i8,
|
min_y: i8,
|
||||||
height: u16,
|
height: u16,
|
||||||
noise_builder: DoublePerlinNoiseBuilder<'a>,
|
noise_builder: DoublePerlinNoiseBuilder<'a>,
|
||||||
random_deriver: &'a RandomDeriver,
|
random_deriver: &'a XoroshiroSplitter,
|
||||||
terrain_builder: &'a SurfaceTerrainBuilder,
|
terrain_builder: &'a SurfaceTerrainBuilder,
|
||||||
surface_noise: &'a DoublePerlinNoiseSampler,
|
surface_noise: &'a DoublePerlinNoiseSampler,
|
||||||
secondary_noise: &'a DoublePerlinNoiseSampler,
|
secondary_noise: &'a DoublePerlinNoiseSampler,
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
use pumpkin_data::{Block, BlockState, chunk::Biome};
|
use pumpkin_data::{Block, BlockState, chunk::Biome};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::vector3::Vector3,
|
math::vector3::Vector3,
|
||||||
random::{RandomDeriver, RandomDeriverImpl, RandomGenerator, RandomImpl},
|
random::{
|
||||||
|
RandomImpl,
|
||||||
|
xoroshiro128::{Xoroshiro, XoroshiroSplitter},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -27,7 +30,10 @@ pub struct SurfaceTerrainBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceTerrainBuilder {
|
impl SurfaceTerrainBuilder {
|
||||||
pub fn new(noise_builder: &DoublePerlinNoiseBuilder, random_deriver: &RandomDeriver) -> Self {
|
pub fn new(
|
||||||
|
noise_builder: &DoublePerlinNoiseBuilder,
|
||||||
|
random_deriver: &XoroshiroSplitter,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
terracotta_bands: Self::create_terracotta_bands(
|
terracotta_bands: Self::create_terracotta_bands(
|
||||||
random_deriver.split_string("minecraft:clay_bands"),
|
random_deriver.split_string("minecraft:clay_bands"),
|
||||||
@ -56,7 +62,7 @@ impl SurfaceTerrainBuilder {
|
|||||||
RawBlockState(Block::LIGHT_GRAY_TERRACOTTA.default_state.id);
|
RawBlockState(Block::LIGHT_GRAY_TERRACOTTA.default_state.id);
|
||||||
const TERRACOTTA: RawBlockState = RawBlockState(Block::TERRACOTTA.default_state.id);
|
const TERRACOTTA: RawBlockState = RawBlockState(Block::TERRACOTTA.default_state.id);
|
||||||
|
|
||||||
fn create_terracotta_bands(mut random: RandomGenerator) -> Box<[RawBlockState]> {
|
fn create_terracotta_bands(mut random: Xoroshiro) -> Box<[RawBlockState]> {
|
||||||
let mut block_states = [Self::TERRACOTTA; 192];
|
let mut block_states = [Self::TERRACOTTA; 192];
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@ -96,7 +102,7 @@ impl SurfaceTerrainBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_terracotta_bands(
|
fn add_terracotta_bands(
|
||||||
random: &mut RandomGenerator,
|
random: &mut Xoroshiro,
|
||||||
terracotta_bands: &mut [RawBlockState],
|
terracotta_bands: &mut [RawBlockState],
|
||||||
min_band_size: i32,
|
min_band_size: i32,
|
||||||
state: RawBlockState,
|
state: RawBlockState,
|
||||||
@ -189,7 +195,7 @@ impl SurfaceTerrainBuilder {
|
|||||||
estimated_surface_y: i32,
|
estimated_surface_y: i32,
|
||||||
current_top_y: i32,
|
current_top_y: i32,
|
||||||
sea_level: i32,
|
sea_level: i32,
|
||||||
random_deriver: &RandomDeriver,
|
random_deriver: &XoroshiroSplitter,
|
||||||
) {
|
) {
|
||||||
let iceburg_surface_noise =
|
let iceburg_surface_noise =
|
||||||
(self.iceberg_surface_noise.sample(x as f64, 0.0, z as f64) * 8.25).abs();
|
(self.iceberg_surface_noise.sample(x as f64, 0.0, z as f64) * 8.25).abs();
|
||||||
|
|||||||
@ -122,7 +122,7 @@ pub fn bench_create_and_populate_noise(
|
|||||||
|
|
||||||
chunk.populate_noise(
|
chunk.populate_noise(
|
||||||
&mut noise_sampler,
|
&mut noise_sampler,
|
||||||
random_config,
|
&random_config.ore_random_deriver,
|
||||||
&mut surface_height_estimate_sampler,
|
&mut surface_height_estimate_sampler,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ pub fn bench_create_and_populate_noise_with_surface(
|
|||||||
chunk.populate_biomes(Dimension::OVERWORLD, &mut multi_noise_sampler);
|
chunk.populate_biomes(Dimension::OVERWORLD, &mut multi_noise_sampler);
|
||||||
chunk.populate_noise(
|
chunk.populate_noise(
|
||||||
&mut noise_sampler,
|
&mut noise_sampler,
|
||||||
random_config,
|
&random_config.ore_random_deriver,
|
||||||
&mut surface_height_estimate_sampler,
|
&mut surface_height_estimate_sampler,
|
||||||
);
|
);
|
||||||
chunk.build_surface(
|
chunk.build_surface(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user