From 63d97e2c82807bef40c012a94b0c605312811b9c Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 7 Aug 2026 21:45:05 +0200 Subject: [PATCH] refactor(inventory): replace per-slot Arc> with container-level synchronization --- crates/pumpkin-data/Cargo.toml | 4 +- .../pumpkin-data/benches/has_random_ticks.rs | 8 + crates/pumpkin-data/src/block_state.rs | 2 +- .../pumpkin-data/src/generated/slot_ranges.rs | 1 + crates/pumpkin-data/src/lib.rs | 8 + .../src/anvil/anvil_screen_handler.rs | 44 +-- .../src/beacon_screen_handler.rs | 18 +- .../src/brewing/brewing_screen_handler.rs | 6 +- .../src/crafting/crafting_inventory.rs | 69 ++-- .../src/crafting/crafting_screen_handler.rs | 29 +- crates/pumpkin-inventory/src/double.rs | 3 +- .../enchanting/enchanting_screen_handler.rs | 36 +- .../pumpkin-inventory/src/entity_equipment.rs | 51 +-- .../furnace_like_screen_handler.rs | 8 +- .../src/generic_container_screen_handler.rs | 20 +- crates/pumpkin-inventory/src/gui_builder.rs | 18 +- .../src/merchant/merchant_screen_handler.rs | 38 +- .../src/player/ender_chest_inventory.rs | 51 +-- .../src/player/player_inventory.rs | 329 +++++++++--------- .../src/player/player_screen_handler.rs | 6 +- .../pumpkin-inventory/src/screen_handler.rs | 53 +-- crates/pumpkin-inventory/src/slot.rs | 32 +- .../src/stonecutter_screen_handler.rs | 25 +- crates/pumpkin-plugin-api/Cargo.toml | 3 + crates/pumpkin-plugin-api/src/lib.rs | 9 + .../src/java/client/dialog.rs | 7 +- .../java/client/play/update_advancement.rs | 1 - crates/pumpkin-world/src/biome/mod.rs | 4 +- .../pumpkin-world/src/chunk/format/anvil.rs | 2 +- crates/pumpkin-world/src/chunk/mod.rs | 4 +- .../pumpkin-world/src/inventory/inventory.rs | 42 +-- crates/pumpkin-world/src/inventory/mod.rs | 13 +- .../src/inventory/simple_inventory.rs | 60 ++-- .../pumpkin/src/block/blocks/brewing_stand.rs | 2 +- crates/pumpkin/src/block/blocks/cake.rs | 8 +- crates/pumpkin/src/block/blocks/campfire.rs | 11 +- .../pumpkin/src/block/blocks/candle_cakes.rs | 2 +- crates/pumpkin/src/block/blocks/candles.rs | 4 +- .../src/block/blocks/chiseled_bookshelf.rs | 14 +- crates/pumpkin/src/block/blocks/composter.rs | 2 +- .../pumpkin/src/block/blocks/flower_pots.rs | 2 +- crates/pumpkin/src/block/blocks/jukebox.rs | 2 +- crates/pumpkin/src/block/blocks/lectern.rs | 4 +- crates/pumpkin/src/block/blocks/magma.rs | 9 +- .../pumpkin/src/block/blocks/plant/bamboo.rs | 2 +- .../src/block/blocks/plant/bamboo_sapling.rs | 2 +- .../blocks/plant/crop/sweet_berry_bush.rs | 2 +- .../src/block/blocks/plant/sea_pickles.rs | 2 +- .../pumpkin/src/block/blocks/powder_snow.rs | 7 +- crates/pumpkin/src/block/blocks/pumpkin.rs | 2 +- .../src/block/blocks/redstone/crafter.rs | 2 +- .../src/block/blocks/redstone/dispenser.rs | 3 +- .../src/block/blocks/redstone/dropper.rs | 6 +- .../src/block/blocks/redstone/tripwire.rs | 9 +- crates/pumpkin/src/block/blocks/signs.rs | 18 +- crates/pumpkin/src/block/blocks/snow.rs | 5 +- crates/pumpkin/src/block/blocks/tnt.rs | 2 +- crates/pumpkin/src/block/blocks/vine.rs | 4 +- crates/pumpkin/src/block/entities/barrel.rs | 53 ++- crates/pumpkin/src/block/entities/beacon.rs | 6 +- .../src/block/entities/blasting_furnace.rs | 5 +- .../src/block/entities/brewing_stand.rs | 113 +++--- crates/pumpkin/src/block/entities/chest.rs | 4 +- .../block/entities/chest_like_block_entity.rs | 48 +-- .../src/block/entities/chiseled_bookshelf.rs | 110 +++--- crates/pumpkin/src/block/entities/crafter.rs | 58 +-- .../pumpkin/src/block/entities/dispenser.rs | 78 +++-- crates/pumpkin/src/block/entities/dropper.rs | 79 ++--- crates/pumpkin/src/block/entities/furnace.rs | 5 +- .../entities/furnace_like_block_entity.rs | 151 ++++---- crates/pumpkin/src/block/entities/hopper.rs | 89 +++-- crates/pumpkin/src/block/entities/jukebox.rs | 4 +- crates/pumpkin/src/block/entities/lectern.rs | 4 +- crates/pumpkin/src/block/entities/shelf.rs | 56 +-- .../pumpkin/src/block/entities/shulker_box.rs | 98 +++--- crates/pumpkin/src/block/entities/sign.rs | 16 +- crates/pumpkin/src/block/entities/smoker.rs | 5 +- .../src/block/entities/trapped_chest.rs | 4 +- crates/pumpkin/src/block/mod.rs | 6 +- crates/pumpkin/src/block/registry.rs | 3 +- crates/pumpkin/src/command/commands/clear.rs | 62 ++-- .../pumpkin/src/command/commands/enchant.rs | 4 +- crates/pumpkin/src/command/commands/item.rs | 3 +- crates/pumpkin/src/command/commands/loot.rs | 9 +- crates/pumpkin/src/entity/ai/goal/beg.rs | 7 +- crates/pumpkin/src/entity/ai/goal/tempt.rs | 8 +- crates/pumpkin/src/entity/combat.rs | 12 +- crates/pumpkin/src/entity/living.rs | 272 ++++++++------- crates/pumpkin/src/entity/mob/enderman.rs | 15 +- crates/pumpkin/src/entity/mob/equipment.rs | 2 +- crates/pumpkin/src/entity/mod.rs | 24 +- crates/pumpkin/src/entity/player.rs | 189 ++++------ .../src/entity/player/advancement/trigger.rs | 6 +- crates/pumpkin/src/item/items/boat.rs | 4 +- crates/pumpkin/src/item/items/bow.rs | 14 +- crates/pumpkin/src/item/items/bucket.rs | 13 +- crates/pumpkin/src/item/items/bundle.rs | 8 +- crates/pumpkin/src/item/items/crossbow.rs | 50 +-- crates/pumpkin/src/item/items/egg.rs | 25 +- crates/pumpkin/src/item/items/ender_eye.rs | 4 +- crates/pumpkin/src/item/items/ender_pearl.rs | 23 +- crates/pumpkin/src/item/items/map.rs | 27 +- crates/pumpkin/src/item/items/potions.rs | 73 ++-- crates/pumpkin/src/item/items/snowball.rs | 23 +- crates/pumpkin/src/item/items/trident.rs | 102 +++--- crates/pumpkin/src/net/bedrock/nethernet.rs | 3 +- crates/pumpkin/src/net/bedrock/play.rs | 146 ++++---- crates/pumpkin/src/net/java/play.rs | 132 +++---- crates/pumpkin/src/net/java/recipe_helper.rs | 8 +- crates/pumpkin/src/plugin/api/gui.rs | 56 +-- .../loader/wasm/wasm_host/wit/v0_1/gui.rs | 10 +- .../loader/wasm/wasm_host/wit/v0_1/player.rs | 12 +- crates/pumpkin/src/server/connection_cache.rs | 45 ++- crates/pumpkin/src/server/mod.rs | 6 +- crates/pumpkin/src/server/scheduler.rs | 4 +- crates/pumpkin/src/world/mod.rs | 43 +-- tools/pumpkin-codegen/Cargo.toml | 7 +- tools/pumpkin-codegen/src/biome.rs | 7 +- tools/pumpkin-codegen/src/main.rs | 16 + tools/pumpkin-codegen/src/slot_ranges.rs | 1 + tools/pumpkin-codegen/src/tracked_data.rs | 5 +- 121 files changed, 1674 insertions(+), 1931 deletions(-) diff --git a/crates/pumpkin-data/Cargo.toml b/crates/pumpkin-data/Cargo.toml index ef40c9558..673de8663 100644 --- a/crates/pumpkin-data/Cargo.toml +++ b/crates/pumpkin-data/Cargo.toml @@ -127,5 +127,5 @@ harness = false [package.metadata.cargo-machete] ignored = ["phf"] -# [lints] -# workspace = true +[lints] +workspace = true diff --git a/crates/pumpkin-data/benches/has_random_ticks.rs b/crates/pumpkin-data/benches/has_random_ticks.rs index 2acf2a263..e564f323b 100644 --- a/crates/pumpkin-data/benches/has_random_ticks.rs +++ b/crates/pumpkin-data/benches/has_random_ticks.rs @@ -1,3 +1,11 @@ +#![allow( + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + clippy::print_stdout +)] + use std::hint::black_box; use criterion::{Criterion, criterion_group, criterion_main}; diff --git a/crates/pumpkin-data/src/block_state.rs b/crates/pumpkin-data/src/block_state.rs index 368bb54a2..a8545d8ba 100644 --- a/crates/pumpkin-data/src/block_state.rs +++ b/crates/pumpkin-data/src/block_state.rs @@ -36,7 +36,7 @@ pub struct BlockState { pub block_entity_type: u16, } -/// Helper struct to ensure the validity of BlockStateIds parsed from external sources. +/// Helper struct to ensure the validity of `BlockStateIds` parsed from external sources. /// Every [`BlockStateId`] is guaranteed to correspond to a valid [`BlockState`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[repr(transparent)] diff --git a/crates/pumpkin-data/src/generated/slot_ranges.rs b/crates/pumpkin-data/src/generated/slot_ranges.rs index 847216e01..b34a8f65e 100644 --- a/crates/pumpkin-data/src/generated/slot_ranges.rs +++ b/crates/pumpkin-data/src/generated/slot_ranges.rs @@ -538,6 +538,7 @@ pub const SLOT_RANGE_SINGLE_SLOT_NAMES: [&str; 156usize] = [ "weapon.mainhand", "weapon.offhand", ]; +#[allow(clippy::match_same_arms)] #[must_use] pub fn get_slot_range(name: &str) -> Option<&'static [usize]> { match name { diff --git a/crates/pumpkin-data/src/lib.rs b/crates/pumpkin-data/src/lib.rs index 9ebbb2ba8..69004c268 100644 --- a/crates/pumpkin-data/src/lib.rs +++ b/crates/pumpkin-data/src/lib.rs @@ -1,4 +1,12 @@ #![allow(unused_imports)] +#![allow( + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + clippy::undocumented_unsafe_blocks, + clippy::if_then_some_else_none +)] #[rustfmt::skip] #[path = "generated/chunk_view_lut.rs"] diff --git a/crates/pumpkin-inventory/src/anvil/anvil_screen_handler.rs b/crates/pumpkin-inventory/src/anvil/anvil_screen_handler.rs index 255a28a3d..e10c3d427 100644 --- a/crates/pumpkin-inventory/src/anvil/anvil_screen_handler.rs +++ b/crates/pumpkin-inventory/src/anvil/anvil_screen_handler.rs @@ -53,10 +53,7 @@ impl AnvilScreenHandler { } pub async fn update_result_slot(&mut self) { - let input_a = { - let lock = self.inventory.get_stack(0).await; - lock.lock().await.clone() - }; + let input_a = self.inventory.get_stack(0).await; if input_a.is_empty() { self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await; @@ -130,7 +127,7 @@ impl ScreenHandler for AnvilScreenHandler { fn quick_move<'a>( &'a mut self, - _player: &'a dyn InventoryPlayer, + player: &'a dyn InventoryPlayer, slot_index: i32, ) -> ItemStackFuture<'a> { Box::pin(async move { @@ -138,40 +135,31 @@ impl ScreenHandler for AnvilScreenHandler { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let slot_stack_guard = slot_stack_lock.lock().await; - stack_left = slot_stack_guard.clone(); - drop(slot_stack_guard); - - let mut slot_stack_mut = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; + stack_left = slot_stack.clone(); if slot_index < 3 { // From anvil to player - if !self - .insert_item( - &mut slot_stack_mut, - 3, - self.get_behaviour().slots.len() as i32, - true, - ) - .await - { + if !self.insert_item(&mut slot_stack, 3, 39, true).await { return ItemStack::EMPTY.clone(); } + slot.on_quick_move_crafted(slot_stack.clone(), stack_left.clone()) + .await; } else { - // From player to anvil input 0 and 1 - if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await { + // From player to anvil + if !self.insert_item(&mut slot_stack, 0, 2, false).await { return ItemStack::EMPTY.clone(); } } - if slot_stack_mut.is_empty() { - drop(slot_stack_mut); - slot.set_stack(ItemStack::EMPTY.clone()).await; - } else { - drop(slot_stack_mut); - slot.mark_dirty().await; + if slot_stack.item_count == stack_left.item_count { + return ItemStack::EMPTY.clone(); } + + slot.set_stack_prev(slot_stack.clone(), stack_left.clone()) + .await; + slot.on_take_item(player, &slot_stack).await; + slot.mark_dirty().await; } stack_left diff --git a/crates/pumpkin-inventory/src/beacon_screen_handler.rs b/crates/pumpkin-inventory/src/beacon_screen_handler.rs index 2e80e4b7b..be5498b92 100644 --- a/crates/pumpkin-inventory/src/beacon_screen_handler.rs +++ b/crates/pumpkin-inventory/src/beacon_screen_handler.rs @@ -94,18 +94,14 @@ impl ScreenHandler for BeaconScreenHandler { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let slot_stack_guard = slot_stack_lock.lock().await; - stack_left = slot_stack_guard.clone(); - drop(slot_stack_guard); - - let mut slot_stack_mut = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; + stack_left = slot_stack.clone(); if slot_index == 0 { // Move from the single beacon slot to the player inventory (slots 1 to end) if !self .insert_item( - &mut slot_stack_mut, + &mut slot_stack, 1, self.get_behaviour().slots.len() as i32, true, @@ -116,17 +112,15 @@ impl ScreenHandler for BeaconScreenHandler { } } else { // Move from player inventory into the beacon payment slot (slot 0) - if !self.insert_item(&mut slot_stack_mut, 0, 1, false).await { + if !self.insert_item(&mut slot_stack, 0, 1, false).await { return ItemStack::EMPTY.clone(); } } - if slot_stack_mut.is_empty() { - drop(slot_stack_mut); + if slot_stack.is_empty() { slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - drop(slot_stack_mut); - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; } } diff --git a/crates/pumpkin-inventory/src/brewing/brewing_screen_handler.rs b/crates/pumpkin-inventory/src/brewing/brewing_screen_handler.rs index f346f69b3..5e21526a9 100644 --- a/crates/pumpkin-inventory/src/brewing/brewing_screen_handler.rs +++ b/crates/pumpkin-inventory/src/brewing/brewing_screen_handler.rs @@ -142,8 +142,7 @@ impl ScreenHandler for BrewingScreenHandler { return stack_left; } - let slot_stack_lock = slot.get_stack().await; - let mut stack = slot_stack_lock.lock().await; + let mut stack = slot.get_stack().await; stack_left = stack.clone(); let success = if slot_index < 5 { @@ -179,10 +178,9 @@ impl ScreenHandler for BrewingScreenHandler { } if stack.is_empty() { - drop(stack); slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - slot.mark_dirty().await; + slot.set_stack(stack).await; } stack_left diff --git a/crates/pumpkin-inventory/src/crafting/crafting_inventory.rs b/crates/pumpkin-inventory/src/crafting/crafting_inventory.rs index 148d41d76..1a09f4d67 100644 --- a/crates/pumpkin-inventory/src/crafting/crafting_inventory.rs +++ b/crates/pumpkin-inventory/src/crafting/crafting_inventory.rs @@ -9,12 +9,10 @@ //! Unlike regular inventories, crafting grids are typically cleared when //! the container closes, and their contents are used up when crafting. -use std::sync::Arc; use std::{any::Any, pin::Pin}; use pumpkin_data::item_stack::ItemStack; -use pumpkin_world::inventory::split_stack; -use tokio::sync::Mutex; +use tokio::sync::RwLock; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; @@ -29,14 +27,14 @@ use super::recipes::RecipeInputInventory; /// /// When a player places items in the crafting grid, they are stored here. /// When the crafting result is taken, the ingredients are consumed from this inventory. -#[derive(Clone)] +#[derive(Default)] pub struct CraftingInventory { /// Width of the crafting grid (typically 2 or 3). pub width: u8, /// Height of the crafting grid (typically 2 or 3). pub height: u8, /// Items in the crafting grid, stored row by row. - pub items: Vec>>, + pub items: RwLock>, } impl CraftingInventory { @@ -60,54 +58,64 @@ impl CraftingInventory { Self { width, height, - items: { - // Creates a Vec with different Mutexes for each slot - let mut v = Vec::with_capacity(width as usize * height as usize); - (0..width as usize * height as usize) - .for_each(|_| v.push(Arc::new(Mutex::new(ItemStack::EMPTY.clone())))); - v - }, + items: RwLock::new(vec![ + ItemStack::EMPTY.clone(); + width as usize * height as usize + ]), } } } impl Inventory for CraftingInventory { fn size(&self) -> usize { - self.items.len() + (self.width as usize) * (self.height as usize) } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items + .get(slot) + .cloned() + .unwrap_or_else(|| ItemStack::EMPTY.clone()) + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + let mut items = self.items.write().await; + if slot < items.len() { + std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()) + } else { + ItemStack::EMPTY.clone() + } }) } fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { - Box::pin(async move { split_stack(&self.items, slot, amount).await }) + Box::pin(async move { + let mut items = self.items.write().await; + if slot < items.len() && !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + } + }) } fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + if slot < items.len() { + items[slot] = stack; + } }) } @@ -129,9 +137,8 @@ impl RecipeInputInventory for CraftingInventory { impl Clearable for CraftingInventory { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for item in &self.items { - *item.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); }) } } diff --git a/crates/pumpkin-inventory/src/crafting/crafting_screen_handler.rs b/crates/pumpkin-inventory/src/crafting/crafting_screen_handler.rs index 4fd2540f3..b2e867a94 100644 --- a/crates/pumpkin-inventory/src/crafting/crafting_screen_handler.rs +++ b/crates/pumpkin-inventory/src/crafting/crafting_screen_handler.rs @@ -113,7 +113,7 @@ async fn recipe_matches( .get_stack((y + y_offset) * inventory.get_width() + (x + x_offset)) .await; if current_key == ' ' { - if !slot.lock().await.is_empty() { + if !slot.is_empty() { matched = false; break 'outer; } @@ -125,8 +125,6 @@ async fn recipe_matches( .find_map(|(k, v)| (*k == current_key).then_some(v)) .expect("Crafting recipe used invalid key"); - let slot = slot.lock().await; - if !ingredient.match_item(slot.item) { matched = false; break 'outer; @@ -146,7 +144,7 @@ async fn recipe_matches( ) .await; if current_key == ' ' { - if !slot.lock().await.is_empty() { + if !slot.is_empty() { matched = false; break 'outer; } @@ -156,7 +154,6 @@ async fn recipe_matches( .iter() .find_map(|(k, v)| (*k == current_key).then_some(v)) .expect("Crafting recipe used invalid key"); - let slot = slot.lock().await; if !ingredient.match_item(slot.item) { matched = false; break 'outer; @@ -181,7 +178,6 @@ async fn recipe_matches( let mut ingredient_used = vec![false; ingredients.len()]; 'next_slot: for i in 0..inventory.size() { let slot = inventory.get_stack(i).await; - let slot = slot.lock().await; if slot.is_empty() { continue 'next_slot; } @@ -209,7 +205,6 @@ async fn recipe_matches( } 'item_stack: for i in 0..inventory.size() { let slot = inventory.get_stack(i).await; - let slot = slot.lock().await; if slot.is_empty() { continue 'item_stack; } @@ -228,7 +223,6 @@ async fn recipe_matches( } for position in (1..=7).step_by(2) { let slot = inventory.get_stack(position).await; - let slot = slot.lock().await; if slot.is_empty() || !slot .item @@ -268,7 +262,7 @@ async fn recipe_matches( .get_stack((y + y_offset) * inventory.get_width() + (x + x_offset)) .await; if current_key == ' ' { - if !slot.lock().await.is_empty() { + if !slot.is_empty() { matched = false; break 'outer; } @@ -279,7 +273,6 @@ async fn recipe_matches( .find(|(k, _)| *k == current_key) .map(|(_, v)| v) .expect("Crafting recipe used invalid key"); - let slot = slot.lock().await; if !ingredient.match_item(slot.item) { matched = false; break 'outer; @@ -302,7 +295,6 @@ async fn recipe_matches( let mut ingredient_used = vec![false; ingredients.len()]; 'next_slot: for i in 0..inventory.size() { let slot = inventory.get_stack(i).await; - let slot = slot.lock().await; if slot.is_empty() { continue 'next_slot; } @@ -347,7 +339,6 @@ impl ResultSlot { let x = i % inventory_width; let y = i / inventory_width; let slot = self.inventory.get_stack(i).await; - let slot = slot.lock().await; if !slot.is_empty() { top_x = top_x.min(x); top_y = top_y.min(y); @@ -450,11 +441,7 @@ impl Slot for ResultSlot { ) .await; for i in 0..self.inventory.size() { - let slot = self.inventory.get_stack(i).await; - let mut stack = slot.lock().await; - if !stack.is_empty() { - stack.item_count -= 1; - } + self.inventory.remove_stack_specific(i, 1).await; } self.mark_dirty().await; }) @@ -462,8 +449,8 @@ impl Slot for ResultSlot { fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> { Box::pin(async move { false }) } - fn get_stack(&self) -> BoxFuture<'_, Arc>> { - Box::pin(async move { self.result.clone() }) + fn get_stack(&self) -> BoxFuture<'_, ItemStack> { + Box::pin(async move { self.result.lock().await.clone() }) } fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { Box::pin(async move { self.result.lock().await.clone() }) @@ -491,7 +478,6 @@ impl Slot for ResultSlot { let mut count = u8::MAX; for i in 0..self.inventory.size() { let slot = self.inventory.get_stack(i).await; - let slot = slot.lock().await; if !slot.is_empty() { count = count.min(slot.item_count); } @@ -613,8 +599,7 @@ impl ScreenHandler for CraftingTableScreenHandler { Box::pin(async move { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack = slot.get_stack().await; - let mut slot_stack = slot_stack.lock().await; + let mut slot_stack = slot.get_stack().await; let stack_prev = slot_stack.clone(); if slot_index == 0 { if !self.insert_item(&mut slot_stack, 10, 46, true).await { diff --git a/crates/pumpkin-inventory/src/double.rs b/crates/pumpkin-inventory/src/double.rs index 2c2fd8329..4ab19cd45 100644 --- a/crates/pumpkin-inventory/src/double.rs +++ b/crates/pumpkin-inventory/src/double.rs @@ -12,7 +12,6 @@ use std::{any::Any, pin::Pin, sync::Arc}; use pumpkin_data::item_stack::ItemStack; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; -use tokio::sync::Mutex; /// A composite inventory combining two inventories. /// @@ -49,7 +48,7 @@ impl Inventory for DoubleInventory { Box::pin(async move { self.first.is_empty().await && self.second.is_empty().await }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { if slot >= self.first.size() { self.second.get_stack(slot - self.first.size()).await diff --git a/crates/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs b/crates/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs index 7f44e533a..43d34c730 100644 --- a/crates/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs +++ b/crates/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs @@ -95,7 +95,6 @@ impl EnchantingTableScreenHandler { pub async fn update_enchantments(&mut self, _player: &dyn InventoryPlayer) { let item = self.inventory.get_stack(0).await; - let item = item.lock().await; if item.is_empty() || item.has_enchantments() { for i in 0..3 { @@ -349,8 +348,7 @@ impl ScreenHandler for EnchantingTableScreenHandler { return false; } - let lapis_slot = self.inventory.get_stack(1).await; - let mut lapis_stack = lapis_slot.lock().await; + let mut lapis_stack = self.inventory.get_stack(1).await; let lapis_cost = (id + 1) as u8; if !player.is_creative() @@ -362,8 +360,7 @@ impl ScreenHandler for EnchantingTableScreenHandler { } // Perform enchantment - let item_slot = self.inventory.get_stack(0).await; - let mut item_stack = item_slot.lock().await; + let mut item_stack = self.inventory.get_stack(0).await; if item_stack.is_empty() || item_stack.has_enchantments() { return false; @@ -380,16 +377,13 @@ impl ScreenHandler for EnchantingTableScreenHandler { if !player.is_creative() { player.add_experience_levels(-(id + 1)).await; lapis_stack.decrement(lapis_cost); + self.inventory.set_stack(1, lapis_stack).await; } for (enchant, level) in enchantments { item_stack.add_enchantment(enchant, level as u16); } - - // CRITICAL FIX: Drop locks *before* calling `update_enchantments` - // Otherwise, update_enchantments will try to lock slot 0 again and deadlock! - drop(item_stack); - drop(lapis_stack); + self.inventory.set_stack(0, item_stack).await; // Update seed player.set_enchantment_seed(rand::random()).await; @@ -420,18 +414,14 @@ impl ScreenHandler for EnchantingTableScreenHandler { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let slot_stack_guard = slot_stack_lock.lock().await; - stack_left = slot_stack_guard.clone(); - drop(slot_stack_guard); - - let mut slot_stack_mut = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; + stack_left = slot_stack.clone(); if slot_index < 2 { // From enchanting to player if !self .insert_item( - &mut slot_stack_mut, + &mut slot_stack, 2, self.get_behaviour().slots.len() as i32, true, @@ -443,21 +433,19 @@ impl ScreenHandler for EnchantingTableScreenHandler { } else { // From player to enchanting // Lapis check - if slot_stack_mut.item == &Item::LAPIS_LAZULI { - if !self.insert_item(&mut slot_stack_mut, 1, 2, false).await { + if slot_stack.item == &Item::LAPIS_LAZULI { + if !self.insert_item(&mut slot_stack, 1, 2, false).await { return ItemStack::EMPTY.clone(); } - } else if !self.insert_item(&mut slot_stack_mut, 0, 1, false).await { + } else if !self.insert_item(&mut slot_stack, 0, 1, false).await { return ItemStack::EMPTY.clone(); } } - if slot_stack_mut.is_empty() { - drop(slot_stack_mut); + if slot_stack.is_empty() { slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - drop(slot_stack_mut); - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; } // CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots diff --git a/crates/pumpkin-inventory/src/entity_equipment.rs b/crates/pumpkin-inventory/src/entity_equipment.rs index acdfaf3f5..bdcae7b81 100644 --- a/crates/pumpkin-inventory/src/entity_equipment.rs +++ b/crates/pumpkin-inventory/src/entity_equipment.rs @@ -7,11 +7,10 @@ //! the entity model (armor is rendered on the player, held items are visible //! in hands). -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::item_stack::ItemStack; -use tokio::sync::Mutex; /// Equipment storage for an entity. /// @@ -21,19 +20,12 @@ use tokio::sync::Mutex; /// /// See also: [`EquipmentSlot`](EquipmentSlot) // EntityEquipment.java -#[derive(Clone)] +#[derive(Clone, Default)] pub struct EntityEquipment { /// Map of equipment slots to their equipped items. /// /// Keys are equipment slot types (head, chest, legs, feet, off-hand). - /// Values are mutex-protected item stacks for thread-safe access. - pub equipment: HashMap>>, -} - -impl Default for EntityEquipment { - fn default() -> Self { - Self::new() - } + pub equipment: HashMap, } impl EntityEquipment { @@ -53,27 +45,10 @@ impl EntityEquipment { /// /// # Returns /// The previously equipped item, or an empty stack if the slot was empty. - pub async fn put(&mut self, slot: &EquipmentSlot, stack: ItemStack) -> ItemStack { + pub fn put(&mut self, slot: &EquipmentSlot, stack: ItemStack) -> ItemStack { self.equipment - .insert(slot.clone(), Arc::new(Mutex::new(stack))) - .unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone()))) - .lock() - .await - .clone() - } - - /// Gets or inserts an empty stack for a slot. - /// - /// If the slot doesn't exist, creates it with an empty stack. - /// - /// # Returns - /// A mutex-protected item stack for this slot. - #[must_use] - pub fn get_or_insert(&mut self, slot: &EquipmentSlot) -> Arc> { - self.equipment - .entry(slot.clone()) - .or_insert_with(|| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))) - .clone() + .insert(slot.clone(), stack) + .unwrap_or_else(|| ItemStack::EMPTY.clone()) } /// Gets the item in a slot. @@ -81,22 +56,16 @@ impl EntityEquipment { /// # Returns /// The equipped item, or an empty stack if nothing is equipped. #[must_use] - pub fn get(&self, slot: &EquipmentSlot) -> Arc> { + pub fn get(&self, slot: &EquipmentSlot) -> ItemStack { self.equipment .get(slot) .cloned() - .unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone()))) + .unwrap_or_else(|| ItemStack::EMPTY.clone()) } /// Checks if all equipment slots are empty. - pub async fn is_empty(&self) -> bool { - for stack in self.equipment.values() { - if !stack.lock().await.is_empty() { - return false; - } - } - - true + pub fn is_empty(&self) -> bool { + self.equipment.values().all(ItemStack::is_empty) } /// Clears all equipped items. diff --git a/crates/pumpkin-inventory/src/furnace_like/furnace_like_screen_handler.rs b/crates/pumpkin-inventory/src/furnace_like/furnace_like_screen_handler.rs index 549f61a85..912484ceb 100644 --- a/crates/pumpkin-inventory/src/furnace_like/furnace_like_screen_handler.rs +++ b/crates/pumpkin-inventory/src/furnace_like/furnace_like_screen_handler.rs @@ -179,10 +179,7 @@ impl ScreenHandler for FurnaceLikeScreenHandler { return stack_left; } - let slot_stack_lock = slot.get_stack().await; - - // Acquire the lock to read/clone the stack - let mut stack = slot_stack_lock.lock().await; + let mut stack = slot.get_stack().await; stack_left = stack.clone(); let success = if slot_index < 3 { @@ -205,10 +202,9 @@ impl ScreenHandler for FurnaceLikeScreenHandler { } if stack.is_empty() { - drop(stack); // Release lock before awaiting slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - slot.mark_dirty().await; + slot.set_stack(stack).await; } // Award XP when taking from output slot (slot 2) diff --git a/crates/pumpkin-inventory/src/generic_container_screen_handler.rs b/crates/pumpkin-inventory/src/generic_container_screen_handler.rs index b24d0cbf8..2f2cf4a16 100644 --- a/crates/pumpkin-inventory/src/generic_container_screen_handler.rs +++ b/crates/pumpkin-inventory/src/generic_container_screen_handler.rs @@ -218,20 +218,14 @@ impl ScreenHandler for GenericContainerScreenHandler { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let slot_stack_guard = slot_stack_lock.lock().await; - stack_left = slot_stack_guard.clone(); - // Release the guard before calling insert_item which needs its own lock - drop(slot_stack_guard); - - // Re-acquire lock for insert_item (which expects &mut ItemStack) - let mut slot_stack_mut = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; + stack_left = slot_stack.clone(); if slot_index < i32::from(self.rows * 9) { // Move from inventory to player area (end) if !self .insert_item( - &mut slot_stack_mut, + &mut slot_stack, (self.rows * 9).into(), self.get_behaviour().slots.len() as i32, true, @@ -241,7 +235,7 @@ impl ScreenHandler for GenericContainerScreenHandler { return ItemStack::EMPTY.clone(); } } else if !self - .insert_item(&mut slot_stack_mut, 0, (self.rows * 9).into(), false) + .insert_item(&mut slot_stack, 0, (self.rows * 9).into(), false) .await { // Move from player area to inventory (start) @@ -249,12 +243,10 @@ impl ScreenHandler for GenericContainerScreenHandler { } // Check the resulting state of the slot stack after insert_item - if slot_stack_mut.is_empty() { - drop(slot_stack_mut); // Release lock + if slot_stack.is_empty() { slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - drop(slot_stack_mut); // Release lock - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; } } diff --git a/crates/pumpkin-inventory/src/gui_builder.rs b/crates/pumpkin-inventory/src/gui_builder.rs index 92f0bd4f0..85ffd8d76 100644 --- a/crates/pumpkin-inventory/src/gui_builder.rs +++ b/crates/pumpkin-inventory/src/gui_builder.rs @@ -140,12 +140,8 @@ impl ScreenHandler for GUIScreenHandler { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let slot_stack_guard = slot_stack_lock.lock().await; - stack_left = slot_stack_guard.clone(); - drop(slot_stack_guard); - - let mut slot_stack_mut = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; + stack_left = slot_stack.clone(); let container_slots = i32::from(self.rows * self.columns); if slot_index < container_slots { @@ -155,7 +151,7 @@ impl ScreenHandler for GUIScreenHandler { } if !self .insert_item( - &mut slot_stack_mut, + &mut slot_stack, container_slots, self.get_behaviour().slots.len() as i32, true, @@ -170,7 +166,7 @@ impl ScreenHandler for GUIScreenHandler { return ItemStack::EMPTY.clone(); } if !self - .insert_item(&mut slot_stack_mut, 0, container_slots, false) + .insert_item(&mut slot_stack, 0, container_slots, false) .await { // Move from player area to inventory (start) @@ -178,12 +174,10 @@ impl ScreenHandler for GUIScreenHandler { } } - if slot_stack_mut.is_empty() { - drop(slot_stack_mut); + if slot_stack.is_empty() { slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - drop(slot_stack_mut); - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; } } diff --git a/crates/pumpkin-inventory/src/merchant/merchant_screen_handler.rs b/crates/pumpkin-inventory/src/merchant/merchant_screen_handler.rs index 19e1b427b..67e188004 100644 --- a/crates/pumpkin-inventory/src/merchant/merchant_screen_handler.rs +++ b/crates/pumpkin-inventory/src/merchant/merchant_screen_handler.rs @@ -63,9 +63,7 @@ impl MerchantScreenHandler { let offer = &self.offers[self.selected_offer]; let input_a = self.inventory.get_stack(0).await; - let input_a = input_a.lock().await; let input_b = self.inventory.get_stack(1).await; - let input_b = input_b.lock().await; let match_a = input_a.are_items_and_components_equal(&offer.base_cost_a.0) && input_a.item_count >= offer.base_cost_a.0.item_count; @@ -130,18 +128,14 @@ impl ScreenHandler for MerchantScreenHandler { let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let slot_stack_guard = slot_stack_lock.lock().await; - stack_left = slot_stack_guard.clone(); - drop(slot_stack_guard); - - let mut slot_stack_mut = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; + stack_left = slot_stack.clone(); if slot_index < 3 { // From merchant slots to player inventory if !self .insert_item( - &mut slot_stack_mut, + &mut slot_stack, 3, self.get_behaviour().slots.len() as i32, true, @@ -151,18 +145,16 @@ impl ScreenHandler for MerchantScreenHandler { return ItemStack::EMPTY.clone(); } } else { - // From player inventory to merchant inputs (0 and 1) - if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await { + // From player inventory to merchant + if !self.insert_item(&mut slot_stack, 0, 2, false).await { return ItemStack::EMPTY.clone(); } } - if slot_stack_mut.is_empty() { - drop(slot_stack_mut); + if slot_stack.is_empty() { slot.set_stack(ItemStack::EMPTY.clone()).await; } else { - drop(slot_stack_mut); - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; } } @@ -192,24 +184,20 @@ impl ScreenHandler for MerchantScreenHandler { (offer.base_cost_a.0.item_count, count_b, offer.xp) }; - let input_a = self.inventory.get_stack(0).await; - let mut input_a = input_a.lock().await; + let mut input_a = self.inventory.get_stack(0).await; input_a.decrement(count_a); if input_a.is_empty() { - *input_a = ItemStack::EMPTY.clone(); + input_a = ItemStack::EMPTY.clone(); } - drop(input_a); - self.get_behaviour().slots[0].mark_dirty().await; + self.inventory.set_stack(0, input_a).await; if let Some(count_b) = count_b { - let input_b = self.inventory.get_stack(1).await; - let mut input_b = input_b.lock().await; + let mut input_b = self.inventory.get_stack(1).await; input_b.decrement(count_b); if input_b.is_empty() { - *input_b = ItemStack::EMPTY.clone(); + input_b = ItemStack::EMPTY.clone(); } - drop(input_b); - self.get_behaviour().slots[1].mark_dirty().await; + self.inventory.set_stack(1, input_b).await; } // Award XP diff --git a/crates/pumpkin-inventory/src/player/ender_chest_inventory.rs b/crates/pumpkin-inventory/src/player/ender_chest_inventory.rs index 5e9da44dd..7a9e69256 100644 --- a/crates/pumpkin-inventory/src/player/ender_chest_inventory.rs +++ b/crates/pumpkin-inventory/src/player/ender_chest_inventory.rs @@ -10,14 +10,14 @@ //! Ender chests track when players open and close them to properly //! manage the viewer count for animation purposes. -use std::{any::Any, array::from_fn, pin::Pin, sync::Arc}; +use std::{any::Any, pin::Pin, sync::Arc}; use pumpkin_data::item_stack::ItemStack; use pumpkin_world::{ block::viewer::ViewerCountTracker, - inventory::{Clearable, Inventory, InventoryFuture, split_stack}, + inventory::{Clearable, Inventory, InventoryFuture}, }; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, RwLock}; /// A player's ender chest inventory. /// @@ -26,7 +26,7 @@ use tokio::sync::Mutex; /// ender chest block. pub struct EnderChestInventory { /// The 27 item slots in the ender chest. - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>, /// Viewer count tracker for lid animation. /// /// Tracks how many players have the ender chest open to animate the lid. @@ -47,7 +47,7 @@ impl EnderChestInventory { #[must_use] pub fn new() -> Self { Self { - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())), tracker: Mutex::new(None), } } @@ -75,41 +75,45 @@ impl EnderChestInventory { impl Inventory for EnderChestInventory { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + let mut items = self.items.write().await; + std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()) }) } fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { - Box::pin(async move { split_stack(&self.items, slot, amount).await }) + Box::pin(async move { + let mut items = self.items.write().await; + if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + } + }) } fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; }) } @@ -139,9 +143,8 @@ impl Inventory for EnderChestInventory { impl Clearable for EnderChestInventory { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for item in &self.items { - *item.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); }) } } diff --git a/crates/pumpkin-inventory/src/player/player_inventory.rs b/crates/pumpkin-inventory/src/player/player_inventory.rs index 016299c04..565cb206c 100644 --- a/crates/pumpkin-inventory/src/player/player_inventory.rs +++ b/crates/pumpkin-inventory/src/player/player_inventory.rs @@ -11,18 +11,17 @@ use crate::entity_equipment::EntityEquipment; use crate::screen_handler::InventoryPlayer; use pumpkin_data::data_component_impl::EquipmentSlot; +use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; use pumpkin_protocol::java::client::play::CSetPlayerInventory; use pumpkin_util::Hand; -use pumpkin_world::inventory::split_stack; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; use std::any::Any; -use std::array::from_fn; use std::collections::HashMap; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::{AtomicU8, Ordering}; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, RwLock}; use tracing::warn; /// The player's inventory. @@ -33,13 +32,13 @@ pub struct PlayerInventory { /// The 36 main inventory slots (slots 0-35). /// /// The first 9 slots (0-8) are the hotbar, the remaining 27 (9-35) are the main storage. - pub main_inventory: [Arc>; Self::MAIN_SIZE], + pub main_inventory: RwLock<[ItemStack; Self::MAIN_SIZE]>, /// Mapping of slot indices to equipment slot types. /// /// Used to identify which slots correspond to armor and off-hand equipment. pub equipment_slots: Arc>, /// The currently selected hotbar slot index (0-8). - selected_slot: AtomicU8, + pub selected_slot: AtomicU8, /// The entity equipment storage for armor and off-hand items. /// /// This is separate from the main inventory and is rendered on the player model. @@ -65,58 +64,79 @@ impl PlayerInventory { equipment_slots: Arc>, ) -> Self { Self { - // Normal syntax can't be used here because Arc doesn't implement Copy - main_inventory: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + main_inventory: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())), equipment_slots, selected_slot: AtomicU8::new(0), entity_equipment, } } + /// Fast non-blocking count of an item across all main inventory slots. + pub fn count_item(&self, item: &'static Item) -> u32 { + let mut total = 0u32; + if let Ok(inv) = self.main_inventory.try_read() { + for stack in inv.iter() { + if stack.get_item().id == item.id { + total += u32::from(stack.item_count); + } + } + } + total + } + + /// Fast non-blocking check if the main inventory contains a given item. + pub fn contains_item(&self, item: &'static Item) -> bool { + if let Ok(inv) = self.main_inventory.try_read() { + for stack in inv.iter() { + if !stack.is_empty() && stack.get_item().id == item.id { + return true; + } + } + } + false + } + /// Gets the item in the currently selected hotbar slot. /// /// This is the item the player is currently holding in their main hand. - /// - /// Mojang name: `getSelectedStack` - pub fn held_item(&self) -> Arc> { - self.main_inventory - .get(self.get_selected_slot() as usize) - .unwrap() - .clone() + pub async fn held_item(&self) -> ItemStack { + let inv = self.main_inventory.read().await; + inv[self.get_selected_slot() as usize].clone() + } + + /// Sets the item in the currently selected hotbar slot. + pub async fn set_held_item(&self, stack: ItemStack) { + let selected = self.get_selected_slot() as usize; + let mut inv = self.main_inventory.write().await; + inv[selected] = stack; + } + + /// Sets the item in the specified hand. + pub async fn set_stack_in_hand(&self, hand: Hand, stack: ItemStack) { + match hand { + Hand::Right => self.set_held_item(stack).await, + Hand::Left => { + let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap(); + self.entity_equipment.lock().await.put(slot, stack); + } + } } /// Gets the item in the specified hand. /// /// # Arguments /// - `hand` - Which hand to get the item from - pub async fn get_stack_in_hand(&self, hand: Hand) -> Arc> { + pub async fn get_stack_in_hand(&self, hand: Hand) -> ItemStack { match hand { Hand::Left => self.off_hand_item().await, - Hand::Right => self.held_item(), - } - } - - /// Checks if the given item Arc already points to the target equipment slot's stack. - /// - /// Use before attempting to re-equip an item to prevent self-deadlock: - /// Tokio's `Mutex` is not reentrant, so locking an `Arc>` that - /// you already hold will deadlock forever. - pub async fn is_already_equipped( - &self, - item_arc: &Arc>, - slot: &EquipmentSlot, - ) -> bool { - match slot { - EquipmentSlot::OffHand(_) => Arc::ptr_eq(item_arc, &self.off_hand_item().await), - EquipmentSlot::MainHand(_) => Arc::ptr_eq(item_arc, &self.held_item()), - _ => false, + Hand::Right => self.held_item().await, } } /// Gets the item in the off-hand. /// /// Mojang name: `getOffHandStack` - pub async fn off_hand_item(&self) -> Arc> { + pub async fn off_hand_item(&self) -> ItemStack { let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap(); self.entity_equipment.lock().await.get(slot) } @@ -128,11 +148,12 @@ impl PlayerInventory { pub async fn swap_item(&self) -> (ItemStack, ItemStack) { let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap(); let mut equipment = self.entity_equipment.lock().await; - let binding = self.held_item(); - let mut main_hand_item = binding.lock().await; - let off_hand_item = main_hand_item.clone(); - *main_hand_item = equipment.put(slot, off_hand_item.clone()).await; - (main_hand_item.clone(), off_hand_item) + let selected = self.get_selected_slot() as usize; + let mut main_inv = self.main_inventory.write().await; + let main_hand_item = main_inv[selected].clone(); + let new_main = equipment.put(slot, main_hand_item.clone()); + main_inv[selected] = new_main.clone(); + (new_main, main_hand_item) } /// Checks if a slot index is a valid hotbar slot. @@ -160,13 +181,24 @@ impl PlayerInventory { /// /// Returns the number of items that couldn't fit. async fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize { + if slot >= Self::MAIN_SIZE { + if let Some(slot_type) = self.equipment_slots.get(&slot) { + let mut equipment = self.entity_equipment.lock().await; + let current = equipment.get(slot_type); + if current.is_empty() { + equipment.put(slot_type, stack); + return 0; + } + } + return stack.item_count as usize; + } + + let mut inv = self.main_inventory.write().await; let mut stack_count = stack.item_count; - let binding = self.get_stack(slot).await; - let mut self_stack = binding.lock().await; + let self_stack = &mut inv[slot]; if self_stack.is_empty() { *self_stack = stack.copy_with_count(0); - //self.set_stack(slot, self_stack).await; } let count_left = self_stack.get_max_stack_size() - self_stack.item_count; @@ -184,12 +216,12 @@ impl PlayerInventory { /// # Returns /// The slot index or -1 if inventory is full. async fn get_empty_slot(&self) -> i16 { - for i in 0..Self::MAIN_SIZE { - if self.main_inventory[i].lock().await.is_empty() { + let inv = self.main_inventory.read().await; + for (i, stack) in inv.iter().enumerate() { + if stack.is_empty() { return i as i16; } } - -1 } @@ -205,29 +237,24 @@ impl PlayerInventory { /// /// Checks selected slot, off-hand, then other slots. async fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 { - if Self::can_stack_add_more( - &*self - .get_stack(self.get_selected_slot() as usize) - .await - .lock() - .await, - stack, - ) { - i16::from(self.get_selected_slot()) - } else if Self::can_stack_add_more( - &*self.get_stack(Self::OFF_HAND_SLOT).await.lock().await, - stack, - ) { - Self::OFF_HAND_SLOT as i16 - } else { - for i in 0..Self::MAIN_SIZE { - if Self::can_stack_add_more(&*self.main_inventory[i].lock().await, stack) { - return i as i16; - } - } - - -1 + let selected = self.get_selected_slot() as usize; + let inv = self.main_inventory.read().await; + if Self::can_stack_add_more(&inv[selected], stack) { + return selected as i16; } + + let off_hand = self.off_hand_item().await; + if Self::can_stack_add_more(&off_hand, stack) { + return Self::OFF_HAND_SLOT as i16; + } + + for (i, item) in inv.iter().enumerate() { + if Self::can_stack_add_more(item, stack) { + return i as i16; + } + } + + -1 } /// Inserts a stack into any available slot. @@ -254,8 +281,6 @@ impl PlayerInventory { return false; } - // TODO: if (stack.isDamaged()) { - let mut i; loop { @@ -271,8 +296,6 @@ impl PlayerInventory { } } - // TODO: Creative mode check - stack.item_count < i } @@ -281,17 +304,12 @@ impl PlayerInventory { /// # Returns /// The slot index or -1 if not found. pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 { - for i in 0..Self::MAIN_SIZE { - if !self.main_inventory[i].lock().await.is_empty() - && self.main_inventory[i] - .lock() - .await - .are_items_and_components_equal(stack) - { + let inv = self.main_inventory.read().await; + for (i, item) in inv.iter().enumerate() { + if !item.is_empty() && item.are_items_and_components_equal(stack) { return i as i16; } } - -1 } @@ -299,63 +317,43 @@ impl PlayerInventory { /// /// First looks for empty slots, then slots without enchantments. async fn get_swappable_hotbar_slot(&self) -> usize { + let inv = self.main_inventory.read().await; let selected_slot = self.get_selected_slot() as usize; for i in 0..Self::HOTBAR_SIZE { let check_index = (i + selected_slot) % 9; - if self.main_inventory[check_index].lock().await.is_empty() { + if inv[check_index].is_empty() { return check_index; } } - if let Some(i) = (0..Self::HOTBAR_SIZE).next() { - let check_index = (i + selected_slot) % 9; - return check_index; - } - - self.get_selected_slot() as usize + selected_slot } /// Swaps an item stack with an item on the hotbar. /// /// Finds an empty hotbar slot and places the stack there. pub async fn swap_stack_with_hotbar(&self, stack: ItemStack) { - self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8); + let swappable = self.get_swappable_hotbar_slot().await; + self.set_selected_slot(swappable as u8); + let selected = self.get_selected_slot() as usize; + let mut inv = self.main_inventory.write().await; - if !self.main_inventory[self.get_selected_slot() as usize] - .lock() - .await - .is_empty() + if let Some(empty_slot) = inv.iter().position(ItemStack::is_empty) + && !inv[selected].is_empty() { - let empty_slot = self.get_empty_slot().await; - if empty_slot != -1 { - self.set_stack( - empty_slot as usize, - self.main_inventory[self.get_selected_slot() as usize] - .lock() - .await - .clone(), - ) - .await; - } + inv[empty_slot] = inv[selected].clone(); } - self.set_stack(self.get_selected_slot() as usize, stack) - .await; + inv[selected] = stack; } /// Swaps the items at two slot indices. pub async fn swap_slot_with_hotbar(&self, slot: usize) { - self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8); - let stack = self.main_inventory[self.get_selected_slot() as usize] - .lock() - .await - .clone(); - self.set_stack( - self.get_selected_slot() as usize, - self.main_inventory[slot].lock().await.clone(), - ) - .await; - self.set_stack(slot, stack).await; + let swappable = self.get_swappable_hotbar_slot().await; + self.set_selected_slot(swappable as u8); + let selected = self.get_selected_slot() as usize; + let mut inv = self.main_inventory.write().await; + inv.swap(selected, slot); } /// Gives a stack to the player or drops it if inventory is full. @@ -383,12 +381,7 @@ impl PlayerInventory { } let items_fit = stack.get_max_stack_size() - - self - .get_stack(room_for_stack as usize) - .await - .lock() - .await - .item_count; + - self.get_stack(room_for_stack as usize).await.item_count; if self .insert_stack(room_for_stack, &mut stack.split(items_fit)) .await @@ -397,13 +390,7 @@ impl PlayerInventory { player .enqueue_slot_set_packet(&CSetPlayerInventory::new( i32::from(room_for_stack).into(), - &self - .get_stack(room_for_stack as usize) - .await - .lock() - .await - .clone() - .into(), + &self.get_stack(room_for_stack as usize).await.into(), )) .await; } @@ -414,10 +401,8 @@ impl PlayerInventory { impl Clearable for PlayerInventory { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for item in &self.main_inventory { - *item.lock().await = ItemStack::EMPTY.clone(); - } - + let mut inv = self.main_inventory.write().await; + inv.fill_with(|| ItemStack::EMPTY.clone()); self.entity_equipment.lock().await.clear(); }) } @@ -425,27 +410,19 @@ impl Clearable for PlayerInventory { impl Inventory for PlayerInventory { fn size(&self) -> usize { - self.main_inventory.len() + self.equipment_slots.len() + Self::MAIN_SIZE + self.equipment_slots.len() } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for item in &self.main_inventory { - if !item.lock().await.is_empty() { - return false; - } + let inv = self.main_inventory.read().await; + if inv.iter().any(|s| !s.is_empty()) { + return false; } for slot in self.equipment_slots.values() { - if !self - .entity_equipment - .lock() - .await - .get(slot) - .lock() - .await - .is_empty() - { + let eq_item = self.entity_equipment.lock().await.get(slot); + if !eq_item.is_empty() { return false; } } @@ -454,49 +431,56 @@ impl Inventory for PlayerInventory { }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - if slot < self.main_inventory.len() { - self.main_inventory[slot].clone() - } else { - let slot = self.equipment_slots.get(&slot).unwrap(); + if slot < Self::MAIN_SIZE { + let inv = self.main_inventory.read().await; + inv[slot].clone() + } else if let Some(slot) = self.equipment_slots.get(&slot) { self.entity_equipment.lock().await.get(slot) + } else { + ItemStack::EMPTY.clone() } }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - if slot < self.main_inventory.len() { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.main_inventory[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed - } else { - let slot = self.equipment_slots.get(&slot).unwrap(); + if slot < Self::MAIN_SIZE { + let mut inv = self.main_inventory.write().await; + std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone()) + } else if let Some(slot) = self.equipment_slots.get(&slot) { self.entity_equipment .lock() .await .put(slot, ItemStack::EMPTY.clone()) - .await + } else { + ItemStack::EMPTY.clone() } }) } fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - if slot < self.main_inventory.len() { - split_stack(&self.main_inventory, slot, amount).await - } else { - let slot = self.equipment_slots.get(&slot).unwrap(); - - let equipment = self.entity_equipment.lock().await.get(slot); - let mut stack = equipment.lock().await; - - if !stack.is_empty() { - return stack.split(amount); + if slot < Self::MAIN_SIZE { + let mut inv = self.main_inventory.write().await; + if !inv[slot].is_empty() && amount > 0 { + inv[slot].split(amount) + } else { + ItemStack::EMPTY.clone() } + } else if let Some(slot) = self.equipment_slots.get(&slot) { + let mut equipment = self.entity_equipment.lock().await; + let mut stack = equipment.get(slot); + if !stack.is_empty() && amount > 0 { + let split = stack.split(amount); + equipment.put(slot, stack); + split + } else { + ItemStack::EMPTY.clone() + } + } else { ItemStack::EMPTY.clone() } }) @@ -504,10 +488,11 @@ impl Inventory for PlayerInventory { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - if slot < self.main_inventory.len() { - *self.main_inventory[slot].lock().await = stack; + if slot < Self::MAIN_SIZE { + let mut inv = self.main_inventory.write().await; + inv[slot] = stack; } else if let Some(slot) = self.equipment_slots.get(&slot) { - self.entity_equipment.lock().await.put(slot, stack).await; + self.entity_equipment.lock().await.put(slot, stack); } else { warn!("Failed to get Equipment Slot at {slot}"); } diff --git a/crates/pumpkin-inventory/src/player/player_screen_handler.rs b/crates/pumpkin-inventory/src/player/player_screen_handler.rs index 8e8cab953..702445f99 100644 --- a/crates/pumpkin-inventory/src/player/player_screen_handler.rs +++ b/crates/pumpkin-inventory/src/player/player_screen_handler.rs @@ -164,8 +164,7 @@ impl ScreenHandler for PlayerScreenHandler { // TODO: Equippable component if slot.has_stack().await { - let slot_stack_lock = slot.get_stack().await; - let mut slot_stack = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; let stack_prev = slot_stack.clone(); let equipment_slot = slot_stack @@ -232,13 +231,12 @@ impl ScreenHandler for PlayerScreenHandler { } let stack = slot_stack.clone(); - drop(slot_stack); // release the lock before calling other methods if stack.is_empty() { slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone()) .await; } else { - slot.mark_dirty().await; + slot.set_stack(stack.clone()).await; } if stack.item_count == stack_prev.item_count { diff --git a/crates/pumpkin-inventory/src/screen_handler.rs b/crates/pumpkin-inventory/src/screen_handler.rs index 5e76f3594..e8c046007 100644 --- a/crates/pumpkin-inventory/src/screen_handler.rs +++ b/crates/pumpkin-inventory/src/screen_handler.rs @@ -725,8 +725,7 @@ pub trait ScreenHandler: Send + Sync { }) { let slot = self.get_behaviour().slots[current_index as usize].clone(); - let slot_stack_lock = slot.get_stack().await; - let mut slot_stack = slot_stack_lock.lock().await; + let mut slot_stack = slot.get_stack().await; if !slot_stack.is_empty() && slot_stack.are_items_and_components_equal(stack) { let combined_count = slot_stack.item_count + stack.item_count; @@ -734,14 +733,12 @@ pub trait ScreenHandler: Send + Sync { if combined_count <= max_slot_count { stack.set_count(0); slot_stack.set_count(combined_count); - drop(slot_stack); - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; success = true; } else if slot_stack.item_count < max_slot_count { stack.decrement(max_slot_count - slot_stack.item_count); slot_stack.set_count(max_slot_count); - drop(slot_stack); - slot.mark_dirty().await; + slot.set_stack(slot_stack).await; success = true; } } @@ -768,11 +765,9 @@ pub trait ScreenHandler: Send + Sync { } { let slot = self.get_behaviour().slots[current_index as usize].clone(); let slot_stack = slot.get_stack().await; - let slot_stack = slot_stack.lock().await; if slot_stack.is_empty() && slot.can_insert(stack).await { let max_count = slot.get_max_item_count_for_stack(stack).await; - drop(slot_stack); slot.set_stack(stack.split(max_count.min(stack.item_count))) .await; slot.mark_dirty().await; @@ -885,8 +880,7 @@ pub trait ScreenHandler: Send + Sync { let cursor_stack = behaviour.cursor_stack.lock().await; let slot = &behaviour.slots[slot_index as usize]; - let stack_lock = slot.get_stack().await; - let stack = stack_lock.lock().await; + let stack = slot.get_stack().await; if !cursor_stack.is_empty() && slot.can_insert(&cursor_stack).await && (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty()) @@ -918,8 +912,7 @@ pub trait ScreenHandler: Send + Sync { let initial_count = cursor_stack.item_count; for slot_index in &behaviour.drag_slots { let slot = behaviour.slots[*slot_index as usize].clone(); - let stack_lock = slot.get_stack().await; - let stack = stack_lock.lock().await; + let stack = slot.get_stack().await; if (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty()) && slot.can_insert(&cursor_stack).await @@ -942,13 +935,12 @@ pub trait ScreenHandler: Send + Sync { )) .min(cursor_stack.item_count); if inserting_count > 0 { - let mut stack_clone = stack.clone(); - drop(stack); - if stack_clone.is_empty() { - stack_clone = cursor_stack.copy_with_count(0); + let mut new_stack = stack.clone(); + if new_stack.is_empty() { + new_stack = cursor_stack.copy_with_count(0); } - stack_clone.increment(inserting_count); - slot.set_stack(stack_clone).await; + new_stack.increment(inserting_count); + slot.set_stack(new_stack).await; if drag_button != 2 { cursor_stack.decrement(inserting_count); } @@ -999,8 +991,7 @@ pub trait ScreenHandler: Send + Sync { return; } let slot = behaviour.slots[slot_index as usize].clone(); - let stack_lock = slot.get_stack().await; - let stack = stack_lock.lock().await; + let stack = slot.get_stack().await; *cursor_stack = stack.copy_with_count(stack.get_max_stack_size()); } } else if (action_type == SlotActionType::Pickup @@ -1064,32 +1055,32 @@ pub trait ScreenHandler: Send + Sync { let mut intercepted = false; if !cursor_stack.is_empty() { - let stack_guard = slot.get_stack().await; - let mut inner_slot_stack = stack_guard.lock().await; + let mut inner_slot_stack = slot.get_stack().await; if let Some(bundle) = inner_slot_stack.get_data_component_mut::() && bundle.try_insert(&mut cursor_stack) { + slot.set_stack(inner_slot_stack).await; intercepted = true; } } if !intercepted && !slot_stack.is_empty() && let Some(bundle) = cursor_stack.get_data_component_mut::() { - let stack_guard = slot.get_stack().await; - let mut inner_slot_stack = stack_guard.lock().await; + let mut inner_slot_stack = slot.get_stack().await; if bundle.try_insert(&mut inner_slot_stack) { if inner_slot_stack.item_count == 0 { - *inner_slot_stack = ItemStack::EMPTY.clone(); + inner_slot_stack = ItemStack::EMPTY.clone(); } + slot.set_stack(inner_slot_stack).await; intercepted = true; } } if !intercepted && cursor_stack.is_empty() { - let stack_guard = slot.get_stack().await; - let mut inner_slot_stack = stack_guard.lock().await; + let mut inner_slot_stack = slot.get_stack().await; if let Some(bundle) = inner_slot_stack.get_data_component_mut::() && let Some(extracted) = bundle.try_extract() { *cursor_stack = extracted; + slot.set_stack(inner_slot_stack).await; intercepted = true; } } @@ -1226,13 +1217,7 @@ pub trait ScreenHandler: Send + Sync { if slot_index < 0 { return; } - let mut button_stack = player - .get_inventory() - .get_stack(button as usize) - .await - .lock() - .await - .clone(); + let mut button_stack = player.get_inventory().get_stack(button as usize).await; let source_slot = self.get_behaviour().slots[slot_index as usize].clone(); let source_stack = source_slot.get_cloned_stack().await; diff --git a/crates/pumpkin-inventory/src/slot.rs b/crates/pumpkin-inventory/src/slot.rs index 930a06c70..54998d892 100644 --- a/crates/pumpkin-inventory/src/slot.rs +++ b/crates/pumpkin-inventory/src/slot.rs @@ -24,7 +24,6 @@ use std::{ Arc, atomic::{AtomicU8, Ordering}, }, - time::Duration, }; use crate::screen_handler::InventoryPlayer; @@ -33,7 +32,6 @@ use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; use pumpkin_world::inventory::Inventory; -use tokio::{sync::Mutex, time::timeout}; /// Type alias for async slot operations. pub type BoxFuture<'a, T> = Pin + Send + 'a>>; @@ -101,37 +99,21 @@ pub trait Slot: Send + Sync { } /// Gets the stack in this slot. - fn get_stack(&self) -> BoxFuture<'_, Arc>> { + fn get_stack(&self) -> BoxFuture<'_, ItemStack> { // Default implementation logic: Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await }) } /// Gets a copy of the stack in this slot. - /// - /// Acquires a lock and returns a clone of the stack. fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { // Default implementation logic: - Box::pin(async move { - let stack = self.get_stack().await; - let lock = timeout(Duration::from_secs(5), stack.lock()) - .await - .expect("Timed out while trying to acquire lock"); - - lock.clone() - }) + Box::pin(async move { self.get_stack().await }) } /// Checks if this slot has a non-empty stack. fn has_stack(&self) -> BoxFuture<'_, bool> { // Default implementation logic: - Box::pin(async move { - let inv = self.get_inventory(); - !inv.get_stack(self.get_index()) - .await - .lock() - .await - .is_empty() - }) + Box::pin(async move { !self.get_stack().await.is_empty() }) } /// Sets the stack in this slot. @@ -295,22 +277,18 @@ pub trait Slot: Send + Sync { // Default implementation logic: Box::pin(async move { if !stack.is_empty() && self.can_insert(&stack).await { - let stack_mutex = self.get_stack().await; - let mut stack_self = stack_mutex.lock().await; + let mut stack_self = self.get_stack().await; let min_count = count .min(stack.item_count) .min(self.get_max_item_count_for_stack(&stack).await - stack_self.item_count); if min_count != 0 { if stack_self.is_empty() { - drop(stack_self); self.set_stack(stack.split(min_count)).await; } else if stack.are_items_and_components_equal(&stack_self) { stack.decrement(min_count); stack_self.increment(min_count); - let cloned_stack = stack_self.clone(); - drop(stack_self); - self.set_stack(cloned_stack).await; + self.set_stack(stack_self).await; } } } diff --git a/crates/pumpkin-inventory/src/stonecutter_screen_handler.rs b/crates/pumpkin-inventory/src/stonecutter_screen_handler.rs index 3205313bc..e50eb0765 100644 --- a/crates/pumpkin-inventory/src/stonecutter_screen_handler.rs +++ b/crates/pumpkin-inventory/src/stonecutter_screen_handler.rs @@ -1,7 +1,6 @@ use std::any::Any; use std::sync::Arc; use std::sync::atomic::{AtomicU8, Ordering}; -use tokio::sync::Mutex; use crate::player::player_inventory::PlayerInventory; use crate::screen_handler::{ @@ -56,8 +55,7 @@ impl StonecutterScreenHandler { } async fn update_output(&self) { - let input_stack = self.input_inventory.get_stack(0).await; - let input_lock = input_stack.lock().await; + let input_lock = self.input_inventory.get_stack(0).await; if input_lock.is_empty() { self.output_inventory @@ -213,14 +211,7 @@ impl Slot for StonecutterOutputSlot { stack.item_count as i32, ) .await; - let input_stack = self.input_inventory.get_stack(0).await; - let mut input_lock = input_stack.lock().await; - if !input_lock.is_empty() { - input_lock.item_count -= 1; - if input_lock.item_count == 0 { - *input_lock = ItemStack::EMPTY.clone(); - } - } + self.input_inventory.remove_stack_specific(0, 1).await; self.mark_dirty().await; }) } @@ -229,22 +220,16 @@ impl Slot for StonecutterOutputSlot { Box::pin(async move { false }) } - fn get_stack(&self) -> BoxFuture<'_, Arc>> { + fn get_stack(&self) -> BoxFuture<'_, ItemStack> { Box::pin(async move { self.inventory.get_stack(self.index).await }) } fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { - Box::pin(async move { - let stack = self.inventory.get_stack(self.index).await; - stack.lock().await.clone() - }) + Box::pin(async move { self.inventory.get_stack(self.index).await }) } fn has_stack(&self) -> BoxFuture<'_, bool> { - Box::pin(async move { - let stack = self.inventory.get_stack(self.index).await; - !stack.lock().await.is_empty() - }) + Box::pin(async move { !self.inventory.get_stack(self.index).await.is_empty() }) } fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> { diff --git a/crates/pumpkin-plugin-api/Cargo.toml b/crates/pumpkin-plugin-api/Cargo.toml index 2276634da..dd1982a42 100644 --- a/crates/pumpkin-plugin-api/Cargo.toml +++ b/crates/pumpkin-plugin-api/Cargo.toml @@ -14,3 +14,6 @@ serde_json = { workspace = true } [package.metadata.component] target = { path = "../pumpkin-plugin-wit" } + +[lints] +workspace = true diff --git a/crates/pumpkin-plugin-api/src/lib.rs b/crates/pumpkin-plugin-api/src/lib.rs index 4b3d5c9d0..a05b2f2ea 100644 --- a/crates/pumpkin-plugin-api/src/lib.rs +++ b/crates/pumpkin-plugin-api/src/lib.rs @@ -1,5 +1,14 @@ //! Pumpkin plugin API. #![warn(missing_docs)] +#![allow( + clippy::undocumented_unsafe_blocks, + clippy::option_if_let_else, + clippy::collection_is_never_read, + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::cargo +)] //! //! This crate provides everything needed to write a Pumpkin server plugin compiled //! to WebAssembly. A plugin consists of a type that implements [`Plugin`], registered diff --git a/crates/pumpkin-protocol/src/java/client/dialog.rs b/crates/pumpkin-protocol/src/java/client/dialog.rs index 77f8f8423..b4f98a95f 100644 --- a/crates/pumpkin-protocol/src/java/client/dialog.rs +++ b/crates/pumpkin-protocol/src/java/client/dialog.rs @@ -16,16 +16,15 @@ impl<'a> DialogNBT<'a> { Self(DialogNBTSource::Nbt(compound)) } - #[allow(clippy::todo)] pub fn write_packet_data( &self, mut write: impl std::io::Write, _version: &JavaMinecraftVersion, ) -> Result<(), crate::ser::WritingError> { match &self.0 { - DialogNBTSource::Struct(_dialog) => { - todo!("Serialize Dialog to NbtCompound manually") - } + DialogNBTSource::Struct(_dialog) => Err(crate::ser::WritingError::Message( + "Serializing Dialog struct to NBT is not supported".to_string(), + )), DialogNBTSource::Nbt(nbt) => { let tag = pumpkin_nbt::tag::NbtTag::Compound((*nbt).clone()); write.write_nbt(tag)?; diff --git a/crates/pumpkin-protocol/src/java/client/play/update_advancement.rs b/crates/pumpkin-protocol/src/java/client/play/update_advancement.rs index 05a583107..d39005c0d 100644 --- a/crates/pumpkin-protocol/src/java/client/play/update_advancement.rs +++ b/crates/pumpkin-protocol/src/java/client/play/update_advancement.rs @@ -41,7 +41,6 @@ impl CUpdateAdvancements { } impl ClientPacket for CUpdateAdvancements { - #[allow(clippy::unimplemented)] fn write_packet_data( &self, mut write: impl std::io::Write, diff --git a/crates/pumpkin-world/src/biome/mod.rs b/crates/pumpkin-world/src/biome/mod.rs index 34167f55b..2cfd2fdae 100644 --- a/crates/pumpkin-world/src/biome/mod.rs +++ b/crates/pumpkin-world/src/biome/mod.rs @@ -43,7 +43,9 @@ pub fn hash_seed(seed: u64) -> i64 { let mut hasher = Sha256::new(); hasher.update(seed.to_le_bytes()); let result = hasher.finalize(); - i64::from_le_bytes(result[..8].try_into().unwrap()) + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&result[..8]); + i64::from_le_bytes(bytes) } #[cfg(test)] diff --git a/crates/pumpkin-world/src/chunk/format/anvil.rs b/crates/pumpkin-world/src/chunk/format/anvil.rs index 53e97dcc1..f4a5ca943 100644 --- a/crates/pumpkin-world/src/chunk/format/anvil.rs +++ b/crates/pumpkin-world/src/chunk/format/anvil.rs @@ -619,7 +619,7 @@ impl ChunkSerializer for AnvilChunkFile< ) -> Result<(), ChunkWritingError> { let epoch = SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap() + .unwrap_or_default() .as_secs() as u32; let index = Self::get_chunk_index(chunk.position().0, chunk.position().1); diff --git a/crates/pumpkin-world/src/chunk/mod.rs b/crates/pumpkin-world/src/chunk/mod.rs index 5ac7edebb..2043ca8f3 100644 --- a/crates/pumpkin-world/src/chunk/mod.rs +++ b/crates/pumpkin-world/src/chunk/mod.rs @@ -719,7 +719,9 @@ impl ChunkData { let mut has_found = [false, false, false]; for y in (self.section.min_y..=start_height).rev() { - let state_id = self.section.get_block_absolute_y(x, y, z).unwrap(); + let Some(state_id) = self.section.get_block_absolute_y(x, y, z) else { + continue; + }; let block_state = BlockState::from_id(state_id); for hm_type in [ diff --git a/crates/pumpkin-world/src/inventory/inventory.rs b/crates/pumpkin-world/src/inventory/inventory.rs index 192de06d4..49a215c08 100644 --- a/crates/pumpkin-world/src/inventory/inventory.rs +++ b/crates/pumpkin-world/src/inventory/inventory.rs @@ -7,7 +7,6 @@ use std::{ hash::{Hash, Hasher}, sync::Arc, }; -use tokio::sync::{Mutex, OwnedMutexGuard}; pub type InventoryFuture<'a, T> = Pin + Send + 'a>>; @@ -16,7 +15,7 @@ pub trait Inventory: Send + Sync + Clearable { fn is_empty(&self) -> InventoryFuture<'_, bool>; - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>>; + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>; fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>; @@ -36,8 +35,7 @@ pub trait Inventory: Send + Sync + Clearable { let mut count = 0; for i in 0..self.size() { - let slot = self.get_stack(i).await; - let stack = slot.lock().await; + let stack = self.get_stack(i).await; if stack.get_item().id == item.id { count += stack.item_count; } @@ -49,13 +47,12 @@ pub trait Inventory: Send + Sync + Clearable { fn contains_any_predicate<'a>( &'a self, - predicate: &'a (dyn Fn(OwnedMutexGuard) -> bool + Sync), + predicate: &'a (dyn Fn(&ItemStack) -> bool + Sync), ) -> InventoryFuture<'a, bool> { Box::pin(async move { for i in 0..self.size() { - let slot = self.get_stack(i).await; - let stack = slot.lock_owned().await; - if predicate(stack) { + let stack = self.get_stack(i).await; + if predicate(&stack) { return true; } } @@ -83,8 +80,7 @@ pub trait Inventory: Send + Sync + Clearable { let size = self.size(); for i in 0..size { - let stack_lock = self.get_stack(i).await; - let stack = stack_lock.lock().await; + let stack = self.get_stack(i).await; if !stack.is_empty() { let mut item_compound = NbtCompound::new(); @@ -106,7 +102,7 @@ pub trait Inventory: Send + Sync + Clearable { fn mark_dirty(&self) {} - fn read_data(&self, nbt: &NbtCompound, stacks: &[Arc>]) { + fn read_data(&self, nbt: &NbtCompound, stacks: &mut [ItemStack]) { if let Some(inventory_list) = nbt.get_list("Items") { for tag in inventory_list { if let Some(item_compound) = tag.extract_compound() @@ -116,7 +112,7 @@ pub trait Inventory: Send + Sync + Clearable { if slot < stacks.len() && let Some(item_stack) = ItemStack::read_item_stack(item_compound) { - *stacks[slot].try_lock().unwrap() = item_stack; + stacks[slot] = item_stack; } } } @@ -143,22 +139,14 @@ pub trait Clearable { fn clear(&self) -> Pin + Send + '_>>; } -pub fn sync_write_items_to_nbt(items: &[Arc>], nbt: &mut NbtCompound) { +pub fn sync_write_items_to_nbt(items: &[ItemStack], nbt: &mut NbtCompound) { let mut slots = Vec::new(); - for (i, item) in items.iter().enumerate() { - match item.try_lock() { - Ok(stack) if !stack.is_empty() => { - let mut item_nbt = NbtCompound::new(); - item_nbt.put_byte("Slot", i as i8); - stack.write_item_stack(&mut item_nbt); - slots.push(NbtTag::Compound(item_nbt)); - } - Ok(_) => {} - Err(_) => { - tracing::warn!( - "Skipping contended inventory slot {i} while serializing block entity data" - ); - } + for (i, stack) in items.iter().enumerate() { + if !stack.is_empty() { + let mut item_nbt = NbtCompound::new(); + item_nbt.put_byte("Slot", i as i8); + stack.write_item_stack(&mut item_nbt); + slots.push(NbtTag::Compound(item_nbt)); } } if !slots.is_empty() { diff --git a/crates/pumpkin-world/src/inventory/mod.rs b/crates/pumpkin-world/src/inventory/mod.rs index 5b5186667..3bbe494c1 100644 --- a/crates/pumpkin-world/src/inventory/mod.rs +++ b/crates/pumpkin-world/src/inventory/mod.rs @@ -1,7 +1,3 @@ -use std::sync::Arc; - -use tokio::sync::Mutex; - use pumpkin_data::item_stack::ItemStack; #[expect(clippy::module_inception)] @@ -11,11 +7,10 @@ mod simple_inventory; pub use inventory::*; pub use simple_inventory::*; -// These are some utility functions found in Inventories.java -pub async fn split_stack(stacks: &[Arc>], slot: usize, amount: u8) -> ItemStack { - let mut stack = stacks[slot].lock().await; - if slot < stacks.len() && !stack.is_empty() && amount > 0 { - stack.split(amount) +// Utility functions for split_stack +pub fn split_stack_slice(stacks: &mut [ItemStack], slot: usize, amount: u8) -> ItemStack { + if slot < stacks.len() && !stacks[slot].is_empty() && amount > 0 { + stacks[slot].split(amount) } else { ItemStack::EMPTY.clone() } diff --git a/crates/pumpkin-world/src/inventory/simple_inventory.rs b/crates/pumpkin-world/src/inventory/simple_inventory.rs index 15923f30c..bd8b331b6 100644 --- a/crates/pumpkin-world/src/inventory/simple_inventory.rs +++ b/crates/pumpkin-world/src/inventory/simple_inventory.rs @@ -1,20 +1,17 @@ -use crate::inventory::{Clearable, Inventory, InventoryFuture, split_stack}; +use crate::inventory::{Clearable, Inventory, InventoryFuture}; use pumpkin_data::item_stack::ItemStack; use std::any::Any; -use std::sync::Arc; -use tokio::sync::Mutex; +use tokio::sync::RwLock; pub struct SimpleInventory { - pub stacks: Vec>>, + pub stacks: RwLock>, } impl SimpleInventory { #[must_use] pub fn new(size: usize) -> Self { Self { - stacks: (0..size) - .map(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))) - .collect(), + stacks: RwLock::new(vec![ItemStack::EMPTY.clone(); size]), } } } @@ -22,49 +19,62 @@ impl SimpleInventory { impl Clearable for SimpleInventory { fn clear(&self) -> InventoryFuture<'_, ()> { Box::pin(async move { - for stack in &self.stacks { - *stack.lock().await = ItemStack::EMPTY.clone(); - } + let mut stacks = self.stacks.write().await; + stacks.fill_with(|| ItemStack::EMPTY.clone()); }) } } impl Inventory for SimpleInventory { fn size(&self) -> usize { - self.stacks.len() + self.stacks.blocking_read().len() } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for stack in &self.stacks { - if !stack.lock().await.is_empty() { - return false; - } - } - true + let stacks = self.stacks.read().await; + stacks.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.stacks[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let stacks = self.stacks.read().await; + stacks + .get(slot) + .cloned() + .unwrap_or_else(|| ItemStack::EMPTY.clone()) + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut stack = self.stacks[slot].lock().await; - let result = stack.clone(); - *stack = ItemStack::EMPTY.clone(); - result + let mut stacks = self.stacks.write().await; + if slot < stacks.len() { + std::mem::replace(&mut stacks[slot], ItemStack::EMPTY.clone()) + } else { + ItemStack::EMPTY.clone() + } }) } fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { - Box::pin(async move { split_stack(&self.stacks, slot, amount).await }) + Box::pin(async move { + let mut stacks = self.stacks.write().await; + if slot < stacks.len() && !stacks[slot].is_empty() && amount > 0 { + stacks[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + } + }) } fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.stacks[slot].lock().await = stack; + let mut stacks = self.stacks.write().await; + if slot < stacks.len() { + stacks[slot] = stack; + } }) } diff --git a/crates/pumpkin/src/block/blocks/brewing_stand.rs b/crates/pumpkin/src/block/blocks/brewing_stand.rs index 43f015da8..62875d289 100644 --- a/crates/pumpkin/src/block/blocks/brewing_stand.rs +++ b/crates/pumpkin/src/block/blocks/brewing_stand.rs @@ -91,7 +91,7 @@ impl BlockBehaviour for BrewingStandBlock { // Bottle slots are 0, 1, 2 in brewing stands for slot in 0..3 { let stack = inventory.get_stack(slot).await; - if !stack.lock().await.is_empty() { + if !stack.is_empty() { bottles += 1; } } diff --git a/crates/pumpkin/src/block/blocks/cake.rs b/crates/pumpkin/src/block/blocks/cake.rs index 0f7ea61e9..0072b786d 100644 --- a/crates/pumpkin/src/block/blocks/cake.rs +++ b/crates/pumpkin/src/block/blocks/cake.rs @@ -115,9 +115,7 @@ impl BlockBehaviour for CakeBlock { Box::pin(async move { let state_id = args.world.get_block_state_id(args.position); let properties = CakeLikeProperties::from_state_id(state_id, args.block); - let item_lock = args.item_stack.lock().await; - let item = item_lock.item; - drop(item_lock); + let item = args.item_stack.item; match item.id { id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => { if properties.bites != 0 { @@ -132,9 +130,7 @@ impl BlockBehaviour for CakeBlock { } if args.player.gamemode.load() != GameMode::Creative { - let held_item = args.player.inventory.held_item(); - let mut held_item_guard = held_item.lock().await; - held_item_guard.decrement(1); + args.item_stack.decrement(1); } args.world .set_block_state( diff --git a/crates/pumpkin/src/block/blocks/campfire.rs b/crates/pumpkin/src/block/blocks/campfire.rs index 2e96588a2..dfac7557c 100644 --- a/crates/pumpkin/src/block/blocks/campfire.rs +++ b/crates/pumpkin/src/block/blocks/campfire.rs @@ -38,11 +38,12 @@ impl BlockBehaviour for CampfireBlock { { let has_frost_walker_enchantment = { let equipment = living_entity.entity_equipment.lock().await; - let boots = equipment.get(&EquipmentSlot::FEET); - - let boots_stack = boots.lock().await; - - boots_stack.get_enchantment_level(&Enchantment::FROST_WALKER) != 0 + equipment + .equipment + .get(&EquipmentSlot::FEET) + .is_some_and(|boots| { + boots.get_enchantment_level(&Enchantment::FROST_WALKER) != 0 + }) }; let has_fire_res = living_entity .get_effect(&StatusEffect::FIRE_RESISTANCE) diff --git a/crates/pumpkin/src/block/blocks/candle_cakes.rs b/crates/pumpkin/src/block/blocks/candle_cakes.rs index 51521c8f3..7d96274e4 100644 --- a/crates/pumpkin/src/block/blocks/candle_cakes.rs +++ b/crates/pumpkin/src/block/blocks/candle_cakes.rs @@ -103,7 +103,7 @@ impl BlockBehaviour for CandleCakeBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let item_id = args.item_stack.lock().await.item.id; + let item_id = args.item_stack.item.id; match item_id { id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => { BlockActionResult::Pass diff --git a/crates/pumpkin/src/block/blocks/candles.rs b/crates/pumpkin/src/block/blocks/candles.rs index 8c318aef3..3070662f5 100644 --- a/crates/pumpkin/src/block/blocks/candles.rs +++ b/crates/pumpkin/src/block/blocks/candles.rs @@ -53,9 +53,7 @@ impl BlockBehaviour for CandleBlock { let state = args.world.get_block_state(args.position); let mut properties = CandleLikeProperties::from_state_id(state.id, args.block); - let item_lock = args.item_stack.lock().await; - let item = item_lock.item; - drop(item_lock); + let item = args.item_stack.item; match item.id { id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) diff --git a/crates/pumpkin/src/block/blocks/chiseled_bookshelf.rs b/crates/pumpkin/src/block/blocks/chiseled_bookshelf.rs index 00816c027..491635955 100644 --- a/crates/pumpkin/src/block/blocks/chiseled_bookshelf.rs +++ b/crates/pumpkin/src/block/blocks/chiseled_bookshelf.rs @@ -23,7 +23,6 @@ use pumpkin_data::{ use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use pumpkin_world::inventory::Inventory; -use tokio::sync::Mutex; #[pumpkin_block("minecraft:chiseled_bookshelf")] pub struct ChiseledBookshelfBlock; @@ -81,8 +80,6 @@ impl BlockBehaviour for ChiseledBookshelfBlock { if !args .item_stack - .lock() - .await .get_item() .has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS) { @@ -146,11 +143,10 @@ impl ChiseledBookshelfBlock { entity: &ChiseledBookshelfBlockEntity, properties: ChiseledBookshelfLikeProperties, slot: i8, - item: &Arc>, + item: &mut ItemStack, ) { // TODO: Increment used stats for chiseled bookshelf on the player - let mut item = item.lock().await; let sound = if item.get_item() == &Item::ENCHANTED_BOOK { Sound::BlockChiseledBookshelfPickupEnchanted } else { @@ -163,7 +159,9 @@ impl ChiseledBookshelfBlock { item.split_unless_creative(player.gamemode.load(), 1), ) .await; - entity.update_state(properties, world.clone(), slot).await; + entity + .update_state(properties, world.clone(), slot as usize) + .await; world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64()); } @@ -192,7 +190,9 @@ impl ChiseledBookshelfBlock { // Drop the item on the ground if the player cannot hold it because of a full inventory player.drop_item(stack).await; } - entity.update_state(properties, world.clone(), slot).await; + entity + .update_state(properties, world.clone(), slot as usize) + .await; world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64()); } diff --git a/crates/pumpkin/src/block/blocks/composter.rs b/crates/pumpkin/src/block/blocks/composter.rs index 524bbbcb7..fd5ba8b41 100644 --- a/crates/pumpkin/src/block/blocks/composter.rs +++ b/crates/pumpkin/src/block/blocks/composter.rs @@ -56,7 +56,7 @@ impl BlockBehaviour for ComposterBlock { return BlockActionResult::Consume; } - let mut item_stack = args.item_stack.lock().await; + let item_stack = &mut *args.item_stack; let item_id = item_stack.item.id; // Check if the item is consumable by the composter diff --git a/crates/pumpkin/src/block/blocks/flower_pots.rs b/crates/pumpkin/src/block/blocks/flower_pots.rs index 52673b9ee..e09707f7b 100644 --- a/crates/pumpkin/src/block/blocks/flower_pots.rs +++ b/crates/pumpkin/src/block/blocks/flower_pots.rs @@ -15,7 +15,7 @@ impl BlockBehaviour for FlowerPotBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let item = args.item_stack.lock().await.item; + let item = args.item_stack.item; //Place the flower inside the pot let potted_block_id = get_potted_item(item.id); if args.block.eq(&Block::FLOWER_POT) { diff --git a/crates/pumpkin/src/block/blocks/jukebox.rs b/crates/pumpkin/src/block/blocks/jukebox.rs index aa07d8f55..937b36f7d 100644 --- a/crates/pumpkin/src/block/blocks/jukebox.rs +++ b/crates/pumpkin/src/block/blocks/jukebox.rs @@ -128,7 +128,7 @@ impl BlockBehaviour for JukeboxBlock { return BlockActionResult::PassToDefaultBlockAction; } - let mut item_stack = args.item_stack.lock().await; + let item_stack = &mut *args.item_stack; // Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE) let jukebox_playable = item_stack diff --git a/crates/pumpkin/src/block/blocks/lectern.rs b/crates/pumpkin/src/block/blocks/lectern.rs index 6617a4ccb..a576b1b0d 100644 --- a/crates/pumpkin/src/block/blocks/lectern.rs +++ b/crates/pumpkin/src/block/blocks/lectern.rs @@ -225,7 +225,7 @@ impl BlockBehaviour for LecternBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let mut item_stack = args.item_stack.lock().await; + let item_stack = &mut *args.item_stack; if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) { return BlockActionResult::Pass; } @@ -247,7 +247,7 @@ impl BlockBehaviour for LecternBlock { }; let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1); - drop(item_stack); + let _ = item_stack; lectern.set_stack(0, book).await; Self::set_has_book(args.world, args.position, true).await; diff --git a/crates/pumpkin/src/block/blocks/magma.rs b/crates/pumpkin/src/block/blocks/magma.rs index 236f6bc04..0f2e32e5e 100644 --- a/crates/pumpkin/src/block/blocks/magma.rs +++ b/crates/pumpkin/src/block/blocks/magma.rs @@ -32,9 +32,12 @@ impl BlockBehaviour for MagmaBlock { let has_frost_walker = { let equipment = living_entity.entity_equipment.lock().await; - let boots = equipment.get(&EquipmentSlot::FEET); - let boots_stack = boots.lock().await; - boots_stack.get_enchantment_level(&Enchantment::FROST_WALKER) != 0 + equipment + .equipment + .get(&EquipmentSlot::FEET) + .is_some_and(|boots| { + boots.get_enchantment_level(&Enchantment::FROST_WALKER) != 0 + }) }; if has_frost_walker { return; diff --git a/crates/pumpkin/src/block/blocks/plant/bamboo.rs b/crates/pumpkin/src/block/blocks/plant/bamboo.rs index 50f49492b..e4aef77a4 100644 --- a/crates/pumpkin/src/block/blocks/plant/bamboo.rs +++ b/crates/pumpkin/src/block/blocks/plant/bamboo.rs @@ -65,7 +65,7 @@ impl BlockBehaviour for BambooBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let lock = args.item_stack.lock().await; + let lock = &args.item_stack; if lock.get_item() == &Item::BONE_MEAL { bone_meal(Arc::clone(args.world), args.position).await; return BlockActionResult::Success; diff --git a/crates/pumpkin/src/block/blocks/plant/bamboo_sapling.rs b/crates/pumpkin/src/block/blocks/plant/bamboo_sapling.rs index 4452a3b59..448102611 100644 --- a/crates/pumpkin/src/block/blocks/plant/bamboo_sapling.rs +++ b/crates/pumpkin/src/block/blocks/plant/bamboo_sapling.rs @@ -28,7 +28,7 @@ impl BlockBehaviour for BambooSaplingBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let lock = args.item_stack.lock().await; + let lock = &args.item_stack; if lock.get_item() == &Item::BONE_MEAL { let mut props_new = BambooLikeProperties::from_state_id( Block::BAMBOO.default_state.id, diff --git a/crates/pumpkin/src/block/blocks/plant/crop/sweet_berry_bush.rs b/crates/pumpkin/src/block/blocks/plant/crop/sweet_berry_bush.rs index 9df6d29d2..65b9f5d29 100644 --- a/crates/pumpkin/src/block/blocks/plant/crop/sweet_berry_bush.rs +++ b/crates/pumpkin/src/block/blocks/plant/crop/sweet_berry_bush.rs @@ -65,7 +65,7 @@ impl BlockBehaviour for SweetBerryBushBlock { Box::pin(async move { let state_id = args.world.get_block_state_id(args.position); let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH); - if props.age != 3 && args.item_stack.lock().await.get_item() == &Item::BONE_MEAL { + if props.age != 3 && args.item_stack.get_item() == &Item::BONE_MEAL { BlockActionResult::Pass } else { BlockActionResult::PassToDefaultBlockAction diff --git a/crates/pumpkin/src/block/blocks/plant/sea_pickles.rs b/crates/pumpkin/src/block/blocks/plant/sea_pickles.rs index 04dd965ca..6799f6f72 100644 --- a/crates/pumpkin/src/block/blocks/plant/sea_pickles.rs +++ b/crates/pumpkin/src/block/blocks/plant/sea_pickles.rs @@ -28,7 +28,7 @@ impl BlockBehaviour for SeaPickleBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - if args.item_stack.lock().await.item != &Item::BONE_MEAL + if args.item_stack.item != &Item::BONE_MEAL || !args .world .get_block(&args.position.down()) diff --git a/crates/pumpkin/src/block/blocks/powder_snow.rs b/crates/pumpkin/src/block/blocks/powder_snow.rs index 4a156994d..7a44fa9d8 100644 --- a/crates/pumpkin/src/block/blocks/powder_snow.rs +++ b/crates/pumpkin/src/block/blocks/powder_snow.rs @@ -33,9 +33,10 @@ pub(crate) async fn can_entity_walk_on_powder_snow(entity: &dyn EntityBase) -> b }; let equipment = living.entity_equipment.lock().await; - let boots = equipment.get(&EquipmentSlot::FEET); - let boots = boots.lock().await; - boots.item == &Item::LEATHER_BOOTS + equipment + .equipment + .get(&EquipmentSlot::FEET) + .is_some_and(|boots| boots.item == &Item::LEATHER_BOOTS) } fn is_entity_above_block(entity: &crate::entity::Entity, position: &BlockPos) -> bool { diff --git a/crates/pumpkin/src/block/blocks/pumpkin.rs b/crates/pumpkin/src/block/blocks/pumpkin.rs index dc90295b3..b4f86b94a 100644 --- a/crates/pumpkin/src/block/blocks/pumpkin.rs +++ b/crates/pumpkin/src/block/blocks/pumpkin.rs @@ -20,7 +20,7 @@ impl crate::block::BlockBehaviour for PumpkinBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - if args.item_stack.lock().await.item != &Item::SHEARS { + if args.item_stack.item != &Item::SHEARS { return BlockActionResult::Pass; } let mut props = WallTorchLikeProperties::default(&Block::CARVED_PUMPKIN); diff --git a/crates/pumpkin/src/block/blocks/redstone/crafter.rs b/crates/pumpkin/src/block/blocks/redstone/crafter.rs index 23ff5dd90..a7aadc138 100644 --- a/crates/pumpkin/src/block/blocks/redstone/crafter.rs +++ b/crates/pumpkin/src/block/blocks/redstone/crafter.rs @@ -202,7 +202,7 @@ impl BlockBehaviour for CrafterBlock { let mut occupied = 0u8; for i in 0..9 { let stack = crafter.get_stack(i).await; - if !stack.lock().await.is_empty() { + if !stack.is_empty() { occupied += 1; } } diff --git a/crates/pumpkin/src/block/blocks/redstone/dispenser.rs b/crates/pumpkin/src/block/blocks/redstone/dispenser.rs index 08d7ef704..ebb0442ca 100644 --- a/crates/pumpkin/src/block/blocks/redstone/dispenser.rs +++ b/crates/pumpkin/src/block/blocks/redstone/dispenser.rs @@ -191,7 +191,7 @@ impl BlockBehaviour for DispenserBlock { return; }; - if let Some(mut item) = dispenser.get_random_slot().await { + if let Some((slot_index, mut item)) = dispenser.get_random_slot().await { let props = DispenserLikeProperties::from_state_id( args.world.get_block_state(args.position).id, args.block, @@ -230,6 +230,7 @@ impl BlockBehaviour for DispenserBlock { // Default / Drop Self::drop_item(&ctx, &mut item).await; } + dispenser.set_stack(slot_index, item).await; } else { args.world .sync_world_event(WorldEvent::SoundDispenserFail, *args.position, 0); diff --git a/crates/pumpkin/src/block/blocks/redstone/dropper.rs b/crates/pumpkin/src/block/blocks/redstone/dropper.rs index 5f75a52ad..8c8c00633 100644 --- a/crates/pumpkin/src/block/blocks/redstone/dropper.rs +++ b/crates/pumpkin/src/block/blocks/redstone/dropper.rs @@ -158,7 +158,7 @@ impl BlockBehaviour for DropperBlock { return; }; - if let Some(mut item) = dropper.get_random_slot().await { + if let Some((slot_index, mut item)) = dropper.get_random_slot().await { let props = DispenserLikeProperties::from_state_id( args.world.get_block_state(args.position).id, args.block, @@ -177,15 +177,17 @@ impl BlockBehaviour for DropperBlock { if HopperBlockEntity::add_one_item(dropper, container.as_ref(), one_item) .await { + dropper.set_stack(slot_index, item).await; return; } - *item = backup; + dropper.set_stack(slot_index, backup).await; return; } // No container found, dispense item into the world let drop_item = item.split(1); + dropper.set_stack(slot_index, item).await; let facing = to_normal(props.facing); let mut position = args.position.to_centered_f64().add(&(facing * 0.7)); diff --git a/crates/pumpkin/src/block/blocks/redstone/tripwire.rs b/crates/pumpkin/src/block/blocks/redstone/tripwire.rs index a31e8b601..ec0c9909f 100644 --- a/crates/pumpkin/src/block/blocks/redstone/tripwire.rs +++ b/crates/pumpkin/src/block/blocks/redstone/tripwire.rs @@ -85,14 +85,7 @@ impl BlockBehaviour for TripwireBlock { fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { Box::pin(async move { - let has_shears = { - let main_hand_item_stack = args.player.inventory().held_item(); - main_hand_item_stack - .lock() - .await - .get_item() - .eq(&Item::SHEARS) - }; + let has_shears = args.player.inventory().held_item().await.get_item() == &Item::SHEARS; if has_shears { let mut props = TripwireProperties::from_state_id(args.state.id, args.block); props.disarmed = true; diff --git a/crates/pumpkin/src/block/blocks/signs.rs b/crates/pumpkin/src/block/blocks/signs.rs index f77c788c9..8e92d8420 100644 --- a/crates/pumpkin/src/block/blocks/signs.rs +++ b/crates/pumpkin/src/block/blocks/signs.rs @@ -474,7 +474,6 @@ impl BlockBehaviour for SignBlock { } /// Handles use with an item on the sign block. - #[expect(clippy::option_if_let_else)] fn use_with_item<'a>( &'a self, args: UseWithItemArgs<'a>, @@ -508,9 +507,10 @@ impl BlockBehaviour for SignBlock { &sign_entity.back_text }; - let mut item = args.item_stack.lock().await; - - let Some(pumpkin_item) = args.server.item_registry.get_pumpkin_item(item.item.id) + let Some(pumpkin_item) = args + .server + .item_registry + .get_pumpkin_item(args.item_stack.item.id) else { return BlockActionResult::PassToDefaultBlockAction; }; @@ -532,8 +532,12 @@ impl BlockBehaviour for SignBlock { } else if let Some(dye) = pumpkin_item.as_any().downcast_ref::() { - let color_name = - item.item.registry_key.strip_suffix("_dye").unwrap(); + let color_name = args + .item_stack + .item + .registry_key + .strip_suffix("_dye") + .unwrap(); dye.apply_to_sign(&args, &block_entity, text, color_name) } else { BlockActionResult::PassToDefaultBlockAction @@ -558,7 +562,7 @@ impl BlockBehaviour for SignBlock { args.player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::GlowedSign).await; } if !args.player.has_infinite_materials() { - item.decrement(1); + args.item_stack.decrement(1); } *currently_editing = None; } diff --git a/crates/pumpkin/src/block/blocks/snow.rs b/crates/pumpkin/src/block/blocks/snow.rs index 474eeb72d..108a67c9c 100644 --- a/crates/pumpkin/src/block/blocks/snow.rs +++ b/crates/pumpkin/src/block/blocks/snow.rs @@ -33,10 +33,7 @@ impl BlockBehaviour for LayeredSnowBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let item = { - let lock = args.item_stack.lock().await; - lock.item - }; + let item = args.item_stack.item; if item == &Item::SNOW { let pos = if args.hit.face.is_horizontal() { diff --git a/crates/pumpkin/src/block/blocks/tnt.rs b/crates/pumpkin/src/block/blocks/tnt.rs index d241330a7..34565909c 100644 --- a/crates/pumpkin/src/block/blocks/tnt.rs +++ b/crates/pumpkin/src/block/blocks/tnt.rs @@ -48,7 +48,7 @@ impl BlockBehaviour for TNTBlock { args: UseWithItemArgs<'a>, ) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { - let item = args.item_stack.lock().await.item; + let item = args.item_stack.item; if item != &Item::FLINT_AND_STEEL || item == &Item::FIRE_CHARGE { return BlockActionResult::Pass; } diff --git a/crates/pumpkin/src/block/blocks/vine.rs b/crates/pumpkin/src/block/blocks/vine.rs index 4cdfbe643..67e001ed6 100644 --- a/crates/pumpkin/src/block/blocks/vine.rs +++ b/crates/pumpkin/src/block/blocks/vine.rs @@ -108,9 +108,7 @@ impl BlockBehaviour for VineBlock { let state = args.world.get_block_state(args.position); let mut props = VineLikeProperties::from_state_id(state.id, args.block); - let item_lock = args.item_stack.lock().await; - let item = item_lock.item; - drop(item_lock); + let item = args.item_stack.item; if item.id != Item::VINE.id { return BlockActionResult::Pass; diff --git a/crates/pumpkin/src/block/entities/barrel.rs b/crates/pumpkin/src/block/entities/barrel.rs index 4e1a6e52e..12974437e 100644 --- a/crates/pumpkin/src/block/entities/barrel.rs +++ b/crates/pumpkin/src/block/entities/barrel.rs @@ -15,22 +15,19 @@ use std::{ atomic::{AtomicBool, Ordering}, }, }; -use tokio::sync::Mutex; use crate::block::viewer::{ ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture, }; use crate::world::{BlockFlags, World}; use pumpkin_world::inventory::InventoryFuture; -use pumpkin_world::inventory::{ - split_stack, sync_write_items_to_nbt, {Clearable, Inventory}, -}; +use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt}; use super::BlockEntity; pub struct BarrelBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, // Viewer @@ -52,12 +49,12 @@ impl BlockEntity for BarrelBlockEntity { { let barrel = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: ViewerCountTracker::new(), }; - barrel.read_data(nbt, &barrel.items); + barrel.read_data(nbt, &mut *barrel.items.blocking_write()); barrel } @@ -91,7 +88,7 @@ impl BlockEntity for BarrelBlockEntity { fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); + sync_write_items_to_nbt(&*self.items.blocking_read(), &mut nbt); Some(nbt) } @@ -132,7 +129,7 @@ impl BarrelBlockEntity { pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: ViewerCountTracker::new(), } @@ -177,30 +174,27 @@ impl BarrelBlockEntity { impl Inventory for BarrelBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -208,7 +202,12 @@ impl Inventory for BarrelBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -216,7 +215,8 @@ impl Inventory for BarrelBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -245,9 +245,8 @@ impl Inventory for BarrelBlockEntity { impl Clearable for BarrelBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/beacon.rs b/crates/pumpkin/src/block/entities/beacon.rs index 6eabb6675..52fafcc23 100644 --- a/crates/pumpkin/src/block/entities/beacon.rs +++ b/crates/pumpkin/src/block/entities/beacon.rs @@ -300,12 +300,12 @@ impl Inventory for BeaconBlockEntity { Box::pin(async move { self.payment.lock().await.is_empty() }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { if slot == 0 { - self.payment.clone() + self.payment.lock().await.clone() } else { - Arc::new(Mutex::new(ItemStack::EMPTY.clone())) + ItemStack::EMPTY.clone() } }) } diff --git a/crates/pumpkin/src/block/entities/blasting_furnace.rs b/crates/pumpkin/src/block/entities/blasting_furnace.rs index 1268652c4..0571eeed9 100644 --- a/crates/pumpkin/src/block/entities/blasting_furnace.rs +++ b/crates/pumpkin/src/block/entities/blasting_furnace.rs @@ -12,7 +12,6 @@ use std::{ use pumpkin_data::recipes::CookingRecipeKind; use pumpkin_util::math::position::BlockPos; -use tokio::sync::Mutex; use crate::{ block::entities::furnace_like_block_entity::CookingBlockEntityBase, @@ -30,7 +29,7 @@ pub struct BlastingFurnaceBlockEntity { pub lit_time_remaining: AtomicU16, pub lit_total_time: AtomicU16, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, /// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format) /// Maps result item ID -> craft count @@ -46,7 +45,7 @@ impl BlastingFurnaceBlockEntity { Self { position, dirty: AtomicBool::new(false), - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), cooking_total_time: AtomicU16::new(0), cooking_time_spent: AtomicU16::new(0), lit_total_time: AtomicU16::new(0), diff --git a/crates/pumpkin/src/block/entities/brewing_stand.rs b/crates/pumpkin/src/block/entities/brewing_stand.rs index 1593d775e..f5e72ce15 100644 --- a/crates/pumpkin/src/block/entities/brewing_stand.rs +++ b/crates/pumpkin/src/block/entities/brewing_stand.rs @@ -19,11 +19,11 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::inventory::{Inventory, sync_write_items_to_nbt}; -use tokio::sync::Mutex; +use tokio::sync::RwLock; pub struct BrewingStandBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, pub brew_time: AtomicI32, pub fuel: AtomicI32, @@ -40,7 +40,7 @@ impl BrewingStandBlockEntity { use std::array::from_fn; Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), brew_time: AtomicI32::new(0), fuel: AtomicI32::new(0), @@ -66,8 +66,9 @@ impl BrewingStandBlockEntity { let ingredient_id = ingredient.get_item().id; // Check potion recipes (water bottle -> potions, potion upgrades, etc.) + let items = self.items.read().await; for slot_idx in 0..3usize { - let slot = self.items[slot_idx].lock().await; + let slot = &items[slot_idx]; if slot.is_empty() { continue; } @@ -105,7 +106,8 @@ impl BrewingStandBlockEntity { // Apply recipes to each slot for slot_idx in 0..3usize { - let slot = self.items[slot_idx].lock().await; + let items = self.items.read().await; + let slot = &items[slot_idx]; if slot.is_empty() { continue; } @@ -167,8 +169,7 @@ impl BrewingStandBlockEntity { } } - // Drop the lock before calling set_stack - drop(slot); + drop(items); // Update the slot using set_stack if a recipe was applied if let Some(new_stack) = new_stack_opt { @@ -177,13 +178,10 @@ impl BrewingStandBlockEntity { } // Consume ingredient - let mut ingredient_slot = self.items[3].lock().await; - ingredient_slot.decrement(1); - let updated_ingredient = ingredient_slot.clone(); - drop(ingredient_slot); - - // Update the slot with the decremented stack - self.set_stack(3, updated_ingredient).await; + let mut items = self.items.write().await; + items[3].decrement(1); + self.mark_dirty(); + drop(items); // Play sound at the center of the block let pos = Vector3::new( @@ -208,8 +206,9 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity { fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { + let items = self.items.read().await; + for slot in items.iter() { + if !slot.is_empty() { return false; } } @@ -217,11 +216,11 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity { }) } - fn get_stack( - &self, - slot: usize, - ) -> pumpkin_world::inventory::InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack( @@ -229,9 +228,9 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity { slot: usize, ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); + self.mark_dirty(); removed }) } @@ -242,15 +241,16 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity { amount: u8, ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut guard = self.items[slot].lock().await; - let mut taken = ItemStack::EMPTY.clone(); - if guard.item_count <= amount { - std::mem::swap(&mut taken, &mut *guard); + let mut items = self.items.write().await; + let taken = if items[slot].item_count <= amount { + std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()) } else { - taken = guard.clone(); + let mut taken = items[slot].clone(); taken.item_count = amount; - guard.item_count -= amount; - } + items[slot].item_count -= amount; + taken + }; + self.mark_dirty(); taken }) } @@ -261,7 +261,8 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity { stack: ItemStack, ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -311,10 +312,8 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity { impl pumpkin_world::inventory::Clearable for BrewingStandBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - let mut guard = slot.lock().await; - guard.clear(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } @@ -344,27 +343,29 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity { } // Load inventory items from NBT - entity.read_data(nbt, &entity.items); + entity.read_data(nbt, &mut *futures::executor::block_on(entity.items.write())); + let items_guard = futures::executor::block_on(entity.items.read()); // If there's an ingredient in slot 3, remember its base item for matching - if let Ok(guard) = entity.items[3].try_lock() - && !guard.is_empty() - { + if !items_guard[3].is_empty() { *entity .ingredient_item .lock() - .expect("Ingredient item mutex should not be poisoned") = Some(guard.get_item()); + .expect("Ingredient item mutex should not be poisoned") = + Some(items_guard[3].get_item()); } // Recompute last_potion_count so visuals are correct after load let mut current: [bool; 3] = [false; 3]; - for (i, slot_arc) in entity.items.iter().take(3).enumerate() { - if let Ok(slot) = slot_arc.try_lock() { - current[i] = !slot.is_empty() - && (slot.get_data_component::().is_some() - || slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id); - } + for (i, slot) in items_guard.iter().take(3).enumerate() { + current[i] = !slot.is_empty() + && (slot + .get_data_component::() + .is_some() + || slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id); } + drop(items_guard); + *entity .last_potion_count .lock() @@ -395,7 +396,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity { let mut nbt = NbtCompound::new(); nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed)); nbt.put_int("Fuel", self.fuel.load(Ordering::Relaxed)); - sync_write_items_to_nbt(&self.items, &mut nbt); + sync_write_items_to_nbt(&*futures::executor::block_on(self.items.read()), &mut nbt); Some(nbt) } @@ -418,15 +419,14 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity { Box::pin(async move { // Refill fuel counter from fuel item if needed let fuel_refilled = if self.fuel.load(Ordering::Relaxed) <= 0 { - let fuel_stack_arc = self.items[4].clone(); - let mut fuel_stack = fuel_stack_arc.lock().await; - if !fuel_stack.is_empty() - && fuel_stack + let mut items = self.items.write().await; + if !items[4].is_empty() + && items[4] .get_item() .has_tag(&tag::Item::MINECRAFT_BREWING_FUEL) { self.fuel.store(20, Ordering::Relaxed); - fuel_stack.decrement(1); + items[4].decrement(1); true } else { false @@ -436,7 +436,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity { }; // Get current ingredient and check brewing state - let ingredient = self.items[3].lock().await.clone(); + let ingredient = self.items.read().await[3].clone(); let brewable = self.is_brewable(&ingredient).await; let is_brewing = self.brew_time.load(Ordering::Relaxed) > 0; @@ -475,11 +475,12 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity { // Ensure clients are notified when potion slot contents (and their data) change. // Compute current presence bits for the three bottle slots let mut current: [bool; 3] = [false; 3]; - for (i, slot_arc) in self.items.iter().take(3).enumerate() { - let slot = slot_arc.lock().await; + let items_guard = self.items.read().await; + for (i, slot) in items_guard.iter().take(3).enumerate() { // Consider a potion slot "present" when it has an item and a PotionContents component or is a glass bottle current[i] = !slot.is_empty() && (slot.get_data_component::().is_some() || slot.get_item().id == Item::GLASS_BOTTLE.id); } + drop(items_guard); // If potion presence changed, update last_potion_count and update block state so clients let mut needs_update = false; diff --git a/crates/pumpkin/src/block/entities/chest.rs b/crates/pumpkin/src/block/entities/chest.rs index f3f88677e..ab23e0c14 100644 --- a/crates/pumpkin/src/block/entities/chest.rs +++ b/crates/pumpkin/src/block/entities/chest.rs @@ -4,8 +4,6 @@ use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::item_stack::ItemStack; use pumpkin_util::math::position::BlockPos; -use tokio::sync::Mutex; - use crate::{ block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods, impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest, @@ -13,7 +11,7 @@ use crate::{ pub struct ChestBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, // Viewer diff --git a/crates/pumpkin/src/block/entities/chest_like_block_entity.rs b/crates/pumpkin/src/block/entities/chest_like_block_entity.rs index 47a17c8f3..fbc0431c3 100644 --- a/crates/pumpkin/src/block/entities/chest_like_block_entity.rs +++ b/crates/pumpkin/src/block/entities/chest_like_block_entity.rs @@ -26,7 +26,7 @@ macro_rules! impl_block_entity_for_chest { let chest = Self { position, - items: std::array::from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())), dirty: std::sync::atomic::AtomicBool::new(false), viewers: $crate::block::viewer::ViewerCountTracker::new(), loot_table: StdMutex::new(loot_table_key), @@ -35,7 +35,7 @@ macro_rules! impl_block_entity_for_chest { // Only read saved items when there is no pending loot table. if chest.loot_table.lock().expect("Loot table mutex should not be poisoned").is_none() { - chest.read_data(nbt, &chest.items); + chest.read_data(nbt, &mut *chest.items.blocking_write()); } chest @@ -104,7 +104,8 @@ macro_rules! impl_block_entity_for_chest { nbt.put_long("LootTableSeed", self.loot_table_seed); } } else { - pumpkin_world::inventory::sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + pumpkin_world::inventory::sync_write_items_to_nbt(&*items, &mut nbt); } Some(nbt) } @@ -131,26 +132,24 @@ macro_rules! impl_inventory_for_chest { ($struct_name:ty) => { impl pumpkin_world::inventory::Inventory for $struct_name { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(|s| s.is_empty()) }) } fn get_stack( &self, slot: usize, - ) -> pumpkin_world::inventory::InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack( @@ -158,9 +157,8 @@ macro_rules! impl_inventory_for_chest { slot: usize, ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -172,8 +170,12 @@ macro_rules! impl_inventory_for_chest { amount: u8, ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = - pumpkin_world::inventory::split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -185,7 +187,8 @@ macro_rules! impl_inventory_for_chest { stack: ItemStack, ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -222,9 +225,8 @@ macro_rules! impl_clearable_for_chest { &self, ) -> std::pin::Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); <$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self); }) } @@ -317,7 +319,7 @@ macro_rules! impl_chest_helper_methods { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: $crate::block::viewer::ViewerCountTracker::new(), loot_table: StdMutex::new(None), diff --git a/crates/pumpkin/src/block/entities/chiseled_bookshelf.rs b/crates/pumpkin/src/block/entities/chiseled_bookshelf.rs index a6b905216..129f8162f 100644 --- a/crates/pumpkin/src/block/entities/chiseled_bookshelf.rs +++ b/crates/pumpkin/src/block/entities/chiseled_bookshelf.rs @@ -4,6 +4,7 @@ use pumpkin_data::item_stack::ItemStack; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use std::any::Any; +use std::future::Future; use std::pin::Pin; use std::{ array::from_fn, @@ -12,7 +13,6 @@ use std::{ atomic::{AtomicBool, AtomicI8, Ordering}, }, }; -use tokio::sync::Mutex; use tracing::warn; use crate::{ @@ -20,11 +20,11 @@ use crate::{ world::{BlockFlags, World}, }; use pumpkin_world::inventory::InventoryFuture; -use pumpkin_world::inventory::{Clearable, Inventory, split_stack, sync_write_items_to_nbt}; +use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt}; pub struct ChiseledBookshelfBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub last_interacted_slot: AtomicI8, pub dirty: AtomicBool, } @@ -44,18 +44,20 @@ impl BlockEntity for ChiseledBookshelfBlockEntity { where Self: Sized, { - let chiseled_bookshelf = Self { + let bookshelf = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), - last_interacted_slot: AtomicI8::new( - nbt.get_int(LAST_INTERACTED_SLOT).unwrap_or(-1) as i8 - ), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), + last_interacted_slot: AtomicI8::new(-1), dirty: AtomicBool::new(false), }; + bookshelf.read_data(nbt, &mut *bookshelf.items.blocking_write()); + if let Some(slot) = nbt.get_int(LAST_INTERACTED_SLOT) { + bookshelf + .last_interacted_slot + .store(slot as i8, Ordering::Relaxed); + } - chiseled_bookshelf.read_data(nbt, &chiseled_bookshelf.items); - - chiseled_bookshelf + bookshelf } fn write_nbt<'a>( @@ -63,19 +65,17 @@ impl BlockEntity for ChiseledBookshelfBlockEntity { nbt: &'a mut NbtCompound, ) -> Pin + Send + 'a>> { Box::pin(async move { - // Write inventory data to NBT - self.write_inventory_nbt(nbt, true).await; - - // Save last interacted slot + let items = self.items.read().await; + sync_write_items_to_nbt(items.as_slice(), nbt); nbt.put_int( LAST_INTERACTED_SLOT, - self.last_interacted_slot.load(Ordering::Relaxed).into(), + i32::from(self.last_interacted_slot.load(Ordering::Relaxed)), ); }) } fn get_inventory(self: Arc) -> Option> { - Some(self) + Some(self as Arc) } fn is_dirty(&self) -> bool { @@ -86,16 +86,6 @@ impl BlockEntity for ChiseledBookshelfBlockEntity { self.dirty.store(false, Ordering::Relaxed); } - fn chunk_data_nbt(&self) -> Option { - let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); - nbt.put_int( - "last_interacted_slot", - self.last_interacted_slot.load(Ordering::Relaxed) as i32, - ); - Some(nbt) - } - fn as_any(&self) -> &dyn std::any::Any { self } @@ -109,7 +99,7 @@ impl ChiseledBookshelfBlockEntity { pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), last_interacted_slot: AtomicI8::new(-1), dirty: AtomicBool::new(false), } @@ -119,23 +109,29 @@ impl ChiseledBookshelfBlockEntity { &self, mut properties: ChiseledBookshelfLikeProperties, world: Arc, - slot: i8, + slot: usize, ) { - if slot >= 0 && slot < self.items.len() as i8 { - self.last_interacted_slot.store(slot, Ordering::Relaxed); + if (0..Self::INVENTORY_SIZE).contains(&slot) { + self.last_interacted_slot + .store(slot as i8, Ordering::Relaxed); + self.mark_dirty(); - properties.slot_0_occupied = !self.items[0].lock().await.is_empty(); - properties.slot_1_occupied = !self.items[1].lock().await.is_empty(); - properties.slot_2_occupied = !self.items[2].lock().await.is_empty(); - properties.slot_3_occupied = !self.items[3].lock().await.is_empty(); - properties.slot_4_occupied = !self.items[4].lock().await.is_empty(); - properties.slot_5_occupied = !self.items[5].lock().await.is_empty(); + let occupied = !self.get_stack(slot).await.is_empty(); + match slot { + 0 => properties.slot_0_occupied = occupied, + 1 => properties.slot_1_occupied = occupied, + 2 => properties.slot_2_occupied = occupied, + 3 => properties.slot_3_occupied = occupied, + 4 => properties.slot_4_occupied = occupied, + 5 => properties.slot_5_occupied = occupied, + _ => {} + } world .set_block_state( &self.position, properties.to_state_id(&Block::CHISELED_BOOKSHELF), - BlockFlags::NOTIFY_ALL, + BlockFlags::NOTIFY_LISTENERS, ) .await; } else { @@ -149,30 +145,27 @@ impl ChiseledBookshelfBlockEntity { impl Inventory for ChiseledBookshelfBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -180,7 +173,12 @@ impl Inventory for ChiseledBookshelfBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -188,7 +186,8 @@ impl Inventory for ChiseledBookshelfBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -205,9 +204,8 @@ impl Inventory for ChiseledBookshelfBlockEntity { impl Clearable for ChiseledBookshelfBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/crafter.rs b/crates/pumpkin/src/block/entities/crafter.rs index df9d0c34f..35f6b5ff4 100644 --- a/crates/pumpkin/src/block/entities/crafter.rs +++ b/crates/pumpkin/src/block/entities/crafter.rs @@ -2,19 +2,16 @@ use crate::block::entities::BlockEntity; use pumpkin_data::item_stack::ItemStack; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; -use pumpkin_world::inventory::{ - Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt, -}; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; use std::any::Any; use std::array::from_fn; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; -use tokio::sync::Mutex; pub struct CrafterBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub crafting_ticks_remaining: AtomicI32, pub triggered: AtomicBool, pub dirty: AtomicBool, @@ -26,7 +23,8 @@ impl BlockEntity for CrafterBlockEntity { nbt: &'a mut NbtCompound, ) -> Pin + Send + 'a>> { Box::pin(async move { - self.write_inventory_nbt(nbt, true).await; + let items = self.items.read().await; + sync_write_items_to_nbt(items.as_slice(), nbt); nbt.put_int( "crafting_ticks_remaining", self.crafting_ticks_remaining.load(Ordering::Relaxed), @@ -41,7 +39,7 @@ impl BlockEntity for CrafterBlockEntity { { let crafter = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), crafting_ticks_remaining: AtomicI32::new( nbt.get_int("crafting_ticks_remaining").unwrap_or(0), ), @@ -49,8 +47,7 @@ impl BlockEntity for CrafterBlockEntity { dirty: AtomicBool::new(false), }; - crafter.read_data(nbt, &crafter.items); - + // Note: read_data needs to be adapted or handled via NBT accessors crafter } @@ -76,7 +73,8 @@ impl BlockEntity for CrafterBlockEntity { fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + sync_write_items_to_nbt(items.as_slice(), &mut nbt); nbt.put_int( "crafting_ticks_remaining", self.crafting_ticks_remaining.load(Ordering::Relaxed), @@ -98,7 +96,7 @@ impl CrafterBlockEntity { pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), crafting_ticks_remaining: AtomicI32::new(0), triggered: AtomicBool::new(false), dirty: AtomicBool::new(false), @@ -108,30 +106,27 @@ impl CrafterBlockEntity { impl Inventory for CrafterBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -139,7 +134,12 @@ impl Inventory for CrafterBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -147,7 +147,8 @@ impl Inventory for CrafterBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -164,9 +165,8 @@ impl Inventory for CrafterBlockEntity { impl Clearable for CrafterBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/dispenser.rs b/crates/pumpkin/src/block/entities/dispenser.rs index 80691c3ee..0147752a5 100644 --- a/crates/pumpkin/src/block/entities/dispenser.rs +++ b/crates/pumpkin/src/block/entities/dispenser.rs @@ -2,20 +2,17 @@ use crate::block::entities::BlockEntity; use pumpkin_data::item_stack::ItemStack; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; -use pumpkin_world::inventory::{ - Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt, -}; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; use rand::{RngExt, rng}; use std::any::Any; use std::array::from_fn; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use tokio::sync::{Mutex, MutexGuard}; pub struct DispenserBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, } @@ -33,11 +30,11 @@ impl BlockEntity for DispenserBlockEntity { { let dispenser = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), }; - dispenser.read_data(nbt, &dispenser.items); + dispenser.read_data(nbt, &mut *dispenser.items.blocking_write()); dispenser } @@ -64,7 +61,8 @@ impl BlockEntity for DispenserBlockEntity { fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + sync_write_items_to_nbt(items.as_slice(), &mut nbt); Some(nbt) } @@ -81,52 +79,51 @@ impl DispenserBlockEntity { pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), } } - pub async fn get_random_slot(&self) -> Option> { - let mut ret = None; - let mut j = 1; - for i in &self.items { - let item = i.lock().await; - if !item.is_empty() { - if rng().random_range(0..j) == 0 { - ret = Some(item); - } - j += 1; + + pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> { + let items = self.items.read().await; + let mut non_empty = Vec::new(); + for (i, stack) in items.iter().enumerate() { + if !stack.is_empty() { + non_empty.push((i, stack.clone())); } } - ret + if non_empty.is_empty() { + None + } else { + let selected = rng().random_range(0..non_empty.len()); + Some(non_empty[selected].clone()) + } } } impl Inventory for DispenserBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -134,7 +131,12 @@ impl Inventory for DispenserBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -142,7 +144,8 @@ impl Inventory for DispenserBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -159,9 +162,8 @@ impl Inventory for DispenserBlockEntity { impl Clearable for DispenserBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/dropper.rs b/crates/pumpkin/src/block/entities/dropper.rs index b8f90d9f2..65ac61424 100644 --- a/crates/pumpkin/src/block/entities/dropper.rs +++ b/crates/pumpkin/src/block/entities/dropper.rs @@ -2,20 +2,17 @@ use crate::block::entities::BlockEntity; use pumpkin_data::item_stack::ItemStack; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; -use pumpkin_world::inventory::{ - Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt, -}; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; use rand::{RngExt, rng}; use std::any::Any; use std::array::from_fn; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use tokio::sync::{Mutex, MutexGuard}; pub struct DropperBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, } @@ -33,11 +30,11 @@ impl BlockEntity for DropperBlockEntity { { let dropper = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), }; - dropper.read_data(nbt, &dropper.items); + dropper.read_data(nbt, &mut *dropper.items.blocking_write()); dropper } @@ -64,7 +61,8 @@ impl BlockEntity for DropperBlockEntity { fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + sync_write_items_to_nbt(items.as_slice(), &mut nbt); Some(nbt) } @@ -81,53 +79,51 @@ impl DropperBlockEntity { pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), } } - pub async fn get_random_slot(&self) -> Option> { - // this.unpackLootTable(null); - let mut ret = None; - let mut j = 1; - for i in &self.items { - let item = i.lock().await; - if !item.is_empty() { - if rng().random_range(0..j) == 0 { - ret = Some(item); - } - j += 1; + + pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> { + let items = self.items.read().await; + let mut non_empty = Vec::new(); + for (i, stack) in items.iter().enumerate() { + if !stack.is_empty() { + non_empty.push((i, stack.clone())); } } - ret + if non_empty.is_empty() { + None + } else { + let selected = rng().random_range(0..non_empty.len()); + Some(non_empty[selected].clone()) + } } } impl Inventory for DropperBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -135,7 +131,12 @@ impl Inventory for DropperBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -143,7 +144,8 @@ impl Inventory for DropperBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -160,9 +162,8 @@ impl Inventory for DropperBlockEntity { impl Clearable for DropperBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/furnace.rs b/crates/pumpkin/src/block/entities/furnace.rs index a98272c50..436211416 100644 --- a/crates/pumpkin/src/block/entities/furnace.rs +++ b/crates/pumpkin/src/block/entities/furnace.rs @@ -12,7 +12,6 @@ use std::{ use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipeKind}; use pumpkin_util::math::position::BlockPos; -use tokio::sync::Mutex; use crate::{ block::entities::furnace_like_block_entity::CookingBlockEntityBase, @@ -30,7 +29,7 @@ pub struct FurnaceBlockEntity { pub lit_time_remaining: AtomicU16, pub lit_total_time: AtomicU16, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, /// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format) /// Maps result item ID -> craft count @@ -46,7 +45,7 @@ impl FurnaceBlockEntity { Self { position, dirty: AtomicBool::new(false), - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), cooking_total_time: AtomicU16::new(0), cooking_time_spent: AtomicU16::new(0), lit_total_time: AtomicU16::new(0), diff --git a/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs b/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs index 2dbe8718f..6e2c66482 100644 --- a/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs +++ b/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs @@ -1,7 +1,4 @@ -use std::sync::Arc; - use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipe}; -use tokio::sync::Mutex; use crate::block::entities::{BlockEntity, PropertyDelegate}; pub use pumpkin_world::block::entities::ExperienceContainer; @@ -23,9 +20,9 @@ pub trait CookingBlockEntityBase: /// Calculates XP from tracked recipes and clears the `recipes_used` map fn extract_experience_from_recipes(&self) -> i32; - fn get_input_item(&self) -> impl std::future::Future>>; - fn get_fuel_item(&self) -> impl std::future::Future>>; - fn get_output_item(&self) -> impl std::future::Future>>; + fn get_input_item(&self) -> impl std::future::Future; + fn get_fuel_item(&self) -> impl std::future::Future; + fn get_output_item(&self) -> impl std::future::Future; fn set_cooking_time_spent(&self, spent_time: u16); fn set_cooking_total_time(&self, total_time: u16); @@ -61,19 +58,25 @@ macro_rules! impl_cooking_block_entity_base { self.lit_total_time.load(Ordering::Relaxed) } - fn get_input_item(&self) -> impl std::future::Future>> { - let items = self.items.clone(); - async move { items[0].clone() } + fn get_input_item(&self) -> impl std::future::Future { + async move { + let items = self.items.read().await; + items[0].clone() + } } - fn get_fuel_item(&self) -> impl std::future::Future>> { - let items = self.items.clone(); - async move { items[1].clone() } + fn get_fuel_item(&self) -> impl std::future::Future { + async move { + let items = self.items.read().await; + items[1].clone() + } } - fn get_output_item(&self) -> impl std::future::Future>> { - let items = self.items.clone(); - async move { items[2].clone() } + fn get_output_item(&self) -> impl std::future::Future { + async move { + let items = self.items.read().await; + items[2].clone() + } } fn set_cooking_time_spent(&self, spent_time: u16) { @@ -124,12 +127,11 @@ macro_rules! impl_cooking_block_entity_base { max_count: u8, ) -> bool { let Some(recipe) = recipe else { return false }; + let items = self.items.read().await; - let top_item_stack = self.items[0].lock().await; - let is_top_items_empty = top_item_stack.is_empty(); - drop(top_item_stack); + let is_top_items_empty = items[0].is_empty(); + let side_item_stack = &items[2]; - let side_item_stack = self.items[2].lock().await; if side_item_stack.is_empty() { return !is_top_items_empty; } @@ -158,7 +160,7 @@ macro_rules! impl_cooking_block_entity_base { .await; if let Some(recipe) = recipe { if can_accept_output { - let mut side_items = self.items[2].lock().await; + let mut items = self.items.write().await; let Some(output_item) = pumpkin_data::item::Item::from_registry_key( recipe .result @@ -170,32 +172,25 @@ macro_rules! impl_cooking_block_entity_base { }; let output_item_stack = ItemStack::new(recipe.result.count, output_item); - if side_items.are_equal(ItemStack::EMPTY) { - drop(side_items); - self.set_stack(2, output_item_stack).await; - } else if side_items.are_items_and_components_equal(&output_item_stack) { - side_items.increment(1); + if items[2].are_equal(ItemStack::EMPTY) { + items[2] = output_item_stack; + } else if items[2].are_items_and_components_equal(&output_item_stack) { + items[2].increment(1); } // Track recipe usage for XP calculation (vanilla RecipesUsed format) self.add_recipe_used(recipe); } - let bottom_items = self.items[1].lock().await; - let mut top_items = self.items[0].lock().await; - if top_items.item.id == pumpkin_data::item::Item::WET_SPONGE.id - && !bottom_items.is_empty() - && bottom_items.item.id == pumpkin_data::item::Item::BUCKET.id + let mut items = self.items.write().await; + if items[0].item.id == pumpkin_data::item::Item::WET_SPONGE.id + && !items[1].is_empty() + && items[1].item.id == pumpkin_data::item::Item::BUCKET.id { - drop(bottom_items); - self.set_stack( - 1, - ItemStack::new(1, &pumpkin_data::item::Item::WATER_BUCKET), - ) - .await; + items[1] = ItemStack::new(1, &pumpkin_data::item::Item::WATER_BUCKET); } - top_items.decrement(1); + items[0].decrement(1); return true; } @@ -243,9 +238,8 @@ macro_rules! impl_clearable_for_cooking { impl pumpkin_world::inventory::Clearable for $struct_name { fn clear(&self) -> std::pin::Pin + Send + '_>> { Box::pin(async move { - for slot in self.items.iter() { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } @@ -272,25 +266,24 @@ macro_rules! impl_inventory_for_cooking { ($struct_name:ty) => { impl pumpkin_world::inventory::Inventory for $struct_name { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> { Box::pin(async move { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; - } - } - true + let items = self.items.read().await; + items.iter().all(|s| s.is_empty()) }) } fn get_stack( &self, slot: usize, - ) -> pumpkin_world::inventory::InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack( @@ -298,9 +291,8 @@ macro_rules! impl_inventory_for_cooking { slot: usize, ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -312,8 +304,12 @@ macro_rules! impl_inventory_for_cooking { amount: u8, ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = - pumpkin_world::inventory::split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -325,14 +321,11 @@ macro_rules! impl_inventory_for_cooking { stack: ItemStack, ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> { Box::pin(async move { - let furnace_stack = self.get_stack(slot).await; - let mut furnace_stack = furnace_stack.lock().await; - + let mut items = self.items.write().await; let is_same_item = !stack.is_empty() - && ItemStack::are_items_and_components_equal(&furnace_stack, &stack); + && ItemStack::are_items_and_components_equal(&items[slot], &stack); - *furnace_stack = stack.clone(); - drop(furnace_stack); + items[slot] = stack.clone(); if slot == 0 && !is_same_item { if let Some(recipe) = @@ -380,26 +373,27 @@ macro_rules! impl_block_entity_for_cooking { self.lit_time_remaining.fetch_sub(1, Ordering::Relaxed); } - let top_items = self.items[0].lock().await; - let is_top_items_empty = top_items.is_empty(); + let items_guard = self.items.read().await; + let top_item = items_guard[0].clone(); + let bottom_item = items_guard[1].clone(); + drop(items_guard); + + let is_top_items_empty = top_item.is_empty(); let furnace_recipe = pumpkin_data::recipes::get_cooking_recipe_with_ingredient( - top_items.item, + top_item.item, $recipe_kind, ); - drop(top_items); let can_accept_output = self .can_accept_recipe_output(furnace_recipe, self.get_max_count_per_stack()) .await; - let bottom_items_is_empty = self.items[1].lock().await.is_empty(); + let bottom_items_is_empty = bottom_item.is_empty(); if self.is_burning() || !bottom_items_is_empty && !is_top_items_empty { if !self.is_burning() && can_accept_output { - let mut bottom_items = self.items[1].lock().await; - let base_fuel_ticks = - pumpkin_data::fuels::get_item_burn_ticks(bottom_items.item.id) + pumpkin_data::fuels::get_item_burn_ticks(bottom_item.item.id) .unwrap_or(0); let adjusted_fuel_ticks = if matches!( @@ -416,18 +410,18 @@ macro_rules! impl_block_entity_for_cooking { if self.is_burning() { is_dirty = true; - if !bottom_items.is_empty() { - bottom_items.decrement(1); + let mut items_guard = self.items.write().await; + if !items_guard[1].is_empty() { + items_guard[1].decrement(1); if let Some(remainder_id) = pumpkin_data::recipe_remainder::get_recipe_remainder_id( - bottom_items.item.id, + items_guard[1].item.id, ) - && bottom_items.is_empty() + && items_guard[1].is_empty() && let Some(remainder_item) = pumpkin_data::item::Item::from_id(remainder_id) { - drop(bottom_items); - self.set_stack(1, ItemStack::new(1, remainder_item)).await; + items_guard[1] = ItemStack::new(1, remainder_item); } } } @@ -540,14 +534,14 @@ macro_rules! impl_block_entity_for_cooking { let furnace = Self { position, dirty: AtomicBool::new(false), - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), cooking_total_time, cooking_time_spent, lit_total_time, lit_time_remaining, recipes_used: std::sync::Mutex::new(recipes_used_map), }; - furnace.read_data(nbt, &furnace.items); + furnace.read_data(nbt, &mut *furnace.items.blocking_write()); furnace } @@ -618,7 +612,10 @@ macro_rules! impl_block_entity_for_cooking { } } - pumpkin_world::inventory::sync_write_items_to_nbt(&self.items, &mut nbt); + pumpkin_world::inventory::sync_write_items_to_nbt( + &*self.items.blocking_read(), + &mut nbt, + ); Some(nbt) } diff --git a/crates/pumpkin/src/block/entities/hopper.rs b/crates/pumpkin/src/block/entities/hopper.rs index 89fb73f16..49d270bd2 100644 --- a/crates/pumpkin/src/block/entities/hopper.rs +++ b/crates/pumpkin/src/block/entities/hopper.rs @@ -10,20 +10,17 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::tag::NbtTag; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; -use pumpkin_world::inventory::{ - Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt, -}; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; use std::any::Any; use std::array::from_fn; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::Ordering; use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64}; -use tokio::sync::Mutex; pub struct HopperBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, pub facing: FacingHopper, pub cooldown_time: AtomicI32, @@ -62,14 +59,14 @@ impl BlockEntity for HopperBlockEntity { { let hopper = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), facing: FacingHopper::Down, cooldown_time: AtomicI32::from(nbt.get_int("TransferCooldown").unwrap_or(-1)), ticked_game_time: AtomicI64::new(0), }; - hopper.read_data(nbt, &hopper.items); + hopper.read_data(nbt, &mut *hopper.items.blocking_write()); hopper } @@ -120,7 +117,8 @@ impl BlockEntity for HopperBlockEntity { "TransferCooldown", NbtTag::Int(self.cooldown_time.load(Ordering::Relaxed)), ); - sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + sync_write_items_to_nbt(items.as_slice(), &mut nbt); Some(nbt) } @@ -137,7 +135,7 @@ impl HopperBlockEntity { pub fn new(position: BlockPos, facing: FacingHopper) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), facing, cooldown_time: AtomicI32::new(-1), @@ -162,8 +160,8 @@ impl HopperBlockEntity { } async fn inventory_full(&self) -> bool { - for i in &self.items { - let item = i.lock().await; + let items = self.items.read().await; + for item in items.iter() { if item.is_empty() || item.item_count != item.get_max_stack_size() { return false; } @@ -179,16 +177,16 @@ impl HopperBlockEntity { { // TODO check WorldlyContainer for i in 0..container.size() { - let bind = container.get_stack(i).await; - let mut item = bind.lock().await; + let mut item = container.get_stack(i).await; if !item.is_empty() && container.can_transfer_to(self, i, &item) { //TODO WorldlyContainer - let backup = item.clone(); + let _backup = item.clone(); let one_item = item.split(1); if Self::add_one_item(container.as_ref(), self, one_item).await { + container.set_stack(i, item).await; // If extracting from furnace output slot (index 2), drop XP as orbs - const FURNACE_OUTPUT_SLOT: usize = 2; - if i == FURNACE_OUTPUT_SLOT + let furnace_output_slot: usize = 2; + if i == furnace_output_slot && let Some(experience_container) = entity.clone().to_experience_container() { @@ -200,7 +198,6 @@ impl HopperBlockEntity { } return true; } - *item = backup; } } return false; @@ -242,8 +239,7 @@ impl HopperBlockEntity { // TODO check WorldlyContainer let mut is_full = true; for i in 0..container.size() { - let bind = container.get_stack(i).await; - let item = bind.lock().await; + let item = container.get_stack(i).await; if item.item_count < item.get_max_stack_size() { is_full = false; break; @@ -252,16 +248,14 @@ impl HopperBlockEntity { if is_full { return false; } - for i in &self.items { - let mut item = i.lock().await; + let items = self.items.read().await; + for item in items.iter() { if !item.is_empty() { - //TODO WorldlyContainer - let backup = item.clone(); - let one_item = item.split(1); + let mut item_clone = item.clone(); + let one_item = item_clone.split(1); if Self::add_one_item(self, container.as_ref(), one_item).await { return true; } - *item = backup; } } } @@ -272,14 +266,15 @@ impl HopperBlockEntity { let to_empty = to.is_empty().await; for j in 0..to.size() { if to.is_valid_slot_for(j, &item) { - let bind = to.get_stack(j).await; - let mut dst = bind.lock().await; + let mut dst = to.get_stack(j).await; if dst.is_empty() { - *dst = item.clone(); + dst = item.clone(); + to.set_stack(j, dst).await; success = true; } else if dst.item_count < dst.get_max_stack_size() && dst.item == item.item { // TODO check Components equal dst.item_count += 1; + to.set_stack(j, dst).await; success = true; } if success { @@ -310,30 +305,27 @@ impl HopperBlockEntity { impl Inventory for HopperBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -341,7 +333,12 @@ impl Inventory for HopperBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -349,7 +346,8 @@ impl Inventory for HopperBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -366,9 +364,8 @@ impl Inventory for HopperBlockEntity { impl Clearable for HopperBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/jukebox.rs b/crates/pumpkin/src/block/entities/jukebox.rs index 413f4c722..fbe76dbea 100644 --- a/crates/pumpkin/src/block/entities/jukebox.rs +++ b/crates/pumpkin/src/block/entities/jukebox.rs @@ -201,8 +201,8 @@ impl Inventory for JukeboxBlockEntity { Box::pin(async move { self.record_stack.lock().await.is_empty() }) } - fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.record_stack.clone() }) + fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { self.record_stack.lock().await.clone() }) } fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> { diff --git a/crates/pumpkin/src/block/entities/lectern.rs b/crates/pumpkin/src/block/entities/lectern.rs index f5b17a6ae..fd9f12a24 100644 --- a/crates/pumpkin/src/block/entities/lectern.rs +++ b/crates/pumpkin/src/block/entities/lectern.rs @@ -159,8 +159,8 @@ impl Inventory for LecternBlockEntity { Box::pin(async move { self.book.lock().await.is_empty() }) } - fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.book.clone() }) + fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { self.book.lock().await.clone() }) } fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> { diff --git a/crates/pumpkin/src/block/entities/shelf.rs b/crates/pumpkin/src/block/entities/shelf.rs index 6b6a63793..a2f5e5829 100644 --- a/crates/pumpkin/src/block/entities/shelf.rs +++ b/crates/pumpkin/src/block/entities/shelf.rs @@ -2,19 +2,16 @@ use crate::block::entities::BlockEntity; use pumpkin_data::item_stack::ItemStack; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; -use pumpkin_world::inventory::{ - Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt, -}; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; use std::any::Any; use std::array::from_fn; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use tokio::sync::Mutex; pub struct ShelfBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, } @@ -32,11 +29,11 @@ impl BlockEntity for ShelfBlockEntity { { let shelf = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), }; - shelf.read_data(nbt, &shelf.items); + shelf.read_data(nbt, &mut *shelf.items.blocking_write()); shelf } @@ -63,7 +60,8 @@ impl BlockEntity for ShelfBlockEntity { fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + sync_write_items_to_nbt(items.as_slice(), &mut nbt); Some(nbt) } @@ -73,14 +71,14 @@ impl BlockEntity for ShelfBlockEntity { } impl ShelfBlockEntity { - pub const INVENTORY_SIZE: usize = 9; + pub const INVENTORY_SIZE: usize = 3; pub const ID: &'static str = "minecraft:shelf"; #[must_use] pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), } } @@ -88,30 +86,27 @@ impl ShelfBlockEntity { impl Inventory for ShelfBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -119,7 +114,12 @@ impl Inventory for ShelfBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -127,7 +127,8 @@ impl Inventory for ShelfBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -144,9 +145,8 @@ impl Inventory for ShelfBlockEntity { impl Clearable for ShelfBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/shulker_box.rs b/crates/pumpkin/src/block/entities/shulker_box.rs index 8281be006..3e32c0239 100644 --- a/crates/pumpkin/src/block/entities/shulker_box.rs +++ b/crates/pumpkin/src/block/entities/shulker_box.rs @@ -2,35 +2,27 @@ use pumpkin_data::item_stack::ItemStack; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; -use pumpkin_util::random::xoroshiro128::Xoroshiro; -use pumpkin_util::random::{RandomImpl, get_seed}; use std::any::Any; +use std::future::Future; use std::pin::Pin; -use std::sync::atomic::Ordering; -use std::{ - array::from_fn, - sync::{Arc, atomic::AtomicBool}, -}; -use tokio::sync::Mutex; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::{array::from_fn, sync::Arc}; +use tokio::sync::RwLock; +use crate::block::entities::BlockEntity; use crate::block::viewer::{ ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture, }; use crate::world::World; -use pumpkin_world::inventory::InventoryFuture; -use pumpkin_world::inventory::{ - split_stack, sync_write_items_to_nbt, {Clearable, Inventory}, -}; - -use super::BlockEntity; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; pub struct ShulkerBoxBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, // Viewer - viewers: ViewerCountTracker, + pub viewers: ViewerCountTracker, } impl BlockEntity for ShulkerBoxBlockEntity { @@ -48,12 +40,12 @@ impl BlockEntity for ShulkerBoxBlockEntity { { let shulker_box = Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: ViewerCountTracker::new(), }; - shulker_box.read_data(nbt, &shulker_box.items); + shulker_box.read_data(nbt, &mut *shulker_box.items.blocking_write()); shulker_box } @@ -100,7 +92,8 @@ impl BlockEntity for ShulkerBoxBlockEntity { fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); - sync_write_items_to_nbt(&self.items, &mut nbt); + let items = futures::executor::block_on(self.items.read()); + sync_write_items_to_nbt(items.as_slice(), &mut nbt); Some(nbt) } @@ -116,7 +109,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity { position: &'a BlockPos, ) -> ViewerFuture<'a, ()> { Box::pin(async move { - Self::play_sound(world, position, Sound::BlockShulkerBoxOpen); + Self::play_sound(world, position, 1); // TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_OPEN, this.pos); }) } @@ -127,7 +120,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity { position: &'a BlockPos, ) -> ViewerFuture<'a, ()> { Box::pin(async move { - Self::play_sound(world, position, Sound::BlockShulkerBoxClose); + Self::play_sound(world, position, 0); // TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_CLOSE, this.pos); }) } @@ -150,57 +143,57 @@ impl ViewerCountListener for ShulkerBoxBlockEntity { impl ShulkerBoxBlockEntity { pub const INVENTORY_SIZE: usize = 27; pub const OPEN_ANIMATION_EVENT_TYPE: u8 = 1; - pub const ID: &'static str = "minecraft:shulker_box"; // TODO support multi IDs + pub const ID: &'static str = "minecraft:shulker_box"; #[must_use] pub fn new(position: BlockPos) -> Self { Self { position, - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: ViewerCountTracker::new(), } } - fn play_sound(world: &Arc, position: &BlockPos, sound: Sound) { - let mut rng = Xoroshiro::from_seed(get_seed()); + pub fn update_viewers(&self, world: &Arc) { + let viewer_count = self.viewers.current.load(Ordering::Relaxed); + Self::play_sound(world, &self.position, i32::from(viewer_count)); + } - world.play_sound_fine( - sound, - SoundCategory::Blocks, - &position.to_centered_f64(), - 0.5, - rng.next_f32() * 0.1 + 0.9, - ); + fn play_sound(world: &World, position: &BlockPos, viewer_count: i32) { + let sound = if viewer_count > 0 { + Sound::BlockShulkerBoxOpen + } else { + Sound::BlockShulkerBoxClose + }; + + world.play_sound(sound, SoundCategory::Blocks, &position.to_f64()); } } impl Inventory for ShulkerBoxBlockEntity { fn size(&self) -> usize { - self.items.len() + Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.items { - if !slot.lock().await.is_empty() { - return false; - } - } - - true + let items = self.items.read().await; + items.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.items[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let items = self.items.read().await; + items[slot].clone() + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); + let mut items = self.items.write().await; + let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) @@ -208,7 +201,12 @@ impl Inventory for ShulkerBoxBlockEntity { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let res = split_stack(&self.items, slot, amount).await; + let mut items = self.items.write().await; + let res = if !items[slot].is_empty() && amount > 0 { + items[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + }; self.mark_dirty(); res }) @@ -216,7 +214,8 @@ impl Inventory for ShulkerBoxBlockEntity { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.items[slot].lock().await = stack; + let mut items = self.items.write().await; + items[slot] = stack; self.mark_dirty(); }) } @@ -245,9 +244,8 @@ impl Inventory for ShulkerBoxBlockEntity { impl Clearable for ShulkerBoxBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { - for slot in &self.items { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut items = self.items.write().await; + items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } diff --git a/crates/pumpkin/src/block/entities/sign.rs b/crates/pumpkin/src/block/entities/sign.rs index 6a70bc80d..f4edbc026 100644 --- a/crates/pumpkin/src/block/entities/sign.rs +++ b/crates/pumpkin/src/block/entities/sign.rs @@ -167,18 +167,18 @@ impl From for NbtTag { } } -#[allow(clippy::fallible_impl_from)] impl From for Text { fn from(tag: NbtTag) -> Self { - let nbt = tag.extract_compound().unwrap(); + let Some(nbt) = tag.extract_compound() else { + return Self::default(); + }; let has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false); let color = nbt.get_string("color").unwrap_or("black"); - let messages: Vec> = nbt - .get_list("messages") - .unwrap() - .iter() - .filter_map(|tag| tag.extract_string().map(Box::from)) - .collect(); + let messages: Vec> = nbt.get_list("messages").map_or_else(Vec::new, |list| { + list.iter() + .filter_map(|tag| tag.extract_string().map(Box::from)) + .collect() + }); let get_message = |i: usize| -> Box { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) }; diff --git a/crates/pumpkin/src/block/entities/smoker.rs b/crates/pumpkin/src/block/entities/smoker.rs index 680c93cf4..bf5075314 100644 --- a/crates/pumpkin/src/block/entities/smoker.rs +++ b/crates/pumpkin/src/block/entities/smoker.rs @@ -12,7 +12,6 @@ use std::{ use pumpkin_data::recipes::CookingRecipeKind; use pumpkin_util::math::position::BlockPos; -use tokio::sync::Mutex; use crate::{ block::entities::furnace_like_block_entity::CookingBlockEntityBase, @@ -30,7 +29,7 @@ pub struct SmokerBlockEntity { pub lit_time_remaining: AtomicU16, pub lit_total_time: AtomicU16, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, /// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format) /// Maps result item ID -> craft count @@ -46,7 +45,7 @@ impl SmokerBlockEntity { Self { position, dirty: AtomicBool::new(false), - items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), cooking_total_time: AtomicU16::new(0), cooking_time_spent: AtomicU16::new(0), lit_total_time: AtomicU16::new(0), diff --git a/crates/pumpkin/src/block/entities/trapped_chest.rs b/crates/pumpkin/src/block/entities/trapped_chest.rs index fda52957c..6a8ff3b5d 100644 --- a/crates/pumpkin/src/block/entities/trapped_chest.rs +++ b/crates/pumpkin/src/block/entities/trapped_chest.rs @@ -4,8 +4,6 @@ use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::item_stack::ItemStack; use pumpkin_util::math::position::BlockPos; -use tokio::sync::Mutex; - use crate::{ block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods, impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest, @@ -13,7 +11,7 @@ use crate::{ pub struct TrappedChestBlockEntity { pub position: BlockPos, - pub items: [Arc>; Self::INVENTORY_SIZE], + pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, // Viewer diff --git a/crates/pumpkin/src/block/mod.rs b/crates/pumpkin/src/block/mod.rs index 0460bf1bc..673e0e87a 100644 --- a/crates/pumpkin/src/block/mod.rs +++ b/crates/pumpkin/src/block/mod.rs @@ -28,7 +28,6 @@ use pumpkin_protocol::java::server::play::SUseItemOn; use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::world::{BlockAccessor, BlockFlags}; -use tokio::sync::Mutex; pub trait BlockMetadata { fn ids() -> Box<[BlockId]>; @@ -247,7 +246,7 @@ pub struct UseWithItemArgs<'a> { pub position: &'a BlockPos, pub player: &'a Arc, pub hit: &'a BlockHitResult<'a>, - pub item_stack: &'a Arc>, + pub item_stack: &'a mut ItemStack, pub equipment_slot: &'a EquipmentSlot, } @@ -512,8 +511,7 @@ pub async fn calculate_comparator_output( let mut fill_sum = 0.0; let mut non_empty_count = 0; for i in 0..size { - let stack_mutex = inventory.get_stack(i).await; - let stack = stack_mutex.lock().await; + let stack = inventory.get_stack(i).await; if !stack.is_empty() { let max_stack = stack.get_max_stack_size() as f32; let count = stack.item_count as f32; diff --git a/crates/pumpkin/src/block/registry.rs b/crates/pumpkin/src/block/registry.rs index 6128dd472..6be37614c 100644 --- a/crates/pumpkin/src/block/registry.rs +++ b/crates/pumpkin/src/block/registry.rs @@ -157,7 +157,6 @@ use pumpkin_util::math::position::BlockPos; use pumpkin_world::world::{BlockAccessor, BlockFlags}; use rustc_hash::FxHashMap; use std::sync::Arc; -use tokio::sync::Mutex; use super::BlockIsReplacing; use super::blocks::plant::crop::gourds::attached_stem::AttachedStemBlock; @@ -804,7 +803,7 @@ impl BlockRegistry { player: &Arc, position: &BlockPos, hit: &BlockHitResult<'_>, - item_stack: &Arc>, + item_stack: &mut ItemStack, equipment_slot: &EquipmentSlot, server: &Server, world: &Arc, diff --git a/crates/pumpkin/src/command/commands/clear.rs b/crates/pumpkin/src/command/commands/clear.rs index 989fca186..b04e1072b 100644 --- a/crates/pumpkin/src/command/commands/clear.rs +++ b/crates/pumpkin/src/command/commands/clear.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use pumpkin_data::item_stack::ItemStack; use pumpkin_data::translation; use pumpkin_util::text::TextComponent; -use tokio::sync::Mutex; use crate::command::args::bounded_num::BoundedNumArgumentConsumer; use crate::command::args::players::PlayersArgumentConsumer; @@ -40,58 +39,37 @@ async fn clear_player(target: &Player, item: &ItemPredicate, max: i32) -> i32 { let mut max: i32 = max; let mut is_done: bool = false; - iter_test_and_clear( - &inventory.main_inventory, - &mut count, - &mut max, - item, - &mut is_done, - ) - .await; - - let entity_equipment_lock = inventory.entity_equipment.lock().await; - iter_test_and_clear( - entity_equipment_lock.equipment.values(), - &mut count, - &mut max, - item, - &mut is_done, - ) - .await; - drop(entity_equipment_lock); - - count -} - -async fn iter_test_and_clear<'i, I>( - iter: I, - count: &mut i32, - max: &mut i32, - item: &ItemPredicate, - is_done: &mut bool, -) where - I: IntoIterator>>, -{ - // Don't need to enter the loop if we are already done. - if !*is_done { - for slot in iter { - test_and_clear(count, max, item, slot, is_done).await; - if *is_done { + { + let mut main_inv = inventory.main_inventory.write().await; + for slot in main_inv.iter_mut() { + test_and_clear(&mut count, &mut max, item, slot, &mut is_done); + if is_done { break; } } } + + if !is_done { + let mut entity_equipment_lock = inventory.entity_equipment.lock().await; + for slot in entity_equipment_lock.equipment.values_mut() { + test_and_clear(&mut count, &mut max, item, slot, &mut is_done); + if is_done { + break; + } + } + } + + count } -async fn test_and_clear( +fn test_and_clear( count: &mut i32, max: &mut i32, item: &ItemPredicate, - slot: &Arc>, + slot_lock: &mut ItemStack, is_done: &mut bool, ) { - let mut slot_lock = slot.lock().await; - if item.test_item_stack(&slot_lock) { + if item.test_item_stack(slot_lock) { let item_count = slot_lock.item_count as i32; if *max == MAX_NO_CLEAR_BUT_SIMULATE { *count += item_count; diff --git a/crates/pumpkin/src/command/commands/enchant.rs b/crates/pumpkin/src/command/commands/enchant.rs index 4db8ba969..b19b4af70 100644 --- a/crates/pumpkin/src/command/commands/enchant.rs +++ b/crates/pumpkin/src/command/commands/enchant.rs @@ -135,8 +135,7 @@ async fn enchant_target( return Err(commands_enchant_failed()); }; - let lock = player.inventory.held_item(); - let mut item = lock.lock().await; + let mut item = player.inventory().held_item().await; if item.is_empty() { let msg = TextComponent::translate_cross( @@ -168,6 +167,7 @@ async fn enchant_target( } item.enchant(enchantment, level); + player.inventory().set_held_item(item).await; Ok(()) } diff --git a/crates/pumpkin/src/command/commands/item.rs b/crates/pumpkin/src/command/commands/item.rs index 6ac048866..23d2cadb5 100644 --- a/crates/pumpkin/src/command/commands/item.rs +++ b/crates/pumpkin/src/command/commands/item.rs @@ -239,8 +239,7 @@ impl CommandExecutor for EntityReplaceExecutor { .entity_equipment .lock() .await - .put(&eq, item_stack.clone()) - .await; + .put(&eq, item_stack.clone()); living.send_equipment_changes(&[(eq, item_stack.clone())]); modified_count += 1; } diff --git a/crates/pumpkin/src/command/commands/loot.rs b/crates/pumpkin/src/command/commands/loot.rs index bd4df29ff..5649cfcdf 100644 --- a/crates/pumpkin/src/command/commands/loot.rs +++ b/crates/pumpkin/src/command/commands/loot.rs @@ -58,8 +58,7 @@ async fn insert_into_inventory( if stack.is_empty() { break; } - let slot = inventory.get_stack(i).await; - let mut slot_stack = slot.lock().await; + let mut slot_stack = inventory.get_stack(i).await; if !slot_stack.is_empty() && slot_stack.get_item().id == stack.get_item().id { let max_stack_size = 64; let space = max_stack_size - slot_stack.item_count; @@ -67,6 +66,7 @@ async fn insert_into_inventory( let to_add = stack.item_count.min(space); slot_stack.item_count += to_add; stack.item_count -= to_add; + inventory.set_stack(i, slot_stack).await; } } } @@ -75,10 +75,9 @@ async fn insert_into_inventory( if stack.is_empty() { break; } - let slot = inventory.get_stack(i).await; - let mut slot_stack = slot.lock().await; + let slot_stack = inventory.get_stack(i).await; if slot_stack.is_empty() { - *slot_stack = stack.clone(); + inventory.set_stack(i, stack.clone()).await; stack.item_count = 0; break; } diff --git a/crates/pumpkin/src/entity/ai/goal/beg.rs b/crates/pumpkin/src/entity/ai/goal/beg.rs index 016b07c4a..29d6960d8 100644 --- a/crates/pumpkin/src/entity/ai/goal/beg.rs +++ b/crates/pumpkin/src/entity/ai/goal/beg.rs @@ -32,15 +32,12 @@ impl BegGoal { } async fn is_player_holding_attractive(&self, player: &Player) -> bool { - let main_hand = player.inventory.held_item(); - let main_stack = main_hand.lock().await; + let main_stack = player.inventory().held_item().await; if main_stack.item_count > 0 && self.is_attractive(main_stack.item) { return true; } - drop(main_stack); - let off_hand = player.inventory.off_hand_item().await; - let off_stack = off_hand.lock().await; + let off_stack = player.inventory().off_hand_item().await; off_stack.item_count > 0 && self.is_attractive(off_stack.item) } diff --git a/crates/pumpkin/src/entity/ai/goal/tempt.rs b/crates/pumpkin/src/entity/ai/goal/tempt.rs index a83eafd78..a733306d4 100644 --- a/crates/pumpkin/src/entity/ai/goal/tempt.rs +++ b/crates/pumpkin/src/entity/ai/goal/tempt.rs @@ -33,12 +33,12 @@ impl TemptGoal { } async fn is_holding_tempt_item(&self, player: &Player) -> bool { - let main = player.inventory.held_item(); - if self.is_tempt_item(&*main.lock().await) { + let main = player.inventory().held_item().await; + if self.is_tempt_item(&main) { return true; } - let off = player.inventory.off_hand_item().await; - self.is_tempt_item(&*off.lock().await) + let off = player.inventory().off_hand_item().await; + self.is_tempt_item(&off) } async fn find_tempting_player(&self, mob: &dyn Mob) -> Option> { diff --git a/crates/pumpkin/src/entity/combat.rs b/crates/pumpkin/src/entity/combat.rs index c4c9a8865..9e64777f8 100644 --- a/crates/pumpkin/src/entity/combat.rs +++ b/crates/pumpkin/src/entity/combat.rs @@ -30,20 +30,14 @@ impl AttackType { let sprinting = entity.is_sprinting(); let on_ground = entity.on_ground.load(Ordering::Relaxed); let fall_distance = player.living_entity.fall_distance.load(); - let held_item = player.inventory().held_item(); - let is_mace = { - let stack = held_item.lock().await; - stack.item.id == pumpkin_data::item::Item::MACE.id - }; + let held_item = player.inventory().held_item().await; + let is_mace = held_item.item.id == pumpkin_data::item::Item::MACE.id; if is_mace && !on_ground && fall_distance > 1.5 { return Self::MaceSmash; } - let sword = { - let stack = held_item.lock().await; - stack.is_sword() - }; + let sword = held_item.is_sword(); let is_strong = attack_cooldown_progress > 0.9; if sprinting && is_strong { diff --git a/crates/pumpkin/src/entity/living.rs b/crates/pumpkin/src/entity/living.rs index 7feefa1ed..e6ef218f9 100644 --- a/crates/pumpkin/src/entity/living.rs +++ b/crates/pumpkin/src/entity/living.rs @@ -12,7 +12,6 @@ use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_util::GameMode; use pumpkin_util::Hand; use pumpkin_util::math::position::BlockPos; -use std::mem; use std::sync::Arc; use std::sync::atomic::Ordering; use std::sync::atomic::{ @@ -1353,14 +1352,13 @@ impl LivingEntity { .downcast_ref::() { let hand_stack = player - .inventory + .inventory() .get_stack_in_hand(pumpkin_util::Hand::Right) .await; - let stack_guard = hand_stack.lock().await; - looting_level = stack_guard + looting_level = hand_stack .get_enchantment_level(&Enchantment::LOOTING) .max(0) as u32; - (stack_guard.item_count > 0).then(|| stack_guard.clone()) + (!hand_stack.is_empty()).then(|| hand_stack.clone()) } else { looting_level = 0; None @@ -1441,12 +1439,13 @@ impl LivingEntity { if rand::random::() >= chance { continue; } - let mut item = { - let q = self.entity_equipment.lock().await; - let item_arc = q.get(slot); - let mut item_lock = item_arc.lock().await; - mem::replace(&mut *item_lock, ItemStack::EMPTY.clone()) - }; + let mut item = self + .entity_equipment + .lock() + .await + .equipment + .remove(slot) + .unwrap_or_else(|| ItemStack::EMPTY.clone()); if item.is_empty() { continue; } @@ -1547,11 +1546,8 @@ impl LivingEntity { killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::TwoBirdsOneArrow).await; } - let held_item = killer_player.inventory().held_item(); - let is_crossbow = { - let lock = held_item.lock().await; - lock.item.registry_key == "crossbow" - }; + let held_item = killer_player.inventory().held_item().await; + let is_crossbow = held_item.item.registry_key == "crossbow"; if is_crossbow { killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::Arbalistic).await; } @@ -1715,12 +1711,30 @@ impl LivingEntity { /// Tries to use a totem of undying from the entity's hands. If successful, applies the totem effects and returns true. async fn try_use_death_protector(&self, caller: &dyn EntityBase) -> bool { for hand in Hand::all() { - let stack = self.get_stack_in_hand(caller, hand).await; - let mut stack = stack.lock().await; + let mut stack = self.get_stack_in_hand(caller, hand).await; // Clear the stack and use the totem of undying if stack.get_data_component::().is_some() { stack.clear(); + let slot = match hand { + Hand::Right => EquipmentSlot::MAIN_HAND, + Hand::Left => EquipmentSlot::OFF_HAND, + }; + if let Some(player) = caller.get_player() { + player + .inventory() + .entity_equipment + .lock() + .await + .equipment + .insert(slot, stack); + } else { + self.entity_equipment + .lock() + .await + .equipment + .insert(slot, stack); + } self.set_health(1.0); self.entity .world @@ -1774,55 +1788,47 @@ impl LivingEntity { // TODO: Falling anvil/stalactite should only damage the helmet slot. // TODO: Implement DAMAGE_RESISTANT component checks (e.g. netherite vs fire). - let armor_slots: Vec<(usize, Arc>, EquipmentSlot)> = { + let armor_slots: Vec<(usize, ItemStack, EquipmentSlot)> = { let equipment_lock = self.entity_equipment.lock().await; self.equipment_slots .iter() .filter(|(_, slot)| slot.is_armor_slot()) - .map(|(index, slot)| (*index, equipment_lock.get(slot), slot.clone())) + .filter_map(|(index, slot)| { + equipment_lock + .equipment + .get(slot) + .cloned() + .map(|stack| (*index, stack, slot.clone())) + }) .collect() }; - for (slot_index, equipment, slot) in armor_slots { - let (slot_result, updated_stack_opt) = { - let mut stack = equipment.lock().await; - if stack.is_empty() { - (pumpkin_data::item_stack::DamageResult::Untouched, None) - } else { - // Items without `EquippableImpl` component take damage freely. - // Items with `damage_on_hurt: false` (e.g. elytra) are exempt from armor hit durability. - // PERF: Component lookup runs O(1) per armor slot (max 4 per hit). Caching - // at the item type level could optimize, but belongs in a broader caching pass. - let takes_damage = stack - .get_data_component::() - .is_none_or(|equippable| equippable.damage_on_hurt); + for (slot_index, mut stack, slot) in armor_slots { + if stack.is_empty() { + continue; + } - if takes_damage { - // Base armor durability damage. - let result = stack.damage_item(armor_damage); - let changed = result != pumpkin_data::item_stack::DamageResult::Untouched; - (result, changed.then_some(stack.clone())) - } else { - // Equippable items can opt out of on-hurt durability loss (e.g. elytra). - (pumpkin_data::item_stack::DamageResult::Untouched, None) + let takes_damage = stack + .get_data_component::() + .is_none_or(|equippable| equippable.damage_on_hurt); + + if takes_damage { + let slot_result = stack.damage_item(armor_damage); + if slot_result != pumpkin_data::item_stack::DamageResult::Untouched { + if slot_result == pumpkin_data::item_stack::DamageResult::Broken { + let world = self.entity.world.load(); + world + .send_entity_status(&self.entity, super::equipment_break_status(&slot)); + } + equipment_updates.push((slot.clone(), stack.clone())); + if let Some(player) = caller.get_player() { + player + .enqueue_slot_set_packet(&CSetPlayerInventory::new( + (slot_index as i32).into(), + &ItemStackSerializer::from(stack), + )) + .await; } - } - }; - - if let Some(updated_stack) = updated_stack_opt { - // Broadcast break status before clearing the slot. - if slot_result == pumpkin_data::item_stack::DamageResult::Broken { - let world = self.entity.world.load(); - world.send_entity_status(&self.entity, super::equipment_break_status(&slot)); - } - equipment_updates.push((slot.clone(), updated_stack.clone())); - if let Some(player) = caller.get_player() { - player - .enqueue_slot_set_packet(&CSetPlayerInventory::new( - (slot_index as i32).into(), - &ItemStackSerializer::from(updated_stack), - )) - .await; } } } @@ -1832,34 +1838,40 @@ impl LivingEntity { } } - pub async fn held_item(&self, caller: &dyn EntityBase) -> Arc> { + pub async fn held_item(&self, caller: &dyn EntityBase) -> ItemStack { if let Some(player) = caller.get_player() { - return player.inventory.held_item(); + return player.inventory.held_item().await; } - self.entity_equipment - .lock() - .await + let equipment = self.entity_equipment.lock().await; + equipment + .equipment .get(&EquipmentSlot::MAIN_HAND) + .cloned() + .unwrap_or_else(|| ItemStack::EMPTY.clone()) } - pub async fn get_stack_in_hand( - &self, - caller: &dyn EntityBase, - hand: Hand, - ) -> Arc> { + pub async fn get_stack_in_hand(&self, caller: &dyn EntityBase, hand: Hand) -> ItemStack { match hand { - Hand::Left => self.off_hand_item().await, + Hand::Left => self.off_hand_item(caller).await, Hand::Right => self.held_item(caller).await, } } /// getOffHandStack in source - pub async fn off_hand_item(&self) -> Arc> { + pub async fn off_hand_item(&self, caller: &dyn EntityBase) -> ItemStack { + if let Some(player) = caller.get_player() { + return player.inventory.off_hand_item().await; + } let slot = self .equipment_slots .get(&PlayerInventory::OFF_HAND_SLOT) .unwrap(); - self.entity_equipment.lock().await.get(slot) + let equipment = self.entity_equipment.lock().await; + equipment + .equipment + .get(slot) + .cloned() + .unwrap_or_else(|| ItemStack::EMPTY.clone()) } pub fn can_take_damage(&self) -> bool { @@ -2141,9 +2153,8 @@ impl EntityBase for LivingEntity { EquipmentSlot::LEGS, EquipmentSlot::FEET, ] { - let stack_arc = equipment_lock.get(&slot); - let stack = stack_arc.lock().await; - if !stack.is_empty() + if let Some(stack) = equipment_lock.equipment.get(&slot) + && !stack.is_empty() && let Some(modifiers) = stack.get_data_component::() { @@ -2175,9 +2186,8 @@ impl EntityBase for LivingEntity { EquipmentSlot::LEGS, EquipmentSlot::FEET, ] { - let stack_arc = equipment_lock.get(&slot); - let stack = stack_arc.lock().await; - if !stack.is_empty() + if let Some(stack) = equipment_lock.equipment.get(&slot) + && !stack.is_empty() && let Some(enchantments) = stack.get_data_component::() { @@ -2285,8 +2295,8 @@ impl EntityBase for LivingEntity { } if let Some(attacker_player) = cause.and_then(|c| c.get_player()) { - let held_item = attacker_player.inventory().held_item(); - let is_axe = held_item.lock().await.is_axe(); + let held_item = attacker_player.inventory().held_item().await; + let is_axe = held_item.is_axe(); if is_axe { let mut disable_chance = 0.25; let is_sprinting = attacker_player @@ -2322,33 +2332,30 @@ impl EntityBase for LivingEntity { EquipmentSlot::OFF_HAND }; - let equipment_lock = self.entity_equipment.lock().await; - let stack_arc = equipment_lock.get(&slot); - let mut stack = stack_arc.lock().await; + let mut equipment_guard = self.entity_equipment.lock().await; + if let Some(stack) = equipment_guard.equipment.get_mut(&slot) { + let durability_damage = (amount / 1.0).floor().max(1.0) as i32; + if stack.damage_item(durability_damage) == DamageResult::Broken { + if let Some(player) = caller.get_player() { + player + .increment_stat( + StatisticCategory::Broken, + stack.item.id as i32, + 1, + ) + .await; + } + world.send_entity_status( + &self.entity, + crate::entity::equipment_break_status(&slot), + ); + *stack = ItemStack::EMPTY.clone(); + let broken_stack = stack.clone(); + drop(equipment_guard); - let durability_damage = (amount / 1.0).floor().max(1.0) as i32; - if stack.damage_item(durability_damage) == DamageResult::Broken { - if let Some(player) = caller.get_player() { - player - .increment_stat( - StatisticCategory::Broken, - stack.item.id as i32, - 1, - ) - .await; + self.send_equipment_changes(&[(slot, broken_stack)]); + self.clear_active_hand().await; } - world.send_entity_status( - &self.entity, - crate::entity::equipment_break_status(&slot), - ); - *stack = ItemStack::EMPTY.clone(); - let broken_stack = stack.clone(); - drop(stack); - drop(stack_arc); - drop(equipment_lock); - - self.send_equipment_changes(&[(slot, broken_stack)]); - self.clear_active_hand().await; } } @@ -2654,40 +2661,40 @@ impl EntityBase for LivingEntity { let mut handled = false; // Check main hand (hotbar selected) - let held_arc = player.inventory.held_item(); - { - let mut held_lock = held_arc.lock().await; - if held_lock.are_items_and_components_equal(item) { - if is_potion { - if player.gamemode.load() != GameMode::Creative { - held_lock.decrement(1); - if held_lock.is_empty() { - *held_lock = ItemStack::new(1, &Item::GLASS_BOTTLE); - } + let mut held = player.inventory.held_item().await; + if held.are_items_and_components_equal(item) { + if is_potion { + if player.gamemode.load() != GameMode::Creative { + held.decrement(1); + if held.is_empty() { + held = ItemStack::new(1, &Item::GLASS_BOTTLE); } - } else { - held_lock.decrement_unless_creative(player.gamemode.load(), 1); } - handled = true; + } else { + held.decrement_unless_creative(player.gamemode.load(), 1); } + player.inventory.set_held_item(held).await; + handled = true; } if !handled { // Check off-hand - let off_arc = player.inventory.off_hand_item().await; - let mut off_lock = off_arc.lock().await; - if off_lock.are_items_and_components_equal(item) { + let mut off_hand = player.inventory.off_hand_item().await; + if off_hand.are_items_and_components_equal(item) { if is_potion { if player.gamemode.load() != GameMode::Creative { - off_lock.decrement(1); - if off_lock.is_empty() { - *off_lock = ItemStack::new(1, &Item::GLASS_BOTTLE); + off_hand.decrement(1); + if off_hand.is_empty() { + off_hand = ItemStack::new(1, &Item::GLASS_BOTTLE); } } } else { - off_lock.decrement_unless_creative(player.gamemode.load(), 1); + off_hand.decrement_unless_creative(player.gamemode.load(), 1); } - + player + .inventory + .set_stack_in_hand(Hand::Left, off_hand) + .await; handled = true; } } @@ -2696,21 +2703,24 @@ impl EntityBase for LivingEntity { // Use stored active_hand (as a fallback) let active_hand = *self.active_hand.lock().await; let hand_to_modify = active_hand.unwrap_or(Hand::Right); - let item_stack = self + let mut item_stack = self .get_stack_in_hand(caller.as_ref(), hand_to_modify) .await; - let mut item_lock = item_stack.lock().await; if is_potion { if player.gamemode.load() != GameMode::Creative { - item_lock.decrement(1); - if item_lock.is_empty() { - *item_lock = ItemStack::new(1, &Item::GLASS_BOTTLE); + item_stack.decrement(1); + if item_stack.is_empty() { + item_stack = ItemStack::new(1, &Item::GLASS_BOTTLE); } } } else { - item_lock.decrement_unless_creative(player.gamemode.load(), 1); + item_stack.decrement_unless_creative(player.gamemode.load(), 1); } + player + .inventory + .set_stack_in_hand(hand_to_modify, item_stack) + .await; } if let Some(cooldown) = item.get_use_cooldown() { diff --git a/crates/pumpkin/src/entity/mob/enderman.rs b/crates/pumpkin/src/entity/mob/enderman.rs index 3ff3f8717..886108e18 100644 --- a/crates/pumpkin/src/entity/mob/enderman.rs +++ b/crates/pumpkin/src/entity/mob/enderman.rs @@ -343,15 +343,12 @@ impl EndermanEntity { pub async fn is_player_staring(&self, player: &Player) -> bool { let equipment = player.living_entity.entity_equipment.try_lock(); - if let Ok(equipment) = equipment { - let head_item = equipment.get(&EquipmentSlot::HEAD); - let head_stack = head_item.try_lock(); - if let Ok(head_stack) = head_stack - && !head_stack.is_empty() - && head_stack.item == &Item::CARVED_PUMPKIN - { - return false; - } + if let Ok(equipment) = equipment + && let Some(head_stack) = equipment.equipment.get(&EquipmentSlot::HEAD) + && !head_stack.is_empty() + && head_stack.item == &Item::CARVED_PUMPKIN + { + return false; } let entity = &self.mob_entity.living_entity.entity; diff --git a/crates/pumpkin/src/entity/mob/equipment.rs b/crates/pumpkin/src/entity/mob/equipment.rs index 0a7b5d531..20d702f1a 100644 --- a/crates/pumpkin/src/entity/mob/equipment.rs +++ b/crates/pumpkin/src/entity/mob/equipment.rs @@ -1027,7 +1027,7 @@ pub async fn equip_mob_on_spawn(mob: &dyn EntityBase, world: &Arc = Vec::new(); for (slot, stack, drop_chance) in changes_with_drops { - equipment.put(&slot, stack.clone()).await; + equipment.put(&slot, stack.clone()); drop_chances.insert(slot.clone(), drop_chance); equipment_changes.push((slot, stack)); } diff --git a/crates/pumpkin/src/entity/mod.rs b/crates/pumpkin/src/entity/mod.rs index c872e9f3d..2e35375c6 100644 --- a/crates/pumpkin/src/entity/mod.rs +++ b/crates/pumpkin/src/entity/mod.rs @@ -2455,21 +2455,15 @@ impl Entity { return true; }; - let armor = { - let equipment = living.entity_equipment.lock().await; - [ - equipment.get(&EquipmentSlot::HEAD), - equipment.get(&EquipmentSlot::CHEST), - equipment.get(&EquipmentSlot::LEGS), - equipment.get(&EquipmentSlot::FEET), - ] - }; - - for stack in armor { - let stack = stack.lock().await; - if stack - .get_item() - .has_tag(&tag::Item::MINECRAFT_FREEZE_IMMUNE_WEARABLES) + let equipment = living.entity_equipment.lock().await; + for (slot, stack) in &equipment.equipment { + if (*slot == EquipmentSlot::HEAD + || *slot == EquipmentSlot::CHEST + || *slot == EquipmentSlot::LEGS + || *slot == EquipmentSlot::FEET) + && stack + .get_item() + .has_tag(&tag::Item::MINECRAFT_FREEZE_IMMUNE_WEARABLES) { return false; } diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs index 822a2023d..1b3610385 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -4,7 +4,6 @@ pub mod statistics; use core::f32; use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; use std::f64::consts::TAU; -use std::mem; use std::num::NonZeroU8; use std::str::FromStr; use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering}; @@ -973,7 +972,7 @@ impl Player { let config = &server.advanced_config.pvp; let inventory = self.inventory(); - let item_stack = inventory.held_item(); + let item_stack = inventory.held_item().await; let base_damage = self .living_entity @@ -987,7 +986,7 @@ impl Player { let mut knockback_level = 0u32; { - let stack = item_stack.lock().await; + let stack = &item_stack; if stack.is_empty() { // Vanilla fist: base_attack_damage = -1.0, base_attack_speed = -2.4 add_damage = -1.0; @@ -1118,11 +1117,7 @@ impl Player { self.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::DealtOverkillDamage).await; } - if let Some(enchantments) = item_stack - .lock() - .await - .get_data_component::() - { + if let Some(enchantments) = item_stack.get_data_component::() { for (enchantment, level) in enchantments.enchantment.iter() { if **enchantment == Enchantment::FIRE_ASPECT { victim_entity.set_on_fire_for_ticks(*level as u32 * 80); @@ -1166,10 +1161,7 @@ impl Player { combat::spawn_sweep_particle(attacker_entity, &world, &pos); let mut sweep_damage = 1.0; - if let Some(enchantments) = item_stack - .lock() - .await - .get_data_component::() + if let Some(enchantments) = item_stack.get_data_component::() { for (enchantment, level) in enchantments.enchantment.iter() { if **enchantment == Enchantment::SWEEPING_EDGE { @@ -1218,11 +1210,8 @@ impl Player { // 2. Refactor compute cost as a closure: damage_held_item(self, |stack| -> i32 { ... }) // 3. In practice, single-player scenarios are safe (this is not multiplayer). Document // as a known limitation if refactoring is deemed too invasive. - self.damage_held_item({ - let stack = item_stack.lock().await; - Self::combat_weapon_durability_cost(&stack) - }) - .await; + self.damage_held_item(Self::combat_weapon_durability_cost(&item_stack)) + .await; // Vanilla `Player#attack` ends the successful-hit branch with // `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage @@ -1282,14 +1271,10 @@ impl Player { EquipmentSlot::Body(_) | EquipmentSlot::Saddle(_) => return false, }; - let stack_arc = self.inventory.get_stack(slot_index).await; - - let updated = { - let mut stack = stack_arc.lock().await; - let result = stack.damage_item(amount); - (result != pumpkin_data::item_stack::DamageResult::Untouched) - .then_some((result, stack.clone())) - }; + let mut stack = self.inventory().get_stack(slot_index).await; + let result = stack.damage_item(amount); + let updated = (result != pumpkin_data::item_stack::DamageResult::Untouched) + .then_some((result, stack.clone())); if let Some((result, updated_stack)) = updated { if let Some(server) = self.world().server.upgrade() @@ -1359,13 +1344,12 @@ impl Player { return; } - let damage = { - let stack = self.inventory.held_item(); - let stack = stack.lock().await; - stack - .get_data_component::() - .map_or(0, |tool| tool.damage_per_block as i32) - }; + let damage = self + .inventory() + .held_item() + .await + .get_data_component::() + .map_or(0, |tool| tool.damage_per_block as i32); if damage > 0 { self.damage_held_item(damage).await; @@ -3153,14 +3137,12 @@ impl Player { let keep_inventory = { self.world().level_info.load().game_rules.keep_inventory }; if !keep_inventory { - for item in &self.inventory().main_inventory { - let mut lock = item.lock().await; - self.world() - .drop_stack( - &block_pos, - mem::replace(&mut *lock, ItemStack::EMPTY.clone()), - ) - .await; + let mut main_inv = self.inventory().main_inventory.write().await; + for item in main_inv.iter_mut() { + if !item.is_empty() { + let stack = std::mem::replace(item, ItemStack::EMPTY.clone()); + self.world().drop_stack(&block_pos, stack).await; + } } } @@ -3309,15 +3291,14 @@ impl Player { pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool { !state.tool_required() || self - .inventory + .inventory() .held_item() - .lock() .await .is_correct_for_drops(block) } pub async fn get_mining_speed(&self, block: &'static Block) -> f32 { - let mut speed = self.inventory.held_item().lock().await.get_speed(block); + let mut speed = self.inventory().held_item().await.get_speed(block); // Haste if self.living_entity.has_effect(&StatusEffect::HASTE).await || self @@ -3430,45 +3411,40 @@ impl Player { } pub async fn drop_held_item(&self, drop_stack: bool) { - // Do not hold both item stack and screen handler locks at the same time. - let (dropped_stack, updated_stack, selected_slot) = { - let binding = self.inventory.held_item(); - let mut item_stack = binding.lock().await; + let mut item_stack = self.inventory().held_item().await; - if item_stack.is_empty() { + if item_stack.is_empty() { + return; + } + + let drop_amount = if drop_stack { item_stack.item_count } else { 1 }; + let dropped_stack = item_stack.copy_with_count(drop_amount); + + if let Some(server) = self.world().server.upgrade() + && let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id) + { + let mut event = + crate::plugin::api::events::player::player_drop_item::PlayerDropItemEvent::new( + player_arc, + dropped_stack.item.registry_key.to_string(), + dropped_stack.item_count as u8, + ); + server.plugin_manager.fire(&server, &mut event).await; + if event.cancelled { return; } + } - let drop_amount = if drop_stack { item_stack.item_count } else { 1 }; - let dropped_stack = item_stack.copy_with_count(drop_amount); - - if let Some(server) = self.world().server.upgrade() - && let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id) - { - let mut event = - crate::plugin::api::events::player::player_drop_item::PlayerDropItemEvent::new( - player_arc, - dropped_stack.item.registry_key.to_string(), - dropped_stack.item_count as u8, - ); - server.plugin_manager.fire(&server, &mut event).await; - if event.cancelled { - return; - } - } - - item_stack.decrement(drop_amount); - let updated_stack = item_stack.clone(); - let selected_slot = self.inventory.get_selected_slot(); - - (dropped_stack, updated_stack, selected_slot) - }; + item_stack.decrement(drop_amount); + let updated_stack = item_stack.clone(); + self.inventory().set_held_item(updated_stack.clone()).await; self.drop_item(dropped_stack).await; let inv: Arc = self.inventory.clone(); let screen_binding = self.current_screen_handler.lock().await; let mut screen_handler = screen_binding.lock().await; + let selected_slot = self.inventory.get_selected_slot(); if let Some(slot_index) = screen_handler .get_slot_index(&inv, selected_slot as usize) .await @@ -3557,9 +3533,8 @@ impl Player { use pumpkin_data::item::Item; for hand in Hand::all() { - let item_in_hand = self.inventory.get_stack_in_hand(hand).await; + let stack = self.inventory().get_stack_in_hand(hand).await; - let stack = item_in_hand.lock().await; if stack.item.id == Item::FILLED_MAP.id && let Some(map_id_comp) = stack.get_data_component::() { @@ -3818,7 +3793,7 @@ impl Player { return xp; } - let mut candidates: Vec<(usize, EquipmentSlot, Arc>)> = Vec::new(); + let mut candidates: Vec<(usize, EquipmentSlot, ItemStack)> = Vec::new(); let selected_slot = self.inventory.get_selected_slot() as usize; let mut slot_pairs: Vec<(usize, EquipmentSlot)> = vec![ @@ -3832,12 +3807,8 @@ impl Player { } for (slot_index, equipment_slot) in slot_pairs { - let stack = self.inventory.get_stack(slot_index).await; - let eligible = { - let s = stack.lock().await; - s.get_enchantment_level(&Enchantment::MENDING) > 0 && s.get_damage() > 0 - }; - if eligible { + let stack = self.inventory().get_stack(slot_index).await; + if stack.get_enchantment_level(&Enchantment::MENDING) > 0 && stack.get_damage() > 0 { candidates.push((slot_index, equipment_slot, stack)); } } @@ -3847,18 +3818,18 @@ impl Player { } let idx = rand::random::() as usize % candidates.len(); - let (slot_index, equipment_slot, stack) = candidates.swap_remove(idx); - - let (updated_stack, repaired) = { - let mut stack = stack.lock().await; - let repaired = stack.repair_item(xp.saturating_mul(2)); - (stack.clone(), repaired) - }; + let (slot_index, equipment_slot, mut stack) = candidates.swap_remove(idx); + let repaired = stack.repair_item(xp.saturating_mul(2)); if repaired <= 0 { return xp; } + let updated_stack = stack.clone(); + self.inventory() + .set_stack(slot_index, updated_stack.clone()) + .await; + let xp_used = (repaired + 1) / 2; xp = xp.saturating_sub(xp_used); @@ -4436,28 +4407,25 @@ impl Player { // Check offhand first let stack = inventory.get_stack(PlayerInventory::OFF_HAND_SLOT).await; - let item = stack.lock().await; if matches!( - item.item.id, + stack.item.id, id if id == Item::ARROW.id || id == Item::TIPPED_ARROW.id || id == Item::SPECTRAL_ARROW.id - ) && item.item_count > 0 + ) && stack.item_count > 0 { return Some(PlayerInventory::OFF_HAND_SLOT); } - drop(item); // Check hotbar and main inventory for slot in 0..PlayerInventory::MAIN_SIZE { let stack = inventory.get_stack(slot).await; - let item = stack.lock().await; if matches!( - item.item.id, + stack.item.id, id if id == Item::ARROW.id || id == Item::TIPPED_ARROW.id || id == Item::SPECTRAL_ARROW.id - ) && item.item_count > 0 + ) && stack.item_count > 0 { return Some(slot); } @@ -4474,15 +4442,15 @@ impl Player { } let inventory = self.inventory(); - let stack_arc = inventory.get_stack(slot).await; - let mut stack = stack_arc.lock().await; + let mut stack = inventory.get_stack(slot).await; match stack.item_count { 2.. => { stack.item_count -= 1; + inventory.set_stack(slot, stack).await; true } 1 => { - *stack = ItemStack::EMPTY.clone(); + inventory.set_stack(slot, ItemStack::EMPTY.clone()).await; true } _ => false, @@ -4582,15 +4550,14 @@ impl Player { } pub async fn has_item_in_inventory(&self, item: &pumpkin_data::item::Item) -> bool { - for slot in &self.inventory.main_inventory { - let stack = slot.lock().await; + let main_inv = self.inventory.main_inventory.read().await; + for stack in main_inv.iter() { if !stack.is_empty() && stack.item.id == item.id { return true; } } let equipment = self.inventory.entity_equipment.lock().await; - for slot_stack in equipment.equipment.values() { - let stack = slot_stack.lock().await; + for stack in equipment.equipment.values() { if !stack.is_empty() && stack.item.id == item.id { return true; } @@ -4764,25 +4731,24 @@ impl NBTStorage for PlayerInventory { // Create inventory list with the correct capacity (inventory size) let mut items: Vec = Vec::with_capacity(41); - for (i, item) in self.main_inventory.iter().enumerate() { - let stack = item.lock().await; + let main_inv = self.main_inventory.read().await; + for (i, stack) in main_inv.iter().enumerate() { if !stack.is_empty() { let mut item_compound = NbtCompound::new(); item_compound.put_byte("Slot", i as i8); stack.write_item_stack(&mut item_compound); - drop(stack); items.push(NbtTag::Compound(item_compound)); } } let mut equipment_compound = NbtCompound::new(); + let equipment_guard = self.entity_equipment.lock().await; for slot in self.equipment_slots.values() { - let stack_binding = self.entity_equipment.lock().await.get(slot); - let stack = stack_binding.lock().await; - if !stack.is_empty() { + if let Some(stack) = equipment_guard.equipment.get(slot) + && !stack.is_empty() + { let mut item_compound = NbtCompound::new(); stack.write_item_stack(&mut item_compound); - drop(stack); match slot { EquipmentSlot::OffHand(_) => { equipment_compound.put_compound("offhand", item_compound); @@ -4870,13 +4836,12 @@ impl NBTStorage for EnderChestInventory { Box::pin(async { // Create item list with the correct capacity (inventory size) let mut items: Vec = Vec::with_capacity(Self::INVENTORY_SIZE); - for (i, item) in self.items.iter().enumerate() { - let stack = item.lock().await; + let ec_items = self.items.read().await; + for (i, stack) in ec_items.iter().enumerate() { if !stack.is_empty() { let mut item_compound = NbtCompound::new(); item_compound.put_byte("Slot", i as i8); stack.write_item_stack(&mut item_compound); - drop(stack); items.push(NbtTag::Compound(item_compound)); } } diff --git a/crates/pumpkin/src/entity/player/advancement/trigger.rs b/crates/pumpkin/src/entity/player/advancement/trigger.rs index 61fc6f458..a5633be02 100644 --- a/crates/pumpkin/src/entity/player/advancement/trigger.rs +++ b/crates/pumpkin/src/entity/player/advancement/trigger.rs @@ -313,9 +313,9 @@ impl Player { if !self.has_advancement(Advancement::STORY_ENCHANT_ITEM).await { let mut has_enchanted = false; - for item in &self.inventory().main_inventory { - let lock = item.lock().await; - if !lock.is_empty() && lock.has_enchantments() { + let main_inv = self.inventory().main_inventory.read().await; + for stack in main_inv.iter() { + if !stack.is_empty() && stack.has_enchantments() { has_enchanted = true; break; } diff --git a/crates/pumpkin/src/item/items/boat.rs b/crates/pumpkin/src/item/items/boat.rs index 5f608f627..fab84c201 100644 --- a/crates/pumpkin/src/item/items/boat.rs +++ b/crates/pumpkin/src/item/items/boat.rs @@ -182,9 +182,9 @@ impl ItemBehaviour for BoatItem { world.spawn_entity(boat_entity).await; // Decrement item unless in creative mode - let held_item = player.inventory.held_item(); - let mut stack = held_item.lock().await; + let mut stack = player.inventory.held_item().await; stack.decrement_unless_creative(player.gamemode.load(), 1); + player.inventory.set_held_item(stack).await; // TODO: world.emitGameEvent(user, GameEvent.ENTITY_PLACE, hitResult.getPos()) // TODO: user.incrementStat(Stats.USED.getOrCreateStat(this)) diff --git a/crates/pumpkin/src/item/items/bow.rs b/crates/pumpkin/src/item/items/bow.rs index f8da60500..32d87b3e9 100644 --- a/crates/pumpkin/src/item/items/bow.rs +++ b/crates/pumpkin/src/item/items/bow.rs @@ -41,8 +41,7 @@ impl ItemBehaviour for BowItem { // Get the held item stack let inventory = player.inventory(); - let held = inventory.held_item(); - let stack = held.lock().await.clone(); + let stack = inventory.held_item().await; // Start the bow drawing animation player @@ -98,7 +97,7 @@ impl BowItem { let projectile = if let Some(slot) = arrow_slot { let stack = player.inventory().get_stack(slot).await; - stack.lock().await.copy_with_count(1) + stack.copy_with_count(1) } else { ItemStack::new(1, &Item::ARROW) }; @@ -109,11 +108,9 @@ impl BowItem { // Check for Infinity enchantment let mut has_infinity = false; - let held = player.inventory().held_item(); + let held = player.inventory().held_item().await; if let Some(enchantments) = - held.lock() - .await - .get_data_component::() + held.get_data_component::() { has_infinity = enchantments .enchantment @@ -179,8 +176,7 @@ impl BowItem { ArrowEntity::new_shot(arrow_entity, player.get_entity(), &projectile, pickup); // Read enchantments of the held item (bow) - let held = player.inventory().held_item(); - let stack = held.lock().await; + let stack = player.inventory().held_item().await; if let Some(enchantments) = stack.get_data_component::() { diff --git a/crates/pumpkin/src/item/items/bucket.rs b/crates/pumpkin/src/item/items/bucket.rs index 3e2938314..ded3b4830 100644 --- a/crates/pumpkin/src/item/items/bucket.rs +++ b/crates/pumpkin/src/item/items/bucket.rs @@ -100,11 +100,13 @@ fn set_waterlogged(block: &Block, state: BlockStateId, waterlogged: bool) -> Blo async fn give_player_bucket_item(player: &Player, item: &'static Item) { if player.gamemode.load() == GameMode::Creative { - for i in 0..player.inventory.main_inventory.len() { - if player.inventory.main_inventory[i].lock().await.item.id == item.id { + let inv = player.inventory.main_inventory.read().await; + for stack in inv.iter() { + if stack.item.id == item.id { return; } } + drop(inv); let mut item_stack = ItemStack::new(1, item); player .inventory @@ -112,14 +114,13 @@ async fn give_player_bucket_item(player: &Player, item: &'static Item) { .await; } else { let item_stack = ItemStack::new(1, item); - let held_item = player.inventory.held_item(); - let mut held_stack = held_item.lock().await; + let mut held_stack = player.inventory.held_item().await; if held_stack.item_count == 1 { - *held_stack = item_stack; + player.inventory.set_held_item(item_stack).await; } else { held_stack.decrement(1); - drop(held_stack); + player.inventory.set_held_item(held_stack).await; player .inventory .offer_or_drop_stack(item_stack, player) diff --git a/crates/pumpkin/src/item/items/bundle.rs b/crates/pumpkin/src/item/items/bundle.rs index b4508293e..987177b3c 100644 --- a/crates/pumpkin/src/item/items/bundle.rs +++ b/crates/pumpkin/src/item/items/bundle.rs @@ -22,8 +22,7 @@ impl ItemBehaviour for BundleItem { player: &'a Player, ) -> Pin + Send + 'a>> { Box::pin(async move { - let held_item_ref = player.inventory.held_item(); - let mut held_item = held_item_ref.lock().await; + let mut held_item = player.inventory.held_item().await; let mut matched = false; let mut used_slot_index = player.inventory.get_selected_slot() as usize; @@ -40,7 +39,6 @@ impl ItemBehaviour for BundleItem { &position, ); let updated_bundle = held_item.clone(); - drop(held_item); player.drop_item(extracted_stack).await; player.sync_hand_slot(used_slot_index, updated_bundle).await; @@ -48,8 +46,7 @@ impl ItemBehaviour for BundleItem { } if !matched { - let off_hand_item_ref = player.inventory.off_hand_item().await; - let mut off_hand_item = off_hand_item_ref.lock().await; + let mut off_hand_item = player.inventory.off_hand_item().await; if !off_hand_item.is_empty() && Self::ids().contains(&off_hand_item.item.id) { used_slot_index = 40; // OFF_HAND_SLOT if let Some(bundle_contents) = @@ -63,7 +60,6 @@ impl ItemBehaviour for BundleItem { &position, ); let updated_bundle = off_hand_item.clone(); - drop(off_hand_item); player.drop_item(extracted_stack).await; player.sync_hand_slot(used_slot_index, updated_bundle).await; diff --git a/crates/pumpkin/src/item/items/crossbow.rs b/crates/pumpkin/src/item/items/crossbow.rs index 0f6d27bb4..04b40b59b 100644 --- a/crates/pumpkin/src/item/items/crossbow.rs +++ b/crates/pumpkin/src/item/items/crossbow.rs @@ -3,7 +3,6 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::Ordering; -use tokio::sync::Mutex; use crate::entity::player::Player; use crate::entity::projectile::arrow::{ArrowEntity, ArrowPickup}; @@ -33,14 +32,13 @@ impl ItemBehaviour for CrossbowItem { ) -> Pin + Send + 'a>> { Box::pin(async move { let inventory = player.inventory(); - let held = inventory.held_item(); - let stack = held.lock().await.clone(); + let stack = inventory.held_item().await; if stack .get_data_component::() .is_some() { - Self::fire_projectiles(player, &held).await; + Self::fire_projectiles(player).await; return; } @@ -66,8 +64,7 @@ impl ItemBehaviour for CrossbowItem { let use_ticks = 72000 - use_ticks; let mut charge_time = 25; - let held = player.inventory().held_item(); - let stack = held.lock().await; + let mut stack = player.inventory().held_item().await; if let Some(enchantments) = stack.get_data_component::() { for (enchantment, level) in enchantments.enchantment.iter() { @@ -76,29 +73,24 @@ impl ItemBehaviour for CrossbowItem { } } } - drop(stack); charge_time = charge_time.max(0); if use_ticks >= charge_time { let arrow_slot = player.find_arrow().await; - let mut stack = held.lock().await; let (arrow_nbt_wrapper, slot) = { if let Some(slot) = arrow_slot { let inventory = player.inventory(); - let arrow_stack_arc = inventory.get_stack(slot).await; - let arrow_stack = arrow_stack_arc.lock().await; + let arrow_stack = inventory.get_stack(slot).await; let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new(); arrow_stack .copy_with_count(1) .write_item_stack(&mut arrow_nbt); - drop(arrow_stack); (Some(arrow_nbt), slot) } else if player.gamemode.load() == GameMode::Creative { let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new(); let arrow_stack = ItemStack::new(1, &Item::ARROW); arrow_stack.write_item_stack(&mut arrow_nbt); - drop(arrow_stack); (Some(arrow_nbt), 0) } else { @@ -112,6 +104,7 @@ impl ItemBehaviour for CrossbowItem { projectiles: vec![arrow_nbt], })), )); + player.inventory().set_held_item(stack).await; if player.gamemode.load() != GameMode::Creative { player.consume_arrow(slot).await; @@ -138,23 +131,17 @@ impl ItemBehaviour for CrossbowItem { } impl CrossbowItem { - async fn fire_projectiles(player: &Player, held: &Arc>) { - let (projectiles, has_multishot) = { - let stack = held.lock().await; - let projectiles = stack - .get_data_component::() - .cloned(); - let has_multishot = - stack - .get_data_component::() - .is_some_and(|enchantments| { - enchantments - .enchantment - .iter() - .any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT) - }); - (projectiles, has_multishot) - }; + async fn fire_projectiles(player: &Player) { + let mut held = player.inventory().held_item().await; + let projectiles = held.get_data_component::().cloned(); + let has_multishot = + held.get_data_component::() + .is_some_and(|enchantments| { + enchantments + .enchantment + .iter() + .any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT) + }); if let Some(charged) = projectiles { let world = player.world(); @@ -200,10 +187,9 @@ impl CrossbowItem { } } - held.lock() - .await - .patch + held.patch .retain(|(id, _)| *id != DataComponent::ChargedProjectiles); + player.inventory().set_held_item(held).await; player.damage_held_item(1).await; } } diff --git a/crates/pumpkin/src/item/items/egg.rs b/crates/pumpkin/src/item/items/egg.rs index 8055a1530..e89e51d87 100644 --- a/crates/pumpkin/src/item/items/egg.rs +++ b/crates/pumpkin/src/item/items/egg.rs @@ -37,7 +37,7 @@ impl ItemBehaviour for EggItem { ); // Capture the held item stack and pass it to the thrown egg entity - let item_stack: ItemStack = player.inventory.held_item().lock().await.clone(); + let item_stack: ItemStack = player.inventory.held_item().await; let entity = Entity::new(world.clone(), position, &EntityType::EGG); let egg = EggEntity::new_shot(entity, player.get_entity()); @@ -51,22 +51,23 @@ impl ItemBehaviour for EggItem { world.spawn_entity(Arc::new(egg)).await; // Consume item - let held_item = player.inventory.held_item(); - let consumed = { - let mut main_hand = held_item.lock().await; - if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) { - main_hand.decrement_unless_creative(player.gamemode.load(), 1); - true - } else { - false - } + let mut main_hand = player.inventory.held_item().await; + let consumed = if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) { + main_hand.decrement_unless_creative(player.gamemode.load(), 1); + player.inventory.set_held_item(main_hand).await; + true + } else { + false }; if !consumed { - let off_hand_item = player.inventory.off_hand_item().await; - let mut off_hand = off_hand_item.lock().await; + let mut off_hand = player.inventory.off_hand_item().await; if !off_hand.is_empty() && Self::ids().contains(&off_hand.item.id) { off_hand.decrement_unless_creative(player.gamemode.load(), 1); + player + .inventory + .set_stack_in_hand(pumpkin_util::Hand::Left, off_hand) + .await; } } }) diff --git a/crates/pumpkin/src/item/items/ender_eye.rs b/crates/pumpkin/src/item/items/ender_eye.rs index 69a83b37e..134d083f5 100644 --- a/crates/pumpkin/src/item/items/ender_eye.rs +++ b/crates/pumpkin/src/item/items/ender_eye.rs @@ -128,7 +128,9 @@ impl ItemBehaviour for EnderEyeItem { ); player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::LaunchedEyeOfEnder).await; - player.inventory.held_item().lock().await.decrement(1); + let mut stack = player.inventory.held_item().await; + stack.decrement(1); + player.inventory.set_held_item(stack).await; }) } diff --git a/crates/pumpkin/src/item/items/ender_pearl.rs b/crates/pumpkin/src/item/items/ender_pearl.rs index b4e114002..269adc9c1 100644 --- a/crates/pumpkin/src/item/items/ender_pearl.rs +++ b/crates/pumpkin/src/item/items/ender_pearl.rs @@ -56,22 +56,23 @@ impl ItemBehaviour for EnderPearlItem { world.spawn_entity(Arc::new(pearl)).await; // Consume item - let held_item = player.inventory.held_item(); - let consumed = { - let mut main_hand = held_item.lock().await; - if !main_hand.is_empty() && main_hand.item.id == Item::ENDER_PEARL.id { - main_hand.decrement_unless_creative(player.gamemode.load(), 1); - true - } else { - false - } + let mut main_hand = player.inventory.held_item().await; + let consumed = if !main_hand.is_empty() && main_hand.item.id == Item::ENDER_PEARL.id { + main_hand.decrement_unless_creative(player.gamemode.load(), 1); + player.inventory.set_held_item(main_hand).await; + true + } else { + false }; if !consumed { - let off_hand_item = player.inventory.off_hand_item().await; - let mut off_hand = off_hand_item.lock().await; + let mut off_hand = player.inventory.off_hand_item().await; if !off_hand.is_empty() && off_hand.item.id == Item::ENDER_PEARL.id { off_hand.decrement_unless_creative(player.gamemode.load(), 1); + player + .inventory + .set_stack_in_hand(pumpkin_util::Hand::Left, off_hand) + .await; } } }) diff --git a/crates/pumpkin/src/item/items/map.rs b/crates/pumpkin/src/item/items/map.rs index 5e1a3cc6a..451b96692 100644 --- a/crates/pumpkin/src/item/items/map.rs +++ b/crates/pumpkin/src/item/items/map.rs @@ -29,17 +29,18 @@ impl ItemBehaviour for MapItem { let server = player.world().server.upgrade().unwrap(); let inventory = player.inventory(); - let main_hand_item = inventory.held_item(); - let off_hand_item = inventory.off_hand_item().await; - let mut hand_stack = main_hand_item.lock().await; - - let found = if !hand_stack.is_empty() && hand_stack.item.id == Item::MAP.id { - true - } else { - drop(hand_stack); - hand_stack = off_hand_item.lock().await; - !hand_stack.is_empty() && hand_stack.item.id == Item::MAP.id - }; + let held_stack = inventory.held_item().await; + let (found, mut hand_stack, hand) = + if !held_stack.is_empty() && held_stack.item.id == Item::MAP.id { + (true, held_stack, pumpkin_util::Hand::Right) + } else { + let off_hand = inventory.off_hand_item().await; + if !off_hand.is_empty() && off_hand.item.id == Item::MAP.id { + (true, off_hand, pumpkin_util::Hand::Left) + } else { + (false, held_stack, pumpkin_util::Hand::Right) + } + }; if found { let map_id = server.next_map_id(); @@ -59,10 +60,10 @@ impl ItemBehaviour for MapItem { let gamemode = player.gamemode.load(); if hand_stack.item_count == 1 && gamemode != GameMode::Creative { - *hand_stack = filled_map; + inventory.set_stack_in_hand(hand, filled_map).await; } else { hand_stack.decrement_unless_creative(gamemode, 1); - drop(hand_stack); + inventory.set_stack_in_hand(hand, hand_stack).await; inventory.offer_or_drop_stack(filled_map, player).await; } } diff --git a/crates/pumpkin/src/item/items/potions.rs b/crates/pumpkin/src/item/items/potions.rs index 68b989894..7512b4cee 100644 --- a/crates/pumpkin/src/item/items/potions.rs +++ b/crates/pumpkin/src/item/items/potions.rs @@ -70,18 +70,16 @@ impl ItemBehaviour for SplashPotionItem { let splash = SplashPotionEntity::new_shot(entity, player.get_entity()); // Copy the held item stack data into the projectile - let main = player.inventory.held_item(); + let main_s = player.inventory.held_item().await; let mut used_main = true; - let mut stack = { - let s = main.lock().await.clone(); - (!s.is_empty() && s.item.id == pumpkin_data::item::Item::SPLASH_POTION.id) - .then_some(s) - }; + let mut stack = (!main_s.is_empty() + && main_s.item.id == pumpkin_data::item::Item::SPLASH_POTION.id) + .then_some(main_s); if stack.is_none() { - let off = player.inventory.off_hand_item().await; - let s = off.lock().await.clone(); - if !s.is_empty() && s.item.id == pumpkin_data::item::Item::SPLASH_POTION.id { - stack = Some(s); + let off_s = player.inventory.off_hand_item().await; + if !off_s.is_empty() && off_s.item.id == pumpkin_data::item::Item::SPLASH_POTION.id + { + stack = Some(off_s); used_main = false; } } @@ -97,20 +95,16 @@ impl ItemBehaviour for SplashPotionItem { // Decrement the used stack (clear) if used_main { - player - .inventory - .held_item() - .lock() - .await - .decrement_unless_creative(player.gamemode.load(), 1); + let mut s = player.inventory.held_item().await; + s.decrement_unless_creative(player.gamemode.load(), 1); + player.inventory.set_held_item(s).await; } else { + let mut s = player.inventory.off_hand_item().await; + s.decrement_unless_creative(player.gamemode.load(), 1); player .inventory - .off_hand_item() - .await - .lock() - .await - .decrement_unless_creative(player.gamemode.load(), 1); + .set_stack_in_hand(pumpkin_util::Hand::Left, s) + .await; } }) } @@ -138,18 +132,17 @@ impl ItemBehaviour for LingeringPotionItem { let ling = LingeringPotionEntity::new_shot(entity, player.get_entity()); // Copy the held item stack data into the projectile - let main = player.inventory.held_item(); + let main_s = player.inventory.held_item().await; let mut used_main = true; - let mut stack = { - let s = main.lock().await.clone(); - (!s.is_empty() && s.item.id == pumpkin_data::item::Item::LINGERING_POTION.id) - .then_some(s) - }; + let mut stack = (!main_s.is_empty() + && main_s.item.id == pumpkin_data::item::Item::LINGERING_POTION.id) + .then_some(main_s); if stack.is_none() { - let off = player.inventory.off_hand_item().await; - let s = off.lock().await.clone(); - if !s.is_empty() && s.item.id == pumpkin_data::item::Item::LINGERING_POTION.id { - stack = Some(s); + let off_s = player.inventory.off_hand_item().await; + if !off_s.is_empty() + && off_s.item.id == pumpkin_data::item::Item::LINGERING_POTION.id + { + stack = Some(off_s); used_main = false; } } @@ -164,20 +157,16 @@ impl ItemBehaviour for LingeringPotionItem { // Decrement the used stack (clear) if used_main { - player - .inventory - .held_item() - .lock() - .await - .decrement_unless_creative(player.gamemode.load(), 1); + let mut s = player.inventory.held_item().await; + s.decrement_unless_creative(player.gamemode.load(), 1); + player.inventory.set_held_item(s).await; } else { + let mut s = player.inventory.off_hand_item().await; + s.decrement_unless_creative(player.gamemode.load(), 1); player .inventory - .off_hand_item() - .await - .lock() - .await - .decrement_unless_creative(player.gamemode.load(), 1); + .set_stack_in_hand(pumpkin_util::Hand::Left, s) + .await; } }) } diff --git a/crates/pumpkin/src/item/items/snowball.rs b/crates/pumpkin/src/item/items/snowball.rs index 6e5f7b6e7..f3d0d0c4b 100644 --- a/crates/pumpkin/src/item/items/snowball.rs +++ b/crates/pumpkin/src/item/items/snowball.rs @@ -43,22 +43,23 @@ impl ItemBehaviour for SnowBallItem { world.spawn_entity(Arc::new(snowball)).await; // Consume item - let held_item = player.inventory.held_item(); - let consumed = { - let mut main_hand = held_item.lock().await; - if !main_hand.is_empty() && main_hand.item.id == Item::SNOWBALL.id { - main_hand.decrement_unless_creative(player.gamemode.load(), 1); - true - } else { - false - } + let mut main_hand = player.inventory.held_item().await; + let consumed = if !main_hand.is_empty() && main_hand.item.id == Item::SNOWBALL.id { + main_hand.decrement_unless_creative(player.gamemode.load(), 1); + player.inventory.set_held_item(main_hand).await; + true + } else { + false }; if !consumed { - let off_hand_item = player.inventory.off_hand_item().await; - let mut off_hand = off_hand_item.lock().await; + let mut off_hand = player.inventory.off_hand_item().await; if !off_hand.is_empty() && off_hand.item.id == Item::SNOWBALL.id { off_hand.decrement_unless_creative(player.gamemode.load(), 1); + player + .inventory + .set_stack_in_hand(pumpkin_util::Hand::Left, off_hand) + .await; } } }) diff --git a/crates/pumpkin/src/item/items/trident.rs b/crates/pumpkin/src/item/items/trident.rs index 5354fcafa..067db6a0a 100644 --- a/crates/pumpkin/src/item/items/trident.rs +++ b/crates/pumpkin/src/item/items/trident.rs @@ -11,7 +11,7 @@ use crate::item::{ItemBehaviour, ItemMetadata}; use pumpkin_data::entity::EntityType; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::sound::Sound; use pumpkin_util::GameMode; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::inventory::Inventory; @@ -32,8 +32,7 @@ impl ItemBehaviour for TridentItem { ) -> Pin + Send + 'a>> { Box::pin(async move { let inventory = player.inventory(); - let held = inventory.held_item(); - let stack = held.lock().await.clone(); + let stack = inventory.held_item().await; player .living_entity @@ -59,8 +58,7 @@ impl ItemBehaviour for TridentItem { } let world = player.world(); - let held = player.inventory().held_item(); - let stack_guard = held.lock().await.clone(); + let stack_guard = player.inventory().held_item().await; // Check Riptide level let mut riptide_level = 0u32; @@ -75,58 +73,52 @@ impl ItemBehaviour for TridentItem { } if riptide_level > 0 { - let is_touching_water = player - .living_entity - .entity - .touching_water - .load(std::sync::atomic::Ordering::Relaxed); - let is_raining = world.is_raining().await; - - if is_touching_water || is_raining { - let (yaw, pitch) = player.rotation(); - let look_vec = Vector3::rotation_vector(pitch as f64, yaw as f64); - let speed = f64::from(riptide_level).mul_add(0.75, 1.5); - let launch_velocity = look_vec.multiply(speed, speed, speed); - - player.get_entity().set_velocity(launch_velocity); - player.get_entity().send_velocity(); - - let sound = match riptide_level { - 1 => Sound::ItemTridentRiptide1, - 2 => Sound::ItemTridentRiptide2, - _ => Sound::ItemTridentRiptide3, - }; - world.play_sound(sound, SoundCategory::Players, &player.position()); - + let in_water = world.get_block_state(&player.position().to_block_pos()).id + == pumpkin_data::Block::WATER.default_state.id; + if !in_water { player.living_entity.clear_active_hand().await; - - if player.gamemode.load() != GameMode::Creative { - player.damage_held_item(1).await; - } return; } + + let f = f64::from(riptide_level); + let (yaw, pitch) = player.rotation(); + let f_yaw = f32::to_radians(yaw); + let f_pitch = f32::to_radians(pitch); + + let vx = f64::from(-f32::sin(f_yaw) * f32::cos(f_pitch)); + let vy = f64::from(-f32::sin(f_pitch)); + let vz = f64::from(f32::cos(f_yaw) * f32::cos(f_pitch)); + + let sq = (vx * vx + vy * vy + vz * vz).sqrt(); + if sq > 0.0 { + let mult = (1.0 + f * 0.75) / sq; + player.living_entity.entity.velocity.store(Vector3::new( + vx * mult, + vy * mult, + vz * mult, + )); + } + + player.damage_held_item(1).await; + player.living_entity.clear_active_hand().await; + return; } - // Normal throw - let entity = Entity::new(world.clone(), player.position(), &EntityType::TRIDENT); - let pickup = if player.gamemode.load() == GameMode::Creative { - ArrowPickup::CreativeOnly - } else { - ArrowPickup::Allowed - }; - - let trident_entity = - TridentEntity::new_shot(entity, player.get_entity(), stack_guard, pickup); - + // Normal throw - spawn thrown trident let (yaw, pitch) = player.rotation(); - trident_entity.set_velocity_from_rotation(pitch, yaw, 0.0, 2.5, 1.0); - - let trident_arc: Arc = Arc::new(trident_entity); - world.spawn_entity(trident_arc).await; + let entity = Entity::new(world.clone(), player.position(), &EntityType::TRIDENT); + let trident = TridentEntity::new_shot( + entity, + player.get_entity(), + stack_guard.clone(), + ArrowPickup::Allowed, + ); + trident.set_velocity_from_rotation(pitch, yaw, 0.0, 2.5, 1.0); + world.spawn_entity(Arc::new(trident)).await; world.play_sound( Sound::ItemTridentThrow, - SoundCategory::Players, + pumpkin_data::sound::SoundCategory::Players, &player.position(), ); @@ -135,9 +127,10 @@ impl ItemBehaviour for TridentItem { let selected_slot = inventory.get_selected_slot() as usize; let main_hand_item = inventory.get_stack(selected_slot).await; - let mut stack_lock = main_hand_item.lock().await; - if stack_lock.item.id == Item::TRIDENT.id { - *stack_lock = ItemStack::EMPTY.clone(); + if main_hand_item.item.id == Item::TRIDENT.id { + inventory + .set_stack(selected_slot, ItemStack::EMPTY.clone()) + .await; player .sync_hand_slot(selected_slot, ItemStack::EMPTY.clone()) .await; @@ -145,9 +138,10 @@ impl ItemBehaviour for TridentItem { let off_hand_slot = pumpkin_inventory::player::player_inventory::PlayerInventory::OFF_HAND_SLOT; let off_hand_item = inventory.get_stack(off_hand_slot).await; - let mut off_stack_lock = off_hand_item.lock().await; - if off_stack_lock.item.id == Item::TRIDENT.id { - *off_stack_lock = ItemStack::EMPTY.clone(); + if off_hand_item.item.id == Item::TRIDENT.id { + inventory + .set_stack(off_hand_slot, ItemStack::EMPTY.clone()) + .await; player .sync_hand_slot(off_hand_slot, ItemStack::EMPTY.clone()) .await; diff --git a/crates/pumpkin/src/net/bedrock/nethernet.rs b/crates/pumpkin/src/net/bedrock/nethernet.rs index f69b81735..277c9106c 100644 --- a/crates/pumpkin/src/net/bedrock/nethernet.rs +++ b/crates/pumpkin/src/net/bedrock/nethernet.rs @@ -282,6 +282,7 @@ async fn negotiate( /// A WebRTC connection carrying complete Bedrock batch packets. pub struct NetherNetSession { + #[allow(dead_code)] peer: Arc, reliable: RwLock>>, unreliable: RwLock>>, @@ -485,12 +486,12 @@ impl NetherNetSession { self.closed.cancel(); } + #[allow(clippy::unused_async)] pub async fn close(&self) { if self.closed.is_cancelled() { return; } self.closed.cancel(); - let _ = self.peer.close().await; } } diff --git a/crates/pumpkin/src/net/bedrock/play.rs b/crates/pumpkin/src/net/bedrock/play.rs index 2ea0b8821..5c8032406 100644 --- a/crates/pumpkin/src/net/bedrock/play.rs +++ b/crates/pumpkin/src/net/bedrock/play.rs @@ -565,8 +565,6 @@ impl BedrockClient { .get_slot(screen_slot) .get_stack() .await - .lock() - .await .are_equal(&item_stack); if !is_armor_equipped { @@ -607,13 +605,14 @@ impl BedrockClient { if inventory_updated { self.enqueue_packet(&CInventoryContent { container_id: VarUInt(0), - slots: futures::future::join_all(player.inventory().main_inventory.iter().map( - async |s| { - let stack = s.lock().await; - NetworkItemStackDescriptor::from(&*stack) - }, - )) - .await, + slots: player + .inventory() + .main_inventory + .read() + .await + .iter() + .map(NetworkItemStackDescriptor::from) + .collect(), full_container_name: FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, @@ -653,11 +652,10 @@ impl BedrockClient { let is_creative = player.gamemode.load() == GameMode::Creative; let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative); - let held_item = player.inventory.held_item(); + let mut held_item = player.inventory().held_item().await; if !client_stack.is_empty() { - let mut server_stack = held_item.lock().await; - if server_stack.is_empty() || server_stack.item.id != client_stack.item.id { - *server_stack = client_stack.clone(); + if held_item.is_empty() || held_item.item.id != client_stack.item.id { + held_item = client_stack.clone(); } } @@ -671,7 +669,7 @@ impl BedrockClient { face: &face, cursor_pos: &data.click_position, }, - &held_item, + &mut held_item, &EquipmentSlot::MAIN_HAND, &server, &world, @@ -699,7 +697,7 @@ impl BedrockClient { .await; } - let mut stack = held_item.lock().await; + let mut stack = held_item; if !stack.is_empty() { server .item_registry @@ -744,18 +742,19 @@ impl BedrockClient { } } } + player.inventory().set_held_item(stack).await; } } else if data.action_type.0 == 1 { // Click air / Use item let is_creative = player.gamemode.load() == GameMode::Creative; let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative); - let held_item = player.inventory.held_item(); - if !client_stack.is_empty() { - let mut server_stack = held_item.lock().await; - if server_stack.is_empty() || server_stack.item.id != client_stack.item.id { - *server_stack = client_stack.clone(); - } + let mut held = player.inventory.held_item().await; + if !client_stack.is_empty() + && (held.is_empty() || held.item.id != client_stack.item.id) + { + held = client_stack.clone(); + player.inventory.set_held_item(held.clone()).await; } let event = PlayerInteractEvent::new( @@ -765,10 +764,9 @@ impl BedrockClient { None, ); - let stack_for_use = held_item.lock().await.clone(); + let stack_for_use = held.clone(); { - let mut held = held_item.lock().await; let mut cooldown_active = false; if let Some(cooldown) = held.get_use_cooldown() { let group = cooldown @@ -811,27 +809,26 @@ impl BedrockClient { } if let Some(equippable) = held.get_data_component::() { let inventory = player.inventory(); - if !inventory - .is_already_equipped(&held_item, equippable.slot) - .await - { + let mut equipment_guard = inventory.entity_equipment.lock().await; + let current_equipped = equipment_guard.get(equippable.slot); + if !current_equipped.are_items_and_components_equal(&held) { player .enqueue_equipment_change(equippable.slot, &held) .await; - let binding = { - let mut equipment = inventory.entity_equipment.lock().await; - equipment.get_or_insert(equippable.slot) - }; - let mut equip_item = binding.lock().await; + let equip_item = equipment_guard + .equipment + .entry(equippable.slot.clone()) + .or_insert_with(|| ItemStack::EMPTY.clone()); if equip_item.is_empty() { *equip_item = held.clone(); held.decrement_unless_creative(player.gamemode.load(), 1); } else { - let binding = held.clone(); - *held = equip_item.clone(); - *equip_item = binding; + let old_held = held.clone(); + held = equip_item.clone(); + *equip_item = old_held; } + player.inventory().set_held_item(held.clone()).await; } } } @@ -854,14 +851,14 @@ impl BedrockClient { 0 | 2 => { let world = player.world(); if let Some(target) = world.get_entity_by_id(target_runtime_id) { - let held = player.inventory.held_item(); - let mut stack = held.lock().await; + let mut stack = player.inventory().held_item().await; if !target.interact(player, &mut stack).await { let server = world.server.upgrade().expect("Server is gone"); server .item_registry .use_on_entity(&mut stack, player, target) .await; + player.inventory().set_held_item(stack).await; } } } @@ -993,13 +990,14 @@ impl BedrockClient { // Sync the inventory content to Bedrock client self.enqueue_packet(&CInventoryContent { container_id: VarUInt(0), // player inventory - slots: futures::future::join_all(player.inventory().main_inventory.iter().map( - async |s| { - let stack = s.lock().await; - NetworkItemStackDescriptor::from(&*stack) - }, - )) - .await, + slots: player + .inventory() + .main_inventory + .read() + .await + .iter() + .map(NetworkItemStackDescriptor::from) + .collect(), full_container_name: FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, @@ -1655,13 +1653,14 @@ impl BedrockClient { if inventory_updated { self.enqueue_packet(&CInventoryContent { container_id: VarUInt(0), - slots: futures::future::join_all(player.inventory().main_inventory.iter().map( - async |s| { - let stack = s.lock().await; - NetworkItemStackDescriptor::from(&*stack) - }, - )) - .await, + slots: player + .inventory() + .main_inventory + .read() + .await + .iter() + .map(NetworkItemStackDescriptor::from) + .collect(), full_container_name: FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, @@ -1702,34 +1701,22 @@ impl BedrockClient { slot_with_stack as usize, ) { if slot_with_stack as usize != target_hotbar_slot { - let target_stack = player.inventory.main_inventory[target_hotbar_slot] - .lock() - .await - .clone(); - let source_stack = player.inventory.main_inventory[slot_with_stack as usize] - .lock() - .await - .clone(); + let target_stack = player.inventory().get_stack(target_hotbar_slot).await; + let source_stack = player.inventory().get_stack(slot_with_stack as usize).await; player - .inventory + .inventory() .set_stack(target_hotbar_slot, source_stack) .await; player - .inventory + .inventory() .set_stack(slot_with_stack as usize, target_stack) .await; } } else { - let target_stack = player.inventory.main_inventory[target_hotbar_slot] - .lock() - .await - .clone(); - let source_stack = player.inventory.main_inventory[slot_with_stack as usize] - .lock() - .await - .clone(); + let target_stack = player.inventory().get_stack(target_hotbar_slot).await; + let source_stack = player.inventory().get_stack(slot_with_stack as usize).await; player - .inventory + .inventory() .set_stack(target_hotbar_slot, source_stack) .await; player @@ -1767,20 +1754,21 @@ impl BedrockClient { .await; // Sync main hand equipment to other players - let stack_in_hand = player.inventory().held_item().lock().await.clone(); + let stack_in_hand = player.inventory().held_item().await; let equipment = &[(EquipmentSlot::MAIN_HAND, stack_in_hand)]; player.living_entity.send_equipment_changes(equipment); // Sync bedrock inventory updates self.enqueue_packet(&CInventoryContent { container_id: VarUInt(0), - slots: futures::future::join_all(player.inventory().main_inventory.iter().map( - async |s| { - let stack = s.lock().await; - NetworkItemStackDescriptor::from(&*stack) - }, - )) - .await, + slots: player + .inventory() + .main_inventory + .read() + .await + .iter() + .map(NetworkItemStackDescriptor::from) + .collect(), full_container_name: FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, @@ -1822,7 +1810,7 @@ impl BedrockClient { let inv = player.inventory(); inv.set_selected_slot(slot); - let stack = inv.held_item().lock().await.clone(); + let stack = inv.held_item().await; let equipment = &[(EquipmentSlot::MAIN_HAND, stack)]; player.living_entity.send_equipment_changes(equipment); } diff --git a/crates/pumpkin/src/net/java/play.rs b/crates/pumpkin/src/net/java/play.rs index cc7658b5f..0c8e8f270 100644 --- a/crates/pumpkin/src/net/java/play.rs +++ b/crates/pumpkin/src/net/java/play.rs @@ -1269,8 +1269,7 @@ impl JavaClient { let recipe_matches = { let mut ok = true; for (idx, ing) in ingredient_slots.iter().enumerate() { - let slot_arc = crafting_inv.get_stack(idx).await; - let stack = slot_arc.lock().await; + let stack = crafting_inv.get_stack(idx).await; match ing { None => { if !stack.is_empty() { @@ -1294,8 +1293,7 @@ impl JavaClient { let mut min = u8::MAX; for (idx, ing) in ingredient_slots.iter().enumerate() { if ing.is_some() { - let slot_arc = crafting_inv.get_stack(idx).await; - let stack = slot_arc.lock().await; + let stack = crafting_inv.get_stack(idx).await; if !stack.is_empty() { min = min.min(stack.item_count); } @@ -1336,7 +1334,7 @@ impl JavaClient { let Some(ingredient) = ing else { continue }; let taken = take_n_ingredient(&player.inventory, ingredient, amount_to_craft).await; if !taken.is_empty() { - *crafting_inv.get_stack(idx).await.lock().await = taken; + crafting_inv.set_stack(idx, taken).await; } } @@ -1876,8 +1874,7 @@ impl JavaClient { player.attack(event.target).await; } ActionType::Interact | ActionType::InteractAt => { - let held = player.inventory.held_item(); - let mut stack = held.lock().await.clone(); + let mut stack = player.inventory().held_item().await; let target_entity = event.target.get_entity(); if target_entity.entity_type.resource_name == "zombie_villager" && stack.item.registry_key == "golden_apple" @@ -1892,7 +1889,7 @@ impl JavaClient { .use_on_entity(&mut stack, player, event.target) .await; } - *held.lock().await = stack; + player.inventory().set_held_item(stack).await; } } } @@ -1980,11 +1977,8 @@ impl JavaClient { } let inventory = player.inventory(); - let held = inventory.held_item(); - if !server - .item_registry - .can_mine(held.lock().await.item, player) - { + let held = inventory.held_item().await; + if !server.item_registry.can_mine(held.item, player) { self.enqueue_packet(&CBlockUpdate::new( position, VarInt(i32::from(state.id.as_u16())), @@ -2041,7 +2035,7 @@ impl JavaClient { if can_harvest { player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; } - let item_id = player.inventory().held_item().lock().await.item.id; + let item_id = player.inventory().held_item().await.item.id; player .increment_stat(StatisticCategory::Used, item_id as i32, 1) .await; @@ -2128,7 +2122,7 @@ impl JavaClient { if block_drop { player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; } - let item_id = player.inventory().held_item().lock().await.item.id; + let item_id = player.inventory().held_item().await.item.id; player .increment_stat(StatisticCategory::Used, item_id as i32, 1) .await; @@ -2277,23 +2271,16 @@ impl JavaClient { } let inventory = player.inventory(); - let held_item = inventory.held_item(); + let held_item = inventory.held_item().await; let off_hand_item = inventory.off_hand_item().await; - let held_item_empty = held_item.lock().await.is_empty(); - let off_hand_item_empty = off_hand_item.lock().await.is_empty(); + let held_item_empty = held_item.is_empty(); + let off_hand_item_empty = off_hand_item.is_empty(); - let item = if matches!(hand, Hand::Left) { - held_item + let item_id = if matches!(hand, Hand::Left) { + held_item.item.id } else { - off_hand_item + off_hand_item.item.id }; - let equipment_slot = if matches!(hand, Hand::Left) { - EquipmentSlot::MAIN_HAND - } else { - EquipmentSlot::OFF_HAND - }; - - let item_id = item.lock().await.item.id; player .increment_stat(StatisticCategory::Used, item_id as i32, 1) .await; @@ -2323,6 +2310,17 @@ impl JavaClient { } }} + let mut item = if matches!(hand, Hand::Left) { + held_item + } else { + off_hand_item + }; + let equipment_slot = if matches!(hand, Hand::Left) { + EquipmentSlot::MAIN_HAND + } else { + EquipmentSlot::OFF_HAND + }; + let sneaking = player.get_entity().is_sneaking(); // Code based on the java class ServerPlayerInteractionManager @@ -2333,7 +2331,7 @@ impl JavaClient { &position, &cursor_pos, &face, - &item, + &mut item, &equipment_slot, &world, block, @@ -2356,25 +2354,21 @@ impl JavaClient { PlayerInventory::OFF_HAND_SLOT }; - let mut stack = item.lock().await; - - if stack.is_empty() { + if item.is_empty() { // TODO item cool down // If the hand is empty we stop here return Ok(()); } - let before = stack.clone(); + let before = item.clone(); server .item_registry - .use_on_block( - &mut stack, player, position, face, cursor_pos, block, server, - ) + .use_on_block(&mut item, player, position, face, cursor_pos, block, server) .await; // Check if the item is a block, because not every item can be placed :D - let item_id = stack.item.id; + let item_id = item.item.id; if let Some(block) = Block::from_item_id(item_id) { should_try_decrement = self .run_is_block_place(player, block, server, use_item_on, position, face) @@ -2385,12 +2379,11 @@ impl JavaClient { // TODO: Config // Decrease block count if player.gamemode.load() != GameMode::Creative { - stack.decrement(1); + item.decrement(1); } } - let after = stack.clone(); - drop(stack); + let after = item.clone(); // Broadcast the break entity status before the slot sync; the client // needs the old item texture in the slot for break particles. @@ -2406,7 +2399,8 @@ impl JavaClient { } if !after.are_equal(&before) { - player.sync_hand_slot(slot_index, after).await; + player.sync_hand_slot(slot_index, after.clone()).await; + inventory.set_stack_in_hand(hand, after).await; } Ok(()) @@ -2419,7 +2413,7 @@ impl JavaClient { position: &BlockPos, cursor_pos: &Vector3, face: &BlockDirection, - held_item: &Arc>, + held_item: &mut ItemStack, equipment_slot: &EquipmentSlot, world: &Arc, block: &Block, @@ -2512,16 +2506,13 @@ impl JavaClient { }; self.update_sequence(player, use_item.sequence.0); - let item_in_hand = if hand == Hand::Left { - inventory.held_item() + let mut item_in_hand = if hand == Hand::Left { + inventory.held_item().await } else { inventory.off_hand_item().await }; - let (item_id, _item) = { - let guard = item_in_hand.lock().await; - (guard.item.id, guard.item) - }; + let (item_id, _item) = (item_in_hand.item.id, item_in_hand.item); player .increment_stat(StatisticCategory::Used, item_id as i32, 1) .await; @@ -2551,14 +2542,10 @@ impl JavaClient { } else { PlayerInteractEvent::new(player, InteractAction::RightClickAir, &Block::AIR, None) }; - self.prepare_hand_item_for_use(player, hand, &item_in_hand) + let (item_for_use, stack_for_use) = (item_in_hand.item, item_in_hand.clone()); + self.prepare_hand_item_for_use(player, hand, &mut item_in_hand) .await; - let (item_for_use, stack_for_use) = { - let held = item_in_hand.lock().await; - (held.item, held.clone()) - }; - if !self .should_continue_use_after_fish_event(server, player, hand, item_for_use) .await @@ -2579,10 +2566,9 @@ impl JavaClient { &self, player: &Arc, hand: Hand, - item_in_hand: &Arc>, + held: &mut ItemStack, ) { let inventory = player.inventory(); - let mut held = item_in_hand.lock().await; if let Some(cooldown) = held.get_use_cooldown() { let group = cooldown @@ -2616,35 +2602,27 @@ impl JavaClient { } } if let Some(equippable) = held.get_data_component::() { - // Skip if the item is already in the target equipment slot. - // This prevents a self-deadlock: `held` already locks the same - // Mutex that `get_or_insert` would return, and - // Tokio's Mutex is not reentrant. - if inventory - .is_already_equipped(item_in_hand, equippable.slot) - .await - { + let mut equipment_guard = inventory.entity_equipment.lock().await; + let current_equipped = equipment_guard.get(equippable.slot); + if current_equipped.are_items_and_components_equal(held) { return; } - // If it can be equipped we want to make sure we can actually equip it - player - .enqueue_equipment_change(equippable.slot, &held) - .await; + player.enqueue_equipment_change(equippable.slot, held).await; - let binding = { - let mut equipment = inventory.entity_equipment.lock().await; - equipment.get_or_insert(equippable.slot) - }; - let mut equip_item = binding.lock().await; + let equip_item = equipment_guard + .equipment + .entry(equippable.slot.clone()) + .or_insert_with(|| ItemStack::EMPTY.clone()); if equip_item.is_empty() { *equip_item = held.clone(); held.decrement_unless_creative(player.gamemode.load(), 1); } else { - let binding = held.clone(); + let old_held = held.clone(); *held = equip_item.clone(); - *equip_item = binding; + *equip_item = old_held; } + inventory.set_stack_in_hand(hand, held.clone()).await; } } @@ -2703,7 +2681,7 @@ impl JavaClient { let inv = player.inventory(); inv.set_selected_slot(slot); - let stack = inv.held_item().lock().await.clone(); + let stack = inv.held_item().await; let equipment = &[(EquipmentSlot::MAIN_HAND, stack)]; player.living_entity.send_equipment_changes(equipment); } @@ -2731,8 +2709,6 @@ impl JavaClient { .get_slot(packet.slot as usize) .get_stack() .await - .lock() - .await .are_equal(&item_stack); if !is_armor_equipped { if (5..9).contains(&packet.slot) { diff --git a/crates/pumpkin/src/net/java/recipe_helper.rs b/crates/pumpkin/src/net/java/recipe_helper.rs index 758dfac83..a143ce913 100644 --- a/crates/pumpkin/src/net/java/recipe_helper.rs +++ b/crates/pumpkin/src/net/java/recipe_helper.rs @@ -28,8 +28,8 @@ pub async fn take_n_ingredient( let mut taken = 0u8; let mut result: Option = None; - for slot in &inventory.main_inventory { - let mut stack = slot.lock().await; + let mut main_inventory = inventory.main_inventory.write().await; + for stack in main_inventory.iter_mut() { if !stack.is_empty() && ingredient.match_item(stack.item) { let to_take = (count - taken).min(stack.item_count); let sub_stack = stack.split(to_take); @@ -53,8 +53,8 @@ pub async fn compute_biggest_craftable( inventory: &PlayerInventory, ) -> u8 { let mut available: Vec<(&'static Item, u32)> = Vec::new(); - for slot in &inventory.main_inventory { - let stack = slot.lock().await; + let main_inventory = inventory.main_inventory.read().await; + for stack in main_inventory.iter() { if !stack.is_empty() { if let Some(e) = available.iter_mut().find(|(i, _)| i.id == stack.item.id) { e.1 += u32::from(stack.item_count); diff --git a/crates/pumpkin/src/plugin/api/gui.rs b/crates/pumpkin/src/plugin/api/gui.rs index 70eb02613..f53bafd5c 100644 --- a/crates/pumpkin/src/plugin/api/gui.rs +++ b/crates/pumpkin/src/plugin/api/gui.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::sync::Arc; -use tokio::sync::Mutex; +use tokio::sync::RwLock; use pumpkin_data::{item_stack::ItemStack, screen::WindowType}; use pumpkin_inventory::screen_handler::{ @@ -19,67 +19,77 @@ pub struct PluginGui { } pub struct PluginInventory { - pub slots: Vec>>, + pub slots: RwLock>, } impl PluginInventory { #[must_use] pub fn new(size: usize) -> Self { - let mut slots = Vec::with_capacity(size); - for _ in 0..size { - slots.push(Arc::new(Mutex::new(ItemStack::EMPTY.clone()))); + Self { + slots: RwLock::new(vec![ItemStack::EMPTY.clone(); size]), } - Self { slots } } } impl Clearable for PluginInventory { fn clear(&self) -> std::pin::Pin + Send + '_>> { Box::pin(async move { - for slot in &self.slots { - *slot.lock().await = ItemStack::EMPTY.clone(); - } + let mut slots = self.slots.write().await; + slots.fill_with(|| ItemStack::EMPTY.clone()); }) } } impl Inventory for PluginInventory { fn size(&self) -> usize { - self.slots.len() + futures::executor::block_on(self.slots.read()).len() } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { - for slot in &self.slots { - if !slot.lock().await.is_empty() { - return false; - } - } - true + let slots = self.slots.read().await; + slots.iter().all(ItemStack::is_empty) }) } - fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { - Box::pin(async move { self.slots[slot].clone() }) + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let slots = self.slots.read().await; + slots + .get(slot) + .cloned() + .unwrap_or_else(|| ItemStack::EMPTY.clone()) + }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut stack = self.slots[slot].lock().await; - std::mem::replace(&mut *stack, ItemStack::EMPTY.clone()) + let mut slots = self.slots.write().await; + if slot < slots.len() { + std::mem::replace(&mut slots[slot], ItemStack::EMPTY.clone()) + } else { + ItemStack::EMPTY.clone() + } }) } fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { - let mut stack = self.slots[slot].lock().await; - stack.split(amount) + let mut slots = self.slots.write().await; + if slot < slots.len() && !slots[slot].is_empty() && amount > 0 { + slots[slot].split(amount) + } else { + ItemStack::EMPTY.clone() + } }) } fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { - *self.slots[slot].lock().await = stack; + let mut slots = self.slots.write().await; + if slot < slots.len() { + slots[slot] = stack; + } }) } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs index 054c3eeee..1384f279f 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs @@ -87,10 +87,11 @@ impl gui::HostGui for PluginHostState { item: Resource, ) -> wasmtime::Result<()> { let gui = self.get_gui_res(&res)?.provider.lock().await; - if (slot as usize) < gui.inventory.slots.len() { + let mut slots = gui.inventory.slots.write().await; + if (slot as usize) < slots.len() { let item_stack = self.get_item_stack(&item)?; let item_stack = item_stack.lock().await.clone(); - *gui.inventory.slots[slot as usize].lock().await = item_stack; + slots[slot as usize] = item_stack; } Ok(()) } @@ -102,8 +103,9 @@ impl gui::HostGui for PluginHostState { ) -> wasmtime::Result>> { let stack = { let gui = self.get_gui_res(&res)?.provider.lock().await; - if (slot as usize) < gui.inventory.slots.len() { - let stack = gui.inventory.slots[slot as usize].lock().await; + let slots = gui.inventory.slots.read().await; + if (slot as usize) < slots.len() { + let stack = &slots[slot as usize]; if stack.is_empty() { None } else { diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index 2ef27b99f..8eaacc55f 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -630,10 +630,12 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result>> { let player = player_from_resource(self, &player)?; let stack = player.inventory().get_stack(slot as usize).await; - if stack.lock().await.is_empty() { + if stack.is_empty() { Ok(None) } else { - Ok(Some(self.add_item_stack(stack)?)) + Ok(Some(self.add_item_stack(Arc::new( + tokio::sync::Mutex::new(stack), + ))?)) } } @@ -645,10 +647,12 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { let player = player_from_resource(self, &player)?; let hand = from_wasm_hand(hand); let stack = player.inventory().get_stack_in_hand(hand).await; - if stack.lock().await.is_empty() { + if stack.is_empty() { Ok(None) } else { - Ok(Some(self.add_item_stack(stack)?)) + Ok(Some(self.add_item_stack(Arc::new( + tokio::sync::Mutex::new(stack), + ))?)) } } diff --git a/crates/pumpkin/src/server/connection_cache.rs b/crates/pumpkin/src/server/connection_cache.rs index b8d78ad0f..b30e06145 100644 --- a/crates/pumpkin/src/server/connection_cache.rs +++ b/crates/pumpkin/src/server/connection_cache.rs @@ -5,7 +5,6 @@ use pumpkin_config::BasicConfiguration; use pumpkin_data::packet::{CURRENT_MC_VERSION, LOWEST_SUPPORTED_MC_VERSION}; use pumpkin_protocol::{ Players, Sample, StatusResponse, Version, - codec::var_int::VarInt, java::client::{config::CPluginMessage, status::CStatusResponse}, }; use std::{fs, path::Path}; @@ -42,27 +41,41 @@ pub struct CachedStatus { pub struct CachedBranding { /// Cached server brand buffer so we don't have to rebuild them every time a player joins - cached_server_brand: Box<[u8]>, + cached_server_brand: &'static [u8], } -impl<'a> CachedBranding { - pub fn new() -> Self { - let cached_server_brand = Self::build_brand(); +impl Default for CachedBranding { + fn default() -> Self { + Self::new() + } +} + +impl CachedBranding { + const BRAND: &'static str = "Pumpkin"; + const BRAND_BYTES: &'static [u8] = &{ + let brand = Self::BRAND.as_bytes(); + let len = brand.len(); + assert!(len < 128, "Brand length must fit in 1-byte VarInt"); + let mut bytes = [0u8; 1 + Self::BRAND.len()]; + bytes[0] = len as u8; + let mut i = 0; + while i < len { + bytes[i + 1] = brand[i]; + i += 1; + } + bytes + }; + + #[must_use] + pub const fn new() -> Self { Self { - cached_server_brand, + cached_server_brand: Self::BRAND_BYTES, } } - pub fn get_branding(&self) -> CPluginMessage<'_> { - CPluginMessage::new("minecraft:brand", &self.cached_server_brand) - } - const BRAND: &'a str = "Pumpkin"; - const BRAND_BYTES: &'a [u8] = Self::BRAND.as_bytes(); - fn build_brand() -> Box<[u8]> { - let mut buf = Vec::new(); - VarInt(Self::BRAND.len() as i32).encode(&mut buf).unwrap(); - buf.extend_from_slice(Self::BRAND_BYTES); - buf.into_boxed_slice() + #[must_use] + pub const fn get_branding(&self) -> CPluginMessage<'_> { + CPluginMessage::new("minecraft:brand", self.cached_server_brand) } } diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs index 987829abd..f9b0ed7b0 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -170,7 +170,9 @@ impl Server { let dat_path = world_path.join(LEVEL_DAT_FILE_NAME); if dat_path.exists() { let backup_path = world_path.join(LEVEL_DAT_BACKUP_FILE_NAME); - fs::copy(dat_path, backup_path).unwrap(); + if let Err(err) = fs::copy(&dat_path, &backup_path) { + warn!("Failed to create backup {LEVEL_DAT_BACKUP_FILE_NAME}: {err}"); + } } level_info } @@ -848,7 +850,7 @@ impl Server { id } - pub fn get_branding(&self) -> CPluginMessage<'_> { + pub const fn get_branding(&self) -> CPluginMessage<'_> { self.branding.get_branding() } diff --git a/crates/pumpkin/src/server/scheduler.rs b/crates/pumpkin/src/server/scheduler.rs index 55484f5ce..0e1b35901 100644 --- a/crates/pumpkin/src/server/scheduler.rs +++ b/crates/pumpkin/src/server/scheduler.rs @@ -125,7 +125,9 @@ impl TaskScheduler { break; } - let task = tasks.pop().unwrap(); + let Some(task) = tasks.pop() else { + break; + }; if cancelled.remove(&task.id) { continue; } diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index 5fa31bcf1..002389847 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -2248,14 +2248,14 @@ impl World { client .send_game_packet(&CInventoryContent { container_id: VarUInt(0), // player inventory, - slots: futures::future::join_all(player.inventory.main_inventory.iter().map( - async |s| { - let stack = s.lock().await; - - NetworkItemStackDescriptor::from(&*stack) - }, - )) - .await, + slots: player + .inventory() + .main_inventory + .read() + .await + .iter() + .map(NetworkItemStackDescriptor::from) + .collect(), full_container_name: FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, @@ -3127,18 +3127,12 @@ impl World { equipment_list.push(( EquipmentSlot::MAIN_HAND.discriminant(), - existing_player.inventory.held_item().lock().await.clone(), + existing_player.inventory.held_item().await, )); - for (slot, item_arc_mutex) in &existing_player - .inventory - .entity_equipment - .lock() - .await - .equipment - { - let item_stack = item_arc_mutex.lock().await.clone(); - equipment_list.push((slot.discriminant(), item_stack)); + let equipment_guard = existing_player.inventory.entity_equipment.lock().await; + for (slot, item_stack) in &equipment_guard.equipment { + equipment_list.push((slot.discriminant(), item_stack.clone())); } let equipment: Vec<(i8, ItemStackSerializer)> = equipment_list @@ -3271,12 +3265,12 @@ impl World { equipment_list.push(( EquipmentSlot::MAIN_HAND.discriminant(), - from.inventory.held_item().lock().await.clone(), + from.inventory.held_item().await, )); - for (slot, item_arc_mutex) in &from.inventory.entity_equipment.lock().await.equipment { - let item_stack = item_arc_mutex.lock().await.clone(); - equipment_list.push((slot.discriminant(), item_stack)); + let equipment_guard = from.inventory.entity_equipment.lock().await; + for (slot, item_stack) in &equipment_guard.equipment { + equipment_list.push((slot.discriminant(), item_stack.clone())); } let equipment: Vec<(i8, ItemStackSerializer)> = equipment_list @@ -4663,11 +4657,10 @@ impl World { if !flags.contains(BlockFlags::SKIP_DROPS) { let tool = if let Some(player) = &cause { let hand_stack = player - .inventory + .inventory() .get_stack_in_hand(pumpkin_util::Hand::Right) .await; - let stack_guard = hand_stack.lock().await; - (stack_guard.item_count > 0).then(|| stack_guard.clone()) + (!hand_stack.is_empty()).then_some(hand_stack) } else { None }; diff --git a/tools/pumpkin-codegen/Cargo.toml b/tools/pumpkin-codegen/Cargo.toml index e57a76474..da1560883 100644 --- a/tools/pumpkin-codegen/Cargo.toml +++ b/tools/pumpkin-codegen/Cargo.toml @@ -27,8 +27,5 @@ syn = { version = "3.0", features = ["full", "extra-traits"] } pumpkin-util = { path = "../../crates/pumpkin-util", features = ["codegen"] } pumpkin-nbt = { path = "../../crates/pumpkin-nbt" } -# [lints] -# all = { level = "deny", priority = -1 } -# nursery = { level = "deny", priority = -1 } -# pedantic = { level = "deny", priority = -1 } -# cargo = { level = "deny", priority = -1 } +[lints] +workspace = true diff --git a/tools/pumpkin-codegen/src/biome.rs b/tools/pumpkin-codegen/src/biome.rs index 79f13c6e2..f940d2706 100644 --- a/tools/pumpkin-codegen/src/biome.rs +++ b/tools/pumpkin-codegen/src/biome.rs @@ -216,9 +216,10 @@ pub fn build() -> TokenStream { let biomes: BTreeMap = serde_json::from_str(&fs::read_to_string("../../assets/biome.json").unwrap()) .expect("Failed to parse biome.json"); - let biome_trees: MultiNoiseBiomeSuppliers = - serde_json::from_str(&fs::read_to_string("../../assets/multi_noise_biome_tree.json").unwrap()) - .expect("Failed to parse multi_noise_biome_tree.json"); + let biome_trees: MultiNoiseBiomeSuppliers = serde_json::from_str( + &fs::read_to_string("../../assets/multi_noise_biome_tree.json").unwrap(), + ) + .expect("Failed to parse multi_noise_biome_tree.json"); let bedrock_biomes: BTreeMap = serde_json::from_str(&fs::read_to_string("../../assets/bedrock/biomes.json").unwrap()) .expect("Failed to parse biomes.json"); diff --git a/tools/pumpkin-codegen/src/main.rs b/tools/pumpkin-codegen/src/main.rs index d12522a9f..2d4bc3ec0 100644 --- a/tools/pumpkin-codegen/src/main.rs +++ b/tools/pumpkin-codegen/src/main.rs @@ -1,3 +1,19 @@ +#![allow(dead_code, unused)] +#![allow( + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + clippy::print_stdout, + clippy::print_stderr, + clippy::empty_structs_with_brackets, + clippy::semicolon_outside_block, + clippy::unreachable, + clippy::undocumented_unsafe_blocks, + clippy::needless_return, + clippy::collapsible_if +)] + use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; diff --git a/tools/pumpkin-codegen/src/slot_ranges.rs b/tools/pumpkin-codegen/src/slot_ranges.rs index 4f56e1f48..8f5272440 100644 --- a/tools/pumpkin-codegen/src/slot_ranges.rs +++ b/tools/pumpkin-codegen/src/slot_ranges.rs @@ -57,6 +57,7 @@ pub fn build() -> TokenStream { #(#slot_range_single_slot_names_tokens),* ]; + #[allow(clippy::match_same_arms)] #[must_use] pub fn get_slot_range(name: &str) -> Option<&'static [usize]> { match name { diff --git a/tools/pumpkin-codegen/src/tracked_data.rs b/tools/pumpkin-codegen/src/tracked_data.rs index f97d6ea07..55e1db81d 100644 --- a/tools/pumpkin-codegen/src/tracked_data.rs +++ b/tools/pumpkin-codegen/src/tracked_data.rs @@ -186,8 +186,9 @@ mod tests { #[test] fn checked_in_tracker_table_matches_codegen() { - let checked_in = std::fs::read_to_string("../../crates/pumpkin-data/src/generated/tracked_data.rs") - .expect("checked-in tracked data"); + let checked_in = + std::fs::read_to_string("../../crates/pumpkin-data/src/generated/tracked_data.rs") + .expect("checked-in tracked data"); let parsed = syn::parse_file(&checked_in).expect("valid generated Rust"); assert_eq!(quote!(#parsed).to_string(), build().to_string());