Block entities lived in one flat DashMap keyed by BlockPos, and every world
tick rebuilt the tick list by iterating the entire map and filtering it down
to the active chunks. That is O(all loaded block entities): the more of the
world that has been explored (chests, signs, hoppers... all count), the
longer every tick spends walking block entities it is not going to tick, so
TPS slowly bleeds away on long-running worlds.
Key the map by chunk instead (chunk -> its block entities). Ticking now walks
only the handful of active chunks and their entries, inserts/removals/lookups
compute the chunk key up front, and unloading a chunk drops its whole bucket.
Behaviour is unchanged - the same block entities get ticked - it just stops
scanning the ones that are loaded but nowhere near a player.
The world tick iterated every entity in the world and ticked all of them,
regardless of where they were - so entities sitting in loaded-but-not-active
chunks kept running their AI, movement and player-collision checks every tick.
That is O(all loaded entities) and grows as a world is explored, bleeding TPS
on long-running servers.
Skip entities whose chunk isn't in the active (ticking) set - the same set
block-entity ticking and mob spawning already use, and matching vanilla, which
only ticks entities within the simulation distance. The check uses the live
position rather than the cached chunk_pos, since fast movers (minecarts,
projectiles) update pos directly and leave chunk_pos stale.
Players are ticked separately and aren't in this list, so they're unaffected.
* fix: prevent capacity overflow crash when flying with elytra or in creative mode
The NoiseBasedCountPlacementModifier::get_count() can return negative i32
values when foliage noise sampling produces negative results at certain
world coordinates. The previous code cast this negative i32 directly to
usize, causing an integer wrap to ~18 quintillion, which triggered a
capacity overflow panic in Vec allocation during chunk feature generation.
This aligns with vanilla Minecraft behavior which also clamps the count
to a minimum of 0 before using it.
Closes#2345
* Hi
* implementing the advancement configuration for saving or not the advancements
* adding a test for not loading files when save is disabled
* cargo fmt
* fix merge
* Fix variable name for players directory
* sin/cos util
* 4 nieghbor check
* formating
* air helper
* carver wrapper
* fmt
* clippy
* clipp2
* remove local y
* out of bound guard
* fluid tick
* surface rule
* wire up
* math correct
* start fix
* bring back xoroshiro oops
* scheduler guard
* simplify test
* clean test
* comment
* crash fix
* clippy
* clippy
* i hate you codex
* sin cos
* im stupid f32
Rebased onto current master after #2296 (PlayerRespawnEvent).
- Fire PlayerChangeWorldEvent (cancellable) before the cross-dimension
transfer in World::respawn_player; a plugin may redirect new_world,
override position/yaw/pitch, or cancel to keep the player in the
current world.
- Use the safe transfer ordering (remove_player -> unload_watched_chunks
-> change_world -> set_world -> publish) so no observer sees the player
in a world whose chunk manager doesn't match, and update the entity's
world reference (set_world) to fix the latent stale-world bug.
- When cancelled or the target world can't be resolved, fall back to the
current world's spawn.
- The non-cancellable PlayerRespawnEvent (#2296) now fires after this
event and observes the resolved world automatically; document the
ordering and mutation-vs-cancellation semantics on both events.
* refactor: less manual BlockMetadata impls
* refactor: added BlockId type
BlockId is a wrapper for u16: it is valid for any u16 that is the id of a Block.
- Changed the Block.id field type to BlockId
- added named BlockId constants
- BlockMetadata::ids() now returns Box<[BlockId]>
- adjusted pumpkin-macros to use BlockId constants
- fixed some methods that were comparing blockstate ids or item ids (u16) against block ids (previously u16)
TODO: check if unsafe blocks can be removed; The compiler might understand that BlockId is always a valid index into mappings::TYPE_FROM_RAW_ID
* refactor: added BlockStateId type
A BlockStateId is a safe wrapper around the numerical index of a BlockState in pumpkin-data. They help avoiding validity checks (outside of IO and, currently, plugins), and make it easier for other contributors to reason about what they're comparing; BlockStateIds, BlockIds or Item ids (still u16).
pumpkin-data::BlockStateId replaces RawBlockState and BlockStateId from pumpkin-world.
- added the BlockStateId wrapper type
- made (almost; plugins) every function interacting with block states or block state ids use the wrapper type
- changed codegen logic to create/work with BlockStateIds
- made ChunkPalette parsing check BlockStateId validity (pumpkin-world::chunk::format::ChunkSectionBlockStates)
TODO: check if unsafe blocks can be removed; The compiler might understand that BlockStateId is always a valid index into mappings::BLOCK_ID_FROM_STATE_ID and mappings::STATE_FROM_STATE_ID
* refactor: imports
changed every use pumpkin_world::BlockStateId to pumpkin_data::BlockStateId
* refactor: Block- & BlockStateId
finishing touches;
- rebased on latest mater
- ensure there are no bound checks on Block(State)Id conversions, making them extremely cheap
- this required unsafe std::hint::assert_unchecked annotations because the compiler is (occasionally) stupid
- on debug builds the bound checks still exist because of ub_checks (see rust unstable book for the feature of the same name)
- added Safety notes to hopefully prevent anyone from enabling the creation of invalid Block(State)Ids in the future
- fixed a benchmark
The `Swap` branch of `internal_on_slot_click` indexed
`self.get_behaviour().slots[slot_index as usize]` without first checking
`slot_index >= 0`, unlike every sibling branch (Pickup, QuickMove, Throw,
Clone, QuickCraft) which all guard with `if slot_index < 0 { return; }`.
`slot_index` originates from the client `SClickSlot` packet (`i16` slot
field, widened to `i32`). The upstream validation in `Player::on_slot_click`
relies on `ScreenHandler::is_slot_valid`, whose check
`slot == -1 || slot == -999 || slot < slots.len() as i32` returns `true`
for ANY negative slot (a negative value is always `< slots.len()`), so
negative indices are not filtered out before reaching the handler. A
crafted Swap packet with a negative slot (e.g. -5) and a hotbar button in
`0..9` (or 40) therefore evaluated `slots[(-5) as usize]`, an enormous
index, causing an out-of-bounds Vec panic and crashing the server.
Add the same `if slot_index < 0 { return; }` guard at the top of the Swap
branch, matching the sibling branches exactly. Minimal, low-risk: legitimate
Swap clicks always carry a non-negative container slot, so behaviour is
unchanged for valid input.
BlockPalette::liquid_block_count() returned the inverse of what it
should: the Homogeneous arm returned 0 when the section was liquid and
the full VOLUME (4096) when it was not, and the Heterogeneous arm summed
the counts of non-liquid blocks (filtering on !is_liquid).
Flip both arms to actually count liquid blocks, mirroring the correct
sibling non_air_block_count(): Homogeneous yields VOLUME when the single
block is liquid (0 otherwise) and Heterogeneous filters on is_liquid.
This value is sent to clients as the fluid count in chunk data
(MC 26.1+), so the inversion reported wrong fluid counts to players.
* starting implementing the advancement command
* implementing globally of the advancement command
* fixing lib.rs
* implementing the new Advancement argument and updating the AdvancementArgs lifetime
* updating wit by adding advancement as command arg
* cargo fmt
* fixing the advancement
hooking the saving
spawn blocking task for IO task
* fixing the update of load to be async
* adding await to load for a test
* fix when save is disable
* making save_enable check first
* fix conflict
* update hash
* fix wit change
* fix column_pos
* adding rust documentation
* typos fix
* removing example
* invalid advancement packet
* fix itemstack parsing to use ItemStack Template ones
* changing enum type from i32 to VarInt
* fix packet by switching up the frame type and flags
* fixing packet and loading of the advancement
* cargo clippy
* update wit
When a player right-clicks with a shield already in the off-hand,
`prepare_hand_item_for_use` locks the ItemStack Mutex via `held`,
then tries to lock the same Mutex again through `get_or_insert`.
Tokio's Mutex is not reentrant, causing a permanent deadlock that
crashes the server 5 seconds later when the tick loop times out.
Add `PlayerInventory::is_already_equipped()` which uses Arc::ptr_eq
to detect when the item is already in the target slot, and skip the
redundant equip logic before any lock is attempted.
Closes#2288
Adds a non-cancellable PlayerRespawnEvent fired from World::respawn_player
once a respawn destination (world, position, rotation) is determined,
exposing it to plugins through the WIT event API (host + guest). The
previous world, respawned world, position, yaw, pitch and whether the
player kept their data are provided. Bumps the pumpkin-plugin-wit
submodule to the matching event definition.
Closes#1714. Depends on Pumpkin-MC/pumpkin-plugin-wit#8.