chore: some fixes

This commit is contained in:
Alexander Medvedev 2026-08-02 10:49:37 +02:00
parent 141b49f973
commit 98d4451fc9
22 changed files with 44571 additions and 143 deletions

View File

@ -0,0 +1,42 @@
use proc_macro2::TokenStream;
use quote::quote;
const MAX_VIEW_DISTANCE: u8 = 32;
pub fn build() -> TokenStream {
let entries = (0..=MAX_VIEW_DISTANCE).map(|dist| {
if dist < 2 {
return quote! { &[] };
}
let mut positions = vec![];
let d = i64::from(dist);
for z in -(d + 2)..=(d + 2) {
for x in -(d + 2)..=(d + 2) {
let rel_x = (x.abs() - 2).max(0);
let rel_z = (z.abs() - 2).max(0);
if rel_x * rel_x + rel_z * rel_z < d * d {
positions.push((x as i8, z as i8));
}
}
}
positions.sort_by_key(|&(x, z)| i32::from(x).pow(2) + i32::from(z).pow(2));
let array_elems = positions.into_iter().map(|(x, z)| quote!((#x, #z)));
quote! {
&[ #(#array_elems),* ]
}
});
let array_len = MAX_VIEW_DISTANCE as usize + 1;
quote! {
/// The maximum supported view distance
pub const MAX_VIEW_DISTANCE: u8 = #MAX_VIEW_DISTANCE;
/// Static precomputed lookup table for relative chunk offsets by view distance (0..=32).
pub static CHUNK_VIEW_LUT: [&[(i8, i8)]; #array_len] = [ #(#entries),* ];
}
}

View File

@ -18,6 +18,7 @@ mod block;
mod carver; mod carver;
pub mod chest_loot; pub mod chest_loot;
mod chunk_gen_settings; mod chunk_gen_settings;
mod chunk_view_lut;
mod chunk_status; mod chunk_status;
mod composter_increase_chance; mod composter_increase_chance;
mod configured_feature; mod configured_feature;
@ -85,6 +86,7 @@ pub fn main() {
(meta_data_type::build, "meta_data_type.rs"), (meta_data_type::build, "meta_data_type.rs"),
(tracked_data::build, "tracked_data.rs"), (tracked_data::build, "tracked_data.rs"),
(chunk_status::build, "chunk_status.rs"), (chunk_status::build, "chunk_status.rs"),
(chunk_view_lut::build, "chunk_view_lut.rs"),
(game_event::build, "game_event.rs"), (game_event::build, "game_event.rs"),
(game_rules::build, "game_rules.rs"), (game_rules::build, "game_rules.rs"),
(registry::build, "registry.rs"), (registry::build, "registry.rs"),

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,9 @@
#![allow(unused_imports)] #![allow(unused_imports)]
#[rustfmt::skip]
#[path = "generated/chunk_view_lut.rs"]
pub mod chunk_view_lut;
#[rustfmt::skip] #[rustfmt::skip]
#[path = "generated/chest_loot.rs"] #[path = "generated/chest_loot.rs"]
pub mod chest_loot_table; pub mod chest_loot_table;

View File

@ -978,7 +978,6 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn no_fade_chunk() { fn no_fade_chunk() {
let expected_data: Vec<(i32, i32, i32, f64)> = let expected_data: Vec<(i32, i32, i32, f64)> =
read_data_from_file!("../../assets/tests/perlin_7_4.json"); read_data_from_file!("../../assets/tests/perlin_7_4.json");
@ -1013,7 +1012,6 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn map() { fn map() {
let expected_data: Vec<i32> = read_data_from_file!("../../assets/tests/perlin_map.json"); let expected_data: Vec<i32> = read_data_from_file!("../../assets/tests/perlin_map.json");
let mut expected_iter = expected_data.iter(); let mut expected_iter = expected_data.iter();

View File

@ -93,7 +93,6 @@ mod test {
} }
#[test] #[test]
#[ignore]
fn sample_multinoise_biome() { fn sample_multinoise_biome() {
use crate::generation::generator::{GeneratorInit, VanillaGenerator}; use crate::generation::generator::{GeneratorInit, VanillaGenerator};
use pumpkin_util::world_seed::Seed; use pumpkin_util::world_seed::Seed;

View File

@ -1,42 +1,14 @@
use super::{ChunkLevel, ChunkPos, HashMapType, LevelChannel}; use super::{ChunkLevel, ChunkPos, HashMapType, LevelChannel};
use crate::chunk_system::chunk_state::StagedChunkEnum; // Fixed path use crate::chunk_system::chunk_state::StagedChunkEnum; // Fixed path
use itertools::Itertools; use itertools::Itertools;
use std::cmp::{Ordering, PartialEq, min}; use std::cmp::min;
use std::collections::BinaryHeap; use std::collections::VecDeque;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::fmt::Write; use std::fmt::Write;
use std::mem::swap; use std::mem::swap;
use std::sync::Arc; use std::sync::Arc;
use tracing::debug; use tracing::debug;
pub struct HeapNode(i8, ChunkPos);
impl PartialEq for HeapNode {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for HeapNode {}
impl PartialOrd for HeapNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HeapNode {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0).reverse()
}
}
impl From<(ChunkPos, i8)> for HeapNode {
fn from(value: (ChunkPos, i8)) -> Self {
Self(value.1, value.0)
}
}
impl From<HeapNode> for (ChunkPos, i8) {
fn from(val: HeapNode) -> Self {
(val.1, val.0)
}
}
struct LevelCache(i32, i32, usize, [(i8, i8); 256 * 256]); struct LevelCache(i32, i32, usize, [(i8, i8); 256 * 256]);
#[expect(clippy::large_stack_arrays)] #[expect(clippy::large_stack_arrays)]
@ -123,8 +95,8 @@ pub struct ChunkLoading {
pub ticket: HashMapType<ChunkPos, Vec<i8>>, // TODO lifetime & id pub ticket: HashMapType<ChunkPos, Vec<i8>>, // TODO lifetime & id
pub high_priority: Vec<ChunkPos>, pub high_priority: Vec<ChunkPos>,
pub sender: Arc<LevelChannel>, pub sender: Arc<LevelChannel>,
pub increase_update: BinaryHeap<HeapNode>, pub increase_update: VecDeque<(ChunkPos, i8)>,
pub decrease_update: BinaryHeap<HeapNode>, pub decrease_update: VecDeque<(ChunkPos, i8)>,
cache: LevelCache, cache: LevelCache,
} }
@ -207,6 +179,12 @@ impl ChunkLoading {
Self::FULL_CHUNK_LEVEL - (view_distance as i8) Self::FULL_CHUNK_LEVEL - (view_distance as i8)
} }
#[inline]
#[must_use]
pub const fn get_level_from_simulation_distance(simulation_distance: u8) -> i8 {
Self::FULL_CHUNK_LEVEL - (simulation_distance as i8)
}
pub fn new(sender: Arc<LevelChannel>) -> Self { pub fn new(sender: Arc<LevelChannel>) -> Self {
Self { Self {
is_priority_dirty: true, is_priority_dirty: true,
@ -215,8 +193,8 @@ impl ChunkLoading {
ticket: HashMapType::default(), ticket: HashMapType::default(),
high_priority: Vec::new(), high_priority: Vec::new(),
sender, sender,
increase_update: BinaryHeap::default(), increase_update: VecDeque::default(),
decrease_update: BinaryHeap::default(), decrease_update: VecDeque::default(),
cache: LevelCache::new(), cache: LevelCache::new(),
} }
} }
@ -241,8 +219,7 @@ impl ChunkLoading {
} }
fn run_increase_update(&mut self) { fn run_increase_update(&mut self) {
while let Some(node) = self.increase_update.pop() { while let Some((pos, level)) = self.increase_update.pop_front() {
let (pos, level) = node.into();
debug_assert!(level < Self::MAX_LEVEL); debug_assert!(level < Self::MAX_LEVEL);
if level > self.cache.get(&self.pos_level, pos) { if level > self.cache.get(&self.pos_level, pos) {
continue; continue;
@ -270,12 +247,11 @@ impl ChunkLoading {
return; return;
} }
self.cache.set(&self.pos_level, pos, level); self.cache.set(&self.pos_level, pos, level);
self.increase_update.push((pos, level).into()); self.increase_update.push_back((pos, level));
} }
fn run_decrease_update(&mut self, pos: ChunkPos, range: i32) { fn run_decrease_update(&mut self, pos: ChunkPos, range: i32) {
while let Some(node) = self.decrease_update.pop() { while let Some((pos, level)) = self.decrease_update.pop_front() {
let (pos, level) = node.into();
debug_assert!(level < Self::MAX_LEVEL); debug_assert!(level < Self::MAX_LEVEL);
let spread_level = level + 1; let spread_level = level + 1;
for dx in -1..2 { for dx in -1..2 {
@ -292,10 +268,10 @@ impl ChunkLoading {
if new_pos_level == spread_level { if new_pos_level == spread_level {
self.cache.set(&self.pos_level, new_pos, Self::MAX_LEVEL); self.cache.set(&self.pos_level, new_pos, Self::MAX_LEVEL);
if spread_level < Self::MAX_LEVEL { if spread_level < Self::MAX_LEVEL {
self.decrease_update.push((new_pos, spread_level).into()); self.decrease_update.push_back((new_pos, spread_level));
} }
} else { } else {
self.increase_update.push((new_pos, new_pos_level).into()); self.increase_update.push_back((new_pos, new_pos_level));
} }
} }
} }
@ -310,7 +286,7 @@ impl ChunkLoading {
continue; continue;
} }
self.cache.set(&self.pos_level, *ticket_pos, level); self.cache.set(&self.pos_level, *ticket_pos, level);
self.increase_update.push((*ticket_pos, level).into()); self.increase_update.push_back((*ticket_pos, level));
} }
} }
self.run_increase_update(); self.run_increase_update();
@ -354,7 +330,7 @@ impl ChunkLoading {
self.cache.set(&self.pos_level, pos, level); self.cache.set(&self.pos_level, pos, level);
debug_assert!(self.increase_update.is_empty()); debug_assert!(self.increase_update.is_empty());
self.increase_update.push((pos, level).into()); self.increase_update.push_back((pos, level));
self.run_increase_update(); self.run_increase_update();
self.cache.write(&mut self.pos_level, &mut self.change); self.cache.write(&mut self.pos_level, &mut self.change);
debug_assert!(self.debug_check_error()); debug_assert!(self.debug_check_error());
@ -382,7 +358,7 @@ impl ChunkLoading {
self.cache.clean(pos, old_level); self.cache.clean(pos, old_level);
self.cache.set(&self.pos_level, pos, Self::MAX_LEVEL); self.cache.set(&self.pos_level, pos, Self::MAX_LEVEL);
debug_assert!(self.decrease_update.is_empty()); debug_assert!(self.decrease_update.is_empty());
self.decrease_update.push((pos, level).into()); self.decrease_update.push_back((pos, level));
self.run_decrease_update(pos, (Self::MAX_LEVEL - level - 1) as i32); self.run_decrease_update(pos, (Self::MAX_LEVEL - level - 1) as i32);
self.cache.write(&mut self.pos_level, &mut self.change); self.cache.write(&mut self.pos_level, &mut self.change);
} }

View File

@ -38,18 +38,22 @@ impl Cylindrical {
} }
} }
#[allow(dead_code)]
const fn left(&self) -> i32 { const fn left(&self) -> i32 {
self.center.x - self.view_distance.get() as i32 - 1 self.center.x - self.view_distance.get() as i32 - 1
} }
#[allow(dead_code)]
const fn bottom(&self) -> i32 { const fn bottom(&self) -> i32 {
self.center.y - self.view_distance.get() as i32 - 1 self.center.y - self.view_distance.get() as i32 - 1
} }
#[allow(dead_code)]
const fn right(&self) -> i32 { const fn right(&self) -> i32 {
self.center.x + self.view_distance.get() as i32 + 1 self.center.x + self.view_distance.get() as i32 + 1
} }
#[allow(dead_code)]
const fn top(&self) -> i32 { const fn top(&self) -> i32 {
self.center.y + self.view_distance.get() as i32 + 1 self.center.y + self.view_distance.get() as i32 + 1
} }
@ -67,32 +71,22 @@ impl Cylindrical {
hyp_sqr < (self.view_distance.get() as i64).pow(2) hyp_sqr < (self.view_distance.get() as i64).pow(2)
} }
/// Returns an iterator of all chunks within this cylinder /// Returns a precomputed list of relative chunk offset pairs `(dx, dy)` for a given view distance.
#[must_use] #[must_use]
pub fn all_chunks_within(&self) -> Vec<Vector2<i32>> { #[inline]
if self.view_distance.get() == 1 { pub fn get_offsets(view_distance: u8) -> &'static [(i8, i8)] {
return Vec::new(); let idx =
} (view_distance as usize).min(pumpkin_data::chunk_view_lut::MAX_VIEW_DISTANCE as usize);
// I came up with this values by testing pumpkin_data::chunk_view_lut::CHUNK_VIEW_LUT[idx]
// for view distances 2-32 it usually gives 5 - 20 chunks more than needed if the player is on ground
// this looks scary but this few calculations are definitely faster than ~5 reallocations
// this part "3167) >> 10" is a replacement for flointing point multiplication
let estimated_capacity = ((self.view_distance.get() as usize + 3).pow(2) * 3167) >> 10;
let mut all_chunks = Vec::with_capacity(estimated_capacity);
for x in self.left()..=self.right() {
let mut was_in = false;
'inner: for z in self.bottom()..=self.top() {
if self.is_within_distance(x, z) {
all_chunks.push(Vector2::new(x, z));
was_in = true;
} else if was_in {
break 'inner;
}
}
} }
all_chunks /// Returns an iterator of all chunks within this cylinder using the precomputed LUT
#[must_use]
pub fn all_chunks_within(self) -> impl ExactSizeIterator<Item = Vector2<i32>> {
let offsets = Self::get_offsets(self.view_distance.get());
offsets.iter().map(move |&(dx, dy)| {
Vector2::new(self.center.x + i32::from(dx), self.center.y + i32::from(dy))
})
} }
} }

View File

@ -114,6 +114,7 @@ macro_rules! sample_surface_router_function {
// //
// This is equivalent to a Java `NoiseRouter` after being passed into `NoiseConfig` but before being // This is equivalent to a Java `NoiseRouter` after being passed into `NoiseConfig` but before being
// passed into `ChunkNoiseGenerator` // passed into `ChunkNoiseGenerator`
#[expect(clippy::too_many_lines)]
fn normal_surface_noisified() { fn normal_surface_noisified() {
let pos = Vector3 { x: 0, y: 0, z: 0 }; let pos = Vector3 { x: 0, y: 0, z: 0 };
// TODO: Move these values to a file and create an extractor for them // TODO: Move these values to a file and create an extractor for them

View File

@ -172,6 +172,13 @@ impl StructurePieceBase for EndCityPiece {
&[], &[],
Some(chunk_box), Some(chunk_box),
); );
crate::generation::structure::template::place_template_entities(
chunk,
&self.ship,
ship_pos,
self.rotation,
chunk_box,
);
} }
} }
} }

View File

@ -182,6 +182,13 @@ impl StructurePieceBase for IglooPiece {
&[], &[],
Some(chunk_box), Some(chunk_box),
); );
crate::generation::structure::template::place_template_entities(
chunk,
&self.top_template,
origin,
self.rotation,
chunk_box,
);
// Place basement components if present // Place basement components if present
if self.has_basement { if self.has_basement {
@ -233,6 +240,21 @@ impl StructurePieceBase for IglooPiece {
&[], &[],
Some(chunk_box), Some(chunk_box),
); );
let (rotated_ox, rotated_oz) = self
.rotation
.rotate_offset(BASEMENT_OFFSET_X, BASEMENT_OFFSET_Z);
let placement_origin = Vector3::new(
basement_origin.x + rotated_ox,
basement_origin.y,
basement_origin.z + rotated_oz,
);
crate::generation::structure::template::place_template_entities(
chunk,
bottom,
placement_origin,
self.rotation,
chunk_box,
);
} }
} }
} }

View File

@ -497,7 +497,7 @@ impl StructurePieceBase for PoolElementStructurePiece {
pumpkin_util::math::vector3::Vector3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z); pumpkin_util::math::vector3::Vector3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z);
self.element self.element
.for_each_template(|name, processor_list, legacy, template| { .for_each_template(|_name, processor_list, legacy, template| {
let corner = self.rotation.rotate_offset( let corner = self.rotation.rotate_offset(
template.size.x.saturating_sub(1), template.size.x.saturating_sub(1),
template.size.z.saturating_sub(1), template.size.z.saturating_sub(1),
@ -524,7 +524,6 @@ impl StructurePieceBase for PoolElementStructurePiece {
processors.as_ref(), processors.as_ref(),
Some(chunk_box), Some(chunk_box),
); );
if name.starts_with("minecraft:pillager_outpost/") {
crate::generation::structure::template::place_template_entities( crate::generation::structure::template::place_template_entities(
chunk, chunk,
&template, &template,
@ -532,7 +531,6 @@ impl StructurePieceBase for PoolElementStructurePiece {
self.rotation, self.rotation,
chunk_box, chunk_box,
); );
}
}); });
if let Some(feature) = self.element.feature() if let Some(feature) = self.element.feature()
@ -683,7 +681,7 @@ impl StructureGenerator for JigsawGenerator {
&MaxDistance::new(max_distance), &MaxDistance::new(max_distance),
&dimension_padding, &dimension_padding,
liquid_settings, liquid_settings,
&PoolAliasLookup, &PoolAliasLookup::default(),
) )
} }
} }

View File

@ -52,16 +52,57 @@ pub const MAX_TOTAL_STRUCTURE_RANGE: i32 = 128;
pub const MIN_DEPTH: i32 = 0; pub const MIN_DEPTH: i32 = 0;
pub const MAX_DEPTH: i32 = 20; pub const MAX_DEPTH: i32 = 20;
/// Simple lookup for Pool Aliases introduced in 1.20+ /// Dynamic lookup for Pool Aliases introduced in 1.20+ (e.g. Trial Chambers spawner/contents aliases).
pub struct PoolAliasLookup; #[derive(Debug, Clone, Default)]
pub struct PoolAliasLookup {
aliases: std::collections::HashMap<String, Vec<(String, u32)>>,
}
impl PoolAliasLookup { impl PoolAliasLookup {
#[must_use] #[must_use]
pub const fn lookup<'a>(&self, id: &'a str) -> &'a str { pub fn new() -> Self {
// In a complete implementation, this would look up the alias in the context/registry. Self {
// Returning the ID directly acts as a fallback/default behavior. aliases: std::collections::HashMap::new(),
}
}
pub fn add_direct(&mut self, alias: String, target: String) {
self.aliases.insert(alias, vec![(target, 1)]);
}
pub fn add_random(&mut self, alias: String, targets: Vec<(String, u32)>) {
self.aliases.insert(alias, targets);
}
#[must_use]
pub fn lookup<'a>(
&'a self,
id: &'a str,
random: &mut pumpkin_util::random::RandomGenerator,
) -> &'a str {
if let Some(targets) = self.aliases.get(id) {
if targets.is_empty() {
return id;
}
if targets.len() == 1 {
return &targets[0].0;
}
let total_weight: u32 = targets.iter().map(|(_, w)| *w).sum();
if total_weight == 0 {
return &targets[0].0;
}
let mut r = random.next_bounded_i32(total_weight as i32) as u32;
for (target, weight) in targets {
if r < *weight {
return target.as_str();
}
r -= *weight;
}
&targets[0].0
} else {
id id
} }
}
} }
impl JigsawPlacement { impl JigsawPlacement {
@ -86,7 +127,7 @@ impl JigsawPlacement {
let max_depth = max_depth.clamp(MIN_DEPTH, MAX_DEPTH); let max_depth = max_depth.clamp(MIN_DEPTH, MAX_DEPTH);
let actual_start_pool_id = pool_alias_lookup.lookup(start_pool_id); let actual_start_pool_id = pool_alias_lookup.lookup(start_pool_id, &mut context.random);
let pool = TemplatePool::discover(actual_start_pool_id)?; let pool = TemplatePool::discover(actual_start_pool_id)?;
let rotation = Rotation::from_index(context.random.next_bounded_i32(4) as u8); let rotation = Rotation::from_index(context.random.next_bounded_i32(4) as u8);
let element = pool.get_random_element(&mut context.random).clone(); let element = pool.get_random_element(&mut context.random).clone();
@ -235,7 +276,7 @@ impl JigsawPlacement {
continue; continue;
} }
let target_pool_id = pool_alias_lookup.lookup(raw_pool_id); let target_pool_id = pool_alias_lookup.lookup(raw_pool_id, &mut context.random);
let Some(target_pool) = TemplatePool::discover(target_pool_id) else { let Some(target_pool) = TemplatePool::discover(target_pool_id) else {
continue; continue;
}; };
@ -246,7 +287,8 @@ impl JigsawPlacement {
.extend(target_pool.get_shuffled_elements(&mut context.random)); .extend(target_pool.get_shuffled_elements(&mut context.random));
} }
let fallback_pool_id = pool_alias_lookup.lookup(&target_pool.fallback); let fallback_pool_id =
pool_alias_lookup.lookup(&target_pool.fallback, &mut context.random);
if let Some(fallback_pool) = TemplatePool::discover(fallback_pool_id) { if let Some(fallback_pool) = TemplatePool::discover(fallback_pool_id) {
target_elements target_elements
.extend(fallback_pool.get_shuffled_elements(&mut context.random)); .extend(fallback_pool.get_shuffled_elements(&mut context.random));
@ -361,7 +403,8 @@ impl JigsawPlacement {
rotated_tj_target_pos.y, rotated_tj_target_pos.y,
rotated_tj_target_pos.z, rotated_tj_target_pos.z,
) { ) {
let child_pool_id = pool_alias_lookup.lookup(&tj.pool); let child_pool_id = pool_alias_lookup
.lookup(&tj.pool, &mut context.random);
let child_pool_max_y = let child_pool_max_y =
get_pool_max_y_size(child_pool_id); get_pool_max_y_size(child_pool_id);
@ -369,7 +412,8 @@ impl JigsawPlacement {
TemplatePool::discover(child_pool_id) TemplatePool::discover(child_pool_id)
{ {
get_pool_max_y_size( get_pool_max_y_size(
pool_alias_lookup.lookup(&cp.fallback), pool_alias_lookup
.lookup(&cp.fallback, &mut context.random),
) )
} else { } else {
0 0

View File

@ -444,10 +444,14 @@ impl Level {
/// Marks chunks no longer "watched" by a unique player. When no players are watching a chunk, /// Marks chunks no longer "watched" by a unique player. When no players are watching a chunk,
/// it is removed from memory. Should only be called on chunks the player was watching before /// it is removed from memory. Should only be called on chunks the player was watching before
pub async fn mark_chunks_as_not_watched(&self, chunks: &[Vector2<i32>]) -> Vec<Vector2<i32>> { pub async fn mark_chunks_as_not_watched(
&self,
chunks: impl IntoIterator<Item = impl std::borrow::Borrow<Vector2<i32>>>,
) -> Vec<Vector2<i32>> {
let mut chunks_to_clean = Vec::new(); let mut chunks_to_clean = Vec::new();
let chunks_vec: Vec<Vector2<i32>> = chunks.into_iter().map(|c| *c.borrow()).collect();
for chunk in chunks { for chunk in &chunks_vec {
if let Entry::Occupied(mut entry) = self.chunk_watchers.entry(*chunk) { if let Entry::Occupied(mut entry) = self.chunk_watchers.entry(*chunk) {
*entry.get_mut() = entry.get().saturating_sub(1); *entry.get_mut() = entry.get().saturating_sub(1);
if *entry.get() == 0 { if *entry.get() == 0 {
@ -458,7 +462,7 @@ impl Level {
} }
self.entity_saver self.entity_saver
.unwatch_chunks(&self.level_folder, chunks) .unwatch_chunks(&self.level_folder, &chunks_vec)
.await; .await;
chunks_to_clean chunks_to_clean
} }
@ -466,14 +470,18 @@ impl Level {
/// Returns whether the chunk should be removed from memory /// Returns whether the chunk should be removed from memory
#[inline] #[inline]
pub async fn mark_chunk_as_not_watched(&self, chunk: Vector2<i32>) -> bool { pub async fn mark_chunk_as_not_watched(&self, chunk: Vector2<i32>) -> bool {
!self.mark_chunks_as_not_watched(&[chunk]).await.is_empty() !self.mark_chunks_as_not_watched([chunk]).await.is_empty()
} }
// In Level::clean_entity_chunks() // In Level::clean_entity_chunks()
pub fn clean_entity_chunks(self: &Arc<Self>, chunks: &[Vector2<i32>]) { pub fn clean_entity_chunks(
self: &Arc<Self>,
chunks: impl IntoIterator<Item = impl std::borrow::Borrow<Vector2<i32>>>,
) {
let chunks_to_process: Vec<_> = chunks let chunks_to_process: Vec<_> = chunks
.iter() .into_iter()
.filter_map(|pos| { .filter_map(|pos_borrow| {
let pos = pos_borrow.borrow();
// Only include chunks with no watchers // Only include chunks with no watchers
let has_watchers = self let has_watchers = self
.chunk_watchers .chunk_watchers
@ -581,7 +589,7 @@ impl Level {
} }
pub fn clean_entity_chunk(self: &Arc<Self>, chunk: &Vector2<i32>) { pub fn clean_entity_chunk(self: &Arc<Self>, chunk: &Vector2<i32>) {
self.clean_entity_chunks(&[*chunk]); self.clean_entity_chunks([*chunk]);
} }
pub fn is_chunk_watched(&self, chunk: &Vector2<i32>) -> bool { pub fn is_chunk_watched(&self, chunk: &Vector2<i32>) -> bool {

View File

@ -230,23 +230,15 @@ pub struct LevelDat {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use flate2::read::GzDecoder;
use pumpkin_data::game_rules::GameRuleRegistry; use pumpkin_data::game_rules::GameRuleRegistry;
use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes}; use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes};
use pumpkin_util::{Difficulty, world_seed::Seed}; use pumpkin_util::{Difficulty, world_seed::Seed};
use std::{ use std::{io::Cursor, sync::LazyLock};
fs,
io::{Cursor, Read},
sync::LazyLock,
};
use tempfile::TempDir; use tempfile::TempDir;
use crate::{ use crate::world_info::{DataPacks, LevelData, WorldGenSettings, WorldVersion};
global_path,
world_info::{DataPacks, LevelData, WorldGenSettings, WorldInfoError, WorldVersion},
};
use super::{AnvilLevelInfo, LEVEL_DAT_FILE_NAME, LevelDat, WorldInfoReader, WorldInfoWriter}; use super::{AnvilLevelInfo, LevelDat, WorldInfoReader, WorldInfoWriter};
#[test] #[test]
fn preserve_level_dat_seed() { fn preserve_level_dat_seed() {

View File

@ -93,6 +93,11 @@ impl<'a> PistonHandler<'a> {
Self::is_block_sticky(state) || Self::is_block_sticky(adjacent_state) Self::is_block_sticky(state) || Self::is_block_sticky(adjacent_state)
} }
fn is_piston_head_or_base(&self, pos: BlockPos) -> bool {
pos == self.pos_from
|| (!self.retracted && pos == self.pos_from.offset(self.piston_direction.to_offset()))
}
async fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool { async fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool {
let (mut block, block_state) = self.world.get_block_and_state(&pos); let (mut block, block_state) = self.world.get_block_and_state(&pos);
if block_state.is_air() { if block_state.is_air() {
@ -101,7 +106,7 @@ impl<'a> PistonHandler<'a> {
if !PistonBlock::is_movable(block, block_state, self.motion_direction, false, dir) { if !PistonBlock::is_movable(block, block_state, self.motion_direction, false, dir) {
return true; return true;
} }
if pos == self.pos_from { if self.is_piston_head_or_base(pos) {
return true; return true;
} }
if self.moved_blocks.contains(&pos) { if self.moved_blocks.contains(&pos) {
@ -124,7 +129,7 @@ impl<'a> PistonHandler<'a> {
false, false,
self.motion_direction.opposite(), self.motion_direction.opposite(),
) )
|| block_pos == self.pos_from || self.is_piston_head_or_base(block_pos)
{ {
break; break;
} }
@ -157,7 +162,10 @@ impl<'a> PistonHandler<'a> {
return true; return true;
} }
let (block, block_state) = self.world.get_block_and_state(&block_pos2); let (block, block_state) = self.world.get_block_and_state(&block_pos2);
if block_state.is_air() { if block_state.is_air()
|| (!self.retracted
&& block_pos2 == self.pos_from.offset(self.piston_direction.to_offset()))
{
return true; return true;
} }
if !PistonBlock::is_movable( if !PistonBlock::is_movable(
@ -166,7 +174,7 @@ impl<'a> PistonHandler<'a> {
self.motion_direction, self.motion_direction,
true, true,
self.motion_direction, self.motion_direction,
) || block_pos2 == self.pos_from ) || self.is_piston_head_or_base(block_pos2)
{ {
return false; return false;
} }

View File

@ -65,7 +65,12 @@ impl BlockBehaviour for ObserverBlock {
Box::pin(async move { Box::pin(async move {
let props = ObserverLikeProperties::from_state_id(args.state_id, args.block); let props = ObserverLikeProperties::from_state_id(args.state_id, args.block);
if props.facing.to_block_direction() == args.direction && !props.powered { if props.facing.to_block_direction() == args.direction
&& !props.powered
&& !args
.world
.is_block_tick_scheduled(args.position, &Block::OBSERVER)
{
Self::schedule_tick(args.world, args.position); Self::schedule_tick(args.world, args.position);
} }
@ -75,12 +80,9 @@ impl BlockBehaviour for ObserverBlock {
fn emits_redstone_power<'a>( fn emits_redstone_power<'a>(
&'a self, &'a self,
args: EmitsRedstonePowerArgs<'a>, _args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> { ) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move { true })
let props = ObserverLikeProperties::from_state_id(args.state.id, args.block);
props.facing.to_block_direction() == args.direction
})
} }
fn get_weak_redstone_power<'a>( fn get_weak_redstone_power<'a>(
@ -127,12 +129,12 @@ impl ObserverBlock {
block_pos: &BlockPos, block_pos: &BlockPos,
props: &ObserverLikeProperties, props: &ObserverLikeProperties,
) { ) {
let facing = props.facing; let facing = props.facing.to_block_direction();
let opposite_facing_pos = let opposite_facing_pos = block_pos.offset(facing.opposite().to_offset());
block_pos.offset(facing.to_block_direction().opposite().to_offset());
world.update_neighbor(&opposite_facing_pos, block).await; world.update_neighbor(&opposite_facing_pos, block).await;
world world
.update_neighbors(&opposite_facing_pos, Some(facing.to_block_direction())) .update_neighbors(&opposite_facing_pos, Some(facing))
.await; .await;
} }

View File

@ -95,7 +95,7 @@ impl JigsawBlockEntity {
&MaxDistance::new(80), &MaxDistance::new(80),
&DimensionPadding::ZERO, &DimensionPadding::ZERO,
LiquidSettings::ApplyWaterlog, LiquidSettings::ApplyWaterlog,
&PoolAliasLookup, &PoolAliasLookup::default(),
) )
}; };

View File

@ -208,9 +208,7 @@ impl PistonBlockEntity {
.clone() .clone()
.set_block_state(&pos, state, BlockFlags::NOTIFY_ALL) .set_block_state(&pos, state, BlockFlags::NOTIFY_ALL)
.await; .await;
world world.update_neighbors(&pos, None).await;
.update_neighbor(&pos, Block::from_state_id(state))
.await;
} }
} }
} }
@ -260,10 +258,7 @@ impl BlockEntity for PistonBlockEntity {
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED, BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
) )
.await; .await;
world world.clone().update_neighbors(&pos, None).await;
.clone()
.update_neighbor(&pos, Block::from_state_id(updated_state))
.await;
} }
} }
return; return;

View File

@ -225,7 +225,7 @@ impl CommandExecutor for PlaceJigsawExecutor {
&MaxDistance::new(128), &MaxDistance::new(128),
&DimensionPadding::ZERO, &DimensionPadding::ZERO,
LiquidSettings::ApplyWaterlog, LiquidSettings::ApplyWaterlog,
&PoolAliasLookup, &PoolAliasLookup::default(),
) )
.ok_or_else(|| { .ok_or_else(|| {
JIGSAW_FAILED.create_without_context(TextComponent::text(pool.clone())) JIGSAW_FAILED.create_without_context(TextComponent::text(pool.clone()))
@ -334,7 +334,7 @@ impl CommandExecutor for PlaceStructureExecutor {
&MaxDistance::new(structure.max_distance_from_center.unwrap_or(128)), &MaxDistance::new(structure.max_distance_from_center.unwrap_or(128)),
&DimensionPadding::ZERO, &DimensionPadding::ZERO,
LiquidSettings::ApplyWaterlog, LiquidSettings::ApplyWaterlog,
&PoolAliasLookup, &PoolAliasLookup::default(),
) )
.ok_or_else(|| { .ok_or_else(|| {
JIGSAW_FAILED JIGSAW_FAILED

View File

@ -254,10 +254,17 @@ impl ChunkManager {
let new_level = ChunkLoading::get_level_from_view_distance(view_distance); let new_level = ChunkLoading::get_level_from_view_distance(view_distance);
lock.add_ticket(center, new_level); lock.add_ticket(center, new_level);
let sim_dist = self.world.server.upgrade().map_or(10, |s| {
s.advanced_config.networking.java.simulation_distance.get()
});
let sim_level = ChunkLoading::get_level_from_simulation_distance(sim_dist);
lock.add_ticket(center, sim_level);
if (old_center != center || old_view_distance != view_distance) && old_view_distance > 0 if (old_center != center || old_view_distance != view_distance) && old_view_distance > 0
{ {
let old_level = ChunkLoading::get_level_from_view_distance(old_view_distance); let old_level = ChunkLoading::get_level_from_view_distance(old_view_distance);
lock.remove_ticket(old_center, old_level); lock.remove_ticket(old_center, old_level);
lock.remove_ticket(old_center, sim_level);
} }
lock.send_change(); lock.send_change();
}; };
@ -891,7 +898,7 @@ impl Player {
let level = &world.level; let level = &world.level;
// Decrement the value of watched chunks // Decrement the value of watched chunks
let chunks_to_clean = level.mark_chunks_as_not_watched(&radial_chunks).await; let chunks_to_clean = level.mark_chunks_as_not_watched(radial_chunks).await;
// Remove chunks with no watchers from the cache // Remove chunks with no watchers from the cache
if !chunks_to_clean.is_empty() { if !chunks_to_clean.is_empty() {
world.remove_entities_in_chunks(&chunks_to_clean).await; world.remove_entities_in_chunks(&chunks_to_clean).await;
@ -2474,7 +2481,7 @@ impl Player {
pub async fn unload_watched_chunks(&self, world: &World) { pub async fn unload_watched_chunks(&self, world: &World) {
let radial_chunks = self.watched_section.load().all_chunks_within(); let radial_chunks = self.watched_section.load().all_chunks_within();
let level = &world.level; let level = &world.level;
let chunks_to_clean = level.mark_chunks_as_not_watched(&radial_chunks).await; let chunks_to_clean = level.mark_chunks_as_not_watched(radial_chunks).await;
if !chunks_to_clean.is_empty() { if !chunks_to_clean.is_empty() {
world.remove_entities_in_chunks(&chunks_to_clean).await; world.remove_entities_in_chunks(&chunks_to_clean).await;
level.clean_entity_chunks(&chunks_to_clean); level.clean_entity_chunks(&chunks_to_clean);

View File

@ -319,11 +319,13 @@ impl World {
pub fn update_active_chunks(self: &Arc<Self>) { pub fn update_active_chunks(self: &Arc<Self>) {
let mut active_chunks = FxHashSet::default(); let mut active_chunks = FxHashSet::default();
let sim_dist = self.server.upgrade().map_or(10, |s| {
s.advanced_config.networking.java.simulation_distance.get()
}) as i32;
for player in self.players.load().iter() { for player in self.players.load().iter() {
let center = player.get_entity().chunk_pos.load(); let center = player.get_entity().chunk_pos.load();
// TODO: gamerule for view distance/ticking distance for dx in -sim_dist..=sim_dist {
for dx in -8..=8 { for dy in -sim_dist..=sim_dist {
for dy in -8..=8 {
active_chunks.insert(center.add_raw(dx, dy)); active_chunks.insert(center.add_raw(dx, dy));
} }
} }
@ -4213,8 +4215,14 @@ impl World {
); );
} }
pub async fn remove_entities_in_chunks(&self, chunks: &[Vector2<i32>]) { pub async fn remove_entities_in_chunks(
let chunks_set: FxHashSet<_> = chunks.iter().copied().collect(); &self,
chunks: impl IntoIterator<Item = impl std::borrow::Borrow<Vector2<i32>>>,
) {
let chunks_set: FxHashSet<_> = chunks.into_iter().map(|c| *c.borrow()).collect();
if chunks_set.is_empty() {
return;
}
let mut entities_to_remove = Vec::new(); let mut entities_to_remove = Vec::new();
self.entities.rcu(|current_entities| { self.entities.rcu(|current_entities| {