refactor(inventory): replace per-slot Arc<Mutex<ItemStack>> with container-level synchronization

This commit is contained in:
Alexander Medvedev 2026-08-07 21:45:05 +02:00
parent ffcf09ece4
commit 63d97e2c82
121 changed files with 1674 additions and 1931 deletions

View File

@ -127,5 +127,5 @@ harness = false
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["phf"] ignored = ["phf"]
# [lints] [lints]
# workspace = true workspace = true

View File

@ -1,3 +1,11 @@
#![allow(
clippy::all,
clippy::pedantic,
clippy::nursery,
clippy::cargo,
clippy::print_stdout
)]
use std::hint::black_box; use std::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main}; use criterion::{Criterion, criterion_group, criterion_main};

View File

@ -36,7 +36,7 @@ pub struct BlockState {
pub block_entity_type: u16, 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`]. /// Every [`BlockStateId`] is guaranteed to correspond to a valid [`BlockState`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)] #[repr(transparent)]

View File

@ -538,6 +538,7 @@ pub const SLOT_RANGE_SINGLE_SLOT_NAMES: [&str; 156usize] = [
"weapon.mainhand", "weapon.mainhand",
"weapon.offhand", "weapon.offhand",
]; ];
#[allow(clippy::match_same_arms)]
#[must_use] #[must_use]
pub fn get_slot_range(name: &str) -> Option<&'static [usize]> { pub fn get_slot_range(name: &str) -> Option<&'static [usize]> {
match name { match name {

View File

@ -1,4 +1,12 @@
#![allow(unused_imports)] #![allow(unused_imports)]
#![allow(
clippy::all,
clippy::pedantic,
clippy::nursery,
clippy::cargo,
clippy::undocumented_unsafe_blocks,
clippy::if_then_some_else_none
)]
#[rustfmt::skip] #[rustfmt::skip]
#[path = "generated/chunk_view_lut.rs"] #[path = "generated/chunk_view_lut.rs"]

View File

@ -53,10 +53,7 @@ impl AnvilScreenHandler {
} }
pub async fn update_result_slot(&mut self) { pub async fn update_result_slot(&mut self) {
let input_a = { let input_a = self.inventory.get_stack(0).await;
let lock = self.inventory.get_stack(0).await;
lock.lock().await.clone()
};
if input_a.is_empty() { if input_a.is_empty() {
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await; self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
@ -130,7 +127,7 @@ impl ScreenHandler for AnvilScreenHandler {
fn quick_move<'a>( fn quick_move<'a>(
&'a mut self, &'a mut self,
_player: &'a dyn InventoryPlayer, player: &'a dyn InventoryPlayer,
slot_index: i32, slot_index: i32,
) -> ItemStackFuture<'a> { ) -> ItemStackFuture<'a> {
Box::pin(async move { Box::pin(async move {
@ -138,40 +135,31 @@ impl ScreenHandler for AnvilScreenHandler {
let slot = self.get_behaviour().slots[slot_index as usize].clone(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack.clone();
stack_left = slot_stack_guard.clone();
drop(slot_stack_guard);
let mut slot_stack_mut = slot_stack_lock.lock().await;
if slot_index < 3 { if slot_index < 3 {
// From anvil to player // From anvil to player
if !self if !self.insert_item(&mut slot_stack, 3, 39, true).await {
.insert_item(
&mut slot_stack_mut,
3,
self.get_behaviour().slots.len() as i32,
true,
)
.await
{
return ItemStack::EMPTY.clone(); return ItemStack::EMPTY.clone();
} }
slot.on_quick_move_crafted(slot_stack.clone(), stack_left.clone())
.await;
} else { } else {
// From player to anvil input 0 and 1 // From player to anvil
if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await { if !self.insert_item(&mut slot_stack, 0, 2, false).await {
return ItemStack::EMPTY.clone(); return ItemStack::EMPTY.clone();
} }
} }
if slot_stack_mut.is_empty() { if slot_stack.item_count == stack_left.item_count {
drop(slot_stack_mut); return ItemStack::EMPTY.clone();
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
drop(slot_stack_mut);
slot.mark_dirty().await;
} }
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 stack_left

View File

@ -94,18 +94,14 @@ impl ScreenHandler for BeaconScreenHandler {
let slot = self.get_behaviour().slots[slot_index as usize].clone(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack.clone();
stack_left = slot_stack_guard.clone();
drop(slot_stack_guard);
let mut slot_stack_mut = slot_stack_lock.lock().await;
if slot_index == 0 { if slot_index == 0 {
// Move from the single beacon slot to the player inventory (slots 1 to end) // Move from the single beacon slot to the player inventory (slots 1 to end)
if !self if !self
.insert_item( .insert_item(
&mut slot_stack_mut, &mut slot_stack,
1, 1,
self.get_behaviour().slots.len() as i32, self.get_behaviour().slots.len() as i32,
true, true,
@ -116,17 +112,15 @@ impl ScreenHandler for BeaconScreenHandler {
} }
} else { } else {
// Move from player inventory into the beacon payment slot (slot 0) // 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(); return ItemStack::EMPTY.clone();
} }
} }
if slot_stack_mut.is_empty() { if slot_stack.is_empty() {
drop(slot_stack_mut);
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
drop(slot_stack_mut); slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
} }
} }

View File

@ -142,8 +142,7 @@ impl ScreenHandler for BrewingScreenHandler {
return stack_left; return stack_left;
} }
let slot_stack_lock = slot.get_stack().await; let mut stack = slot.get_stack().await;
let mut stack = slot_stack_lock.lock().await;
stack_left = stack.clone(); stack_left = stack.clone();
let success = if slot_index < 5 { let success = if slot_index < 5 {
@ -179,10 +178,9 @@ impl ScreenHandler for BrewingScreenHandler {
} }
if stack.is_empty() { if stack.is_empty() {
drop(stack);
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
slot.mark_dirty().await; slot.set_stack(stack).await;
} }
stack_left stack_left

View File

@ -9,12 +9,10 @@
//! Unlike regular inventories, crafting grids are typically cleared when //! Unlike regular inventories, crafting grids are typically cleared when
//! the container closes, and their contents are used up when crafting. //! the container closes, and their contents are used up when crafting.
use std::sync::Arc;
use std::{any::Any, pin::Pin}; use std::{any::Any, pin::Pin};
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_world::inventory::split_stack; use tokio::sync::RwLock;
use tokio::sync::Mutex;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; 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 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. /// When the crafting result is taken, the ingredients are consumed from this inventory.
#[derive(Clone)] #[derive(Default)]
pub struct CraftingInventory { pub struct CraftingInventory {
/// Width of the crafting grid (typically 2 or 3). /// Width of the crafting grid (typically 2 or 3).
pub width: u8, pub width: u8,
/// Height of the crafting grid (typically 2 or 3). /// Height of the crafting grid (typically 2 or 3).
pub height: u8, pub height: u8,
/// Items in the crafting grid, stored row by row. /// Items in the crafting grid, stored row by row.
pub items: Vec<Arc<Mutex<ItemStack>>>, pub items: RwLock<Vec<ItemStack>>,
} }
impl CraftingInventory { impl CraftingInventory {
@ -60,54 +58,64 @@ impl CraftingInventory {
Self { Self {
width, width,
height, height,
items: { items: RwLock::new(vec![
// Creates a Vec with different Mutexes for each slot ItemStack::EMPTY.clone();
let mut v = Vec::with_capacity(width as usize * height as usize); 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
},
} }
} }
} }
impl Inventory for CraftingInventory { impl Inventory for CraftingInventory {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() (self.width as usize) * (self.height as usize)
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) 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> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; if slot < items.len() {
std::mem::swap(&mut removed, &mut *guard); std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
removed } else {
ItemStack::EMPTY.clone()
}
}) })
} }
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { 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<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { 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 { impl Clearable for CraftingInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for item in &self.items { let mut items = self.items.write().await;
*item.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
}) })
} }
} }

View File

@ -113,7 +113,7 @@ async fn recipe_matches(
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset)) .get_stack((y + y_offset) * inventory.get_width() + (x + x_offset))
.await; .await;
if current_key == ' ' { if current_key == ' ' {
if !slot.lock().await.is_empty() { if !slot.is_empty() {
matched = false; matched = false;
break 'outer; break 'outer;
} }
@ -125,8 +125,6 @@ async fn recipe_matches(
.find_map(|(k, v)| (*k == current_key).then_some(v)) .find_map(|(k, v)| (*k == current_key).then_some(v))
.expect("Crafting recipe used invalid key"); .expect("Crafting recipe used invalid key");
let slot = slot.lock().await;
if !ingredient.match_item(slot.item) { if !ingredient.match_item(slot.item) {
matched = false; matched = false;
break 'outer; break 'outer;
@ -146,7 +144,7 @@ async fn recipe_matches(
) )
.await; .await;
if current_key == ' ' { if current_key == ' ' {
if !slot.lock().await.is_empty() { if !slot.is_empty() {
matched = false; matched = false;
break 'outer; break 'outer;
} }
@ -156,7 +154,6 @@ async fn recipe_matches(
.iter() .iter()
.find_map(|(k, v)| (*k == current_key).then_some(v)) .find_map(|(k, v)| (*k == current_key).then_some(v))
.expect("Crafting recipe used invalid key"); .expect("Crafting recipe used invalid key");
let slot = slot.lock().await;
if !ingredient.match_item(slot.item) { if !ingredient.match_item(slot.item) {
matched = false; matched = false;
break 'outer; break 'outer;
@ -181,7 +178,6 @@ async fn recipe_matches(
let mut ingredient_used = vec![false; ingredients.len()]; let mut ingredient_used = vec![false; ingredients.len()];
'next_slot: for i in 0..inventory.size() { 'next_slot: for i in 0..inventory.size() {
let slot = inventory.get_stack(i).await; let slot = inventory.get_stack(i).await;
let slot = slot.lock().await;
if slot.is_empty() { if slot.is_empty() {
continue 'next_slot; continue 'next_slot;
} }
@ -209,7 +205,6 @@ async fn recipe_matches(
} }
'item_stack: for i in 0..inventory.size() { 'item_stack: for i in 0..inventory.size() {
let slot = inventory.get_stack(i).await; let slot = inventory.get_stack(i).await;
let slot = slot.lock().await;
if slot.is_empty() { if slot.is_empty() {
continue 'item_stack; continue 'item_stack;
} }
@ -228,7 +223,6 @@ async fn recipe_matches(
} }
for position in (1..=7).step_by(2) { for position in (1..=7).step_by(2) {
let slot = inventory.get_stack(position).await; let slot = inventory.get_stack(position).await;
let slot = slot.lock().await;
if slot.is_empty() if slot.is_empty()
|| !slot || !slot
.item .item
@ -268,7 +262,7 @@ async fn recipe_matches(
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset)) .get_stack((y + y_offset) * inventory.get_width() + (x + x_offset))
.await; .await;
if current_key == ' ' { if current_key == ' ' {
if !slot.lock().await.is_empty() { if !slot.is_empty() {
matched = false; matched = false;
break 'outer; break 'outer;
} }
@ -279,7 +273,6 @@ async fn recipe_matches(
.find(|(k, _)| *k == current_key) .find(|(k, _)| *k == current_key)
.map(|(_, v)| v) .map(|(_, v)| v)
.expect("Crafting recipe used invalid key"); .expect("Crafting recipe used invalid key");
let slot = slot.lock().await;
if !ingredient.match_item(slot.item) { if !ingredient.match_item(slot.item) {
matched = false; matched = false;
break 'outer; break 'outer;
@ -302,7 +295,6 @@ async fn recipe_matches(
let mut ingredient_used = vec![false; ingredients.len()]; let mut ingredient_used = vec![false; ingredients.len()];
'next_slot: for i in 0..inventory.size() { 'next_slot: for i in 0..inventory.size() {
let slot = inventory.get_stack(i).await; let slot = inventory.get_stack(i).await;
let slot = slot.lock().await;
if slot.is_empty() { if slot.is_empty() {
continue 'next_slot; continue 'next_slot;
} }
@ -347,7 +339,6 @@ impl ResultSlot {
let x = i % inventory_width; let x = i % inventory_width;
let y = i / inventory_width; let y = i / inventory_width;
let slot = self.inventory.get_stack(i).await; let slot = self.inventory.get_stack(i).await;
let slot = slot.lock().await;
if !slot.is_empty() { if !slot.is_empty() {
top_x = top_x.min(x); top_x = top_x.min(x);
top_y = top_y.min(y); top_y = top_y.min(y);
@ -450,11 +441,7 @@ impl Slot for ResultSlot {
) )
.await; .await;
for i in 0..self.inventory.size() { for i in 0..self.inventory.size() {
let slot = self.inventory.get_stack(i).await; self.inventory.remove_stack_specific(i, 1).await;
let mut stack = slot.lock().await;
if !stack.is_empty() {
stack.item_count -= 1;
}
} }
self.mark_dirty().await; self.mark_dirty().await;
}) })
@ -462,8 +449,8 @@ impl Slot for ResultSlot {
fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> { fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> {
Box::pin(async move { false }) Box::pin(async move { false })
} }
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.result.clone() }) Box::pin(async move { self.result.lock().await.clone() })
} }
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.result.lock().await.clone() }) Box::pin(async move { self.result.lock().await.clone() })
@ -491,7 +478,6 @@ impl Slot for ResultSlot {
let mut count = u8::MAX; let mut count = u8::MAX;
for i in 0..self.inventory.size() { for i in 0..self.inventory.size() {
let slot = self.inventory.get_stack(i).await; let slot = self.inventory.get_stack(i).await;
let slot = slot.lock().await;
if !slot.is_empty() { if !slot.is_empty() {
count = count.min(slot.item_count); count = count.min(slot.item_count);
} }
@ -613,8 +599,7 @@ impl ScreenHandler for CraftingTableScreenHandler {
Box::pin(async move { Box::pin(async move {
let slot = self.get_behaviour().slots[slot_index as usize].clone(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let mut slot_stack = slot_stack.lock().await;
let stack_prev = slot_stack.clone(); let stack_prev = slot_stack.clone();
if slot_index == 0 { if slot_index == 0 {
if !self.insert_item(&mut slot_stack, 10, 46, true).await { if !self.insert_item(&mut slot_stack, 10, 46, true).await {

View File

@ -12,7 +12,6 @@ use std::{any::Any, pin::Pin, sync::Arc};
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use tokio::sync::Mutex;
/// A composite inventory combining two inventories. /// 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 }) Box::pin(async move { self.first.is_empty().await && self.second.is_empty().await })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
if slot >= self.first.size() { if slot >= self.first.size() {
self.second.get_stack(slot - self.first.size()).await self.second.get_stack(slot - self.first.size()).await

View File

@ -95,7 +95,6 @@ impl EnchantingTableScreenHandler {
pub async fn update_enchantments(&mut self, _player: &dyn InventoryPlayer) { pub async fn update_enchantments(&mut self, _player: &dyn InventoryPlayer) {
let item = self.inventory.get_stack(0).await; let item = self.inventory.get_stack(0).await;
let item = item.lock().await;
if item.is_empty() || item.has_enchantments() { if item.is_empty() || item.has_enchantments() {
for i in 0..3 { for i in 0..3 {
@ -349,8 +348,7 @@ impl ScreenHandler for EnchantingTableScreenHandler {
return false; return false;
} }
let lapis_slot = self.inventory.get_stack(1).await; let mut lapis_stack = self.inventory.get_stack(1).await;
let mut lapis_stack = lapis_slot.lock().await;
let lapis_cost = (id + 1) as u8; let lapis_cost = (id + 1) as u8;
if !player.is_creative() if !player.is_creative()
@ -362,8 +360,7 @@ impl ScreenHandler for EnchantingTableScreenHandler {
} }
// Perform enchantment // Perform enchantment
let item_slot = self.inventory.get_stack(0).await; let mut item_stack = self.inventory.get_stack(0).await;
let mut item_stack = item_slot.lock().await;
if item_stack.is_empty() || item_stack.has_enchantments() { if item_stack.is_empty() || item_stack.has_enchantments() {
return false; return false;
@ -380,16 +377,13 @@ impl ScreenHandler for EnchantingTableScreenHandler {
if !player.is_creative() { if !player.is_creative() {
player.add_experience_levels(-(id + 1)).await; player.add_experience_levels(-(id + 1)).await;
lapis_stack.decrement(lapis_cost); lapis_stack.decrement(lapis_cost);
self.inventory.set_stack(1, lapis_stack).await;
} }
for (enchant, level) in enchantments { for (enchant, level) in enchantments {
item_stack.add_enchantment(enchant, level as u16); item_stack.add_enchantment(enchant, level as u16);
} }
self.inventory.set_stack(0, item_stack).await;
// 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);
// Update seed // Update seed
player.set_enchantment_seed(rand::random()).await; 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(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack.clone();
stack_left = slot_stack_guard.clone();
drop(slot_stack_guard);
let mut slot_stack_mut = slot_stack_lock.lock().await;
if slot_index < 2 { if slot_index < 2 {
// From enchanting to player // From enchanting to player
if !self if !self
.insert_item( .insert_item(
&mut slot_stack_mut, &mut slot_stack,
2, 2,
self.get_behaviour().slots.len() as i32, self.get_behaviour().slots.len() as i32,
true, true,
@ -443,21 +433,19 @@ impl ScreenHandler for EnchantingTableScreenHandler {
} else { } else {
// From player to enchanting // From player to enchanting
// Lapis check // Lapis check
if slot_stack_mut.item == &Item::LAPIS_LAZULI { if slot_stack.item == &Item::LAPIS_LAZULI {
if !self.insert_item(&mut slot_stack_mut, 1, 2, false).await { if !self.insert_item(&mut slot_stack, 1, 2, false).await {
return ItemStack::EMPTY.clone(); 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(); return ItemStack::EMPTY.clone();
} }
} }
if slot_stack_mut.is_empty() { if slot_stack.is_empty() {
drop(slot_stack_mut);
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
drop(slot_stack_mut); slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
} }
// CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots // CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots

View File

@ -7,11 +7,10 @@
//! the entity model (armor is rendered on the player, held items are visible //! the entity model (armor is rendered on the player, held items are visible
//! in hands). //! in hands).
use std::{collections::HashMap, sync::Arc}; use std::collections::HashMap;
use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use tokio::sync::Mutex;
/// Equipment storage for an entity. /// Equipment storage for an entity.
/// ///
@ -21,19 +20,12 @@ use tokio::sync::Mutex;
/// ///
/// See also: [`EquipmentSlot`](EquipmentSlot) /// See also: [`EquipmentSlot`](EquipmentSlot)
// EntityEquipment.java // EntityEquipment.java
#[derive(Clone)] #[derive(Clone, Default)]
pub struct EntityEquipment { pub struct EntityEquipment {
/// Map of equipment slots to their equipped items. /// Map of equipment slots to their equipped items.
/// ///
/// Keys are equipment slot types (head, chest, legs, feet, off-hand). /// Keys are equipment slot types (head, chest, legs, feet, off-hand).
/// Values are mutex-protected item stacks for thread-safe access. pub equipment: HashMap<EquipmentSlot, ItemStack>,
pub equipment: HashMap<EquipmentSlot, Arc<Mutex<ItemStack>>>,
}
impl Default for EntityEquipment {
fn default() -> Self {
Self::new()
}
} }
impl EntityEquipment { impl EntityEquipment {
@ -53,27 +45,10 @@ impl EntityEquipment {
/// ///
/// # Returns /// # Returns
/// The previously equipped item, or an empty stack if the slot was empty. /// 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 self.equipment
.insert(slot.clone(), Arc::new(Mutex::new(stack))) .insert(slot.clone(), stack)
.unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone()))) .unwrap_or_else(|| 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<Mutex<ItemStack>> {
self.equipment
.entry(slot.clone())
.or_insert_with(|| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
.clone()
} }
/// Gets the item in a slot. /// Gets the item in a slot.
@ -81,22 +56,16 @@ impl EntityEquipment {
/// # Returns /// # Returns
/// The equipped item, or an empty stack if nothing is equipped. /// The equipped item, or an empty stack if nothing is equipped.
#[must_use] #[must_use]
pub fn get(&self, slot: &EquipmentSlot) -> Arc<Mutex<ItemStack>> { pub fn get(&self, slot: &EquipmentSlot) -> ItemStack {
self.equipment self.equipment
.get(slot) .get(slot)
.cloned() .cloned()
.unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone()))) .unwrap_or_else(|| ItemStack::EMPTY.clone())
} }
/// Checks if all equipment slots are empty. /// Checks if all equipment slots are empty.
pub async fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
for stack in self.equipment.values() { self.equipment.values().all(ItemStack::is_empty)
if !stack.lock().await.is_empty() {
return false;
}
}
true
} }
/// Clears all equipped items. /// Clears all equipped items.

View File

@ -179,10 +179,7 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
return stack_left; return stack_left;
} }
let slot_stack_lock = slot.get_stack().await; let mut stack = slot.get_stack().await;
// Acquire the lock to read/clone the stack
let mut stack = slot_stack_lock.lock().await;
stack_left = stack.clone(); stack_left = stack.clone();
let success = if slot_index < 3 { let success = if slot_index < 3 {
@ -205,10 +202,9 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
} }
if stack.is_empty() { if stack.is_empty() {
drop(stack); // Release lock before awaiting
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
slot.mark_dirty().await; slot.set_stack(stack).await;
} }
// Award XP when taking from output slot (slot 2) // Award XP when taking from output slot (slot 2)

View File

@ -218,20 +218,14 @@ impl ScreenHandler for GenericContainerScreenHandler {
let slot = self.get_behaviour().slots[slot_index as usize].clone(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack.clone();
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;
if slot_index < i32::from(self.rows * 9) { if slot_index < i32::from(self.rows * 9) {
// Move from inventory to player area (end) // Move from inventory to player area (end)
if !self if !self
.insert_item( .insert_item(
&mut slot_stack_mut, &mut slot_stack,
(self.rows * 9).into(), (self.rows * 9).into(),
self.get_behaviour().slots.len() as i32, self.get_behaviour().slots.len() as i32,
true, true,
@ -241,7 +235,7 @@ impl ScreenHandler for GenericContainerScreenHandler {
return ItemStack::EMPTY.clone(); return ItemStack::EMPTY.clone();
} }
} else if !self } 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 .await
{ {
// Move from player area to inventory (start) // 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 // Check the resulting state of the slot stack after insert_item
if slot_stack_mut.is_empty() { if slot_stack.is_empty() {
drop(slot_stack_mut); // Release lock
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
drop(slot_stack_mut); // Release lock slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
} }
} }

View File

@ -140,12 +140,8 @@ impl ScreenHandler for GUIScreenHandler {
let slot = self.get_behaviour().slots[slot_index as usize].clone(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack.clone();
stack_left = slot_stack_guard.clone();
drop(slot_stack_guard);
let mut slot_stack_mut = slot_stack_lock.lock().await;
let container_slots = i32::from(self.rows * self.columns); let container_slots = i32::from(self.rows * self.columns);
if slot_index < container_slots { if slot_index < container_slots {
@ -155,7 +151,7 @@ impl ScreenHandler for GUIScreenHandler {
} }
if !self if !self
.insert_item( .insert_item(
&mut slot_stack_mut, &mut slot_stack,
container_slots, container_slots,
self.get_behaviour().slots.len() as i32, self.get_behaviour().slots.len() as i32,
true, true,
@ -170,7 +166,7 @@ impl ScreenHandler for GUIScreenHandler {
return ItemStack::EMPTY.clone(); return ItemStack::EMPTY.clone();
} }
if !self if !self
.insert_item(&mut slot_stack_mut, 0, container_slots, false) .insert_item(&mut slot_stack, 0, container_slots, false)
.await .await
{ {
// Move from player area to inventory (start) // Move from player area to inventory (start)
@ -178,12 +174,10 @@ impl ScreenHandler for GUIScreenHandler {
} }
} }
if slot_stack_mut.is_empty() { if slot_stack.is_empty() {
drop(slot_stack_mut);
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
drop(slot_stack_mut); slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
} }
} }

View File

@ -63,9 +63,7 @@ impl MerchantScreenHandler {
let offer = &self.offers[self.selected_offer]; let offer = &self.offers[self.selected_offer];
let input_a = self.inventory.get_stack(0).await; 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 = 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) 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; && 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(); let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack.clone();
stack_left = slot_stack_guard.clone();
drop(slot_stack_guard);
let mut slot_stack_mut = slot_stack_lock.lock().await;
if slot_index < 3 { if slot_index < 3 {
// From merchant slots to player inventory // From merchant slots to player inventory
if !self if !self
.insert_item( .insert_item(
&mut slot_stack_mut, &mut slot_stack,
3, 3,
self.get_behaviour().slots.len() as i32, self.get_behaviour().slots.len() as i32,
true, true,
@ -151,18 +145,16 @@ impl ScreenHandler for MerchantScreenHandler {
return ItemStack::EMPTY.clone(); return ItemStack::EMPTY.clone();
} }
} else { } else {
// From player inventory to merchant inputs (0 and 1) // From player inventory to merchant
if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await { if !self.insert_item(&mut slot_stack, 0, 2, false).await {
return ItemStack::EMPTY.clone(); return ItemStack::EMPTY.clone();
} }
} }
if slot_stack_mut.is_empty() { if slot_stack.is_empty() {
drop(slot_stack_mut);
slot.set_stack(ItemStack::EMPTY.clone()).await; slot.set_stack(ItemStack::EMPTY.clone()).await;
} else { } else {
drop(slot_stack_mut); slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
} }
} }
@ -192,24 +184,20 @@ impl ScreenHandler for MerchantScreenHandler {
(offer.base_cost_a.0.item_count, count_b, offer.xp) (offer.base_cost_a.0.item_count, count_b, offer.xp)
}; };
let input_a = self.inventory.get_stack(0).await; let mut input_a = self.inventory.get_stack(0).await;
let mut input_a = input_a.lock().await;
input_a.decrement(count_a); input_a.decrement(count_a);
if input_a.is_empty() { if input_a.is_empty() {
*input_a = ItemStack::EMPTY.clone(); input_a = ItemStack::EMPTY.clone();
} }
drop(input_a); self.inventory.set_stack(0, input_a).await;
self.get_behaviour().slots[0].mark_dirty().await;
if let Some(count_b) = count_b { if let Some(count_b) = count_b {
let input_b = self.inventory.get_stack(1).await; let mut input_b = self.inventory.get_stack(1).await;
let mut input_b = input_b.lock().await;
input_b.decrement(count_b); input_b.decrement(count_b);
if input_b.is_empty() { if input_b.is_empty() {
*input_b = ItemStack::EMPTY.clone(); input_b = ItemStack::EMPTY.clone();
} }
drop(input_b); self.inventory.set_stack(1, input_b).await;
self.get_behaviour().slots[1].mark_dirty().await;
} }
// Award XP // Award XP

View File

@ -10,14 +10,14 @@
//! Ender chests track when players open and close them to properly //! Ender chests track when players open and close them to properly
//! manage the viewer count for animation purposes. //! 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_data::item_stack::ItemStack;
use pumpkin_world::{ use pumpkin_world::{
block::viewer::ViewerCountTracker, 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. /// A player's ender chest inventory.
/// ///
@ -26,7 +26,7 @@ use tokio::sync::Mutex;
/// ender chest block. /// ender chest block.
pub struct EnderChestInventory { pub struct EnderChestInventory {
/// The 27 item slots in the ender chest. /// The 27 item slots in the ender chest.
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Viewer count tracker for lid animation. /// Viewer count tracker for lid animation.
/// ///
/// Tracks how many players have the ender chest open to animate the lid. /// Tracks how many players have the ender chest open to animate the lid.
@ -47,7 +47,7 @@ impl EnderChestInventory {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
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), tracker: Mutex::new(None),
} }
} }
@ -75,41 +75,45 @@ impl EnderChestInventory {
impl Inventory for EnderChestInventory { impl Inventory for EnderChestInventory {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
std::mem::swap(&mut removed, &mut *guard);
removed
}) })
} }
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { 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<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { 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 { impl Clearable for EnderChestInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for item in &self.items { let mut items = self.items.write().await;
*item.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
}) })
} }
} }

View File

@ -11,18 +11,17 @@ use crate::entity_equipment::EntityEquipment;
use crate::screen_handler::InventoryPlayer; use crate::screen_handler::InventoryPlayer;
use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_protocol::java::client::play::CSetPlayerInventory; use pumpkin_protocol::java::client::play::CSetPlayerInventory;
use pumpkin_util::Hand; use pumpkin_util::Hand;
use pumpkin_world::inventory::split_stack;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use std::any::Any; use std::any::Any;
use std::array::from_fn;
use std::collections::HashMap; use std::collections::HashMap;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, Ordering};
use tokio::sync::Mutex; use tokio::sync::{Mutex, RwLock};
use tracing::warn; use tracing::warn;
/// The player's inventory. /// The player's inventory.
@ -33,13 +32,13 @@ pub struct PlayerInventory {
/// The 36 main inventory slots (slots 0-35). /// 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. /// The first 9 slots (0-8) are the hotbar, the remaining 27 (9-35) are the main storage.
pub main_inventory: [Arc<Mutex<ItemStack>>; Self::MAIN_SIZE], pub main_inventory: RwLock<[ItemStack; Self::MAIN_SIZE]>,
/// Mapping of slot indices to equipment slot types. /// Mapping of slot indices to equipment slot types.
/// ///
/// Used to identify which slots correspond to armor and off-hand equipment. /// Used to identify which slots correspond to armor and off-hand equipment.
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>, pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
/// The currently selected hotbar slot index (0-8). /// 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. /// The entity equipment storage for armor and off-hand items.
/// ///
/// This is separate from the main inventory and is rendered on the player model. /// This is separate from the main inventory and is rendered on the player model.
@ -65,58 +64,79 @@ impl PlayerInventory {
equipment_slots: Arc<HashMap<usize, EquipmentSlot>>, equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
) -> Self { ) -> Self {
Self { Self {
// Normal syntax can't be used here because Arc doesn't implement Copy main_inventory: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())),
main_inventory: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
equipment_slots, equipment_slots,
selected_slot: AtomicU8::new(0), selected_slot: AtomicU8::new(0),
entity_equipment, 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. /// Gets the item in the currently selected hotbar slot.
/// ///
/// This is the item the player is currently holding in their main hand. /// This is the item the player is currently holding in their main hand.
/// pub async fn held_item(&self) -> ItemStack {
/// Mojang name: `getSelectedStack` let inv = self.main_inventory.read().await;
pub fn held_item(&self) -> Arc<Mutex<ItemStack>> { inv[self.get_selected_slot() as usize].clone()
self.main_inventory }
.get(self.get_selected_slot() as usize)
.unwrap() /// Sets the item in the currently selected hotbar slot.
.clone() 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. /// Gets the item in the specified hand.
/// ///
/// # Arguments /// # Arguments
/// - `hand` - Which hand to get the item from /// - `hand` - Which hand to get the item from
pub async fn get_stack_in_hand(&self, hand: Hand) -> Arc<Mutex<ItemStack>> { pub async fn get_stack_in_hand(&self, hand: Hand) -> ItemStack {
match hand { match hand {
Hand::Left => self.off_hand_item().await, Hand::Left => self.off_hand_item().await,
Hand::Right => self.held_item(), Hand::Right => self.held_item().await,
}
}
/// 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<Mutex<T>>` that
/// you already hold will deadlock forever.
pub async fn is_already_equipped(
&self,
item_arc: &Arc<Mutex<ItemStack>>,
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,
} }
} }
/// Gets the item in the off-hand. /// Gets the item in the off-hand.
/// ///
/// Mojang name: `getOffHandStack` /// Mojang name: `getOffHandStack`
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> { pub async fn off_hand_item(&self) -> ItemStack {
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap(); let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
self.entity_equipment.lock().await.get(slot) self.entity_equipment.lock().await.get(slot)
} }
@ -128,11 +148,12 @@ impl PlayerInventory {
pub async fn swap_item(&self) -> (ItemStack, ItemStack) { pub async fn swap_item(&self) -> (ItemStack, ItemStack) {
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap(); let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
let mut equipment = self.entity_equipment.lock().await; let mut equipment = self.entity_equipment.lock().await;
let binding = self.held_item(); let selected = self.get_selected_slot() as usize;
let mut main_hand_item = binding.lock().await; let mut main_inv = self.main_inventory.write().await;
let off_hand_item = main_hand_item.clone(); let main_hand_item = main_inv[selected].clone();
*main_hand_item = equipment.put(slot, off_hand_item.clone()).await; let new_main = equipment.put(slot, main_hand_item.clone());
(main_hand_item.clone(), off_hand_item) main_inv[selected] = new_main.clone();
(new_main, main_hand_item)
} }
/// Checks if a slot index is a valid hotbar slot. /// 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. /// Returns the number of items that couldn't fit.
async fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize { 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 mut stack_count = stack.item_count;
let binding = self.get_stack(slot).await; let self_stack = &mut inv[slot];
let mut self_stack = binding.lock().await;
if self_stack.is_empty() { if self_stack.is_empty() {
*self_stack = stack.copy_with_count(0); *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; let count_left = self_stack.get_max_stack_size() - self_stack.item_count;
@ -184,12 +216,12 @@ impl PlayerInventory {
/// # Returns /// # Returns
/// The slot index or -1 if inventory is full. /// The slot index or -1 if inventory is full.
async fn get_empty_slot(&self) -> i16 { async fn get_empty_slot(&self) -> i16 {
for i in 0..Self::MAIN_SIZE { let inv = self.main_inventory.read().await;
if self.main_inventory[i].lock().await.is_empty() { for (i, stack) in inv.iter().enumerate() {
if stack.is_empty() {
return i as i16; return i as i16;
} }
} }
-1 -1
} }
@ -205,29 +237,24 @@ impl PlayerInventory {
/// ///
/// Checks selected slot, off-hand, then other slots. /// Checks selected slot, off-hand, then other slots.
async fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 { async fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 {
if Self::can_stack_add_more( let selected = self.get_selected_slot() as usize;
&*self let inv = self.main_inventory.read().await;
.get_stack(self.get_selected_slot() as usize) if Self::can_stack_add_more(&inv[selected], stack) {
.await return selected as i16;
.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 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. /// Inserts a stack into any available slot.
@ -254,8 +281,6 @@ impl PlayerInventory {
return false; return false;
} }
// TODO: if (stack.isDamaged()) {
let mut i; let mut i;
loop { loop {
@ -271,8 +296,6 @@ impl PlayerInventory {
} }
} }
// TODO: Creative mode check
stack.item_count < i stack.item_count < i
} }
@ -281,17 +304,12 @@ impl PlayerInventory {
/// # Returns /// # Returns
/// The slot index or -1 if not found. /// The slot index or -1 if not found.
pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 { pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
for i in 0..Self::MAIN_SIZE { let inv = self.main_inventory.read().await;
if !self.main_inventory[i].lock().await.is_empty() for (i, item) in inv.iter().enumerate() {
&& self.main_inventory[i] if !item.is_empty() && item.are_items_and_components_equal(stack) {
.lock()
.await
.are_items_and_components_equal(stack)
{
return i as i16; return i as i16;
} }
} }
-1 -1
} }
@ -299,63 +317,43 @@ impl PlayerInventory {
/// ///
/// First looks for empty slots, then slots without enchantments. /// First looks for empty slots, then slots without enchantments.
async fn get_swappable_hotbar_slot(&self) -> usize { async fn get_swappable_hotbar_slot(&self) -> usize {
let inv = self.main_inventory.read().await;
let selected_slot = self.get_selected_slot() as usize; let selected_slot = self.get_selected_slot() as usize;
for i in 0..Self::HOTBAR_SIZE { for i in 0..Self::HOTBAR_SIZE {
let check_index = (i + selected_slot) % 9; 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; return check_index;
} }
} }
if let Some(i) = (0..Self::HOTBAR_SIZE).next() { selected_slot
let check_index = (i + selected_slot) % 9;
return check_index;
}
self.get_selected_slot() as usize
} }
/// Swaps an item stack with an item on the hotbar. /// Swaps an item stack with an item on the hotbar.
/// ///
/// Finds an empty hotbar slot and places the stack there. /// Finds an empty hotbar slot and places the stack there.
pub async fn swap_stack_with_hotbar(&self, stack: ItemStack) { 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] if let Some(empty_slot) = inv.iter().position(ItemStack::is_empty)
.lock() && !inv[selected].is_empty()
.await
.is_empty()
{ {
let empty_slot = self.get_empty_slot().await; inv[empty_slot] = inv[selected].clone();
if empty_slot != -1 {
self.set_stack(
empty_slot as usize,
self.main_inventory[self.get_selected_slot() as usize]
.lock()
.await
.clone(),
)
.await;
}
} }
self.set_stack(self.get_selected_slot() as usize, stack) inv[selected] = stack;
.await;
} }
/// Swaps the items at two slot indices. /// Swaps the items at two slot indices.
pub async fn swap_slot_with_hotbar(&self, slot: usize) { pub async fn swap_slot_with_hotbar(&self, slot: usize) {
self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8); let swappable = self.get_swappable_hotbar_slot().await;
let stack = self.main_inventory[self.get_selected_slot() as usize] self.set_selected_slot(swappable as u8);
.lock() let selected = self.get_selected_slot() as usize;
.await let mut inv = self.main_inventory.write().await;
.clone(); inv.swap(selected, slot);
self.set_stack(
self.get_selected_slot() as usize,
self.main_inventory[slot].lock().await.clone(),
)
.await;
self.set_stack(slot, stack).await;
} }
/// Gives a stack to the player or drops it if inventory is full. /// 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() let items_fit = stack.get_max_stack_size()
- self - self.get_stack(room_for_stack as usize).await.item_count;
.get_stack(room_for_stack as usize)
.await
.lock()
.await
.item_count;
if self if self
.insert_stack(room_for_stack, &mut stack.split(items_fit)) .insert_stack(room_for_stack, &mut stack.split(items_fit))
.await .await
@ -397,13 +390,7 @@ impl PlayerInventory {
player player
.enqueue_slot_set_packet(&CSetPlayerInventory::new( .enqueue_slot_set_packet(&CSetPlayerInventory::new(
i32::from(room_for_stack).into(), i32::from(room_for_stack).into(),
&self &self.get_stack(room_for_stack as usize).await.into(),
.get_stack(room_for_stack as usize)
.await
.lock()
.await
.clone()
.into(),
)) ))
.await; .await;
} }
@ -414,10 +401,8 @@ impl PlayerInventory {
impl Clearable for PlayerInventory { impl Clearable for PlayerInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for item in &self.main_inventory { let mut inv = self.main_inventory.write().await;
*item.lock().await = ItemStack::EMPTY.clone(); inv.fill_with(|| ItemStack::EMPTY.clone());
}
self.entity_equipment.lock().await.clear(); self.entity_equipment.lock().await.clear();
}) })
} }
@ -425,27 +410,19 @@ impl Clearable for PlayerInventory {
impl Inventory for PlayerInventory { impl Inventory for PlayerInventory {
fn size(&self) -> usize { 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> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for item in &self.main_inventory { let inv = self.main_inventory.read().await;
if !item.lock().await.is_empty() { if inv.iter().any(|s| !s.is_empty()) {
return false; return false;
}
} }
for slot in self.equipment_slots.values() { for slot in self.equipment_slots.values() {
if !self let eq_item = self.entity_equipment.lock().await.get(slot);
.entity_equipment if !eq_item.is_empty() {
.lock()
.await
.get(slot)
.lock()
.await
.is_empty()
{
return false; return false;
} }
} }
@ -454,49 +431,56 @@ impl Inventory for PlayerInventory {
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
if slot < self.main_inventory.len() { if slot < Self::MAIN_SIZE {
self.main_inventory[slot].clone() let inv = self.main_inventory.read().await;
} else { inv[slot].clone()
let slot = self.equipment_slots.get(&slot).unwrap(); } else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment.lock().await.get(slot) self.entity_equipment.lock().await.get(slot)
} else {
ItemStack::EMPTY.clone()
} }
}) })
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
if slot < self.main_inventory.len() { if slot < Self::MAIN_SIZE {
let mut removed = ItemStack::EMPTY.clone(); let mut inv = self.main_inventory.write().await;
let mut guard = self.main_inventory[slot].lock().await; std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone())
std::mem::swap(&mut removed, &mut *guard); } else if let Some(slot) = self.equipment_slots.get(&slot) {
removed
} else {
let slot = self.equipment_slots.get(&slot).unwrap();
self.entity_equipment self.entity_equipment
.lock() .lock()
.await .await
.put(slot, ItemStack::EMPTY.clone()) .put(slot, ItemStack::EMPTY.clone())
.await } else {
ItemStack::EMPTY.clone()
} }
}) })
} }
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
if slot < self.main_inventory.len() { if slot < Self::MAIN_SIZE {
split_stack(&self.main_inventory, slot, amount).await let mut inv = self.main_inventory.write().await;
} else { if !inv[slot].is_empty() && amount > 0 {
let slot = self.equipment_slots.get(&slot).unwrap(); inv[slot].split(amount)
} else {
let equipment = self.entity_equipment.lock().await.get(slot); ItemStack::EMPTY.clone()
let mut stack = equipment.lock().await;
if !stack.is_empty() {
return stack.split(amount);
} }
} 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() ItemStack::EMPTY.clone()
} }
}) })
@ -504,10 +488,11 @@ impl Inventory for PlayerInventory {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
if slot < self.main_inventory.len() { if slot < Self::MAIN_SIZE {
*self.main_inventory[slot].lock().await = stack; let mut inv = self.main_inventory.write().await;
inv[slot] = stack;
} else if let Some(slot) = self.equipment_slots.get(&slot) { } 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 { } else {
warn!("Failed to get Equipment Slot at {slot}"); warn!("Failed to get Equipment Slot at {slot}");
} }

View File

@ -164,8 +164,7 @@ impl ScreenHandler for PlayerScreenHandler {
// TODO: Equippable component // TODO: Equippable component
if slot.has_stack().await { if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let mut slot_stack = slot_stack_lock.lock().await;
let stack_prev = slot_stack.clone(); let stack_prev = slot_stack.clone();
let equipment_slot = slot_stack let equipment_slot = slot_stack
@ -232,13 +231,12 @@ impl ScreenHandler for PlayerScreenHandler {
} }
let stack = slot_stack.clone(); let stack = slot_stack.clone();
drop(slot_stack); // release the lock before calling other methods
if stack.is_empty() { if stack.is_empty() {
slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone()) slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone())
.await; .await;
} else { } else {
slot.mark_dirty().await; slot.set_stack(stack.clone()).await;
} }
if stack.item_count == stack_prev.item_count { if stack.item_count == stack_prev.item_count {

View File

@ -725,8 +725,7 @@ pub trait ScreenHandler: Send + Sync {
}) })
{ {
let slot = self.get_behaviour().slots[current_index as usize].clone(); let slot = self.get_behaviour().slots[current_index as usize].clone();
let slot_stack_lock = slot.get_stack().await; let mut slot_stack = slot.get_stack().await;
let mut slot_stack = slot_stack_lock.lock().await;
if !slot_stack.is_empty() && slot_stack.are_items_and_components_equal(stack) { if !slot_stack.is_empty() && slot_stack.are_items_and_components_equal(stack) {
let combined_count = slot_stack.item_count + stack.item_count; 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 { if combined_count <= max_slot_count {
stack.set_count(0); stack.set_count(0);
slot_stack.set_count(combined_count); slot_stack.set_count(combined_count);
drop(slot_stack); slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
success = true; success = true;
} else if slot_stack.item_count < max_slot_count { } else if slot_stack.item_count < max_slot_count {
stack.decrement(max_slot_count - slot_stack.item_count); stack.decrement(max_slot_count - slot_stack.item_count);
slot_stack.set_count(max_slot_count); slot_stack.set_count(max_slot_count);
drop(slot_stack); slot.set_stack(slot_stack).await;
slot.mark_dirty().await;
success = true; success = true;
} }
} }
@ -768,11 +765,9 @@ pub trait ScreenHandler: Send + Sync {
} { } {
let slot = self.get_behaviour().slots[current_index as usize].clone(); let slot = self.get_behaviour().slots[current_index as usize].clone();
let slot_stack = slot.get_stack().await; let slot_stack = slot.get_stack().await;
let slot_stack = slot_stack.lock().await;
if slot_stack.is_empty() && slot.can_insert(stack).await { if slot_stack.is_empty() && slot.can_insert(stack).await {
let max_count = slot.get_max_item_count_for_stack(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))) slot.set_stack(stack.split(max_count.min(stack.item_count)))
.await; .await;
slot.mark_dirty().await; slot.mark_dirty().await;
@ -885,8 +880,7 @@ pub trait ScreenHandler: Send + Sync {
let cursor_stack = behaviour.cursor_stack.lock().await; let cursor_stack = behaviour.cursor_stack.lock().await;
let slot = &behaviour.slots[slot_index as usize]; let slot = &behaviour.slots[slot_index as usize];
let stack_lock = slot.get_stack().await; let stack = slot.get_stack().await;
let stack = stack_lock.lock().await;
if !cursor_stack.is_empty() if !cursor_stack.is_empty()
&& slot.can_insert(&cursor_stack).await && slot.can_insert(&cursor_stack).await
&& (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty()) && (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; let initial_count = cursor_stack.item_count;
for slot_index in &behaviour.drag_slots { for slot_index in &behaviour.drag_slots {
let slot = behaviour.slots[*slot_index as usize].clone(); let slot = behaviour.slots[*slot_index as usize].clone();
let stack_lock = slot.get_stack().await; let stack = slot.get_stack().await;
let stack = stack_lock.lock().await;
if (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty()) if (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty())
&& slot.can_insert(&cursor_stack).await && slot.can_insert(&cursor_stack).await
@ -942,13 +935,12 @@ pub trait ScreenHandler: Send + Sync {
)) ))
.min(cursor_stack.item_count); .min(cursor_stack.item_count);
if inserting_count > 0 { if inserting_count > 0 {
let mut stack_clone = stack.clone(); let mut new_stack = stack.clone();
drop(stack); if new_stack.is_empty() {
if stack_clone.is_empty() { new_stack = cursor_stack.copy_with_count(0);
stack_clone = cursor_stack.copy_with_count(0);
} }
stack_clone.increment(inserting_count); new_stack.increment(inserting_count);
slot.set_stack(stack_clone).await; slot.set_stack(new_stack).await;
if drag_button != 2 { if drag_button != 2 {
cursor_stack.decrement(inserting_count); cursor_stack.decrement(inserting_count);
} }
@ -999,8 +991,7 @@ pub trait ScreenHandler: Send + Sync {
return; return;
} }
let slot = behaviour.slots[slot_index as usize].clone(); let slot = behaviour.slots[slot_index as usize].clone();
let stack_lock = slot.get_stack().await; let stack = slot.get_stack().await;
let stack = stack_lock.lock().await;
*cursor_stack = stack.copy_with_count(stack.get_max_stack_size()); *cursor_stack = stack.copy_with_count(stack.get_max_stack_size());
} }
} else if (action_type == SlotActionType::Pickup } else if (action_type == SlotActionType::Pickup
@ -1064,32 +1055,32 @@ pub trait ScreenHandler: Send + Sync {
let mut intercepted = false; let mut intercepted = false;
if !cursor_stack.is_empty() { if !cursor_stack.is_empty() {
let stack_guard = slot.get_stack().await; let mut inner_slot_stack = slot.get_stack().await;
let mut inner_slot_stack = stack_guard.lock().await;
if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>() if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
&& bundle.try_insert(&mut cursor_stack) { && bundle.try_insert(&mut cursor_stack) {
slot.set_stack(inner_slot_stack).await;
intercepted = true; intercepted = true;
} }
} }
if !intercepted && !slot_stack.is_empty() if !intercepted && !slot_stack.is_empty()
&& let Some(bundle) = cursor_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>() { && let Some(bundle) = cursor_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>() {
let stack_guard = slot.get_stack().await; let mut inner_slot_stack = slot.get_stack().await;
let mut inner_slot_stack = stack_guard.lock().await;
if bundle.try_insert(&mut inner_slot_stack) { if bundle.try_insert(&mut inner_slot_stack) {
if inner_slot_stack.item_count == 0 { 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; intercepted = true;
} }
} }
if !intercepted && cursor_stack.is_empty() { if !intercepted && cursor_stack.is_empty() {
let stack_guard = slot.get_stack().await; let mut inner_slot_stack = slot.get_stack().await;
let mut inner_slot_stack = stack_guard.lock().await;
if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>() if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
&& let Some(extracted) = bundle.try_extract() { && let Some(extracted) = bundle.try_extract() {
*cursor_stack = extracted; *cursor_stack = extracted;
slot.set_stack(inner_slot_stack).await;
intercepted = true; intercepted = true;
} }
} }
@ -1226,13 +1217,7 @@ pub trait ScreenHandler: Send + Sync {
if slot_index < 0 { if slot_index < 0 {
return; return;
} }
let mut button_stack = player let mut button_stack = player.get_inventory().get_stack(button as usize).await;
.get_inventory()
.get_stack(button as usize)
.await
.lock()
.await
.clone();
let source_slot = self.get_behaviour().slots[slot_index as usize].clone(); let source_slot = self.get_behaviour().slots[slot_index as usize].clone();
let source_stack = source_slot.get_cloned_stack().await; let source_stack = source_slot.get_cloned_stack().await;

View File

@ -24,7 +24,6 @@ use std::{
Arc, Arc,
atomic::{AtomicU8, Ordering}, atomic::{AtomicU8, Ordering},
}, },
time::Duration,
}; };
use crate::screen_handler::InventoryPlayer; 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::Item;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_world::inventory::Inventory; use pumpkin_world::inventory::Inventory;
use tokio::{sync::Mutex, time::timeout};
/// Type alias for async slot operations. /// Type alias for async slot operations.
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
@ -101,37 +99,21 @@ pub trait Slot: Send + Sync {
} }
/// Gets the stack in this slot. /// Gets the stack in this slot.
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
// Default implementation logic: // Default implementation logic:
Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await }) Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await })
} }
/// Gets a copy of the stack in this slot. /// 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> { fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
// Default implementation logic: // Default implementation logic:
Box::pin(async move { Box::pin(async move { self.get_stack().await })
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()
})
} }
/// Checks if this slot has a non-empty stack. /// Checks if this slot has a non-empty stack.
fn has_stack(&self) -> BoxFuture<'_, bool> { fn has_stack(&self) -> BoxFuture<'_, bool> {
// Default implementation logic: // Default implementation logic:
Box::pin(async move { Box::pin(async move { !self.get_stack().await.is_empty() })
let inv = self.get_inventory();
!inv.get_stack(self.get_index())
.await
.lock()
.await
.is_empty()
})
} }
/// Sets the stack in this slot. /// Sets the stack in this slot.
@ -295,22 +277,18 @@ pub trait Slot: Send + Sync {
// Default implementation logic: // Default implementation logic:
Box::pin(async move { Box::pin(async move {
if !stack.is_empty() && self.can_insert(&stack).await { if !stack.is_empty() && self.can_insert(&stack).await {
let stack_mutex = self.get_stack().await; let mut stack_self = self.get_stack().await;
let mut stack_self = stack_mutex.lock().await;
let min_count = count let min_count = count
.min(stack.item_count) .min(stack.item_count)
.min(self.get_max_item_count_for_stack(&stack).await - stack_self.item_count); .min(self.get_max_item_count_for_stack(&stack).await - stack_self.item_count);
if min_count != 0 { if min_count != 0 {
if stack_self.is_empty() { if stack_self.is_empty() {
drop(stack_self);
self.set_stack(stack.split(min_count)).await; self.set_stack(stack.split(min_count)).await;
} else if stack.are_items_and_components_equal(&stack_self) { } else if stack.are_items_and_components_equal(&stack_self) {
stack.decrement(min_count); stack.decrement(min_count);
stack_self.increment(min_count); stack_self.increment(min_count);
let cloned_stack = stack_self.clone(); self.set_stack(stack_self).await;
drop(stack_self);
self.set_stack(cloned_stack).await;
} }
} }
} }

View File

@ -1,7 +1,6 @@
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, Ordering};
use tokio::sync::Mutex;
use crate::player::player_inventory::PlayerInventory; use crate::player::player_inventory::PlayerInventory;
use crate::screen_handler::{ use crate::screen_handler::{
@ -56,8 +55,7 @@ impl StonecutterScreenHandler {
} }
async fn update_output(&self) { async fn update_output(&self) {
let input_stack = self.input_inventory.get_stack(0).await; let input_lock = self.input_inventory.get_stack(0).await;
let input_lock = input_stack.lock().await;
if input_lock.is_empty() { if input_lock.is_empty() {
self.output_inventory self.output_inventory
@ -213,14 +211,7 @@ impl Slot for StonecutterOutputSlot {
stack.item_count as i32, stack.item_count as i32,
) )
.await; .await;
let input_stack = self.input_inventory.get_stack(0).await; self.input_inventory.remove_stack_specific(0, 1).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.mark_dirty().await; self.mark_dirty().await;
}) })
} }
@ -229,22 +220,16 @@ impl Slot for StonecutterOutputSlot {
Box::pin(async move { false }) Box::pin(async move { false })
} }
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.inventory.get_stack(self.index).await }) Box::pin(async move { self.inventory.get_stack(self.index).await })
} }
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move { self.inventory.get_stack(self.index).await })
let stack = self.inventory.get_stack(self.index).await;
stack.lock().await.clone()
})
} }
fn has_stack(&self) -> BoxFuture<'_, bool> { fn has_stack(&self) -> BoxFuture<'_, bool> {
Box::pin(async move { Box::pin(async move { !self.inventory.get_stack(self.index).await.is_empty() })
let stack = self.inventory.get_stack(self.index).await;
!stack.lock().await.is_empty()
})
} }
fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> { fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> {

View File

@ -14,3 +14,6 @@ serde_json = { workspace = true }
[package.metadata.component] [package.metadata.component]
target = { path = "../pumpkin-plugin-wit" } target = { path = "../pumpkin-plugin-wit" }
[lints]
workspace = true

View File

@ -1,5 +1,14 @@
//! Pumpkin plugin API. //! Pumpkin plugin API.
#![warn(missing_docs)] #![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 //! This crate provides everything needed to write a Pumpkin server plugin compiled
//! to WebAssembly. A plugin consists of a type that implements [`Plugin`], registered //! to WebAssembly. A plugin consists of a type that implements [`Plugin`], registered

View File

@ -16,16 +16,15 @@ impl<'a> DialogNBT<'a> {
Self(DialogNBTSource::Nbt(compound)) Self(DialogNBTSource::Nbt(compound))
} }
#[allow(clippy::todo)]
pub fn write_packet_data( pub fn write_packet_data(
&self, &self,
mut write: impl std::io::Write, mut write: impl std::io::Write,
_version: &JavaMinecraftVersion, _version: &JavaMinecraftVersion,
) -> Result<(), crate::ser::WritingError> { ) -> Result<(), crate::ser::WritingError> {
match &self.0 { match &self.0 {
DialogNBTSource::Struct(_dialog) => { DialogNBTSource::Struct(_dialog) => Err(crate::ser::WritingError::Message(
todo!("Serialize Dialog to NbtCompound manually") "Serializing Dialog struct to NBT is not supported".to_string(),
} )),
DialogNBTSource::Nbt(nbt) => { DialogNBTSource::Nbt(nbt) => {
let tag = pumpkin_nbt::tag::NbtTag::Compound((*nbt).clone()); let tag = pumpkin_nbt::tag::NbtTag::Compound((*nbt).clone());
write.write_nbt(tag)?; write.write_nbt(tag)?;

View File

@ -41,7 +41,6 @@ impl CUpdateAdvancements {
} }
impl ClientPacket for CUpdateAdvancements { impl ClientPacket for CUpdateAdvancements {
#[allow(clippy::unimplemented)]
fn write_packet_data( fn write_packet_data(
&self, &self,
mut write: impl std::io::Write, mut write: impl std::io::Write,

View File

@ -43,7 +43,9 @@ pub fn hash_seed(seed: u64) -> i64 {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(seed.to_le_bytes()); hasher.update(seed.to_le_bytes());
let result = hasher.finalize(); 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)] #[cfg(test)]

View File

@ -619,7 +619,7 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
) -> Result<(), ChunkWritingError> { ) -> Result<(), ChunkWritingError> {
let epoch = SystemTime::now() let epoch = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap_or_default()
.as_secs() as u32; .as_secs() as u32;
let index = Self::get_chunk_index(chunk.position().0, chunk.position().1); let index = Self::get_chunk_index(chunk.position().0, chunk.position().1);

View File

@ -719,7 +719,9 @@ impl ChunkData {
let mut has_found = [false, false, false]; let mut has_found = [false, false, false];
for y in (self.section.min_y..=start_height).rev() { 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); let block_state = BlockState::from_id(state_id);
for hm_type in [ for hm_type in [

View File

@ -7,7 +7,6 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
sync::Arc, sync::Arc,
}; };
use tokio::sync::{Mutex, OwnedMutexGuard};
pub type InventoryFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; pub type InventoryFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
@ -16,7 +15,7 @@ pub trait Inventory: Send + Sync + Clearable {
fn is_empty(&self) -> InventoryFuture<'_, bool>; fn is_empty(&self) -> InventoryFuture<'_, bool>;
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>>; fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>;
fn remove_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; let mut count = 0;
for i in 0..self.size() { for i in 0..self.size() {
let slot = self.get_stack(i).await; let stack = self.get_stack(i).await;
let stack = slot.lock().await;
if stack.get_item().id == item.id { if stack.get_item().id == item.id {
count += stack.item_count; count += stack.item_count;
} }
@ -49,13 +47,12 @@ pub trait Inventory: Send + Sync + Clearable {
fn contains_any_predicate<'a>( fn contains_any_predicate<'a>(
&'a self, &'a self,
predicate: &'a (dyn Fn(OwnedMutexGuard<ItemStack>) -> bool + Sync), predicate: &'a (dyn Fn(&ItemStack) -> bool + Sync),
) -> InventoryFuture<'a, bool> { ) -> InventoryFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
for i in 0..self.size() { for i in 0..self.size() {
let slot = self.get_stack(i).await; let stack = self.get_stack(i).await;
let stack = slot.lock_owned().await; if predicate(&stack) {
if predicate(stack) {
return true; return true;
} }
} }
@ -83,8 +80,7 @@ pub trait Inventory: Send + Sync + Clearable {
let size = self.size(); let size = self.size();
for i in 0..size { for i in 0..size {
let stack_lock = self.get_stack(i).await; let stack = self.get_stack(i).await;
let stack = stack_lock.lock().await;
if !stack.is_empty() { if !stack.is_empty() {
let mut item_compound = NbtCompound::new(); let mut item_compound = NbtCompound::new();
@ -106,7 +102,7 @@ pub trait Inventory: Send + Sync + Clearable {
fn mark_dirty(&self) {} fn mark_dirty(&self) {}
fn read_data(&self, nbt: &NbtCompound, stacks: &[Arc<Mutex<ItemStack>>]) { fn read_data(&self, nbt: &NbtCompound, stacks: &mut [ItemStack]) {
if let Some(inventory_list) = nbt.get_list("Items") { if let Some(inventory_list) = nbt.get_list("Items") {
for tag in inventory_list { for tag in inventory_list {
if let Some(item_compound) = tag.extract_compound() if let Some(item_compound) = tag.extract_compound()
@ -116,7 +112,7 @@ pub trait Inventory: Send + Sync + Clearable {
if slot < stacks.len() if slot < stacks.len()
&& let Some(item_stack) = ItemStack::read_item_stack(item_compound) && 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<Box<dyn Future<Output = ()> + Send + '_>>; fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
} }
pub fn sync_write_items_to_nbt(items: &[Arc<Mutex<ItemStack>>], nbt: &mut NbtCompound) { pub fn sync_write_items_to_nbt(items: &[ItemStack], nbt: &mut NbtCompound) {
let mut slots = Vec::new(); let mut slots = Vec::new();
for (i, item) in items.iter().enumerate() { for (i, stack) in items.iter().enumerate() {
match item.try_lock() { if !stack.is_empty() {
Ok(stack) if !stack.is_empty() => { let mut item_nbt = NbtCompound::new();
let mut item_nbt = NbtCompound::new(); item_nbt.put_byte("Slot", i as i8);
item_nbt.put_byte("Slot", i as i8); stack.write_item_stack(&mut item_nbt);
stack.write_item_stack(&mut item_nbt); slots.push(NbtTag::Compound(item_nbt));
slots.push(NbtTag::Compound(item_nbt));
}
Ok(_) => {}
Err(_) => {
tracing::warn!(
"Skipping contended inventory slot {i} while serializing block entity data"
);
}
} }
} }
if !slots.is_empty() { if !slots.is_empty() {

View File

@ -1,7 +1,3 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
#[expect(clippy::module_inception)] #[expect(clippy::module_inception)]
@ -11,11 +7,10 @@ mod simple_inventory;
pub use inventory::*; pub use inventory::*;
pub use simple_inventory::*; pub use simple_inventory::*;
// These are some utility functions found in Inventories.java // Utility functions for split_stack
pub async fn split_stack(stacks: &[Arc<Mutex<ItemStack>>], slot: usize, amount: u8) -> ItemStack { pub fn split_stack_slice(stacks: &mut [ItemStack], slot: usize, amount: u8) -> ItemStack {
let mut stack = stacks[slot].lock().await; if slot < stacks.len() && !stacks[slot].is_empty() && amount > 0 {
if slot < stacks.len() && !stack.is_empty() && amount > 0 { stacks[slot].split(amount)
stack.split(amount)
} else { } else {
ItemStack::EMPTY.clone() ItemStack::EMPTY.clone()
} }

View File

@ -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 pumpkin_data::item_stack::ItemStack;
use std::any::Any; use std::any::Any;
use std::sync::Arc; use tokio::sync::RwLock;
use tokio::sync::Mutex;
pub struct SimpleInventory { pub struct SimpleInventory {
pub stacks: Vec<Arc<Mutex<ItemStack>>>, pub stacks: RwLock<Vec<ItemStack>>,
} }
impl SimpleInventory { impl SimpleInventory {
#[must_use] #[must_use]
pub fn new(size: usize) -> Self { pub fn new(size: usize) -> Self {
Self { Self {
stacks: (0..size) stacks: RwLock::new(vec![ItemStack::EMPTY.clone(); size]),
.map(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
.collect(),
} }
} }
} }
@ -22,49 +19,62 @@ impl SimpleInventory {
impl Clearable for SimpleInventory { impl Clearable for SimpleInventory {
fn clear(&self) -> InventoryFuture<'_, ()> { fn clear(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
for stack in &self.stacks { let mut stacks = self.stacks.write().await;
*stack.lock().await = ItemStack::EMPTY.clone(); stacks.fill_with(|| ItemStack::EMPTY.clone());
}
}) })
} }
} }
impl Inventory for SimpleInventory { impl Inventory for SimpleInventory {
fn size(&self) -> usize { fn size(&self) -> usize {
self.stacks.len() self.stacks.blocking_read().len()
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for stack in &self.stacks { let stacks = self.stacks.read().await;
if !stack.lock().await.is_empty() { stacks.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.stacks[slot].clone() }) 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> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut stack = self.stacks[slot].lock().await; let mut stacks = self.stacks.write().await;
let result = stack.clone(); if slot < stacks.len() {
*stack = ItemStack::EMPTY.clone(); std::mem::replace(&mut stacks[slot], ItemStack::EMPTY.clone())
result } else {
ItemStack::EMPTY.clone()
}
}) })
} }
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { 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<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.stacks[slot].lock().await = stack; let mut stacks = self.stacks.write().await;
if slot < stacks.len() {
stacks[slot] = stack;
}
}) })
} }

View File

@ -91,7 +91,7 @@ impl BlockBehaviour for BrewingStandBlock {
// Bottle slots are 0, 1, 2 in brewing stands // Bottle slots are 0, 1, 2 in brewing stands
for slot in 0..3 { for slot in 0..3 {
let stack = inventory.get_stack(slot).await; let stack = inventory.get_stack(slot).await;
if !stack.lock().await.is_empty() { if !stack.is_empty() {
bottles += 1; bottles += 1;
} }
} }

View File

@ -115,9 +115,7 @@ impl BlockBehaviour for CakeBlock {
Box::pin(async move { Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position); let state_id = args.world.get_block_state_id(args.position);
let properties = CakeLikeProperties::from_state_id(state_id, args.block); let properties = CakeLikeProperties::from_state_id(state_id, args.block);
let item_lock = args.item_stack.lock().await; let item = args.item_stack.item;
let item = item_lock.item;
drop(item_lock);
match item.id { match item.id {
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => { id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
if properties.bites != 0 { if properties.bites != 0 {
@ -132,9 +130,7 @@ impl BlockBehaviour for CakeBlock {
} }
if args.player.gamemode.load() != GameMode::Creative { if args.player.gamemode.load() != GameMode::Creative {
let held_item = args.player.inventory.held_item(); args.item_stack.decrement(1);
let mut held_item_guard = held_item.lock().await;
held_item_guard.decrement(1);
} }
args.world args.world
.set_block_state( .set_block_state(

View File

@ -38,11 +38,12 @@ impl BlockBehaviour for CampfireBlock {
{ {
let has_frost_walker_enchantment = { let has_frost_walker_enchantment = {
let equipment = living_entity.entity_equipment.lock().await; let equipment = living_entity.entity_equipment.lock().await;
let boots = equipment.get(&EquipmentSlot::FEET); equipment
.equipment
let boots_stack = boots.lock().await; .get(&EquipmentSlot::FEET)
.is_some_and(|boots| {
boots_stack.get_enchantment_level(&Enchantment::FROST_WALKER) != 0 boots.get_enchantment_level(&Enchantment::FROST_WALKER) != 0
})
}; };
let has_fire_res = living_entity let has_fire_res = living_entity
.get_effect(&StatusEffect::FIRE_RESISTANCE) .get_effect(&StatusEffect::FIRE_RESISTANCE)

View File

@ -103,7 +103,7 @@ impl BlockBehaviour for CandleCakeBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { 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 { match item_id {
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => { id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
BlockActionResult::Pass BlockActionResult::Pass

View File

@ -53,9 +53,7 @@ impl BlockBehaviour for CandleBlock {
let state = args.world.get_block_state(args.position); let state = args.world.get_block_state(args.position);
let mut properties = CandleLikeProperties::from_state_id(state.id, args.block); let mut properties = CandleLikeProperties::from_state_id(state.id, args.block);
let item_lock = args.item_stack.lock().await; let item = args.item_stack.item;
let item = item_lock.item;
drop(item_lock);
match item.id { match item.id {
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id)

View File

@ -23,7 +23,6 @@ use pumpkin_data::{
use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use pumpkin_world::inventory::Inventory; use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
#[pumpkin_block("minecraft:chiseled_bookshelf")] #[pumpkin_block("minecraft:chiseled_bookshelf")]
pub struct ChiseledBookshelfBlock; pub struct ChiseledBookshelfBlock;
@ -81,8 +80,6 @@ impl BlockBehaviour for ChiseledBookshelfBlock {
if !args if !args
.item_stack .item_stack
.lock()
.await
.get_item() .get_item()
.has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS) .has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS)
{ {
@ -146,11 +143,10 @@ impl ChiseledBookshelfBlock {
entity: &ChiseledBookshelfBlockEntity, entity: &ChiseledBookshelfBlockEntity,
properties: ChiseledBookshelfLikeProperties, properties: ChiseledBookshelfLikeProperties,
slot: i8, slot: i8,
item: &Arc<Mutex<ItemStack>>, item: &mut ItemStack,
) { ) {
// TODO: Increment used stats for chiseled bookshelf on the player // 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 { let sound = if item.get_item() == &Item::ENCHANTED_BOOK {
Sound::BlockChiseledBookshelfPickupEnchanted Sound::BlockChiseledBookshelfPickupEnchanted
} else { } else {
@ -163,7 +159,9 @@ impl ChiseledBookshelfBlock {
item.split_unless_creative(player.gamemode.load(), 1), item.split_unless_creative(player.gamemode.load(), 1),
) )
.await; .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()); 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 // Drop the item on the ground if the player cannot hold it because of a full inventory
player.drop_item(stack).await; 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()); world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
} }

View File

@ -56,7 +56,7 @@ impl BlockBehaviour for ComposterBlock {
return BlockActionResult::Consume; 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; let item_id = item_stack.item.id;
// Check if the item is consumable by the composter // Check if the item is consumable by the composter

View File

@ -15,7 +15,7 @@ impl BlockBehaviour for FlowerPotBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
let item = args.item_stack.lock().await.item; let item = args.item_stack.item;
//Place the flower inside the pot //Place the flower inside the pot
let potted_block_id = get_potted_item(item.id); let potted_block_id = get_potted_item(item.id);
if args.block.eq(&Block::FLOWER_POT) { if args.block.eq(&Block::FLOWER_POT) {

View File

@ -128,7 +128,7 @@ impl BlockBehaviour for JukeboxBlock {
return BlockActionResult::PassToDefaultBlockAction; 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) // Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE)
let jukebox_playable = item_stack let jukebox_playable = item_stack

View File

@ -225,7 +225,7 @@ impl BlockBehaviour for LecternBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { 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) { if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }
@ -247,7 +247,7 @@ impl BlockBehaviour for LecternBlock {
}; };
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1); let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
drop(item_stack); let _ = item_stack;
lectern.set_stack(0, book).await; lectern.set_stack(0, book).await;
Self::set_has_book(args.world, args.position, true).await; Self::set_has_book(args.world, args.position, true).await;

View File

@ -32,9 +32,12 @@ impl BlockBehaviour for MagmaBlock {
let has_frost_walker = { let has_frost_walker = {
let equipment = living_entity.entity_equipment.lock().await; let equipment = living_entity.entity_equipment.lock().await;
let boots = equipment.get(&EquipmentSlot::FEET); equipment
let boots_stack = boots.lock().await; .equipment
boots_stack.get_enchantment_level(&Enchantment::FROST_WALKER) != 0 .get(&EquipmentSlot::FEET)
.is_some_and(|boots| {
boots.get_enchantment_level(&Enchantment::FROST_WALKER) != 0
})
}; };
if has_frost_walker { if has_frost_walker {
return; return;

View File

@ -65,7 +65,7 @@ impl BlockBehaviour for BambooBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
let lock = args.item_stack.lock().await; let lock = &args.item_stack;
if lock.get_item() == &Item::BONE_MEAL { if lock.get_item() == &Item::BONE_MEAL {
bone_meal(Arc::clone(args.world), args.position).await; bone_meal(Arc::clone(args.world), args.position).await;
return BlockActionResult::Success; return BlockActionResult::Success;

View File

@ -28,7 +28,7 @@ impl BlockBehaviour for BambooSaplingBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
let lock = args.item_stack.lock().await; let lock = &args.item_stack;
if lock.get_item() == &Item::BONE_MEAL { if lock.get_item() == &Item::BONE_MEAL {
let mut props_new = BambooLikeProperties::from_state_id( let mut props_new = BambooLikeProperties::from_state_id(
Block::BAMBOO.default_state.id, Block::BAMBOO.default_state.id,

View File

@ -65,7 +65,7 @@ impl BlockBehaviour for SweetBerryBushBlock {
Box::pin(async move { Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position); let state_id = args.world.get_block_state_id(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH); 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 BlockActionResult::Pass
} else { } else {
BlockActionResult::PassToDefaultBlockAction BlockActionResult::PassToDefaultBlockAction

View File

@ -28,7 +28,7 @@ impl BlockBehaviour for SeaPickleBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
if args.item_stack.lock().await.item != &Item::BONE_MEAL if args.item_stack.item != &Item::BONE_MEAL
|| !args || !args
.world .world
.get_block(&args.position.down()) .get_block(&args.position.down())

View File

@ -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 equipment = living.entity_equipment.lock().await;
let boots = equipment.get(&EquipmentSlot::FEET); equipment
let boots = boots.lock().await; .equipment
boots.item == &Item::LEATHER_BOOTS .get(&EquipmentSlot::FEET)
.is_some_and(|boots| boots.item == &Item::LEATHER_BOOTS)
} }
fn is_entity_above_block(entity: &crate::entity::Entity, position: &BlockPos) -> bool { fn is_entity_above_block(entity: &crate::entity::Entity, position: &BlockPos) -> bool {

View File

@ -20,7 +20,7 @@ impl crate::block::BlockBehaviour for PumpkinBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
if args.item_stack.lock().await.item != &Item::SHEARS { if args.item_stack.item != &Item::SHEARS {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }
let mut props = WallTorchLikeProperties::default(&Block::CARVED_PUMPKIN); let mut props = WallTorchLikeProperties::default(&Block::CARVED_PUMPKIN);

View File

@ -202,7 +202,7 @@ impl BlockBehaviour for CrafterBlock {
let mut occupied = 0u8; let mut occupied = 0u8;
for i in 0..9 { for i in 0..9 {
let stack = crafter.get_stack(i).await; let stack = crafter.get_stack(i).await;
if !stack.lock().await.is_empty() { if !stack.is_empty() {
occupied += 1; occupied += 1;
} }
} }

View File

@ -191,7 +191,7 @@ impl BlockBehaviour for DispenserBlock {
return; 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( let props = DispenserLikeProperties::from_state_id(
args.world.get_block_state(args.position).id, args.world.get_block_state(args.position).id,
args.block, args.block,
@ -230,6 +230,7 @@ impl BlockBehaviour for DispenserBlock {
// Default / Drop // Default / Drop
Self::drop_item(&ctx, &mut item).await; Self::drop_item(&ctx, &mut item).await;
} }
dispenser.set_stack(slot_index, item).await;
} else { } else {
args.world args.world
.sync_world_event(WorldEvent::SoundDispenserFail, *args.position, 0); .sync_world_event(WorldEvent::SoundDispenserFail, *args.position, 0);

View File

@ -158,7 +158,7 @@ impl BlockBehaviour for DropperBlock {
return; 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( let props = DispenserLikeProperties::from_state_id(
args.world.get_block_state(args.position).id, args.world.get_block_state(args.position).id,
args.block, args.block,
@ -177,15 +177,17 @@ impl BlockBehaviour for DropperBlock {
if HopperBlockEntity::add_one_item(dropper, container.as_ref(), one_item) if HopperBlockEntity::add_one_item(dropper, container.as_ref(), one_item)
.await .await
{ {
dropper.set_stack(slot_index, item).await;
return; return;
} }
*item = backup; dropper.set_stack(slot_index, backup).await;
return; return;
} }
// No container found, dispense item into the world // No container found, dispense item into the world
let drop_item = item.split(1); let drop_item = item.split(1);
dropper.set_stack(slot_index, item).await;
let facing = to_normal(props.facing); let facing = to_normal(props.facing);
let mut position = args.position.to_centered_f64().add(&(facing * 0.7)); let mut position = args.position.to_centered_f64().add(&(facing * 0.7));

View File

@ -85,14 +85,7 @@ impl BlockBehaviour for TripwireBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { Box::pin(async move {
let has_shears = { let has_shears = args.player.inventory().held_item().await.get_item() == &Item::SHEARS;
let main_hand_item_stack = args.player.inventory().held_item();
main_hand_item_stack
.lock()
.await
.get_item()
.eq(&Item::SHEARS)
};
if has_shears { if has_shears {
let mut props = TripwireProperties::from_state_id(args.state.id, args.block); let mut props = TripwireProperties::from_state_id(args.state.id, args.block);
props.disarmed = true; props.disarmed = true;

View File

@ -474,7 +474,6 @@ impl BlockBehaviour for SignBlock {
} }
/// Handles use with an item on the sign block. /// Handles use with an item on the sign block.
#[expect(clippy::option_if_let_else)]
fn use_with_item<'a>( fn use_with_item<'a>(
&'a self, &'a self,
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
@ -508,9 +507,10 @@ impl BlockBehaviour for SignBlock {
&sign_entity.back_text &sign_entity.back_text
}; };
let mut item = args.item_stack.lock().await; let Some(pumpkin_item) = args
.server
let Some(pumpkin_item) = args.server.item_registry.get_pumpkin_item(item.item.id) .item_registry
.get_pumpkin_item(args.item_stack.item.id)
else { else {
return BlockActionResult::PassToDefaultBlockAction; return BlockActionResult::PassToDefaultBlockAction;
}; };
@ -532,8 +532,12 @@ impl BlockBehaviour for SignBlock {
} else if let Some(dye) = } else if let Some(dye) =
pumpkin_item.as_any().downcast_ref::<DyeItem>() pumpkin_item.as_any().downcast_ref::<DyeItem>()
{ {
let color_name = let color_name = args
item.item.registry_key.strip_suffix("_dye").unwrap(); .item_stack
.item
.registry_key
.strip_suffix("_dye")
.unwrap();
dye.apply_to_sign(&args, &block_entity, text, color_name) dye.apply_to_sign(&args, &block_entity, text, color_name)
} else { } else {
BlockActionResult::PassToDefaultBlockAction BlockActionResult::PassToDefaultBlockAction
@ -558,7 +562,7 @@ impl BlockBehaviour for SignBlock {
args.player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::GlowedSign).await; args.player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::GlowedSign).await;
} }
if !args.player.has_infinite_materials() { if !args.player.has_infinite_materials() {
item.decrement(1); args.item_stack.decrement(1);
} }
*currently_editing = None; *currently_editing = None;
} }

View File

@ -33,10 +33,7 @@ impl BlockBehaviour for LayeredSnowBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
let item = { let item = args.item_stack.item;
let lock = args.item_stack.lock().await;
lock.item
};
if item == &Item::SNOW { if item == &Item::SNOW {
let pos = if args.hit.face.is_horizontal() { let pos = if args.hit.face.is_horizontal() {

View File

@ -48,7 +48,7 @@ impl BlockBehaviour for TNTBlock {
args: UseWithItemArgs<'a>, args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> { ) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { 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 { if item != &Item::FLINT_AND_STEEL || item == &Item::FIRE_CHARGE {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }

View File

@ -108,9 +108,7 @@ impl BlockBehaviour for VineBlock {
let state = args.world.get_block_state(args.position); let state = args.world.get_block_state(args.position);
let mut props = VineLikeProperties::from_state_id(state.id, args.block); let mut props = VineLikeProperties::from_state_id(state.id, args.block);
let item_lock = args.item_stack.lock().await; let item = args.item_stack.item;
let item = item_lock.item;
drop(item_lock);
if item.id != Item::VINE.id { if item.id != Item::VINE.id {
return BlockActionResult::Pass; return BlockActionResult::Pass;

View File

@ -15,22 +15,19 @@ use std::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
}, },
}; };
use tokio::sync::Mutex;
use crate::block::viewer::{ use crate::block::viewer::{
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture, ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
}; };
use crate::world::{BlockFlags, World}; use crate::world::{BlockFlags, World};
use pumpkin_world::inventory::InventoryFuture; use pumpkin_world::inventory::InventoryFuture;
use pumpkin_world::inventory::{ use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
split_stack, sync_write_items_to_nbt, {Clearable, Inventory},
};
use super::BlockEntity; use super::BlockEntity;
pub struct BarrelBlockEntity { pub struct BarrelBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
// Viewer // Viewer
@ -52,12 +49,12 @@ impl BlockEntity for BarrelBlockEntity {
{ {
let barrel = Self { let barrel = Self {
position, 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), dirty: AtomicBool::new(false),
viewers: ViewerCountTracker::new(), viewers: ViewerCountTracker::new(),
}; };
barrel.read_data(nbt, &barrel.items); barrel.read_data(nbt, &mut *barrel.items.blocking_write());
barrel barrel
} }
@ -91,7 +88,7 @@ impl BlockEntity for BarrelBlockEntity {
fn chunk_data_nbt(&self) -> Option<NbtCompound> { fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new(); 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) Some(nbt)
} }
@ -132,7 +129,7 @@ impl BarrelBlockEntity {
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, 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), dirty: AtomicBool::new(false),
viewers: ViewerCountTracker::new(), viewers: ViewerCountTracker::new(),
} }
@ -177,30 +174,27 @@ impl BarrelBlockEntity {
impl Inventory for BarrelBlockEntity { impl Inventory for BarrelBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -208,7 +202,12 @@ impl Inventory for BarrelBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -216,7 +215,8 @@ impl Inventory for BarrelBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -245,9 +245,8 @@ impl Inventory for BarrelBlockEntity {
impl Clearable for BarrelBlockEntity { impl Clearable for BarrelBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -300,12 +300,12 @@ impl Inventory for BeaconBlockEntity {
Box::pin(async move { self.payment.lock().await.is_empty() }) Box::pin(async move { self.payment.lock().await.is_empty() })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
if slot == 0 { if slot == 0 {
self.payment.clone() self.payment.lock().await.clone()
} else { } else {
Arc::new(Mutex::new(ItemStack::EMPTY.clone())) ItemStack::EMPTY.clone()
} }
}) })
} }

View File

@ -12,7 +12,6 @@ use std::{
use pumpkin_data::recipes::CookingRecipeKind; use pumpkin_data::recipes::CookingRecipeKind;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::{ use crate::{
block::entities::furnace_like_block_entity::CookingBlockEntityBase, block::entities::furnace_like_block_entity::CookingBlockEntityBase,
@ -30,7 +29,7 @@ pub struct BlastingFurnaceBlockEntity {
pub lit_time_remaining: AtomicU16, pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16, pub lit_total_time: AtomicU16,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format) /// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
/// Maps result item ID -> craft count /// Maps result item ID -> craft count
@ -46,7 +45,7 @@ impl BlastingFurnaceBlockEntity {
Self { Self {
position, position,
dirty: AtomicBool::new(false), 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_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0), cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0), lit_total_time: AtomicU16::new(0),

View File

@ -19,11 +19,11 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::inventory::{Inventory, sync_write_items_to_nbt}; use pumpkin_world::inventory::{Inventory, sync_write_items_to_nbt};
use tokio::sync::Mutex; use tokio::sync::RwLock;
pub struct BrewingStandBlockEntity { pub struct BrewingStandBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
pub brew_time: AtomicI32, pub brew_time: AtomicI32,
pub fuel: AtomicI32, pub fuel: AtomicI32,
@ -40,7 +40,7 @@ impl BrewingStandBlockEntity {
use std::array::from_fn; use std::array::from_fn;
Self { Self {
position, position,
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
brew_time: AtomicI32::new(0), brew_time: AtomicI32::new(0),
fuel: AtomicI32::new(0), fuel: AtomicI32::new(0),
@ -66,8 +66,9 @@ impl BrewingStandBlockEntity {
let ingredient_id = ingredient.get_item().id; let ingredient_id = ingredient.get_item().id;
// Check potion recipes (water bottle -> potions, potion upgrades, etc.) // Check potion recipes (water bottle -> potions, potion upgrades, etc.)
let items = self.items.read().await;
for slot_idx in 0..3usize { for slot_idx in 0..3usize {
let slot = self.items[slot_idx].lock().await; let slot = &items[slot_idx];
if slot.is_empty() { if slot.is_empty() {
continue; continue;
} }
@ -105,7 +106,8 @@ impl BrewingStandBlockEntity {
// Apply recipes to each slot // Apply recipes to each slot
for slot_idx in 0..3usize { 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() { if slot.is_empty() {
continue; continue;
} }
@ -167,8 +169,7 @@ impl BrewingStandBlockEntity {
} }
} }
// Drop the lock before calling set_stack drop(items);
drop(slot);
// Update the slot using set_stack if a recipe was applied // Update the slot using set_stack if a recipe was applied
if let Some(new_stack) = new_stack_opt { if let Some(new_stack) = new_stack_opt {
@ -177,13 +178,10 @@ impl BrewingStandBlockEntity {
} }
// Consume ingredient // Consume ingredient
let mut ingredient_slot = self.items[3].lock().await; let mut items = self.items.write().await;
ingredient_slot.decrement(1); items[3].decrement(1);
let updated_ingredient = ingredient_slot.clone(); self.mark_dirty();
drop(ingredient_slot); drop(items);
// Update the slot with the decremented stack
self.set_stack(3, updated_ingredient).await;
// Play sound at the center of the block // Play sound at the center of the block
let pos = Vector3::new( let pos = Vector3::new(
@ -208,8 +206,9 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> { fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { for slot in items.iter() {
if !slot.is_empty() {
return false; return false;
} }
} }
@ -217,11 +216,11 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
}) })
} }
fn get_stack( fn get_stack(&self, slot: usize) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
&self, Box::pin(async move {
slot: usize, let items = self.items.read().await;
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> { items[slot].clone()
Box::pin(async move { self.items[slot].clone() }) })
} }
fn remove_stack( fn remove_stack(
@ -229,9 +228,9 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
slot: usize, slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard); self.mark_dirty();
removed removed
}) })
} }
@ -242,15 +241,16 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
amount: u8, amount: u8,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut guard = self.items[slot].lock().await; let mut items = self.items.write().await;
let mut taken = ItemStack::EMPTY.clone(); let taken = if items[slot].item_count <= amount {
if guard.item_count <= amount { std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
std::mem::swap(&mut taken, &mut *guard);
} else { } else {
taken = guard.clone(); let mut taken = items[slot].clone();
taken.item_count = amount; taken.item_count = amount;
guard.item_count -= amount; items[slot].item_count -= amount;
} taken
};
self.mark_dirty();
taken taken
}) })
} }
@ -261,7 +261,8 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
stack: ItemStack, stack: ItemStack,
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -311,10 +312,8 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
impl pumpkin_world::inventory::Clearable for BrewingStandBlockEntity { impl pumpkin_world::inventory::Clearable for BrewingStandBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
let mut guard = slot.lock().await; items.fill_with(|| ItemStack::EMPTY.clone());
guard.clear();
}
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -344,27 +343,29 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
} }
// Load inventory items from NBT // 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 there's an ingredient in slot 3, remember its base item for matching
if let Ok(guard) = entity.items[3].try_lock() if !items_guard[3].is_empty() {
&& !guard.is_empty()
{
*entity *entity
.ingredient_item .ingredient_item
.lock() .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 // Recompute last_potion_count so visuals are correct after load
let mut current: [bool; 3] = [false; 3]; let mut current: [bool; 3] = [false; 3];
for (i, slot_arc) in entity.items.iter().take(3).enumerate() { for (i, slot) in items_guard.iter().take(3).enumerate() {
if let Ok(slot) = slot_arc.try_lock() { current[i] = !slot.is_empty()
current[i] = !slot.is_empty() && (slot
&& (slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().is_some() .get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
|| slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id); .is_some()
} || slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id);
} }
drop(items_guard);
*entity *entity
.last_potion_count .last_potion_count
.lock() .lock()
@ -395,7 +396,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed)); nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed));
nbt.put_int("Fuel", self.fuel.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) Some(nbt)
} }
@ -418,15 +419,14 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
Box::pin(async move { Box::pin(async move {
// Refill fuel counter from fuel item if needed // Refill fuel counter from fuel item if needed
let fuel_refilled = if self.fuel.load(Ordering::Relaxed) <= 0 { let fuel_refilled = if self.fuel.load(Ordering::Relaxed) <= 0 {
let fuel_stack_arc = self.items[4].clone(); let mut items = self.items.write().await;
let mut fuel_stack = fuel_stack_arc.lock().await; if !items[4].is_empty()
if !fuel_stack.is_empty() && items[4]
&& fuel_stack
.get_item() .get_item()
.has_tag(&tag::Item::MINECRAFT_BREWING_FUEL) .has_tag(&tag::Item::MINECRAFT_BREWING_FUEL)
{ {
self.fuel.store(20, Ordering::Relaxed); self.fuel.store(20, Ordering::Relaxed);
fuel_stack.decrement(1); items[4].decrement(1);
true true
} else { } else {
false false
@ -436,7 +436,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
}; };
// Get current ingredient and check brewing state // 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 brewable = self.is_brewable(&ingredient).await;
let is_brewing = self.brew_time.load(Ordering::Relaxed) > 0; 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. // Ensure clients are notified when potion slot contents (and their data) change.
// Compute current presence bits for the three bottle slots // Compute current presence bits for the three bottle slots
let mut current: [bool; 3] = [false; 3]; let mut current: [bool; 3] = [false; 3];
for (i, slot_arc) in self.items.iter().take(3).enumerate() { let items_guard = self.items.read().await;
let slot = slot_arc.lock().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 // 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::<pumpkin_data::data_component_impl::PotionContentsImpl>().is_some() || slot.get_item().id == Item::GLASS_BOTTLE.id); current[i] = !slot.is_empty() && (slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().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 // If potion presence changed, update last_potion_count and update block state so clients
let mut needs_update = false; let mut needs_update = false;

View File

@ -4,8 +4,6 @@ use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::{ use crate::{
block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods, 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, impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest,
@ -13,7 +11,7 @@ use crate::{
pub struct ChestBlockEntity { pub struct ChestBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
// Viewer // Viewer

View File

@ -26,7 +26,7 @@ macro_rules! impl_block_entity_for_chest {
let chest = Self { let chest = Self {
position, 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), dirty: std::sync::atomic::AtomicBool::new(false),
viewers: $crate::block::viewer::ViewerCountTracker::new(), viewers: $crate::block::viewer::ViewerCountTracker::new(),
loot_table: StdMutex::new(loot_table_key), 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. // 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() { 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 chest
@ -104,7 +104,8 @@ macro_rules! impl_block_entity_for_chest {
nbt.put_long("LootTableSeed", self.loot_table_seed); nbt.put_long("LootTableSeed", self.loot_table_seed);
} }
} else { } 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) Some(nbt)
} }
@ -131,26 +132,24 @@ macro_rules! impl_inventory_for_chest {
($struct_name:ty) => { ($struct_name:ty) => {
impl pumpkin_world::inventory::Inventory for $struct_name { impl pumpkin_world::inventory::Inventory for $struct_name {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> { fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(|s| s.is_empty())
return false;
}
}
true
}) })
} }
fn get_stack( fn get_stack(
&self, &self,
slot: usize, slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack( fn remove_stack(
@ -158,9 +157,8 @@ macro_rules! impl_inventory_for_chest {
slot: usize, slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -172,8 +170,12 @@ macro_rules! impl_inventory_for_chest {
amount: u8, amount: u8,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let res = let mut items = self.items.write().await;
pumpkin_world::inventory::split_stack(&self.items, slot, amount).await; let res = if !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
};
self.mark_dirty(); self.mark_dirty();
res res
}) })
@ -185,7 +187,8 @@ macro_rules! impl_inventory_for_chest {
stack: ItemStack, stack: ItemStack,
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -222,9 +225,8 @@ macro_rules! impl_clearable_for_chest {
&self, &self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> { ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
<$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self); <$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self);
}) })
} }
@ -317,7 +319,7 @@ macro_rules! impl_chest_helper_methods {
Self { Self {
position, 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), dirty: AtomicBool::new(false),
viewers: $crate::block::viewer::ViewerCountTracker::new(), viewers: $crate::block::viewer::ViewerCountTracker::new(),
loot_table: StdMutex::new(None), loot_table: StdMutex::new(None),

View File

@ -4,6 +4,7 @@ use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use std::any::Any; use std::any::Any;
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::{ use std::{
array::from_fn, array::from_fn,
@ -12,7 +13,6 @@ use std::{
atomic::{AtomicBool, AtomicI8, Ordering}, atomic::{AtomicBool, AtomicI8, Ordering},
}, },
}; };
use tokio::sync::Mutex;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
@ -20,11 +20,11 @@ use crate::{
world::{BlockFlags, World}, world::{BlockFlags, World},
}; };
use pumpkin_world::inventory::InventoryFuture; 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 struct ChiseledBookshelfBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub last_interacted_slot: AtomicI8, pub last_interacted_slot: AtomicI8,
pub dirty: AtomicBool, pub dirty: AtomicBool,
} }
@ -44,18 +44,20 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
where where
Self: Sized, Self: Sized,
{ {
let chiseled_bookshelf = Self { let bookshelf = Self {
position, 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( last_interacted_slot: AtomicI8::new(-1),
nbt.get_int(LAST_INTERACTED_SLOT).unwrap_or(-1) as i8
),
dirty: AtomicBool::new(false), 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); bookshelf
chiseled_bookshelf
} }
fn write_nbt<'a>( fn write_nbt<'a>(
@ -63,19 +65,17 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
nbt: &'a mut NbtCompound, nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move { Box::pin(async move {
// Write inventory data to NBT let items = self.items.read().await;
self.write_inventory_nbt(nbt, true).await; sync_write_items_to_nbt(items.as_slice(), nbt);
// Save last interacted slot
nbt.put_int( nbt.put_int(
LAST_INTERACTED_SLOT, 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<Self>) -> Option<Arc<dyn Inventory>> { fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
Some(self) Some(self as Arc<dyn Inventory>)
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
@ -86,16 +86,6 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
self.dirty.store(false, Ordering::Relaxed); self.dirty.store(false, Ordering::Relaxed);
} }
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
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 { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
@ -109,7 +99,7 @@ impl ChiseledBookshelfBlockEntity {
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, 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), last_interacted_slot: AtomicI8::new(-1),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
} }
@ -119,23 +109,29 @@ impl ChiseledBookshelfBlockEntity {
&self, &self,
mut properties: ChiseledBookshelfLikeProperties, mut properties: ChiseledBookshelfLikeProperties,
world: Arc<World>, world: Arc<World>,
slot: i8, slot: usize,
) { ) {
if slot >= 0 && slot < self.items.len() as i8 { if (0..Self::INVENTORY_SIZE).contains(&slot) {
self.last_interacted_slot.store(slot, Ordering::Relaxed); self.last_interacted_slot
.store(slot as i8, Ordering::Relaxed);
self.mark_dirty();
properties.slot_0_occupied = !self.items[0].lock().await.is_empty(); let occupied = !self.get_stack(slot).await.is_empty();
properties.slot_1_occupied = !self.items[1].lock().await.is_empty(); match slot {
properties.slot_2_occupied = !self.items[2].lock().await.is_empty(); 0 => properties.slot_0_occupied = occupied,
properties.slot_3_occupied = !self.items[3].lock().await.is_empty(); 1 => properties.slot_1_occupied = occupied,
properties.slot_4_occupied = !self.items[4].lock().await.is_empty(); 2 => properties.slot_2_occupied = occupied,
properties.slot_5_occupied = !self.items[5].lock().await.is_empty(); 3 => properties.slot_3_occupied = occupied,
4 => properties.slot_4_occupied = occupied,
5 => properties.slot_5_occupied = occupied,
_ => {}
}
world world
.set_block_state( .set_block_state(
&self.position, &self.position,
properties.to_state_id(&Block::CHISELED_BOOKSHELF), properties.to_state_id(&Block::CHISELED_BOOKSHELF),
BlockFlags::NOTIFY_ALL, BlockFlags::NOTIFY_LISTENERS,
) )
.await; .await;
} else { } else {
@ -149,30 +145,27 @@ impl ChiseledBookshelfBlockEntity {
impl Inventory for ChiseledBookshelfBlockEntity { impl Inventory for ChiseledBookshelfBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -180,7 +173,12 @@ impl Inventory for ChiseledBookshelfBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -188,7 +186,8 @@ impl Inventory for ChiseledBookshelfBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -205,9 +204,8 @@ impl Inventory for ChiseledBookshelfBlockEntity {
impl Clearable for ChiseledBookshelfBlockEntity { impl Clearable for ChiseledBookshelfBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -2,19 +2,16 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt,
};
use std::any::Any; use std::any::Any;
use std::array::from_fn; use std::array::from_fn;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use tokio::sync::Mutex;
pub struct CrafterBlockEntity { pub struct CrafterBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub crafting_ticks_remaining: AtomicI32, pub crafting_ticks_remaining: AtomicI32,
pub triggered: AtomicBool, pub triggered: AtomicBool,
pub dirty: AtomicBool, pub dirty: AtomicBool,
@ -26,7 +23,8 @@ impl BlockEntity for CrafterBlockEntity {
nbt: &'a mut NbtCompound, nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move { 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( nbt.put_int(
"crafting_ticks_remaining", "crafting_ticks_remaining",
self.crafting_ticks_remaining.load(Ordering::Relaxed), self.crafting_ticks_remaining.load(Ordering::Relaxed),
@ -41,7 +39,7 @@ impl BlockEntity for CrafterBlockEntity {
{ {
let crafter = Self { let crafter = Self {
position, 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( crafting_ticks_remaining: AtomicI32::new(
nbt.get_int("crafting_ticks_remaining").unwrap_or(0), nbt.get_int("crafting_ticks_remaining").unwrap_or(0),
), ),
@ -49,8 +47,7 @@ impl BlockEntity for CrafterBlockEntity {
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
}; };
crafter.read_data(nbt, &crafter.items); // Note: read_data needs to be adapted or handled via NBT accessors
crafter crafter
} }
@ -76,7 +73,8 @@ impl BlockEntity for CrafterBlockEntity {
fn chunk_data_nbt(&self) -> Option<NbtCompound> { fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new(); 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( nbt.put_int(
"crafting_ticks_remaining", "crafting_ticks_remaining",
self.crafting_ticks_remaining.load(Ordering::Relaxed), self.crafting_ticks_remaining.load(Ordering::Relaxed),
@ -98,7 +96,7 @@ impl CrafterBlockEntity {
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, 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), crafting_ticks_remaining: AtomicI32::new(0),
triggered: AtomicBool::new(false), triggered: AtomicBool::new(false),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
@ -108,30 +106,27 @@ impl CrafterBlockEntity {
impl Inventory for CrafterBlockEntity { impl Inventory for CrafterBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -139,7 +134,12 @@ impl Inventory for CrafterBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -147,7 +147,8 @@ impl Inventory for CrafterBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -164,9 +165,8 @@ impl Inventory for CrafterBlockEntity {
impl Clearable for CrafterBlockEntity { impl Clearable for CrafterBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -2,20 +2,17 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt,
};
use rand::{RngExt, rng}; use rand::{RngExt, rng};
use std::any::Any; use std::any::Any;
use std::array::from_fn; use std::array::from_fn;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::{Mutex, MutexGuard};
pub struct DispenserBlockEntity { pub struct DispenserBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
} }
@ -33,11 +30,11 @@ impl BlockEntity for DispenserBlockEntity {
{ {
let dispenser = Self { let dispenser = Self {
position, 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), dirty: AtomicBool::new(false),
}; };
dispenser.read_data(nbt, &dispenser.items); dispenser.read_data(nbt, &mut *dispenser.items.blocking_write());
dispenser dispenser
} }
@ -64,7 +61,8 @@ impl BlockEntity for DispenserBlockEntity {
fn chunk_data_nbt(&self) -> Option<NbtCompound> { fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new(); 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) Some(nbt)
} }
@ -81,52 +79,51 @@ impl DispenserBlockEntity {
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, 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), dirty: AtomicBool::new(false),
} }
} }
pub async fn get_random_slot(&self) -> Option<MutexGuard<'_, ItemStack>> {
let mut ret = None; pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
let mut j = 1; let items = self.items.read().await;
for i in &self.items { let mut non_empty = Vec::new();
let item = i.lock().await; for (i, stack) in items.iter().enumerate() {
if !item.is_empty() { if !stack.is_empty() {
if rng().random_range(0..j) == 0 { non_empty.push((i, stack.clone()));
ret = Some(item);
}
j += 1;
} }
} }
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 { impl Inventory for DispenserBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -134,7 +131,12 @@ impl Inventory for DispenserBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -142,7 +144,8 @@ impl Inventory for DispenserBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -159,9 +162,8 @@ impl Inventory for DispenserBlockEntity {
impl Clearable for DispenserBlockEntity { impl Clearable for DispenserBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -2,20 +2,17 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt,
};
use rand::{RngExt, rng}; use rand::{RngExt, rng};
use std::any::Any; use std::any::Any;
use std::array::from_fn; use std::array::from_fn;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::{Mutex, MutexGuard};
pub struct DropperBlockEntity { pub struct DropperBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
} }
@ -33,11 +30,11 @@ impl BlockEntity for DropperBlockEntity {
{ {
let dropper = Self { let dropper = Self {
position, 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), dirty: AtomicBool::new(false),
}; };
dropper.read_data(nbt, &dropper.items); dropper.read_data(nbt, &mut *dropper.items.blocking_write());
dropper dropper
} }
@ -64,7 +61,8 @@ impl BlockEntity for DropperBlockEntity {
fn chunk_data_nbt(&self) -> Option<NbtCompound> { fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new(); 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) Some(nbt)
} }
@ -81,53 +79,51 @@ impl DropperBlockEntity {
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, 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), dirty: AtomicBool::new(false),
} }
} }
pub async fn get_random_slot(&self) -> Option<MutexGuard<'_, ItemStack>> {
// this.unpackLootTable(null); pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
let mut ret = None; let items = self.items.read().await;
let mut j = 1; let mut non_empty = Vec::new();
for i in &self.items { for (i, stack) in items.iter().enumerate() {
let item = i.lock().await; if !stack.is_empty() {
if !item.is_empty() { non_empty.push((i, stack.clone()));
if rng().random_range(0..j) == 0 {
ret = Some(item);
}
j += 1;
} }
} }
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 { impl Inventory for DropperBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -135,7 +131,12 @@ impl Inventory for DropperBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -143,7 +144,8 @@ impl Inventory for DropperBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -160,9 +162,8 @@ impl Inventory for DropperBlockEntity {
impl Clearable for DropperBlockEntity { impl Clearable for DropperBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -12,7 +12,6 @@ use std::{
use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipeKind}; use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipeKind};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::{ use crate::{
block::entities::furnace_like_block_entity::CookingBlockEntityBase, block::entities::furnace_like_block_entity::CookingBlockEntityBase,
@ -30,7 +29,7 @@ pub struct FurnaceBlockEntity {
pub lit_time_remaining: AtomicU16, pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16, pub lit_total_time: AtomicU16,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format) /// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
/// Maps result item ID -> craft count /// Maps result item ID -> craft count
@ -46,7 +45,7 @@ impl FurnaceBlockEntity {
Self { Self {
position, position,
dirty: AtomicBool::new(false), 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_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0), cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0), lit_total_time: AtomicU16::new(0),

View File

@ -1,7 +1,4 @@
use std::sync::Arc;
use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipe}; use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipe};
use tokio::sync::Mutex;
use crate::block::entities::{BlockEntity, PropertyDelegate}; use crate::block::entities::{BlockEntity, PropertyDelegate};
pub use pumpkin_world::block::entities::ExperienceContainer; 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 /// Calculates XP from tracked recipes and clears the `recipes_used` map
fn extract_experience_from_recipes(&self) -> i32; fn extract_experience_from_recipes(&self) -> i32;
fn get_input_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>>; fn get_input_item(&self) -> impl std::future::Future<Output = ItemStack>;
fn get_fuel_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>>; fn get_fuel_item(&self) -> impl std::future::Future<Output = ItemStack>;
fn get_output_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>>; fn get_output_item(&self) -> impl std::future::Future<Output = ItemStack>;
fn set_cooking_time_spent(&self, spent_time: u16); fn set_cooking_time_spent(&self, spent_time: u16);
fn set_cooking_total_time(&self, total_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) self.lit_total_time.load(Ordering::Relaxed)
} }
fn get_input_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>> { fn get_input_item(&self) -> impl std::future::Future<Output = ItemStack> {
let items = self.items.clone(); async move {
async move { items[0].clone() } let items = self.items.read().await;
items[0].clone()
}
} }
fn get_fuel_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>> { fn get_fuel_item(&self) -> impl std::future::Future<Output = ItemStack> {
let items = self.items.clone(); async move {
async move { items[1].clone() } let items = self.items.read().await;
items[1].clone()
}
} }
fn get_output_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>> { fn get_output_item(&self) -> impl std::future::Future<Output = ItemStack> {
let items = self.items.clone(); async move {
async move { items[2].clone() } let items = self.items.read().await;
items[2].clone()
}
} }
fn set_cooking_time_spent(&self, spent_time: u16) { fn set_cooking_time_spent(&self, spent_time: u16) {
@ -124,12 +127,11 @@ macro_rules! impl_cooking_block_entity_base {
max_count: u8, max_count: u8,
) -> bool { ) -> bool {
let Some(recipe) = recipe else { return false }; 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 = items[0].is_empty();
let is_top_items_empty = top_item_stack.is_empty(); let side_item_stack = &items[2];
drop(top_item_stack);
let side_item_stack = self.items[2].lock().await;
if side_item_stack.is_empty() { if side_item_stack.is_empty() {
return !is_top_items_empty; return !is_top_items_empty;
} }
@ -158,7 +160,7 @@ macro_rules! impl_cooking_block_entity_base {
.await; .await;
if let Some(recipe) = recipe { if let Some(recipe) = recipe {
if can_accept_output { 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( let Some(output_item) = pumpkin_data::item::Item::from_registry_key(
recipe recipe
.result .result
@ -170,32 +172,25 @@ macro_rules! impl_cooking_block_entity_base {
}; };
let output_item_stack = ItemStack::new(recipe.result.count, output_item); let output_item_stack = ItemStack::new(recipe.result.count, output_item);
if side_items.are_equal(ItemStack::EMPTY) { if items[2].are_equal(ItemStack::EMPTY) {
drop(side_items); items[2] = output_item_stack;
self.set_stack(2, output_item_stack).await; } else if items[2].are_items_and_components_equal(&output_item_stack) {
} else if side_items.are_items_and_components_equal(&output_item_stack) { items[2].increment(1);
side_items.increment(1);
} }
// Track recipe usage for XP calculation (vanilla RecipesUsed format) // Track recipe usage for XP calculation (vanilla RecipesUsed format)
self.add_recipe_used(recipe); self.add_recipe_used(recipe);
} }
let bottom_items = self.items[1].lock().await; let mut items = self.items.write().await;
let mut top_items = self.items[0].lock().await; if items[0].item.id == pumpkin_data::item::Item::WET_SPONGE.id
if top_items.item.id == pumpkin_data::item::Item::WET_SPONGE.id && !items[1].is_empty()
&& !bottom_items.is_empty() && items[1].item.id == pumpkin_data::item::Item::BUCKET.id
&& bottom_items.item.id == pumpkin_data::item::Item::BUCKET.id
{ {
drop(bottom_items); items[1] = ItemStack::new(1, &pumpkin_data::item::Item::WATER_BUCKET);
self.set_stack(
1,
ItemStack::new(1, &pumpkin_data::item::Item::WATER_BUCKET),
)
.await;
} }
top_items.decrement(1); items[0].decrement(1);
return true; return true;
} }
@ -243,9 +238,8 @@ macro_rules! impl_clearable_for_cooking {
impl pumpkin_world::inventory::Clearable for $struct_name { impl pumpkin_world::inventory::Clearable for $struct_name {
fn clear(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in self.items.iter() { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -272,25 +266,24 @@ macro_rules! impl_inventory_for_cooking {
($struct_name:ty) => { ($struct_name:ty) => {
impl pumpkin_world::inventory::Inventory for $struct_name { impl pumpkin_world::inventory::Inventory for $struct_name {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> { fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in self.items.iter() { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(|s| s.is_empty())
return false;
}
}
true
}) })
} }
fn get_stack( fn get_stack(
&self, &self,
slot: usize, slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack( fn remove_stack(
@ -298,9 +291,8 @@ macro_rules! impl_inventory_for_cooking {
slot: usize, slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -312,8 +304,12 @@ macro_rules! impl_inventory_for_cooking {
amount: u8, amount: u8,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let res = let mut items = self.items.write().await;
pumpkin_world::inventory::split_stack(&self.items, slot, amount).await; let res = if !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
};
self.mark_dirty(); self.mark_dirty();
res res
}) })
@ -325,14 +321,11 @@ macro_rules! impl_inventory_for_cooking {
stack: ItemStack, stack: ItemStack,
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> { ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
let furnace_stack = self.get_stack(slot).await; let mut items = self.items.write().await;
let mut furnace_stack = furnace_stack.lock().await;
let is_same_item = !stack.is_empty() 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(); items[slot] = stack.clone();
drop(furnace_stack);
if slot == 0 && !is_same_item { if slot == 0 && !is_same_item {
if let Some(recipe) = if let Some(recipe) =
@ -380,26 +373,27 @@ macro_rules! impl_block_entity_for_cooking {
self.lit_time_remaining.fetch_sub(1, Ordering::Relaxed); self.lit_time_remaining.fetch_sub(1, Ordering::Relaxed);
} }
let top_items = self.items[0].lock().await; let items_guard = self.items.read().await;
let is_top_items_empty = top_items.is_empty(); 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( let furnace_recipe = pumpkin_data::recipes::get_cooking_recipe_with_ingredient(
top_items.item, top_item.item,
$recipe_kind, $recipe_kind,
); );
drop(top_items);
let can_accept_output = self let can_accept_output = self
.can_accept_recipe_output(furnace_recipe, self.get_max_count_per_stack()) .can_accept_recipe_output(furnace_recipe, self.get_max_count_per_stack())
.await; .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() || !bottom_items_is_empty && !is_top_items_empty {
if !self.is_burning() && can_accept_output { if !self.is_burning() && can_accept_output {
let mut bottom_items = self.items[1].lock().await;
let base_fuel_ticks = 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); .unwrap_or(0);
let adjusted_fuel_ticks = if matches!( let adjusted_fuel_ticks = if matches!(
@ -416,18 +410,18 @@ macro_rules! impl_block_entity_for_cooking {
if self.is_burning() { if self.is_burning() {
is_dirty = true; is_dirty = true;
if !bottom_items.is_empty() { let mut items_guard = self.items.write().await;
bottom_items.decrement(1); if !items_guard[1].is_empty() {
items_guard[1].decrement(1);
if let Some(remainder_id) = if let Some(remainder_id) =
pumpkin_data::recipe_remainder::get_recipe_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) = && let Some(remainder_item) =
pumpkin_data::item::Item::from_id(remainder_id) pumpkin_data::item::Item::from_id(remainder_id)
{ {
drop(bottom_items); items_guard[1] = ItemStack::new(1, remainder_item);
self.set_stack(1, ItemStack::new(1, remainder_item)).await;
} }
} }
} }
@ -540,14 +534,14 @@ macro_rules! impl_block_entity_for_cooking {
let furnace = Self { let furnace = Self {
position, position,
dirty: AtomicBool::new(false), 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_total_time,
cooking_time_spent, cooking_time_spent,
lit_total_time, lit_total_time,
lit_time_remaining, lit_time_remaining,
recipes_used: std::sync::Mutex::new(recipes_used_map), 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 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) Some(nbt)
} }

View File

@ -10,20 +10,17 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag; use pumpkin_nbt::tag::NbtTag;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::inventory::{ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt,
};
use std::any::Any; use std::any::Any;
use std::array::from_fn; use std::array::from_fn;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64}; use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64};
use tokio::sync::Mutex;
pub struct HopperBlockEntity { pub struct HopperBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
pub facing: FacingHopper, pub facing: FacingHopper,
pub cooldown_time: AtomicI32, pub cooldown_time: AtomicI32,
@ -62,14 +59,14 @@ impl BlockEntity for HopperBlockEntity {
{ {
let hopper = Self { let hopper = Self {
position, 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), dirty: AtomicBool::new(false),
facing: FacingHopper::Down, facing: FacingHopper::Down,
cooldown_time: AtomicI32::from(nbt.get_int("TransferCooldown").unwrap_or(-1)), cooldown_time: AtomicI32::from(nbt.get_int("TransferCooldown").unwrap_or(-1)),
ticked_game_time: AtomicI64::new(0), ticked_game_time: AtomicI64::new(0),
}; };
hopper.read_data(nbt, &hopper.items); hopper.read_data(nbt, &mut *hopper.items.blocking_write());
hopper hopper
} }
@ -120,7 +117,8 @@ impl BlockEntity for HopperBlockEntity {
"TransferCooldown", "TransferCooldown",
NbtTag::Int(self.cooldown_time.load(Ordering::Relaxed)), 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) Some(nbt)
} }
@ -137,7 +135,7 @@ impl HopperBlockEntity {
pub fn new(position: BlockPos, facing: FacingHopper) -> Self { pub fn new(position: BlockPos, facing: FacingHopper) -> Self {
Self { Self {
position, 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), dirty: AtomicBool::new(false),
facing, facing,
cooldown_time: AtomicI32::new(-1), cooldown_time: AtomicI32::new(-1),
@ -162,8 +160,8 @@ impl HopperBlockEntity {
} }
async fn inventory_full(&self) -> bool { async fn inventory_full(&self) -> bool {
for i in &self.items { let items = self.items.read().await;
let item = i.lock().await; for item in items.iter() {
if item.is_empty() || item.item_count != item.get_max_stack_size() { if item.is_empty() || item.item_count != item.get_max_stack_size() {
return false; return false;
} }
@ -179,16 +177,16 @@ impl HopperBlockEntity {
{ {
// TODO check WorldlyContainer // TODO check WorldlyContainer
for i in 0..container.size() { for i in 0..container.size() {
let bind = container.get_stack(i).await; let mut item = container.get_stack(i).await;
let mut item = bind.lock().await;
if !item.is_empty() && container.can_transfer_to(self, i, &item) { if !item.is_empty() && container.can_transfer_to(self, i, &item) {
//TODO WorldlyContainer //TODO WorldlyContainer
let backup = item.clone(); let _backup = item.clone();
let one_item = item.split(1); let one_item = item.split(1);
if Self::add_one_item(container.as_ref(), self, one_item).await { 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 // If extracting from furnace output slot (index 2), drop XP as orbs
const FURNACE_OUTPUT_SLOT: usize = 2; let furnace_output_slot: usize = 2;
if i == FURNACE_OUTPUT_SLOT if i == furnace_output_slot
&& let Some(experience_container) = && let Some(experience_container) =
entity.clone().to_experience_container() entity.clone().to_experience_container()
{ {
@ -200,7 +198,6 @@ impl HopperBlockEntity {
} }
return true; return true;
} }
*item = backup;
} }
} }
return false; return false;
@ -242,8 +239,7 @@ impl HopperBlockEntity {
// TODO check WorldlyContainer // TODO check WorldlyContainer
let mut is_full = true; let mut is_full = true;
for i in 0..container.size() { for i in 0..container.size() {
let bind = container.get_stack(i).await; let item = container.get_stack(i).await;
let item = bind.lock().await;
if item.item_count < item.get_max_stack_size() { if item.item_count < item.get_max_stack_size() {
is_full = false; is_full = false;
break; break;
@ -252,16 +248,14 @@ impl HopperBlockEntity {
if is_full { if is_full {
return false; return false;
} }
for i in &self.items { let items = self.items.read().await;
let mut item = i.lock().await; for item in items.iter() {
if !item.is_empty() { if !item.is_empty() {
//TODO WorldlyContainer let mut item_clone = item.clone();
let backup = item.clone(); let one_item = item_clone.split(1);
let one_item = item.split(1);
if Self::add_one_item(self, container.as_ref(), one_item).await { if Self::add_one_item(self, container.as_ref(), one_item).await {
return true; return true;
} }
*item = backup;
} }
} }
} }
@ -272,14 +266,15 @@ impl HopperBlockEntity {
let to_empty = to.is_empty().await; let to_empty = to.is_empty().await;
for j in 0..to.size() { for j in 0..to.size() {
if to.is_valid_slot_for(j, &item) { if to.is_valid_slot_for(j, &item) {
let bind = to.get_stack(j).await; let mut dst = to.get_stack(j).await;
let mut dst = bind.lock().await;
if dst.is_empty() { if dst.is_empty() {
*dst = item.clone(); dst = item.clone();
to.set_stack(j, dst).await;
success = true; success = true;
} else if dst.item_count < dst.get_max_stack_size() && dst.item == item.item { } else if dst.item_count < dst.get_max_stack_size() && dst.item == item.item {
// TODO check Components equal // TODO check Components equal
dst.item_count += 1; dst.item_count += 1;
to.set_stack(j, dst).await;
success = true; success = true;
} }
if success { if success {
@ -310,30 +305,27 @@ impl HopperBlockEntity {
impl Inventory for HopperBlockEntity { impl Inventory for HopperBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -341,7 +333,12 @@ impl Inventory for HopperBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -349,7 +346,8 @@ impl Inventory for HopperBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -366,9 +364,8 @@ impl Inventory for HopperBlockEntity {
impl Clearable for HopperBlockEntity { impl Clearable for HopperBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -201,8 +201,8 @@ impl Inventory for JukeboxBlockEntity {
Box::pin(async move { self.record_stack.lock().await.is_empty() }) Box::pin(async move { self.record_stack.lock().await.is_empty() })
} }
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.record_stack.clone() }) Box::pin(async move { self.record_stack.lock().await.clone() })
} }
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {

View File

@ -159,8 +159,8 @@ impl Inventory for LecternBlockEntity {
Box::pin(async move { self.book.lock().await.is_empty() }) Box::pin(async move { self.book.lock().await.is_empty() })
} }
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.book.clone() }) Box::pin(async move { self.book.lock().await.clone() })
} }
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {

View File

@ -2,19 +2,16 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
Clearable, Inventory, InventoryFuture, split_stack, sync_write_items_to_nbt,
};
use std::any::Any; use std::any::Any;
use std::array::from_fn; use std::array::from_fn;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::Mutex;
pub struct ShelfBlockEntity { pub struct ShelfBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
} }
@ -32,11 +29,11 @@ impl BlockEntity for ShelfBlockEntity {
{ {
let shelf = Self { let shelf = Self {
position, 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), dirty: AtomicBool::new(false),
}; };
shelf.read_data(nbt, &shelf.items); shelf.read_data(nbt, &mut *shelf.items.blocking_write());
shelf shelf
} }
@ -63,7 +60,8 @@ impl BlockEntity for ShelfBlockEntity {
fn chunk_data_nbt(&self) -> Option<NbtCompound> { fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new(); 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) Some(nbt)
} }
@ -73,14 +71,14 @@ impl BlockEntity for ShelfBlockEntity {
} }
impl ShelfBlockEntity { impl ShelfBlockEntity {
pub const INVENTORY_SIZE: usize = 9; pub const INVENTORY_SIZE: usize = 3;
pub const ID: &'static str = "minecraft:shelf"; pub const ID: &'static str = "minecraft:shelf";
#[must_use] #[must_use]
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, 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), dirty: AtomicBool::new(false),
} }
} }
@ -88,30 +86,27 @@ impl ShelfBlockEntity {
impl Inventory for ShelfBlockEntity { impl Inventory for ShelfBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -119,7 +114,12 @@ impl Inventory for ShelfBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -127,7 +127,8 @@ impl Inventory for ShelfBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -144,9 +145,8 @@ impl Inventory for ShelfBlockEntity {
impl Clearable for ShelfBlockEntity { impl Clearable for ShelfBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -2,35 +2,27 @@ use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; 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::any::Any;
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::Ordering; use std::sync::atomic::{AtomicBool, Ordering};
use std::{ use std::{array::from_fn, sync::Arc};
array::from_fn, use tokio::sync::RwLock;
sync::{Arc, atomic::AtomicBool},
};
use tokio::sync::Mutex;
use crate::block::entities::BlockEntity;
use crate::block::viewer::{ use crate::block::viewer::{
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture, ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
}; };
use crate::world::World; use crate::world::World;
use pumpkin_world::inventory::InventoryFuture; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{
split_stack, sync_write_items_to_nbt, {Clearable, Inventory},
};
use super::BlockEntity;
pub struct ShulkerBoxBlockEntity { pub struct ShulkerBoxBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
// Viewer // Viewer
viewers: ViewerCountTracker, pub viewers: ViewerCountTracker,
} }
impl BlockEntity for ShulkerBoxBlockEntity { impl BlockEntity for ShulkerBoxBlockEntity {
@ -48,12 +40,12 @@ impl BlockEntity for ShulkerBoxBlockEntity {
{ {
let shulker_box = Self { let shulker_box = Self {
position, position,
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
viewers: ViewerCountTracker::new(), viewers: ViewerCountTracker::new(),
}; };
shulker_box.read_data(nbt, &shulker_box.items); shulker_box.read_data(nbt, &mut *shulker_box.items.blocking_write());
shulker_box shulker_box
} }
@ -100,7 +92,8 @@ impl BlockEntity for ShulkerBoxBlockEntity {
fn chunk_data_nbt(&self) -> Option<NbtCompound> { fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new(); 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) Some(nbt)
} }
@ -116,7 +109,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
position: &'a BlockPos, position: &'a BlockPos,
) -> ViewerFuture<'a, ()> { ) -> ViewerFuture<'a, ()> {
Box::pin(async move { 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); // TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_OPEN, this.pos);
}) })
} }
@ -127,7 +120,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
position: &'a BlockPos, position: &'a BlockPos,
) -> ViewerFuture<'a, ()> { ) -> ViewerFuture<'a, ()> {
Box::pin(async move { 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); // TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_CLOSE, this.pos);
}) })
} }
@ -150,57 +143,57 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
impl ShulkerBoxBlockEntity { impl ShulkerBoxBlockEntity {
pub const INVENTORY_SIZE: usize = 27; pub const INVENTORY_SIZE: usize = 27;
pub const OPEN_ANIMATION_EVENT_TYPE: u8 = 1; 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] #[must_use]
pub fn new(position: BlockPos) -> Self { pub fn new(position: BlockPos) -> Self {
Self { Self {
position, position,
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
viewers: ViewerCountTracker::new(), viewers: ViewerCountTracker::new(),
} }
} }
fn play_sound(world: &Arc<World>, position: &BlockPos, sound: Sound) { pub fn update_viewers(&self, world: &Arc<World>) {
let mut rng = Xoroshiro::from_seed(get_seed()); let viewer_count = self.viewers.current.load(Ordering::Relaxed);
Self::play_sound(world, &self.position, i32::from(viewer_count));
}
world.play_sound_fine( fn play_sound(world: &World, position: &BlockPos, viewer_count: i32) {
sound, let sound = if viewer_count > 0 {
SoundCategory::Blocks, Sound::BlockShulkerBoxOpen
&position.to_centered_f64(), } else {
0.5, Sound::BlockShulkerBoxClose
rng.next_f32() * 0.1 + 0.9, };
);
world.play_sound(sound, SoundCategory::Blocks, &position.to_f64());
} }
} }
impl Inventory for ShulkerBoxBlockEntity { impl Inventory for ShulkerBoxBlockEntity {
fn size(&self) -> usize { fn size(&self) -> usize {
self.items.len() Self::INVENTORY_SIZE
} }
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let items = self.items.read().await;
if !slot.lock().await.is_empty() { items.iter().all(ItemStack::is_empty)
return false;
}
}
true
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items[slot].clone() }) Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
} }
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone(); let mut items = self.items.write().await;
let mut guard = self.items[slot].lock().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty(); self.mark_dirty();
removed removed
}) })
@ -208,7 +201,12 @@ impl Inventory for ShulkerBoxBlockEntity {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { 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(); self.mark_dirty();
res res
}) })
@ -216,7 +214,8 @@ impl Inventory for ShulkerBoxBlockEntity {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
*self.items[slot].lock().await = stack; let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty(); self.mark_dirty();
}) })
} }
@ -245,9 +244,8 @@ impl Inventory for ShulkerBoxBlockEntity {
impl Clearable for ShulkerBoxBlockEntity { impl Clearable for ShulkerBoxBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
for slot in &self.items { let mut items = self.items.write().await;
*slot.lock().await = ItemStack::EMPTY.clone(); items.fill_with(|| ItemStack::EMPTY.clone());
}
self.mark_dirty(); self.mark_dirty();
}) })
} }

View File

@ -167,18 +167,18 @@ impl From<Text> for NbtTag {
} }
} }
#[allow(clippy::fallible_impl_from)]
impl From<NbtTag> for Text { impl From<NbtTag> for Text {
fn from(tag: NbtTag) -> Self { 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 has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false);
let color = nbt.get_string("color").unwrap_or("black"); let color = nbt.get_string("color").unwrap_or("black");
let messages: Vec<Box<str>> = nbt let messages: Vec<Box<str>> = nbt.get_list("messages").map_or_else(Vec::new, |list| {
.get_list("messages") list.iter()
.unwrap() .filter_map(|tag| tag.extract_string().map(Box::from))
.iter() .collect()
.filter_map(|tag| tag.extract_string().map(Box::from)) });
.collect();
let get_message = let get_message =
|i: usize| -> Box<str> { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) }; |i: usize| -> Box<str> { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) };

View File

@ -12,7 +12,6 @@ use std::{
use pumpkin_data::recipes::CookingRecipeKind; use pumpkin_data::recipes::CookingRecipeKind;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::{ use crate::{
block::entities::furnace_like_block_entity::CookingBlockEntityBase, block::entities::furnace_like_block_entity::CookingBlockEntityBase,
@ -30,7 +29,7 @@ pub struct SmokerBlockEntity {
pub lit_time_remaining: AtomicU16, pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16, pub lit_total_time: AtomicU16,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format) /// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
/// Maps result item ID -> craft count /// Maps result item ID -> craft count
@ -46,7 +45,7 @@ impl SmokerBlockEntity {
Self { Self {
position, position,
dirty: AtomicBool::new(false), 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_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0), cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0), lit_total_time: AtomicU16::new(0),

View File

@ -4,8 +4,6 @@ use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::{ use crate::{
block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods, 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, impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest,
@ -13,7 +11,7 @@ use crate::{
pub struct TrappedChestBlockEntity { pub struct TrappedChestBlockEntity {
pub position: BlockPos, pub position: BlockPos,
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE], pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
// Viewer // Viewer

View File

@ -28,7 +28,6 @@ use pumpkin_protocol::java::server::play::SUseItemOn;
use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::world::{BlockAccessor, BlockFlags}; use pumpkin_world::world::{BlockAccessor, BlockFlags};
use tokio::sync::Mutex;
pub trait BlockMetadata { pub trait BlockMetadata {
fn ids() -> Box<[BlockId]>; fn ids() -> Box<[BlockId]>;
@ -247,7 +246,7 @@ pub struct UseWithItemArgs<'a> {
pub position: &'a BlockPos, pub position: &'a BlockPos,
pub player: &'a Arc<Player>, pub player: &'a Arc<Player>,
pub hit: &'a BlockHitResult<'a>, pub hit: &'a BlockHitResult<'a>,
pub item_stack: &'a Arc<Mutex<ItemStack>>, pub item_stack: &'a mut ItemStack,
pub equipment_slot: &'a EquipmentSlot, pub equipment_slot: &'a EquipmentSlot,
} }
@ -512,8 +511,7 @@ pub async fn calculate_comparator_output(
let mut fill_sum = 0.0; let mut fill_sum = 0.0;
let mut non_empty_count = 0; let mut non_empty_count = 0;
for i in 0..size { for i in 0..size {
let stack_mutex = inventory.get_stack(i).await; let stack = inventory.get_stack(i).await;
let stack = stack_mutex.lock().await;
if !stack.is_empty() { if !stack.is_empty() {
let max_stack = stack.get_max_stack_size() as f32; let max_stack = stack.get_max_stack_size() as f32;
let count = stack.item_count as f32; let count = stack.item_count as f32;

View File

@ -157,7 +157,6 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::{BlockAccessor, BlockFlags}; use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex;
use super::BlockIsReplacing; use super::BlockIsReplacing;
use super::blocks::plant::crop::gourds::attached_stem::AttachedStemBlock; use super::blocks::plant::crop::gourds::attached_stem::AttachedStemBlock;
@ -804,7 +803,7 @@ impl BlockRegistry {
player: &Arc<Player>, player: &Arc<Player>,
position: &BlockPos, position: &BlockPos,
hit: &BlockHitResult<'_>, hit: &BlockHitResult<'_>,
item_stack: &Arc<Mutex<ItemStack>>, item_stack: &mut ItemStack,
equipment_slot: &EquipmentSlot, equipment_slot: &EquipmentSlot,
server: &Server, server: &Server,
world: &Arc<World>, world: &Arc<World>,

View File

@ -3,7 +3,6 @@ use std::sync::Arc;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use tokio::sync::Mutex;
use crate::command::args::bounded_num::BoundedNumArgumentConsumer; use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::players::PlayersArgumentConsumer; 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 max: i32 = max;
let mut is_done: bool = false; let mut is_done: bool = false;
iter_test_and_clear( {
&inventory.main_inventory, let mut main_inv = inventory.main_inventory.write().await;
&mut count, for slot in main_inv.iter_mut() {
&mut max, test_and_clear(&mut count, &mut max, item, slot, &mut is_done);
item, if is_done {
&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<Item = &'i Arc<Mutex<ItemStack>>>,
{
// 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 {
break; 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, count: &mut i32,
max: &mut i32, max: &mut i32,
item: &ItemPredicate, item: &ItemPredicate,
slot: &Arc<Mutex<ItemStack>>, slot_lock: &mut ItemStack,
is_done: &mut bool, 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; let item_count = slot_lock.item_count as i32;
if *max == MAX_NO_CLEAR_BUT_SIMULATE { if *max == MAX_NO_CLEAR_BUT_SIMULATE {
*count += item_count; *count += item_count;

View File

@ -135,8 +135,7 @@ async fn enchant_target(
return Err(commands_enchant_failed()); return Err(commands_enchant_failed());
}; };
let lock = player.inventory.held_item(); let mut item = player.inventory().held_item().await;
let mut item = lock.lock().await;
if item.is_empty() { if item.is_empty() {
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
@ -168,6 +167,7 @@ async fn enchant_target(
} }
item.enchant(enchantment, level); item.enchant(enchantment, level);
player.inventory().set_held_item(item).await;
Ok(()) Ok(())
} }

View File

@ -239,8 +239,7 @@ impl CommandExecutor for EntityReplaceExecutor {
.entity_equipment .entity_equipment
.lock() .lock()
.await .await
.put(&eq, item_stack.clone()) .put(&eq, item_stack.clone());
.await;
living.send_equipment_changes(&[(eq, item_stack.clone())]); living.send_equipment_changes(&[(eq, item_stack.clone())]);
modified_count += 1; modified_count += 1;
} }

View File

@ -58,8 +58,7 @@ async fn insert_into_inventory(
if stack.is_empty() { if stack.is_empty() {
break; break;
} }
let slot = inventory.get_stack(i).await; let mut slot_stack = inventory.get_stack(i).await;
let mut slot_stack = slot.lock().await;
if !slot_stack.is_empty() && slot_stack.get_item().id == stack.get_item().id { if !slot_stack.is_empty() && slot_stack.get_item().id == stack.get_item().id {
let max_stack_size = 64; let max_stack_size = 64;
let space = max_stack_size - slot_stack.item_count; 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); let to_add = stack.item_count.min(space);
slot_stack.item_count += to_add; slot_stack.item_count += to_add;
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() { if stack.is_empty() {
break; break;
} }
let slot = inventory.get_stack(i).await; let slot_stack = inventory.get_stack(i).await;
let mut slot_stack = slot.lock().await;
if slot_stack.is_empty() { if slot_stack.is_empty() {
*slot_stack = stack.clone(); inventory.set_stack(i, stack.clone()).await;
stack.item_count = 0; stack.item_count = 0;
break; break;
} }

View File

@ -32,15 +32,12 @@ impl BegGoal {
} }
async fn is_player_holding_attractive(&self, player: &Player) -> bool { async fn is_player_holding_attractive(&self, player: &Player) -> bool {
let main_hand = player.inventory.held_item(); let main_stack = player.inventory().held_item().await;
let main_stack = main_hand.lock().await;
if main_stack.item_count > 0 && self.is_attractive(main_stack.item) { if main_stack.item_count > 0 && self.is_attractive(main_stack.item) {
return true; return true;
} }
drop(main_stack);
let off_hand = player.inventory.off_hand_item().await; let off_stack = player.inventory().off_hand_item().await;
let off_stack = off_hand.lock().await;
off_stack.item_count > 0 && self.is_attractive(off_stack.item) off_stack.item_count > 0 && self.is_attractive(off_stack.item)
} }

View File

@ -33,12 +33,12 @@ impl TemptGoal {
} }
async fn is_holding_tempt_item(&self, player: &Player) -> bool { async fn is_holding_tempt_item(&self, player: &Player) -> bool {
let main = player.inventory.held_item(); let main = player.inventory().held_item().await;
if self.is_tempt_item(&*main.lock().await) { if self.is_tempt_item(&main) {
return true; return true;
} }
let off = player.inventory.off_hand_item().await; let off = player.inventory().off_hand_item().await;
self.is_tempt_item(&*off.lock().await) self.is_tempt_item(&off)
} }
async fn find_tempting_player(&self, mob: &dyn Mob) -> Option<Arc<Player>> { async fn find_tempting_player(&self, mob: &dyn Mob) -> Option<Arc<Player>> {

View File

@ -30,20 +30,14 @@ impl AttackType {
let sprinting = entity.is_sprinting(); let sprinting = entity.is_sprinting();
let on_ground = entity.on_ground.load(Ordering::Relaxed); let on_ground = entity.on_ground.load(Ordering::Relaxed);
let fall_distance = player.living_entity.fall_distance.load(); let fall_distance = player.living_entity.fall_distance.load();
let held_item = player.inventory().held_item(); let held_item = player.inventory().held_item().await;
let is_mace = { let is_mace = held_item.item.id == pumpkin_data::item::Item::MACE.id;
let stack = held_item.lock().await;
stack.item.id == pumpkin_data::item::Item::MACE.id
};
if is_mace && !on_ground && fall_distance > 1.5 { if is_mace && !on_ground && fall_distance > 1.5 {
return Self::MaceSmash; return Self::MaceSmash;
} }
let sword = { let sword = held_item.is_sword();
let stack = held_item.lock().await;
stack.is_sword()
};
let is_strong = attack_cooldown_progress > 0.9; let is_strong = attack_cooldown_progress > 0.9;
if sprinting && is_strong { if sprinting && is_strong {

View File

@ -12,7 +12,6 @@ use pumpkin_protocol::codec::var_ulong::VarULong;
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use pumpkin_util::Hand; use pumpkin_util::Hand;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use std::mem;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::atomic::{ use std::sync::atomic::{
@ -1353,14 +1352,13 @@ impl LivingEntity {
.downcast_ref::<crate::entity::player::Player>() .downcast_ref::<crate::entity::player::Player>()
{ {
let hand_stack = player let hand_stack = player
.inventory .inventory()
.get_stack_in_hand(pumpkin_util::Hand::Right) .get_stack_in_hand(pumpkin_util::Hand::Right)
.await; .await;
let stack_guard = hand_stack.lock().await; looting_level = hand_stack
looting_level = stack_guard
.get_enchantment_level(&Enchantment::LOOTING) .get_enchantment_level(&Enchantment::LOOTING)
.max(0) as u32; .max(0) as u32;
(stack_guard.item_count > 0).then(|| stack_guard.clone()) (!hand_stack.is_empty()).then(|| hand_stack.clone())
} else { } else {
looting_level = 0; looting_level = 0;
None None
@ -1441,12 +1439,13 @@ impl LivingEntity {
if rand::random::<f32>() >= chance { if rand::random::<f32>() >= chance {
continue; continue;
} }
let mut item = { let mut item = self
let q = self.entity_equipment.lock().await; .entity_equipment
let item_arc = q.get(slot); .lock()
let mut item_lock = item_arc.lock().await; .await
mem::replace(&mut *item_lock, ItemStack::EMPTY.clone()) .equipment
}; .remove(slot)
.unwrap_or_else(|| ItemStack::EMPTY.clone());
if item.is_empty() { if item.is_empty() {
continue; continue;
} }
@ -1547,11 +1546,8 @@ impl LivingEntity {
killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::TwoBirdsOneArrow).await; killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::TwoBirdsOneArrow).await;
} }
let held_item = killer_player.inventory().held_item(); let held_item = killer_player.inventory().held_item().await;
let is_crossbow = { let is_crossbow = held_item.item.registry_key == "crossbow";
let lock = held_item.lock().await;
lock.item.registry_key == "crossbow"
};
if is_crossbow { if is_crossbow {
killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::Arbalistic).await; 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. /// 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 { async fn try_use_death_protector(&self, caller: &dyn EntityBase) -> bool {
for hand in Hand::all() { for hand in Hand::all() {
let stack = self.get_stack_in_hand(caller, hand).await; let mut stack = self.get_stack_in_hand(caller, hand).await;
let mut stack = stack.lock().await;
// Clear the stack and use the totem of undying // Clear the stack and use the totem of undying
if stack.get_data_component::<DeathProtectionImpl>().is_some() { if stack.get_data_component::<DeathProtectionImpl>().is_some() {
stack.clear(); 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.set_health(1.0);
self.entity self.entity
.world .world
@ -1774,55 +1788,47 @@ impl LivingEntity {
// TODO: Falling anvil/stalactite should only damage the helmet slot. // TODO: Falling anvil/stalactite should only damage the helmet slot.
// TODO: Implement DAMAGE_RESISTANT component checks (e.g. netherite vs fire). // TODO: Implement DAMAGE_RESISTANT component checks (e.g. netherite vs fire).
let armor_slots: Vec<(usize, Arc<Mutex<ItemStack>>, EquipmentSlot)> = { let armor_slots: Vec<(usize, ItemStack, EquipmentSlot)> = {
let equipment_lock = self.entity_equipment.lock().await; let equipment_lock = self.entity_equipment.lock().await;
self.equipment_slots self.equipment_slots
.iter() .iter()
.filter(|(_, slot)| slot.is_armor_slot()) .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() .collect()
}; };
for (slot_index, equipment, slot) in armor_slots { for (slot_index, mut stack, slot) in armor_slots {
let (slot_result, updated_stack_opt) = { if stack.is_empty() {
let mut stack = equipment.lock().await; continue;
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::<EquippableImpl>()
.is_none_or(|equippable| equippable.damage_on_hurt);
if takes_damage { let takes_damage = stack
// Base armor durability damage. .get_data_component::<EquippableImpl>()
let result = stack.damage_item(armor_damage); .is_none_or(|equippable| equippable.damage_on_hurt);
let changed = result != pumpkin_data::item_stack::DamageResult::Untouched;
(result, changed.then_some(stack.clone())) if takes_damage {
} else { let slot_result = stack.damage_item(armor_damage);
// Equippable items can opt out of on-hurt durability loss (e.g. elytra). if slot_result != pumpkin_data::item_stack::DamageResult::Untouched {
(pumpkin_data::item_stack::DamageResult::Untouched, None) 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<Mutex<ItemStack>> { pub async fn held_item(&self, caller: &dyn EntityBase) -> ItemStack {
if let Some(player) = caller.get_player() { if let Some(player) = caller.get_player() {
return player.inventory.held_item(); return player.inventory.held_item().await;
} }
self.entity_equipment let equipment = self.entity_equipment.lock().await;
.lock() equipment
.await .equipment
.get(&EquipmentSlot::MAIN_HAND) .get(&EquipmentSlot::MAIN_HAND)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
} }
pub async fn get_stack_in_hand( pub async fn get_stack_in_hand(&self, caller: &dyn EntityBase, hand: Hand) -> ItemStack {
&self,
caller: &dyn EntityBase,
hand: Hand,
) -> Arc<Mutex<ItemStack>> {
match hand { match hand {
Hand::Left => self.off_hand_item().await, Hand::Left => self.off_hand_item(caller).await,
Hand::Right => self.held_item(caller).await, Hand::Right => self.held_item(caller).await,
} }
} }
/// getOffHandStack in source /// getOffHandStack in source
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> { 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 let slot = self
.equipment_slots .equipment_slots
.get(&PlayerInventory::OFF_HAND_SLOT) .get(&PlayerInventory::OFF_HAND_SLOT)
.unwrap(); .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 { pub fn can_take_damage(&self) -> bool {
@ -2141,9 +2153,8 @@ impl EntityBase for LivingEntity {
EquipmentSlot::LEGS, EquipmentSlot::LEGS,
EquipmentSlot::FEET, EquipmentSlot::FEET,
] { ] {
let stack_arc = equipment_lock.get(&slot); if let Some(stack) = equipment_lock.equipment.get(&slot)
let stack = stack_arc.lock().await; && !stack.is_empty()
if !stack.is_empty()
&& let Some(modifiers) = && let Some(modifiers) =
stack.get_data_component::<AttributeModifiersImpl>() stack.get_data_component::<AttributeModifiersImpl>()
{ {
@ -2175,9 +2186,8 @@ impl EntityBase for LivingEntity {
EquipmentSlot::LEGS, EquipmentSlot::LEGS,
EquipmentSlot::FEET, EquipmentSlot::FEET,
] { ] {
let stack_arc = equipment_lock.get(&slot); if let Some(stack) = equipment_lock.equipment.get(&slot)
let stack = stack_arc.lock().await; && !stack.is_empty()
if !stack.is_empty()
&& let Some(enchantments) = && let Some(enchantments) =
stack.get_data_component::<EnchantmentsImpl>() stack.get_data_component::<EnchantmentsImpl>()
{ {
@ -2285,8 +2295,8 @@ impl EntityBase for LivingEntity {
} }
if let Some(attacker_player) = cause.and_then(|c| c.get_player()) { if let Some(attacker_player) = cause.and_then(|c| c.get_player()) {
let held_item = attacker_player.inventory().held_item(); let held_item = attacker_player.inventory().held_item().await;
let is_axe = held_item.lock().await.is_axe(); let is_axe = held_item.is_axe();
if is_axe { if is_axe {
let mut disable_chance = 0.25; let mut disable_chance = 0.25;
let is_sprinting = attacker_player let is_sprinting = attacker_player
@ -2322,33 +2332,30 @@ impl EntityBase for LivingEntity {
EquipmentSlot::OFF_HAND EquipmentSlot::OFF_HAND
}; };
let equipment_lock = self.entity_equipment.lock().await; let mut equipment_guard = self.entity_equipment.lock().await;
let stack_arc = equipment_lock.get(&slot); if let Some(stack) = equipment_guard.equipment.get_mut(&slot) {
let mut stack = stack_arc.lock().await; 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; self.send_equipment_changes(&[(slot, broken_stack)]);
if stack.damage_item(durability_damage) == DamageResult::Broken { self.clear_active_hand().await;
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(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; let mut handled = false;
// Check main hand (hotbar selected) // Check main hand (hotbar selected)
let held_arc = player.inventory.held_item(); let mut held = player.inventory.held_item().await;
{ if held.are_items_and_components_equal(item) {
let mut held_lock = held_arc.lock().await; if is_potion {
if held_lock.are_items_and_components_equal(item) { if player.gamemode.load() != GameMode::Creative {
if is_potion { held.decrement(1);
if player.gamemode.load() != GameMode::Creative { if held.is_empty() {
held_lock.decrement(1); held = ItemStack::new(1, &Item::GLASS_BOTTLE);
if held_lock.is_empty() {
*held_lock = 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 { if !handled {
// Check off-hand // Check off-hand
let off_arc = player.inventory.off_hand_item().await; let mut off_hand = player.inventory.off_hand_item().await;
let mut off_lock = off_arc.lock().await; if off_hand.are_items_and_components_equal(item) {
if off_lock.are_items_and_components_equal(item) {
if is_potion { if is_potion {
if player.gamemode.load() != GameMode::Creative { if player.gamemode.load() != GameMode::Creative {
off_lock.decrement(1); off_hand.decrement(1);
if off_lock.is_empty() { if off_hand.is_empty() {
*off_lock = ItemStack::new(1, &Item::GLASS_BOTTLE); off_hand = ItemStack::new(1, &Item::GLASS_BOTTLE);
} }
} }
} else { } 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; handled = true;
} }
} }
@ -2696,21 +2703,24 @@ impl EntityBase for LivingEntity {
// Use stored active_hand (as a fallback) // Use stored active_hand (as a fallback)
let active_hand = *self.active_hand.lock().await; let active_hand = *self.active_hand.lock().await;
let hand_to_modify = active_hand.unwrap_or(Hand::Right); 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) .get_stack_in_hand(caller.as_ref(), hand_to_modify)
.await; .await;
let mut item_lock = item_stack.lock().await;
if is_potion { if is_potion {
if player.gamemode.load() != GameMode::Creative { if player.gamemode.load() != GameMode::Creative {
item_lock.decrement(1); item_stack.decrement(1);
if item_lock.is_empty() { if item_stack.is_empty() {
*item_lock = ItemStack::new(1, &Item::GLASS_BOTTLE); item_stack = ItemStack::new(1, &Item::GLASS_BOTTLE);
} }
} }
} else { } 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() { if let Some(cooldown) = item.get_use_cooldown() {

View File

@ -343,15 +343,12 @@ impl EndermanEntity {
pub async fn is_player_staring(&self, player: &Player) -> bool { pub async fn is_player_staring(&self, player: &Player) -> bool {
let equipment = player.living_entity.entity_equipment.try_lock(); let equipment = player.living_entity.entity_equipment.try_lock();
if let Ok(equipment) = equipment { if let Ok(equipment) = equipment
let head_item = equipment.get(&EquipmentSlot::HEAD); && let Some(head_stack) = equipment.equipment.get(&EquipmentSlot::HEAD)
let head_stack = head_item.try_lock(); && !head_stack.is_empty()
if let Ok(head_stack) = head_stack && head_stack.item == &Item::CARVED_PUMPKIN
&& !head_stack.is_empty() {
&& head_stack.item == &Item::CARVED_PUMPKIN return false;
{
return false;
}
} }
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;

View File

@ -1027,7 +1027,7 @@ pub async fn equip_mob_on_spawn(mob: &dyn EntityBase, world: &Arc<crate::world::
let mut equipment_changes: Vec<(EquipmentSlot, ItemStack)> = Vec::new(); let mut equipment_changes: Vec<(EquipmentSlot, ItemStack)> = Vec::new();
for (slot, stack, drop_chance) in changes_with_drops { 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); drop_chances.insert(slot.clone(), drop_chance);
equipment_changes.push((slot, stack)); equipment_changes.push((slot, stack));
} }

View File

@ -2455,21 +2455,15 @@ impl Entity {
return true; return true;
}; };
let armor = { let equipment = living.entity_equipment.lock().await;
let equipment = living.entity_equipment.lock().await; for (slot, stack) in &equipment.equipment {
[ if (*slot == EquipmentSlot::HEAD
equipment.get(&EquipmentSlot::HEAD), || *slot == EquipmentSlot::CHEST
equipment.get(&EquipmentSlot::CHEST), || *slot == EquipmentSlot::LEGS
equipment.get(&EquipmentSlot::LEGS), || *slot == EquipmentSlot::FEET)
equipment.get(&EquipmentSlot::FEET), && stack
] .get_item()
}; .has_tag(&tag::Item::MINECRAFT_FREEZE_IMMUNE_WEARABLES)
for stack in armor {
let stack = stack.lock().await;
if stack
.get_item()
.has_tag(&tag::Item::MINECRAFT_FREEZE_IMMUNE_WEARABLES)
{ {
return false; return false;
} }

View File

@ -4,7 +4,6 @@ pub mod statistics;
use core::f32; use core::f32;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use std::f64::consts::TAU; use std::f64::consts::TAU;
use std::mem;
use std::num::NonZeroU8; use std::num::NonZeroU8;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering};
@ -973,7 +972,7 @@ impl Player {
let config = &server.advanced_config.pvp; let config = &server.advanced_config.pvp;
let inventory = self.inventory(); let inventory = self.inventory();
let item_stack = inventory.held_item(); let item_stack = inventory.held_item().await;
let base_damage = self let base_damage = self
.living_entity .living_entity
@ -987,7 +986,7 @@ impl Player {
let mut knockback_level = 0u32; let mut knockback_level = 0u32;
{ {
let stack = item_stack.lock().await; let stack = &item_stack;
if stack.is_empty() { if stack.is_empty() {
// Vanilla fist: base_attack_damage = -1.0, base_attack_speed = -2.4 // Vanilla fist: base_attack_damage = -1.0, base_attack_speed = -2.4
add_damage = -1.0; add_damage = -1.0;
@ -1118,11 +1117,7 @@ impl Player {
self.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::DealtOverkillDamage).await; self.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::DealtOverkillDamage).await;
} }
if let Some(enchantments) = item_stack if let Some(enchantments) = item_stack.get_data_component::<EnchantmentsImpl>() {
.lock()
.await
.get_data_component::<EnchantmentsImpl>()
{
for (enchantment, level) in enchantments.enchantment.iter() { for (enchantment, level) in enchantments.enchantment.iter() {
if **enchantment == Enchantment::FIRE_ASPECT { if **enchantment == Enchantment::FIRE_ASPECT {
victim_entity.set_on_fire_for_ticks(*level as u32 * 80); 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); combat::spawn_sweep_particle(attacker_entity, &world, &pos);
let mut sweep_damage = 1.0; let mut sweep_damage = 1.0;
if let Some(enchantments) = item_stack if let Some(enchantments) = item_stack.get_data_component::<EnchantmentsImpl>()
.lock()
.await
.get_data_component::<EnchantmentsImpl>()
{ {
for (enchantment, level) in enchantments.enchantment.iter() { for (enchantment, level) in enchantments.enchantment.iter() {
if **enchantment == Enchantment::SWEEPING_EDGE { if **enchantment == Enchantment::SWEEPING_EDGE {
@ -1218,11 +1210,8 @@ impl Player {
// 2. Refactor compute cost as a closure: damage_held_item(self, |stack| -> i32 { ... }) // 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 // 3. In practice, single-player scenarios are safe (this is not multiplayer). Document
// as a known limitation if refactoring is deemed too invasive. // as a known limitation if refactoring is deemed too invasive.
self.damage_held_item({ self.damage_held_item(Self::combat_weapon_durability_cost(&item_stack))
let stack = item_stack.lock().await; .await;
Self::combat_weapon_durability_cost(&stack)
})
.await;
// Vanilla `Player#attack` ends the successful-hit branch with // Vanilla `Player#attack` ends the successful-hit branch with
// `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage // `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage
@ -1282,14 +1271,10 @@ impl Player {
EquipmentSlot::Body(_) | EquipmentSlot::Saddle(_) => return false, EquipmentSlot::Body(_) | EquipmentSlot::Saddle(_) => return false,
}; };
let stack_arc = self.inventory.get_stack(slot_index).await; let mut stack = self.inventory().get_stack(slot_index).await;
let result = stack.damage_item(amount);
let updated = { let updated = (result != pumpkin_data::item_stack::DamageResult::Untouched)
let mut stack = stack_arc.lock().await; .then_some((result, stack.clone()));
let result = stack.damage_item(amount);
(result != pumpkin_data::item_stack::DamageResult::Untouched)
.then_some((result, stack.clone()))
};
if let Some((result, updated_stack)) = updated { if let Some((result, updated_stack)) = updated {
if let Some(server) = self.world().server.upgrade() if let Some(server) = self.world().server.upgrade()
@ -1359,13 +1344,12 @@ impl Player {
return; return;
} }
let damage = { let damage = self
let stack = self.inventory.held_item(); .inventory()
let stack = stack.lock().await; .held_item()
stack .await
.get_data_component::<ToolImpl>() .get_data_component::<ToolImpl>()
.map_or(0, |tool| tool.damage_per_block as i32) .map_or(0, |tool| tool.damage_per_block as i32);
};
if damage > 0 { if damage > 0 {
self.damage_held_item(damage).await; self.damage_held_item(damage).await;
@ -3153,14 +3137,12 @@ impl Player {
let keep_inventory = { self.world().level_info.load().game_rules.keep_inventory }; let keep_inventory = { self.world().level_info.load().game_rules.keep_inventory };
if !keep_inventory { if !keep_inventory {
for item in &self.inventory().main_inventory { let mut main_inv = self.inventory().main_inventory.write().await;
let mut lock = item.lock().await; for item in main_inv.iter_mut() {
self.world() if !item.is_empty() {
.drop_stack( let stack = std::mem::replace(item, ItemStack::EMPTY.clone());
&block_pos, self.world().drop_stack(&block_pos, stack).await;
mem::replace(&mut *lock, ItemStack::EMPTY.clone()), }
)
.await;
} }
} }
@ -3309,15 +3291,14 @@ impl Player {
pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool { pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool {
!state.tool_required() !state.tool_required()
|| self || self
.inventory .inventory()
.held_item() .held_item()
.lock()
.await .await
.is_correct_for_drops(block) .is_correct_for_drops(block)
} }
pub async fn get_mining_speed(&self, block: &'static Block) -> f32 { 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 // Haste
if self.living_entity.has_effect(&StatusEffect::HASTE).await if self.living_entity.has_effect(&StatusEffect::HASTE).await
|| self || self
@ -3430,45 +3411,40 @@ impl Player {
} }
pub async fn drop_held_item(&self, drop_stack: bool) { 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 mut item_stack = self.inventory().held_item().await;
let (dropped_stack, updated_stack, selected_slot) = {
let binding = self.inventory.held_item();
let mut item_stack = binding.lock().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; return;
} }
}
let drop_amount = if drop_stack { item_stack.item_count } else { 1 }; item_stack.decrement(drop_amount);
let dropped_stack = item_stack.copy_with_count(drop_amount); let updated_stack = item_stack.clone();
self.inventory().set_held_item(updated_stack.clone()).await;
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)
};
self.drop_item(dropped_stack).await; self.drop_item(dropped_stack).await;
let inv: Arc<dyn Inventory> = self.inventory.clone(); let inv: Arc<dyn Inventory> = self.inventory.clone();
let screen_binding = self.current_screen_handler.lock().await; let screen_binding = self.current_screen_handler.lock().await;
let mut screen_handler = screen_binding.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 if let Some(slot_index) = screen_handler
.get_slot_index(&inv, selected_slot as usize) .get_slot_index(&inv, selected_slot as usize)
.await .await
@ -3557,9 +3533,8 @@ impl Player {
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
for hand in Hand::all() { 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 if stack.item.id == Item::FILLED_MAP.id
&& let Some(map_id_comp) = stack.get_data_component::<MapIdImpl>() && let Some(map_id_comp) = stack.get_data_component::<MapIdImpl>()
{ {
@ -3818,7 +3793,7 @@ impl Player {
return xp; return xp;
} }
let mut candidates: Vec<(usize, EquipmentSlot, Arc<Mutex<ItemStack>>)> = Vec::new(); let mut candidates: Vec<(usize, EquipmentSlot, ItemStack)> = Vec::new();
let selected_slot = self.inventory.get_selected_slot() as usize; let selected_slot = self.inventory.get_selected_slot() as usize;
let mut slot_pairs: Vec<(usize, EquipmentSlot)> = vec![ let mut slot_pairs: Vec<(usize, EquipmentSlot)> = vec![
@ -3832,12 +3807,8 @@ impl Player {
} }
for (slot_index, equipment_slot) in slot_pairs { for (slot_index, equipment_slot) in slot_pairs {
let stack = self.inventory.get_stack(slot_index).await; let stack = self.inventory().get_stack(slot_index).await;
let eligible = { if stack.get_enchantment_level(&Enchantment::MENDING) > 0 && stack.get_damage() > 0 {
let s = stack.lock().await;
s.get_enchantment_level(&Enchantment::MENDING) > 0 && s.get_damage() > 0
};
if eligible {
candidates.push((slot_index, equipment_slot, stack)); candidates.push((slot_index, equipment_slot, stack));
} }
} }
@ -3847,18 +3818,18 @@ impl Player {
} }
let idx = rand::random::<u32>() as usize % candidates.len(); let idx = rand::random::<u32>() as usize % candidates.len();
let (slot_index, equipment_slot, stack) = candidates.swap_remove(idx); let (slot_index, equipment_slot, mut 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 repaired = stack.repair_item(xp.saturating_mul(2));
if repaired <= 0 { if repaired <= 0 {
return xp; return xp;
} }
let updated_stack = stack.clone();
self.inventory()
.set_stack(slot_index, updated_stack.clone())
.await;
let xp_used = (repaired + 1) / 2; let xp_used = (repaired + 1) / 2;
xp = xp.saturating_sub(xp_used); xp = xp.saturating_sub(xp_used);
@ -4436,28 +4407,25 @@ impl Player {
// Check offhand first // Check offhand first
let stack = inventory.get_stack(PlayerInventory::OFF_HAND_SLOT).await; let stack = inventory.get_stack(PlayerInventory::OFF_HAND_SLOT).await;
let item = stack.lock().await;
if matches!( if matches!(
item.item.id, stack.item.id,
id if id == Item::ARROW.id id if id == Item::ARROW.id
|| id == Item::TIPPED_ARROW.id || id == Item::TIPPED_ARROW.id
|| id == Item::SPECTRAL_ARROW.id || id == Item::SPECTRAL_ARROW.id
) && item.item_count > 0 ) && stack.item_count > 0
{ {
return Some(PlayerInventory::OFF_HAND_SLOT); return Some(PlayerInventory::OFF_HAND_SLOT);
} }
drop(item);
// Check hotbar and main inventory // Check hotbar and main inventory
for slot in 0..PlayerInventory::MAIN_SIZE { for slot in 0..PlayerInventory::MAIN_SIZE {
let stack = inventory.get_stack(slot).await; let stack = inventory.get_stack(slot).await;
let item = stack.lock().await;
if matches!( if matches!(
item.item.id, stack.item.id,
id if id == Item::ARROW.id id if id == Item::ARROW.id
|| id == Item::TIPPED_ARROW.id || id == Item::TIPPED_ARROW.id
|| id == Item::SPECTRAL_ARROW.id || id == Item::SPECTRAL_ARROW.id
) && item.item_count > 0 ) && stack.item_count > 0
{ {
return Some(slot); return Some(slot);
} }
@ -4474,15 +4442,15 @@ impl Player {
} }
let inventory = self.inventory(); let inventory = self.inventory();
let stack_arc = inventory.get_stack(slot).await; let mut stack = inventory.get_stack(slot).await;
let mut stack = stack_arc.lock().await;
match stack.item_count { match stack.item_count {
2.. => { 2.. => {
stack.item_count -= 1; stack.item_count -= 1;
inventory.set_stack(slot, stack).await;
true true
} }
1 => { 1 => {
*stack = ItemStack::EMPTY.clone(); inventory.set_stack(slot, ItemStack::EMPTY.clone()).await;
true true
} }
_ => false, _ => false,
@ -4582,15 +4550,14 @@ impl Player {
} }
pub async fn has_item_in_inventory(&self, item: &pumpkin_data::item::Item) -> bool { pub async fn has_item_in_inventory(&self, item: &pumpkin_data::item::Item) -> bool {
for slot in &self.inventory.main_inventory { let main_inv = self.inventory.main_inventory.read().await;
let stack = slot.lock().await; for stack in main_inv.iter() {
if !stack.is_empty() && stack.item.id == item.id { if !stack.is_empty() && stack.item.id == item.id {
return true; return true;
} }
} }
let equipment = self.inventory.entity_equipment.lock().await; let equipment = self.inventory.entity_equipment.lock().await;
for slot_stack in equipment.equipment.values() { for stack in equipment.equipment.values() {
let stack = slot_stack.lock().await;
if !stack.is_empty() && stack.item.id == item.id { if !stack.is_empty() && stack.item.id == item.id {
return true; return true;
} }
@ -4764,25 +4731,24 @@ impl NBTStorage for PlayerInventory {
// Create inventory list with the correct capacity (inventory size) // Create inventory list with the correct capacity (inventory size)
let mut items: Vec<NbtTag> = Vec::with_capacity(41); let mut items: Vec<NbtTag> = Vec::with_capacity(41);
for (i, item) in self.main_inventory.iter().enumerate() { let main_inv = self.main_inventory.read().await;
let stack = item.lock().await; for (i, stack) in main_inv.iter().enumerate() {
if !stack.is_empty() { if !stack.is_empty() {
let mut item_compound = NbtCompound::new(); let mut item_compound = NbtCompound::new();
item_compound.put_byte("Slot", i as i8); item_compound.put_byte("Slot", i as i8);
stack.write_item_stack(&mut item_compound); stack.write_item_stack(&mut item_compound);
drop(stack);
items.push(NbtTag::Compound(item_compound)); items.push(NbtTag::Compound(item_compound));
} }
} }
let mut equipment_compound = NbtCompound::new(); let mut equipment_compound = NbtCompound::new();
let equipment_guard = self.entity_equipment.lock().await;
for slot in self.equipment_slots.values() { for slot in self.equipment_slots.values() {
let stack_binding = self.entity_equipment.lock().await.get(slot); if let Some(stack) = equipment_guard.equipment.get(slot)
let stack = stack_binding.lock().await; && !stack.is_empty()
if !stack.is_empty() { {
let mut item_compound = NbtCompound::new(); let mut item_compound = NbtCompound::new();
stack.write_item_stack(&mut item_compound); stack.write_item_stack(&mut item_compound);
drop(stack);
match slot { match slot {
EquipmentSlot::OffHand(_) => { EquipmentSlot::OffHand(_) => {
equipment_compound.put_compound("offhand", item_compound); equipment_compound.put_compound("offhand", item_compound);
@ -4870,13 +4836,12 @@ impl NBTStorage for EnderChestInventory {
Box::pin(async { Box::pin(async {
// Create item list with the correct capacity (inventory size) // Create item list with the correct capacity (inventory size)
let mut items: Vec<NbtTag> = Vec::with_capacity(Self::INVENTORY_SIZE); let mut items: Vec<NbtTag> = Vec::with_capacity(Self::INVENTORY_SIZE);
for (i, item) in self.items.iter().enumerate() { let ec_items = self.items.read().await;
let stack = item.lock().await; for (i, stack) in ec_items.iter().enumerate() {
if !stack.is_empty() { if !stack.is_empty() {
let mut item_compound = NbtCompound::new(); let mut item_compound = NbtCompound::new();
item_compound.put_byte("Slot", i as i8); item_compound.put_byte("Slot", i as i8);
stack.write_item_stack(&mut item_compound); stack.write_item_stack(&mut item_compound);
drop(stack);
items.push(NbtTag::Compound(item_compound)); items.push(NbtTag::Compound(item_compound));
} }
} }

View File

@ -313,9 +313,9 @@ impl Player {
if !self.has_advancement(Advancement::STORY_ENCHANT_ITEM).await { if !self.has_advancement(Advancement::STORY_ENCHANT_ITEM).await {
let mut has_enchanted = false; let mut has_enchanted = false;
for item in &self.inventory().main_inventory { let main_inv = self.inventory().main_inventory.read().await;
let lock = item.lock().await; for stack in main_inv.iter() {
if !lock.is_empty() && lock.has_enchantments() { if !stack.is_empty() && stack.has_enchantments() {
has_enchanted = true; has_enchanted = true;
break; break;
} }

View File

@ -182,9 +182,9 @@ impl ItemBehaviour for BoatItem {
world.spawn_entity(boat_entity).await; world.spawn_entity(boat_entity).await;
// Decrement item unless in creative mode // Decrement item unless in creative mode
let held_item = player.inventory.held_item(); let mut stack = player.inventory.held_item().await;
let mut stack = held_item.lock().await;
stack.decrement_unless_creative(player.gamemode.load(), 1); 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: world.emitGameEvent(user, GameEvent.ENTITY_PLACE, hitResult.getPos())
// TODO: user.incrementStat(Stats.USED.getOrCreateStat(this)) // TODO: user.incrementStat(Stats.USED.getOrCreateStat(this))

View File

@ -41,8 +41,7 @@ impl ItemBehaviour for BowItem {
// Get the held item stack // Get the held item stack
let inventory = player.inventory(); let inventory = player.inventory();
let held = inventory.held_item(); let stack = inventory.held_item().await;
let stack = held.lock().await.clone();
// Start the bow drawing animation // Start the bow drawing animation
player player
@ -98,7 +97,7 @@ impl BowItem {
let projectile = if let Some(slot) = arrow_slot { let projectile = if let Some(slot) = arrow_slot {
let stack = player.inventory().get_stack(slot).await; let stack = player.inventory().get_stack(slot).await;
stack.lock().await.copy_with_count(1) stack.copy_with_count(1)
} else { } else {
ItemStack::new(1, &Item::ARROW) ItemStack::new(1, &Item::ARROW)
}; };
@ -109,11 +108,9 @@ impl BowItem {
// Check for Infinity enchantment // Check for Infinity enchantment
let mut has_infinity = false; let mut has_infinity = false;
let held = player.inventory().held_item(); let held = player.inventory().held_item().await;
if let Some(enchantments) = if let Some(enchantments) =
held.lock() held.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
.await
.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
{ {
has_infinity = enchantments has_infinity = enchantments
.enchantment .enchantment
@ -179,8 +176,7 @@ impl BowItem {
ArrowEntity::new_shot(arrow_entity, player.get_entity(), &projectile, pickup); ArrowEntity::new_shot(arrow_entity, player.get_entity(), &projectile, pickup);
// Read enchantments of the held item (bow) // Read enchantments of the held item (bow)
let held = player.inventory().held_item(); let stack = player.inventory().held_item().await;
let stack = held.lock().await;
if let Some(enchantments) = if let Some(enchantments) =
stack.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>() stack.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
{ {

View File

@ -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) { async fn give_player_bucket_item(player: &Player, item: &'static Item) {
if player.gamemode.load() == GameMode::Creative { if player.gamemode.load() == GameMode::Creative {
for i in 0..player.inventory.main_inventory.len() { let inv = player.inventory.main_inventory.read().await;
if player.inventory.main_inventory[i].lock().await.item.id == item.id { for stack in inv.iter() {
if stack.item.id == item.id {
return; return;
} }
} }
drop(inv);
let mut item_stack = ItemStack::new(1, item); let mut item_stack = ItemStack::new(1, item);
player player
.inventory .inventory
@ -112,14 +114,13 @@ async fn give_player_bucket_item(player: &Player, item: &'static Item) {
.await; .await;
} else { } else {
let item_stack = ItemStack::new(1, item); let item_stack = ItemStack::new(1, item);
let held_item = player.inventory.held_item(); let mut held_stack = player.inventory.held_item().await;
let mut held_stack = held_item.lock().await;
if held_stack.item_count == 1 { if held_stack.item_count == 1 {
*held_stack = item_stack; player.inventory.set_held_item(item_stack).await;
} else { } else {
held_stack.decrement(1); held_stack.decrement(1);
drop(held_stack); player.inventory.set_held_item(held_stack).await;
player player
.inventory .inventory
.offer_or_drop_stack(item_stack, player) .offer_or_drop_stack(item_stack, player)

View File

@ -22,8 +22,7 @@ impl ItemBehaviour for BundleItem {
player: &'a Player, player: &'a Player,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move { Box::pin(async move {
let held_item_ref = player.inventory.held_item(); let mut held_item = player.inventory.held_item().await;
let mut held_item = held_item_ref.lock().await;
let mut matched = false; let mut matched = false;
let mut used_slot_index = player.inventory.get_selected_slot() as usize; let mut used_slot_index = player.inventory.get_selected_slot() as usize;
@ -40,7 +39,6 @@ impl ItemBehaviour for BundleItem {
&position, &position,
); );
let updated_bundle = held_item.clone(); let updated_bundle = held_item.clone();
drop(held_item);
player.drop_item(extracted_stack).await; player.drop_item(extracted_stack).await;
player.sync_hand_slot(used_slot_index, updated_bundle).await; player.sync_hand_slot(used_slot_index, updated_bundle).await;
@ -48,8 +46,7 @@ impl ItemBehaviour for BundleItem {
} }
if !matched { if !matched {
let off_hand_item_ref = player.inventory.off_hand_item().await; let mut off_hand_item = player.inventory.off_hand_item().await;
let mut off_hand_item = off_hand_item_ref.lock().await;
if !off_hand_item.is_empty() && Self::ids().contains(&off_hand_item.item.id) { if !off_hand_item.is_empty() && Self::ids().contains(&off_hand_item.item.id) {
used_slot_index = 40; // OFF_HAND_SLOT used_slot_index = 40; // OFF_HAND_SLOT
if let Some(bundle_contents) = if let Some(bundle_contents) =
@ -63,7 +60,6 @@ impl ItemBehaviour for BundleItem {
&position, &position,
); );
let updated_bundle = off_hand_item.clone(); let updated_bundle = off_hand_item.clone();
drop(off_hand_item);
player.drop_item(extracted_stack).await; player.drop_item(extracted_stack).await;
player.sync_hand_slot(used_slot_index, updated_bundle).await; player.sync_hand_slot(used_slot_index, updated_bundle).await;

View File

@ -3,7 +3,6 @@ use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tokio::sync::Mutex;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::entity::projectile::arrow::{ArrowEntity, ArrowPickup}; use crate::entity::projectile::arrow::{ArrowEntity, ArrowPickup};
@ -33,14 +32,13 @@ impl ItemBehaviour for CrossbowItem {
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move { Box::pin(async move {
let inventory = player.inventory(); let inventory = player.inventory();
let held = inventory.held_item(); let stack = inventory.held_item().await;
let stack = held.lock().await.clone();
if stack if stack
.get_data_component::<ChargedProjectilesImpl>() .get_data_component::<ChargedProjectilesImpl>()
.is_some() .is_some()
{ {
Self::fire_projectiles(player, &held).await; Self::fire_projectiles(player).await;
return; return;
} }
@ -66,8 +64,7 @@ impl ItemBehaviour for CrossbowItem {
let use_ticks = 72000 - use_ticks; let use_ticks = 72000 - use_ticks;
let mut charge_time = 25; let mut charge_time = 25;
let held = player.inventory().held_item(); let mut stack = player.inventory().held_item().await;
let stack = held.lock().await;
if let Some(enchantments) = stack.get_data_component::<EnchantmentsImpl>() { if let Some(enchantments) = stack.get_data_component::<EnchantmentsImpl>() {
for (enchantment, level) in enchantments.enchantment.iter() { for (enchantment, level) in enchantments.enchantment.iter() {
@ -76,29 +73,24 @@ impl ItemBehaviour for CrossbowItem {
} }
} }
} }
drop(stack);
charge_time = charge_time.max(0); charge_time = charge_time.max(0);
if use_ticks >= charge_time { if use_ticks >= charge_time {
let arrow_slot = player.find_arrow().await; let arrow_slot = player.find_arrow().await;
let mut stack = held.lock().await;
let (arrow_nbt_wrapper, slot) = { let (arrow_nbt_wrapper, slot) = {
if let Some(slot) = arrow_slot { if let Some(slot) = arrow_slot {
let inventory = player.inventory(); let inventory = player.inventory();
let arrow_stack_arc = inventory.get_stack(slot).await; let arrow_stack = inventory.get_stack(slot).await;
let arrow_stack = arrow_stack_arc.lock().await;
let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new(); let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new();
arrow_stack arrow_stack
.copy_with_count(1) .copy_with_count(1)
.write_item_stack(&mut arrow_nbt); .write_item_stack(&mut arrow_nbt);
drop(arrow_stack);
(Some(arrow_nbt), slot) (Some(arrow_nbt), slot)
} else if player.gamemode.load() == GameMode::Creative { } else if player.gamemode.load() == GameMode::Creative {
let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new(); let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new();
let arrow_stack = ItemStack::new(1, &Item::ARROW); let arrow_stack = ItemStack::new(1, &Item::ARROW);
arrow_stack.write_item_stack(&mut arrow_nbt); arrow_stack.write_item_stack(&mut arrow_nbt);
drop(arrow_stack);
(Some(arrow_nbt), 0) (Some(arrow_nbt), 0)
} else { } else {
@ -112,6 +104,7 @@ impl ItemBehaviour for CrossbowItem {
projectiles: vec![arrow_nbt], projectiles: vec![arrow_nbt],
})), })),
)); ));
player.inventory().set_held_item(stack).await;
if player.gamemode.load() != GameMode::Creative { if player.gamemode.load() != GameMode::Creative {
player.consume_arrow(slot).await; player.consume_arrow(slot).await;
@ -138,23 +131,17 @@ impl ItemBehaviour for CrossbowItem {
} }
impl CrossbowItem { impl CrossbowItem {
async fn fire_projectiles(player: &Player, held: &Arc<Mutex<ItemStack>>) { async fn fire_projectiles(player: &Player) {
let (projectiles, has_multishot) = { let mut held = player.inventory().held_item().await;
let stack = held.lock().await; let projectiles = held.get_data_component::<ChargedProjectilesImpl>().cloned();
let projectiles = stack let has_multishot =
.get_data_component::<ChargedProjectilesImpl>() held.get_data_component::<EnchantmentsImpl>()
.cloned(); .is_some_and(|enchantments| {
let has_multishot = enchantments
stack .enchantment
.get_data_component::<EnchantmentsImpl>() .iter()
.is_some_and(|enchantments| { .any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT)
enchantments });
.enchantment
.iter()
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT)
});
(projectiles, has_multishot)
};
if let Some(charged) = projectiles { if let Some(charged) = projectiles {
let world = player.world(); let world = player.world();
@ -200,10 +187,9 @@ impl CrossbowItem {
} }
} }
held.lock() held.patch
.await
.patch
.retain(|(id, _)| *id != DataComponent::ChargedProjectiles); .retain(|(id, _)| *id != DataComponent::ChargedProjectiles);
player.inventory().set_held_item(held).await;
player.damage_held_item(1).await; player.damage_held_item(1).await;
} }
} }

View File

@ -37,7 +37,7 @@ impl ItemBehaviour for EggItem {
); );
// Capture the held item stack and pass it to the thrown egg entity // 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 entity = Entity::new(world.clone(), position, &EntityType::EGG);
let egg = EggEntity::new_shot(entity, player.get_entity()); let egg = EggEntity::new_shot(entity, player.get_entity());
@ -51,22 +51,23 @@ impl ItemBehaviour for EggItem {
world.spawn_entity(Arc::new(egg)).await; world.spawn_entity(Arc::new(egg)).await;
// Consume item // Consume item
let held_item = player.inventory.held_item(); let mut main_hand = player.inventory.held_item().await;
let consumed = { let consumed = if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) {
let mut main_hand = held_item.lock().await; main_hand.decrement_unless_creative(player.gamemode.load(), 1);
if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) { player.inventory.set_held_item(main_hand).await;
main_hand.decrement_unless_creative(player.gamemode.load(), 1); true
true } else {
} else { false
false
}
}; };
if !consumed { if !consumed {
let off_hand_item = player.inventory.off_hand_item().await; let mut off_hand = player.inventory.off_hand_item().await;
let mut off_hand = off_hand_item.lock().await;
if !off_hand.is_empty() && Self::ids().contains(&off_hand.item.id) { if !off_hand.is_empty() && Self::ids().contains(&off_hand.item.id) {
off_hand.decrement_unless_creative(player.gamemode.load(), 1); off_hand.decrement_unless_creative(player.gamemode.load(), 1);
player
.inventory
.set_stack_in_hand(pumpkin_util::Hand::Left, off_hand)
.await;
} }
} }
}) })

View File

@ -128,7 +128,9 @@ impl ItemBehaviour for EnderEyeItem {
); );
player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::LaunchedEyeOfEnder).await; 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;
}) })
} }

Some files were not shown because too many files have changed in this diff Show More