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;
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"),

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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();

View File

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

View File

@ -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);
}

View File

@ -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);
#[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]
}
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
// 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

View File

@ -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,
);
}
}
}

View File

@ -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,
);
}
}
}

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);
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,15 +524,13 @@ 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,
placement_origin,
self.rotation,
chunk_box,
);
}
crate::generation::structure::template::place_template_entities(
chunk,
&template,
placement_origin,
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(),
)
}
}

View File

@ -52,15 +52,56 @@ 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.
id
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
}
}
}
@ -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

View File

@ -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 {

View File

@ -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() {

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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| {