chore: some fixes
This commit is contained in:
parent
141b49f973
commit
98d4451fc9
42
pumpkin-codegen/src/chunk_view_lut.rs
Normal file
42
pumpkin-codegen/src/chunk_view_lut.rs
Normal 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),* ];
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ mod block;
|
||||
mod carver;
|
||||
pub mod chest_loot;
|
||||
mod chunk_gen_settings;
|
||||
mod chunk_view_lut;
|
||||
mod chunk_status;
|
||||
mod composter_increase_chance;
|
||||
mod configured_feature;
|
||||
@ -85,6 +86,7 @@ pub fn main() {
|
||||
(meta_data_type::build, "meta_data_type.rs"),
|
||||
(tracked_data::build, "tracked_data.rs"),
|
||||
(chunk_status::build, "chunk_status.rs"),
|
||||
(chunk_view_lut::build, "chunk_view_lut.rs"),
|
||||
(game_event::build, "game_event.rs"),
|
||||
(game_rules::build, "game_rules.rs"),
|
||||
(registry::build, "registry.rs"),
|
||||
|
||||
44321
pumpkin-data/src/generated/chunk_view_lut.rs
Normal file
44321
pumpkin-data/src/generated/chunk_view_lut.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,9 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[path = "generated/chunk_view_lut.rs"]
|
||||
pub mod chunk_view_lut;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[path = "generated/chest_loot.rs"]
|
||||
pub mod chest_loot_table;
|
||||
|
||||
@ -978,7 +978,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn no_fade_chunk() {
|
||||
let expected_data: Vec<(i32, i32, i32, f64)> =
|
||||
read_data_from_file!("../../assets/tests/perlin_7_4.json");
|
||||
@ -1013,7 +1012,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn map() {
|
||||
let expected_data: Vec<i32> = read_data_from_file!("../../assets/tests/perlin_map.json");
|
||||
let mut expected_iter = expected_data.iter();
|
||||
|
||||
@ -93,7 +93,6 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn sample_multinoise_biome() {
|
||||
use crate::generation::generator::{GeneratorInit, VanillaGenerator};
|
||||
use pumpkin_util::world_seed::Seed;
|
||||
|
||||
@ -1,42 +1,14 @@
|
||||
use super::{ChunkLevel, ChunkPos, HashMapType, LevelChannel};
|
||||
use crate::chunk_system::chunk_state::StagedChunkEnum; // Fixed path
|
||||
use itertools::Itertools;
|
||||
use std::cmp::{Ordering, PartialEq, min};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::cmp::min;
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt::Write;
|
||||
use std::mem::swap;
|
||||
use std::sync::Arc;
|
||||
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]);
|
||||
|
||||
#[expect(clippy::large_stack_arrays)]
|
||||
@ -123,8 +95,8 @@ pub struct ChunkLoading {
|
||||
pub ticket: HashMapType<ChunkPos, Vec<i8>>, // TODO lifetime & id
|
||||
pub high_priority: Vec<ChunkPos>,
|
||||
pub sender: Arc<LevelChannel>,
|
||||
pub increase_update: BinaryHeap<HeapNode>,
|
||||
pub decrease_update: BinaryHeap<HeapNode>,
|
||||
pub increase_update: VecDeque<(ChunkPos, i8)>,
|
||||
pub decrease_update: VecDeque<(ChunkPos, i8)>,
|
||||
cache: LevelCache,
|
||||
}
|
||||
|
||||
@ -207,6 +179,12 @@ impl ChunkLoading {
|
||||
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 {
|
||||
Self {
|
||||
is_priority_dirty: true,
|
||||
@ -215,8 +193,8 @@ impl ChunkLoading {
|
||||
ticket: HashMapType::default(),
|
||||
high_priority: Vec::new(),
|
||||
sender,
|
||||
increase_update: BinaryHeap::default(),
|
||||
decrease_update: BinaryHeap::default(),
|
||||
increase_update: VecDeque::default(),
|
||||
decrease_update: VecDeque::default(),
|
||||
cache: LevelCache::new(),
|
||||
}
|
||||
}
|
||||
@ -241,8 +219,7 @@ impl ChunkLoading {
|
||||
}
|
||||
|
||||
fn run_increase_update(&mut self) {
|
||||
while let Some(node) = self.increase_update.pop() {
|
||||
let (pos, level) = node.into();
|
||||
while let Some((pos, level)) = self.increase_update.pop_front() {
|
||||
debug_assert!(level < Self::MAX_LEVEL);
|
||||
if level > self.cache.get(&self.pos_level, pos) {
|
||||
continue;
|
||||
@ -270,12 +247,11 @@ impl ChunkLoading {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
while let Some(node) = self.decrease_update.pop() {
|
||||
let (pos, level) = node.into();
|
||||
while let Some((pos, level)) = self.decrease_update.pop_front() {
|
||||
debug_assert!(level < Self::MAX_LEVEL);
|
||||
let spread_level = level + 1;
|
||||
for dx in -1..2 {
|
||||
@ -292,10 +268,10 @@ impl ChunkLoading {
|
||||
if new_pos_level == spread_level {
|
||||
self.cache.set(&self.pos_level, new_pos, 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 {
|
||||
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;
|
||||
}
|
||||
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();
|
||||
@ -354,7 +330,7 @@ impl ChunkLoading {
|
||||
self.cache.set(&self.pos_level, pos, level);
|
||||
|
||||
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.cache.write(&mut self.pos_level, &mut self.change);
|
||||
debug_assert!(self.debug_check_error());
|
||||
@ -382,7 +358,7 @@ impl ChunkLoading {
|
||||
self.cache.clean(pos, old_level);
|
||||
self.cache.set(&self.pos_level, pos, Self::MAX_LEVEL);
|
||||
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.cache.write(&mut self.pos_level, &mut self.change);
|
||||
}
|
||||
|
||||
@ -38,18 +38,22 @@ impl Cylindrical {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const fn left(&self) -> i32 {
|
||||
self.center.x - self.view_distance.get() as i32 - 1
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const fn bottom(&self) -> i32 {
|
||||
self.center.y - self.view_distance.get() as i32 - 1
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const fn right(&self) -> i32 {
|
||||
self.center.x + self.view_distance.get() as i32 + 1
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const fn top(&self) -> i32 {
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn all_chunks_within(&self) -> Vec<Vector2<i32>> {
|
||||
if self.view_distance.get() == 1 {
|
||||
return Vec::new();
|
||||
}
|
||||
// I came up with this values by testing
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_offsets(view_distance: u8) -> &'static [(i8, i8)] {
|
||||
let idx =
|
||||
(view_distance as usize).min(pumpkin_data::chunk_view_lut::MAX_VIEW_DISTANCE as usize);
|
||||
pumpkin_data::chunk_view_lut::CHUNK_VIEW_LUT[idx]
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
// passed into `ChunkNoiseGenerator`
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn normal_surface_noisified() {
|
||||
let pos = Vector3 { x: 0, y: 0, z: 0 };
|
||||
// TODO: Move these values to a file and create an extractor for them
|
||||
|
||||
@ -172,6 +172,13 @@ impl StructurePieceBase for EndCityPiece {
|
||||
&[],
|
||||
Some(chunk_box),
|
||||
);
|
||||
crate::generation::structure::template::place_template_entities(
|
||||
chunk,
|
||||
&self.ship,
|
||||
ship_pos,
|
||||
self.rotation,
|
||||
chunk_box,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,6 +182,13 @@ impl StructurePieceBase for IglooPiece {
|
||||
&[],
|
||||
Some(chunk_box),
|
||||
);
|
||||
crate::generation::structure::template::place_template_entities(
|
||||
chunk,
|
||||
&self.top_template,
|
||||
origin,
|
||||
self.rotation,
|
||||
chunk_box,
|
||||
);
|
||||
|
||||
// Place basement components if present
|
||||
if self.has_basement {
|
||||
@ -233,6 +240,21 @@ impl StructurePieceBase for IglooPiece {
|
||||
&[],
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
self.element
|
||||
.for_each_template(|name, processor_list, legacy, template| {
|
||||
.for_each_template(|_name, processor_list, legacy, template| {
|
||||
let corner = self.rotation.rotate_offset(
|
||||
template.size.x.saturating_sub(1),
|
||||
template.size.z.saturating_sub(1),
|
||||
@ -524,7 +524,6 @@ impl StructurePieceBase for PoolElementStructurePiece {
|
||||
processors.as_ref(),
|
||||
Some(chunk_box),
|
||||
);
|
||||
if name.starts_with("minecraft:pillager_outpost/") {
|
||||
crate::generation::structure::template::place_template_entities(
|
||||
chunk,
|
||||
&template,
|
||||
@ -532,7 +531,6 @@ impl StructurePieceBase for PoolElementStructurePiece {
|
||||
self.rotation,
|
||||
chunk_box,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(feature) = self.element.feature()
|
||||
@ -683,7 +681,7 @@ impl StructureGenerator for JigsawGenerator {
|
||||
&MaxDistance::new(max_distance),
|
||||
&dimension_padding,
|
||||
liquid_settings,
|
||||
&PoolAliasLookup,
|
||||
&PoolAliasLookup::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,16 +52,57 @@ pub const MAX_TOTAL_STRUCTURE_RANGE: i32 = 128;
|
||||
pub const MIN_DEPTH: i32 = 0;
|
||||
pub const MAX_DEPTH: i32 = 20;
|
||||
|
||||
/// Simple lookup for Pool Aliases introduced in 1.20+
|
||||
pub struct PoolAliasLookup;
|
||||
/// Dynamic lookup for Pool Aliases introduced in 1.20+ (e.g. Trial Chambers spawner/contents aliases).
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PoolAliasLookup {
|
||||
aliases: std::collections::HashMap<String, Vec<(String, u32)>>,
|
||||
}
|
||||
|
||||
impl PoolAliasLookup {
|
||||
#[must_use]
|
||||
pub const fn lookup<'a>(&self, id: &'a str) -> &'a str {
|
||||
// In a complete implementation, this would look up the alias in the context/registry.
|
||||
// Returning the ID directly acts as a fallback/default behavior.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JigsawPlacement {
|
||||
@ -86,7 +127,7 @@ impl JigsawPlacement {
|
||||
|
||||
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 rotation = Rotation::from_index(context.random.next_bounded_i32(4) as u8);
|
||||
let element = pool.get_random_element(&mut context.random).clone();
|
||||
@ -235,7 +276,7 @@ impl JigsawPlacement {
|
||||
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 {
|
||||
continue;
|
||||
};
|
||||
@ -246,7 +287,8 @@ impl JigsawPlacement {
|
||||
.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) {
|
||||
target_elements
|
||||
.extend(fallback_pool.get_shuffled_elements(&mut context.random));
|
||||
@ -361,7 +403,8 @@ impl JigsawPlacement {
|
||||
rotated_tj_target_pos.y,
|
||||
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 =
|
||||
get_pool_max_y_size(child_pool_id);
|
||||
|
||||
@ -369,7 +412,8 @@ impl JigsawPlacement {
|
||||
TemplatePool::discover(child_pool_id)
|
||||
{
|
||||
get_pool_max_y_size(
|
||||
pool_alias_lookup.lookup(&cp.fallback),
|
||||
pool_alias_lookup
|
||||
.lookup(&cp.fallback, &mut context.random),
|
||||
)
|
||||
} else {
|
||||
0
|
||||
|
||||
@ -444,10 +444,14 @@ impl Level {
|
||||
|
||||
/// 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
|
||||
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 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) {
|
||||
*entry.get_mut() = entry.get().saturating_sub(1);
|
||||
if *entry.get() == 0 {
|
||||
@ -458,7 +462,7 @@ impl Level {
|
||||
}
|
||||
|
||||
self.entity_saver
|
||||
.unwatch_chunks(&self.level_folder, chunks)
|
||||
.unwatch_chunks(&self.level_folder, &chunks_vec)
|
||||
.await;
|
||||
chunks_to_clean
|
||||
}
|
||||
@ -466,14 +470,18 @@ impl Level {
|
||||
/// Returns whether the chunk should be removed from memory
|
||||
#[inline]
|
||||
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()
|
||||
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
|
||||
.iter()
|
||||
.filter_map(|pos| {
|
||||
.into_iter()
|
||||
.filter_map(|pos_borrow| {
|
||||
let pos = pos_borrow.borrow();
|
||||
// Only include chunks with no watchers
|
||||
let has_watchers = self
|
||||
.chunk_watchers
|
||||
@ -581,7 +589,7 @@ impl Level {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@ -230,23 +230,15 @@ pub struct LevelDat {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use flate2::read::GzDecoder;
|
||||
use pumpkin_data::game_rules::GameRuleRegistry;
|
||||
use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes};
|
||||
use pumpkin_util::{Difficulty, world_seed::Seed};
|
||||
use std::{
|
||||
fs,
|
||||
io::{Cursor, Read},
|
||||
sync::LazyLock,
|
||||
};
|
||||
use std::{io::Cursor, sync::LazyLock};
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::{
|
||||
global_path,
|
||||
world_info::{DataPacks, LevelData, WorldGenSettings, WorldInfoError, WorldVersion},
|
||||
};
|
||||
use crate::world_info::{DataPacks, LevelData, WorldGenSettings, WorldVersion};
|
||||
|
||||
use super::{AnvilLevelInfo, LEVEL_DAT_FILE_NAME, LevelDat, WorldInfoReader, WorldInfoWriter};
|
||||
use super::{AnvilLevelInfo, LevelDat, WorldInfoReader, WorldInfoWriter};
|
||||
|
||||
#[test]
|
||||
fn preserve_level_dat_seed() {
|
||||
|
||||
@ -93,6 +93,11 @@ impl<'a> PistonHandler<'a> {
|
||||
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 {
|
||||
let (mut block, block_state) = self.world.get_block_and_state(&pos);
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
if pos == self.pos_from {
|
||||
if self.is_piston_head_or_base(pos) {
|
||||
return true;
|
||||
}
|
||||
if self.moved_blocks.contains(&pos) {
|
||||
@ -124,7 +129,7 @@ impl<'a> PistonHandler<'a> {
|
||||
false,
|
||||
self.motion_direction.opposite(),
|
||||
)
|
||||
|| block_pos == self.pos_from
|
||||
|| self.is_piston_head_or_base(block_pos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -157,7 +162,10 @@ impl<'a> PistonHandler<'a> {
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if !PistonBlock::is_movable(
|
||||
@ -166,7 +174,7 @@ impl<'a> PistonHandler<'a> {
|
||||
self.motion_direction,
|
||||
true,
|
||||
self.motion_direction,
|
||||
) || block_pos2 == self.pos_from
|
||||
) || self.is_piston_head_or_base(block_pos2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -65,7 +65,12 @@ impl BlockBehaviour for ObserverBlock {
|
||||
Box::pin(async move {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -75,12 +80,9 @@ impl BlockBehaviour for ObserverBlock {
|
||||
|
||||
fn emits_redstone_power<'a>(
|
||||
&'a self,
|
||||
args: EmitsRedstonePowerArgs<'a>,
|
||||
_args: EmitsRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let props = ObserverLikeProperties::from_state_id(args.state.id, args.block);
|
||||
props.facing.to_block_direction() == args.direction
|
||||
})
|
||||
Box::pin(async move { true })
|
||||
}
|
||||
|
||||
fn get_weak_redstone_power<'a>(
|
||||
@ -127,12 +129,12 @@ impl ObserverBlock {
|
||||
block_pos: &BlockPos,
|
||||
props: &ObserverLikeProperties,
|
||||
) {
|
||||
let facing = props.facing;
|
||||
let opposite_facing_pos =
|
||||
block_pos.offset(facing.to_block_direction().opposite().to_offset());
|
||||
let facing = props.facing.to_block_direction();
|
||||
let opposite_facing_pos = block_pos.offset(facing.opposite().to_offset());
|
||||
|
||||
world.update_neighbor(&opposite_facing_pos, block).await;
|
||||
world
|
||||
.update_neighbors(&opposite_facing_pos, Some(facing.to_block_direction()))
|
||||
.update_neighbors(&opposite_facing_pos, Some(facing))
|
||||
.await;
|
||||
}
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ impl JigsawBlockEntity {
|
||||
&MaxDistance::new(80),
|
||||
&DimensionPadding::ZERO,
|
||||
LiquidSettings::ApplyWaterlog,
|
||||
&PoolAliasLookup,
|
||||
&PoolAliasLookup::default(),
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
@ -208,9 +208,7 @@ impl PistonBlockEntity {
|
||||
.clone()
|
||||
.set_block_state(&pos, state, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world
|
||||
.update_neighbor(&pos, Block::from_state_id(state))
|
||||
.await;
|
||||
world.update_neighbors(&pos, None).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -260,10 +258,7 @@ impl BlockEntity for PistonBlockEntity {
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
|
||||
)
|
||||
.await;
|
||||
world
|
||||
.clone()
|
||||
.update_neighbor(&pos, Block::from_state_id(updated_state))
|
||||
.await;
|
||||
world.clone().update_neighbors(&pos, None).await;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@ -225,7 +225,7 @@ impl CommandExecutor for PlaceJigsawExecutor {
|
||||
&MaxDistance::new(128),
|
||||
&DimensionPadding::ZERO,
|
||||
LiquidSettings::ApplyWaterlog,
|
||||
&PoolAliasLookup,
|
||||
&PoolAliasLookup::default(),
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
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)),
|
||||
&DimensionPadding::ZERO,
|
||||
LiquidSettings::ApplyWaterlog,
|
||||
&PoolAliasLookup,
|
||||
&PoolAliasLookup::default(),
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
JIGSAW_FAILED
|
||||
|
||||
@ -254,10 +254,17 @@ impl ChunkManager {
|
||||
let new_level = ChunkLoading::get_level_from_view_distance(view_distance);
|
||||
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
|
||||
{
|
||||
let old_level = ChunkLoading::get_level_from_view_distance(old_view_distance);
|
||||
lock.remove_ticket(old_center, old_level);
|
||||
lock.remove_ticket(old_center, sim_level);
|
||||
}
|
||||
lock.send_change();
|
||||
};
|
||||
@ -891,7 +898,7 @@ impl Player {
|
||||
let level = &world.level;
|
||||
|
||||
// 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
|
||||
if !chunks_to_clean.is_empty() {
|
||||
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
||||
@ -2474,7 +2481,7 @@ impl Player {
|
||||
pub async fn unload_watched_chunks(&self, world: &World) {
|
||||
let radial_chunks = self.watched_section.load().all_chunks_within();
|
||||
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() {
|
||||
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
||||
level.clean_entity_chunks(&chunks_to_clean);
|
||||
|
||||
@ -319,11 +319,13 @@ impl World {
|
||||
|
||||
pub fn update_active_chunks(self: &Arc<Self>) {
|
||||
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() {
|
||||
let center = player.get_entity().chunk_pos.load();
|
||||
// TODO: gamerule for view distance/ticking distance
|
||||
for dx in -8..=8 {
|
||||
for dy in -8..=8 {
|
||||
for dx in -sim_dist..=sim_dist {
|
||||
for dy in -sim_dist..=sim_dist {
|
||||
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>]) {
|
||||
let chunks_set: FxHashSet<_> = chunks.iter().copied().collect();
|
||||
pub async fn remove_entities_in_chunks(
|
||||
&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();
|
||||
|
||||
self.entities.rcu(|current_entities| {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user