From 70b75a0ac5f41f190508e65bde5ea748d43c92b7 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 4 Dec 2025 23:50:00 +0100 Subject: [PATCH] remove async-trait entirely --- Cargo.lock | 7 +- Cargo.toml | 2 - pumpkin-api-macros/src/lib.rs | 1 - pumpkin-inventory/Cargo.toml | 1 - .../src/crafting/crafting_inventory.rs | 44 +- .../src/crafting/crafting_screen_handler.rs | 117 +++-- pumpkin-inventory/src/double.rs | 84 ++-- pumpkin-inventory/src/furnace/furnace_slot.rs | 26 +- .../src/player/ender_chest_inventory.rs | 64 +-- .../src/player/player_inventory.rs | 165 ++++--- pumpkin-inventory/src/screen_handler.rs | 16 +- pumpkin-inventory/src/slot.rs | 338 ++++++++----- pumpkin-world/Cargo.toml | 1 - pumpkin-world/benches/chunk_io.rs | 3 +- pumpkin-world/src/block/entities/barrel.rs | 55 ++- pumpkin-world/src/block/entities/chest.rs | 56 ++- .../src/block/entities/chiseled_bookshelf.rs | 43 +- pumpkin-world/src/block/entities/dropper.rs | 44 +- pumpkin-world/src/block/entities/furnace.rs | 72 +-- pumpkin-world/src/block/entities/hopper.rs | 44 +- .../src/block/entities/shulker_box.rs | 58 ++- pumpkin-world/src/chunk/format/anvil.rs | 5 +- pumpkin-world/src/chunk/format/linear.rs | 5 +- pumpkin-world/src/chunk/io/file_manager.rs | 457 ++++++++++-------- pumpkin-world/src/chunk/io/mod.rs | 57 ++- pumpkin-world/src/inventory/inventory.rs | 180 +++---- pumpkin/Cargo.toml | 1 - pumpkin/src/block/blocks/barrel.rs | 26 +- pumpkin/src/block/blocks/chests.rs | 32 +- pumpkin/src/block/blocks/crafting_table.rs | 26 +- pumpkin/src/block/blocks/ender_chest.rs | 23 +- pumpkin/src/block/blocks/furnace.rs | 37 +- pumpkin/src/block/blocks/hopper.rs | 27 +- pumpkin/src/block/blocks/plant/segmented.rs | 48 +- pumpkin/src/block/blocks/redstone/dropper.rs | 26 +- pumpkin/src/block/blocks/shulker_box.rs | 26 +- pumpkin/src/plugin/api/mod.rs | 17 +- pumpkin/src/plugin/mod.rs | 65 ++- 38 files changed, 1282 insertions(+), 1017 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1cf09ca52..c2fb47b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,9 +1733,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", @@ -2167,7 +2167,6 @@ dependencies = [ name = "pumpkin" version = "0.1.0-dev+1.21.10" dependencies = [ - "async-trait", "base64 0.22.1", "bytes", "console-subscriber", @@ -2252,7 +2251,6 @@ dependencies = [ name = "pumpkin-inventory" version = "0.1.0-dev+1.21.10" dependencies = [ - "async-trait", "crossbeam-utils", "log", "pumpkin-data", @@ -2347,7 +2345,6 @@ dependencies = [ name = "pumpkin-world" version = "0.1.0-dev+1.21.10" dependencies = [ - "async-trait", "bitflags", "bytes", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 2a3d2006d..0af0ae9ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,5 @@ uuid = { version = "1.19", features = ["serde", "v3", "v4"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -async-trait = "0.1" - tempfile = { version = "3.23.0", default-features = false } proc-macro-error2 = "2" diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 4cb4b9e1e..478bc5920 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -68,7 +68,6 @@ pub fn plugin_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { #input_struct - #[async_trait::async_trait] impl pumpkin::plugin::Plugin for #struct_ident { #(#methods)* } diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 9abefaef3..19df2950b 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -14,6 +14,5 @@ pumpkin-util = { path = "../pumpkin-util" } log.workspace = true tokio.workspace = true thiserror.workspace = true -async-trait.workspace = true crossbeam-utils = "0.8.21" diff --git a/pumpkin-inventory/src/crafting/crafting_inventory.rs b/pumpkin-inventory/src/crafting/crafting_inventory.rs index 8b482b27c..b1322ade1 100644 --- a/pumpkin-inventory/src/crafting/crafting_inventory.rs +++ b/pumpkin-inventory/src/crafting/crafting_inventory.rs @@ -1,11 +1,10 @@ use std::sync::Arc; use std::{any::Any, pin::Pin}; -use async_trait::async_trait; use pumpkin_world::{inventory::split_stack, item::ItemStack}; use tokio::sync::Mutex; -use pumpkin_world::inventory::{Clearable, Inventory}; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture}; use super::recipes::RecipeInputInventory; @@ -32,39 +31,44 @@ impl CraftingInventory { } } -#[async_trait] impl Inventory for CraftingInventory { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } fn as_any(&self) -> &dyn Any { diff --git a/pumpkin-inventory/src/crafting/crafting_screen_handler.rs b/pumpkin-inventory/src/crafting/crafting_screen_handler.rs index f415c1eb3..b9e0e35ad 100644 --- a/pumpkin-inventory/src/crafting/crafting_screen_handler.rs +++ b/pumpkin-inventory/src/crafting/crafting_screen_handler.rs @@ -10,8 +10,8 @@ use crate::screen_handler::{ InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture, ScreenHandlerListener, }; -use crate::slot::{NormalSlot, Slot}; -use async_trait::async_trait; +use crate::slot::{BoxFuture, NormalSlot, Slot}; + use crossbeam_utils::atomic::AtomicCell; use pumpkin_data::recipes::{CraftingRecipeTypes, RECIPES_CRAFTING, RecipeResultStruct}; use pumpkin_data::screen::WindowType; @@ -324,7 +324,6 @@ impl ResultSlot { } } -#[async_trait] impl Slot for ResultSlot { fn get_inventory(&self) -> Arc { self.inventory.clone() @@ -338,73 +337,95 @@ impl Slot for ResultSlot { self.id.store(id as u8, Ordering::Relaxed); } - async fn on_quick_move_crafted(&self, _stack: ItemStack, _stack_prev: ItemStack) { - // refill the result slot with the recipe result - self.refill_output().await; + fn on_quick_move_crafted( + &self, + _stack: ItemStack, + _stack_prev: ItemStack, + ) -> BoxFuture<'_, ()> { + Box::pin(async move { + // refill the result slot with the recipe result + self.refill_output().await; + }) } - async fn on_take_item(&self, player: &dyn InventoryPlayer, stack: &ItemStack) { - for i in 0..self.inventory.size() { - let slot = self.inventory.get_stack(i).await; - let mut stack = slot.lock().await; - if !stack.is_empty() { - //TODO: Handle remaining items. - stack.item_count -= 1; + fn on_take_item<'a>( + &'a self, + player: &'a dyn InventoryPlayer, + stack: &'a ItemStack, + ) -> BoxFuture<'a, ()> { + Box::pin(async move { + for i in 0..self.inventory.size() { + let slot = self.inventory.get_stack(i).await; + let mut stack = slot.lock().await; + if !stack.is_empty() { + //TODO: Handle remaining items. + stack.item_count -= 1; + } } - } - self.stat_crafted(stack.item_count, player); - self.mark_dirty().await; + self.stat_crafted(stack.item_count, player); + self.mark_dirty().await; + }) } - async fn can_insert(&self, _stack: &ItemStack) -> bool { - false + fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> { + Box::pin(async move { false }) } - async fn get_stack(&self) -> Arc> { - self.result.clone() + fn get_stack(&self) -> BoxFuture<'_, Arc>> { + Box::pin(async move { self.result.clone() }) } - async fn get_cloned_stack(&self) -> ItemStack { - self.result.lock().await.clone() + fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { + Box::pin(async move { self.result.lock().await.clone() }) } - async fn has_stack(&self) -> bool { - !self.result.lock().await.is_empty() + fn has_stack(&self) -> BoxFuture<'_, bool> { + Box::pin(async move { !self.result.lock().await.is_empty() }) } - async fn set_stack(&self, _stack: ItemStack) { - self.refill_output().await; + fn set_stack(&self, _stack: ItemStack) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.refill_output().await; + }) } - async fn set_stack_prev(&self, _stack: ItemStack, _previous_stack: ItemStack) { - self.refill_output().await; + fn set_stack_prev(&self, _stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.refill_output().await; + }) } - async fn mark_dirty(&self) { - self.inventory.mark_dirty(); + fn mark_dirty(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.inventory.mark_dirty(); + }) } - async fn get_max_item_count(&self) -> u8 { - let mut count = u8::MAX; - for i in 0..self.inventory.size() { - let slot = self.inventory.get_stack(i).await; - let slot = slot.lock().await; - if !slot.is_empty() { - count = count.min(slot.item_count); + fn get_max_item_count(&self) -> BoxFuture<'_, u8> { + Box::pin(async move { + let mut count = u8::MAX; + for i in 0..self.inventory.size() { + let slot = self.inventory.get_stack(i).await; + let slot = slot.lock().await; + if !slot.is_empty() { + count = count.min(slot.item_count); + } } - } - count + count + }) } - async fn take_stack(&self, _amount: u8) -> ItemStack { - if self.has_stack().await { - let stack = self.result.lock().await; - // Vanilla: net.minecraft.world.inventory.ResultContainer#removeItem - // Regardless of the amount, we always return the full stack - stack.clone() - } else { - ItemStack::EMPTY.clone() - } + fn take_stack(&self, _amount: u8) -> BoxFuture<'_, ItemStack> { + Box::pin(async move { + if self.has_stack().await { + let stack = self.result.lock().await; + // Vanilla: net.minecraft.world.inventory.ResultContainer#removeItem + // Regardless of the amount, we always return the full stack + stack.clone() + } else { + ItemStack::EMPTY.clone() + } + }) } } diff --git a/pumpkin-inventory/src/double.rs b/pumpkin-inventory/src/double.rs index 88549214a..cfce253cc 100644 --- a/pumpkin-inventory/src/double.rs +++ b/pumpkin-inventory/src/double.rs @@ -1,8 +1,7 @@ use std::{any::Any, pin::Pin, sync::Arc}; -use async_trait::async_trait; use pumpkin_world::{ - inventory::{Clearable, Inventory}, + inventory::{Clearable, Inventory, InventoryFuture}, item::ItemStack, }; use tokio::sync::Mutex; @@ -19,52 +18,59 @@ impl DoubleInventory { } } -#[async_trait] impl Inventory for DoubleInventory { fn size(&self) -> usize { self.first.size() + self.second.size() } - async fn is_empty(&self) -> bool { - self.first.is_empty().await && self.second.is_empty().await + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { self.first.is_empty().await && self.second.is_empty().await }) } - async fn get_stack(&self, slot: usize) -> Arc> { - if slot >= self.first.size() { - self.second.get_stack(slot - self.first.size()).await - } else { - self.first.get_stack(slot).await - } + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { + if slot >= self.first.size() { + self.second.get_stack(slot - self.first.size()).await + } else { + self.first.get_stack(slot).await + } + }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - if slot >= self.first.size() { - self.second.remove_stack(slot - self.first.size()).await - } else { - self.first.remove_stack(slot).await - } + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + if slot >= self.first.size() { + self.second.remove_stack(slot - self.first.size()).await + } else { + self.first.remove_stack(slot).await + } + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - if slot >= self.first.size() { - self.second - .remove_stack_specific(slot - self.first.size(), amount) - .await - } else { - self.first.remove_stack_specific(slot, amount).await - } + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + if slot >= self.first.size() { + self.second + .remove_stack_specific(slot - self.first.size(), amount) + .await + } else { + self.first.remove_stack_specific(slot, amount).await + } + }) } fn get_max_count_per_stack(&self) -> u8 { self.first.get_max_count_per_stack() } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - if slot >= self.first.size() { - self.second.set_stack(slot - self.first.size(), stack).await - } else { - self.first.set_stack(slot, stack).await - } + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + if slot >= self.first.size() { + self.second.set_stack(slot - self.first.size(), stack).await + } else { + self.first.set_stack(slot, stack).await + } + }) } fn mark_dirty(&self) { @@ -72,14 +78,18 @@ impl Inventory for DoubleInventory { self.second.mark_dirty(); } - async fn on_open(&self) { - self.first.on_open().await; - self.second.on_open().await; + fn on_open(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.first.on_open().await; + self.second.on_open().await; + }) } - async fn on_close(&self) { - self.first.on_close().await; - self.second.on_close().await; + fn on_close(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.first.on_close().await; + self.second.on_close().await; + }) } fn is_valid_slot_for(&self, slot: usize, stack: &ItemStack) -> bool { diff --git a/pumpkin-inventory/src/furnace/furnace_slot.rs b/pumpkin-inventory/src/furnace/furnace_slot.rs index e5f17cb1b..fd4b40c18 100644 --- a/pumpkin-inventory/src/furnace/furnace_slot.rs +++ b/pumpkin-inventory/src/furnace/furnace_slot.rs @@ -1,10 +1,9 @@ use std::sync::{Arc, atomic::AtomicU8}; -use async_trait::async_trait; use pumpkin_data::{fuels::is_fuel, item::Item}; use pumpkin_world::inventory::Inventory; -use crate::slot::Slot; +use crate::slot::{BoxFuture, Slot}; #[derive(Debug, Clone, Copy)] pub enum FurnaceSlotType { @@ -31,7 +30,6 @@ impl FurnaceSlot { } } } -#[async_trait] impl Slot for FurnaceSlot { fn get_inventory(&self) -> Arc { self.inventory.clone() @@ -46,15 +44,21 @@ impl Slot for FurnaceSlot { .store(id as u8, std::sync::atomic::Ordering::Relaxed); } - async fn mark_dirty(&self) { - self.inventory.mark_dirty(); + fn mark_dirty(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.inventory.mark_dirty(); + }) } - async fn can_insert(&self, stack: &pumpkin_world::item::ItemStack) -> bool { - match self.slot_type { - FurnaceSlotType::Top => true, - FurnaceSlotType::Bottom => is_fuel(stack.item.id) || stack.item.id == Item::BUCKET.id, - FurnaceSlotType::Side => false, - } + fn can_insert<'a>(&'a self, stack: &'a pumpkin_world::item::ItemStack) -> BoxFuture<'a, bool> { + Box::pin(async move { + match self.slot_type { + FurnaceSlotType::Top => true, + FurnaceSlotType::Bottom => { + is_fuel(stack.item.id) || stack.item.id == Item::BUCKET.id + } + FurnaceSlotType::Side => false, + } + }) } } diff --git a/pumpkin-inventory/src/player/ender_chest_inventory.rs b/pumpkin-inventory/src/player/ender_chest_inventory.rs index 5d8a6845c..bc742edba 100644 --- a/pumpkin-inventory/src/player/ender_chest_inventory.rs +++ b/pumpkin-inventory/src/player/ender_chest_inventory.rs @@ -1,9 +1,8 @@ use std::{any::Any, array::from_fn, pin::Pin, sync::Arc}; -use async_trait::async_trait; use pumpkin_world::{ block::viewer::ViewerCountTracker, - inventory::{Clearable, Inventory, split_stack}, + inventory::{Clearable, Inventory, InventoryFuture, split_stack}, item::ItemStack, }; use tokio::sync::Mutex; @@ -46,51 +45,60 @@ impl EnderChestInventory { } } -#[async_trait] impl Inventory for EnderChestInventory { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } - async fn on_open(&self) { - if let Some(tracker) = self.tracker.lock().await.as_ref() { - tracker.open_container(); - } + fn on_open(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + if let Some(tracker) = self.tracker.lock().await.as_ref() { + tracker.open_container(); + } + }) } - async fn on_close(&self) { - if let Some(tracker) = self.tracker.lock().await.as_ref() { - tracker.close_container(); - } + fn on_close(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + if let Some(tracker) = self.tracker.lock().await.as_ref() { + tracker.close_container(); + } + }) } fn mark_dirty(&self) {} diff --git a/pumpkin-inventory/src/player/player_inventory.rs b/pumpkin-inventory/src/player/player_inventory.rs index 54970e92f..0bd0d58e1 100644 --- a/pumpkin-inventory/src/player/player_inventory.rs +++ b/pumpkin-inventory/src/player/player_inventory.rs @@ -1,11 +1,11 @@ use crate::entity_equipment::EntityEquipment; use crate::screen_handler::InventoryPlayer; -use async_trait::async_trait; + use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_protocol::java::client::play::CSetPlayerInventory; use pumpkin_util::Hand; -use pumpkin_world::inventory::split_stack; use pumpkin_world::inventory::{Clearable, Inventory}; +use pumpkin_world::inventory::{InventoryFuture, split_stack}; use pumpkin_world::item::ItemStack; use std::any::Any; use std::array::from_fn; @@ -320,89 +320,98 @@ impl Clearable for PlayerInventory { } } -#[async_trait] impl Inventory for PlayerInventory { fn size(&self) -> usize { self.main_inventory.len() + self.equipment_slots.len() } - async fn is_empty(&self) -> bool { - for item in self.main_inventory.iter() { - if !item.lock().await.is_empty() { - return false; - } - } - - for slot in self.equipment_slots.values() { - if !self - .entity_equipment - .lock() - .await - .get(slot) - .lock() - .await - .is_empty() - { - return false; - } - } - - true - } - - async fn get_stack(&self, slot: usize) -> Arc> { - if slot < self.main_inventory.len() { - self.main_inventory[slot].clone() - } else { - let slot = self.equipment_slots.get(&slot).unwrap(); - self.entity_equipment.lock().await.get(slot) - } - } - - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - if slot < self.main_inventory.len() { - split_stack(&self.main_inventory, slot, amount).await - } else { - let slot = self.equipment_slots.get(&slot).unwrap(); - - let equipment = self.entity_equipment.lock().await.get(slot); - let mut stack = equipment.lock().await; - - if !stack.is_empty() { - return stack.split(amount); - } - - ItemStack::EMPTY.clone() - } - } - - async fn remove_stack(&self, slot: usize) -> ItemStack { - if slot < self.main_inventory.len() { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.main_inventory[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed - } else { - let slot = self.equipment_slots.get(&slot).unwrap(); - self.entity_equipment - .lock() - .await - .put(slot, ItemStack::EMPTY.clone()) - .await - } - } - - async fn set_stack(&self, slot: usize, stack: ItemStack) { - if slot < self.main_inventory.len() { - *self.main_inventory[slot].lock().await = stack; - } else { - match self.equipment_slots.get(&slot) { - Some(slot) => { - self.entity_equipment.lock().await.put(slot, stack).await; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for item in self.main_inventory.iter() { + if !item.lock().await.is_empty() { + return false; } - None => log::warn!("Failed to get Equipment Slot at {slot}"), } - } + + for slot in self.equipment_slots.values() { + if !self + .entity_equipment + .lock() + .await + .get(slot) + .lock() + .await + .is_empty() + { + return false; + } + } + + true + }) + } + + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { + if slot < self.main_inventory.len() { + self.main_inventory[slot].clone() + } else { + let slot = self.equipment_slots.get(&slot).unwrap(); + self.entity_equipment.lock().await.get(slot) + } + }) + } + + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + if slot < self.main_inventory.len() { + split_stack(&self.main_inventory, slot, amount).await + } else { + let slot = self.equipment_slots.get(&slot).unwrap(); + + let equipment = self.entity_equipment.lock().await.get(slot); + let mut stack = equipment.lock().await; + + if !stack.is_empty() { + return stack.split(amount); + } + + ItemStack::EMPTY.clone() + } + }) + } + + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + if slot < self.main_inventory.len() { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.main_inventory[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + } else { + let slot = self.equipment_slots.get(&slot).unwrap(); + self.entity_equipment + .lock() + .await + .put(slot, ItemStack::EMPTY.clone()) + .await + } + }) + } + + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + if slot < self.main_inventory.len() { + *self.main_inventory[slot].lock().await = stack; + } else { + match self.equipment_slots.get(&slot) { + Some(slot) => { + self.entity_equipment.lock().await.put(slot, stack).await; + } + None => log::warn!("Failed to get Equipment Slot at {slot}"), + } + } + }) } fn mark_dirty(&self) {} diff --git a/pumpkin-inventory/src/screen_handler.rs b/pumpkin-inventory/src/screen_handler.rs index b47ce365b..1d37165bd 100644 --- a/pumpkin-inventory/src/screen_handler.rs +++ b/pumpkin-inventory/src/screen_handler.rs @@ -4,7 +4,6 @@ use crate::{ slot::{NormalSlot, Slot}, sync_handler::{SyncHandler, TrackedStack}, }; -use async_trait::async_trait; use log::warn; use pumpkin_data::{ data_component_impl::{EquipmentSlot, EquipmentType, EquippableImpl}, @@ -1038,14 +1037,17 @@ pub trait ScreenHandlerListener: Send + Sync { } } -#[async_trait] +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +pub type SharedScreenHandler = Arc>; + pub trait ScreenHandlerFactory: Send + Sync { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - player: &dyn InventoryPlayer, - ) -> Option>>; + player_inventory: &'a Arc, + player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option>; fn get_display_name(&self) -> TextComponent; } diff --git a/pumpkin-inventory/src/slot.rs b/pumpkin-inventory/src/slot.rs index 7b2dc8598..d09f04281 100644 --- a/pumpkin-inventory/src/slot.rs +++ b/pumpkin-inventory/src/slot.rs @@ -1,6 +1,7 @@ #![warn(unused)] use std::{ fmt::Debug, + pin::Pin, sync::{ Arc, atomic::{AtomicU8, Ordering}, @@ -9,16 +10,17 @@ use std::{ }; use crate::screen_handler::InventoryPlayer; -use async_trait::async_trait; + use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::item::Item; use pumpkin_world::inventory::Inventory; use pumpkin_world::item::ItemStack; use tokio::{sync::Mutex, time::timeout}; +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + // Slot.java // This is a trait due to crafting slots being a thing -#[async_trait] pub trait Slot: Send + Sync + Debug { fn get_inventory(&self) -> Arc; @@ -31,167 +33,227 @@ pub trait Slot: Send + Sync + Debug { /// locks to the stack to avoid deadlocks. /// /// Also see: `ScreenHandler::quick_move` - async fn on_quick_move_crafted(&self, _stack: ItemStack, _stack_prev: ItemStack) {} + fn on_quick_move_crafted( + &self, + _stack: ItemStack, + _stack_prev: ItemStack, + ) -> BoxFuture<'_, ()> { + Box::pin(async move {}) // Default implementation + } /// Callback for when an item is taken from the slot. /// /// Also see: `safe_take` - async fn on_take_item(&self, _player: &dyn InventoryPlayer, _stack: &ItemStack) { - self.mark_dirty().await; + fn on_take_item<'a>( + &'a self, + _player: &'a dyn InventoryPlayer, + _stack: &'a ItemStack, + ) -> BoxFuture<'a, ()> { + // Default implementation logic: + Box::pin(async move { + self.mark_dirty().await; + }) } // Used for plugins - async fn on_click(&self, _player: &dyn InventoryPlayer) {} - - async fn can_insert(&self, _stack: &ItemStack) -> bool { - true + fn on_click(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, ()> { + Box::pin(async move {}) // Default implementation } - async fn get_stack(&self) -> Arc> { - self.get_inventory().get_stack(self.get_index()).await + fn can_insert<'a>(&'a self, _stack: &'a ItemStack) -> BoxFuture<'a, bool> { + // Default implementation logic: + Box::pin(async move { true }) } - async fn get_cloned_stack(&self) -> ItemStack { - 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() + fn get_stack(&self) -> BoxFuture<'_, Arc>> { + // Default implementation logic: + Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await }) } - async fn has_stack(&self) -> bool { - let inv = self.get_inventory(); - !inv.get_stack(self.get_index()) - .await - .lock() - .await - .is_empty() + fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> { + // Default implementation logic: + Box::pin(async move { + let stack = self.get_stack().await; + let lock = timeout(Duration::from_secs(5), stack.lock()) + .await + .expect("Timed out while trying to acquire lock"); + + lock.clone() + }) + } + + fn has_stack(&self) -> BoxFuture<'_, bool> { + // Default implementation logic: + Box::pin(async move { + let inv = self.get_inventory(); + !inv.get_stack(self.get_index()) + .await + .lock() + .await + .is_empty() + }) } /// Make sure to drop any locks to the slot stack before calling this - async fn set_stack(&self, stack: ItemStack) { - self.set_stack_no_callbacks(stack).await; + fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> { + // Default implementation logic: + Box::pin(async move { + self.set_stack_no_callbacks(stack).await; + }) } /// Changes the stack in the slot with the given `stack`. - async fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) { - self.set_stack_no_callbacks(stack).await; + fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> { + // Default implementation logic: + Box::pin(async move { + self.set_stack_no_callbacks(stack).await; + }) } - async fn set_stack_no_callbacks(&self, stack: ItemStack) { - let inv = self.get_inventory(); - inv.set_stack(self.get_index(), stack).await; - self.mark_dirty().await; + fn set_stack_no_callbacks(&self, stack: ItemStack) -> BoxFuture<'_, ()> { + // Default implementation logic: + Box::pin(async move { + let inv = self.get_inventory(); + inv.set_stack(self.get_index(), stack).await; + self.mark_dirty().await; + }) } - async fn mark_dirty(&self); + fn mark_dirty(&self) -> BoxFuture<'_, ()>; // This method must be implemented by concrete types - async fn get_max_item_count(&self) -> u8 { - self.get_inventory().get_max_count_per_stack() + fn get_max_item_count(&self) -> BoxFuture<'_, u8> { + // Default implementation logic: + Box::pin(async move { self.get_inventory().get_max_count_per_stack() }) } - async fn get_max_item_count_for_stack(&self, stack: &ItemStack) -> u8 { - self.get_max_item_count() - .await - .min(stack.get_max_stack_size()) + fn get_max_item_count_for_stack<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, u8> { + // Default implementation logic: + Box::pin(async move { + self.get_max_item_count() + .await + .min(stack.get_max_stack_size()) + }) } /// Removes a specific amount of items from the slot. /// /// Mojang name: `remove` - async fn take_stack(&self, amount: u8) -> ItemStack { - let inv = self.get_inventory(); - - inv.remove_stack_specific(self.get_index(), amount).await + fn take_stack(&self, amount: u8) -> BoxFuture<'_, ItemStack> { + // Default implementation logic: + Box::pin(async move { + let inv = self.get_inventory(); + inv.remove_stack_specific(self.get_index(), amount).await + }) } /// Mojang name: `mayPickup` - async fn can_take_items(&self, _player: &dyn InventoryPlayer) -> bool { - true + fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> { + // Default implementation logic: + Box::pin(async move { true }) } /// Mojang name: `allowModification` - async fn allow_modification(&self, player: &dyn InventoryPlayer) -> bool { - self.can_insert(&self.get_cloned_stack().await).await && self.can_take_items(player).await + fn allow_modification<'a>(&'a self, player: &'a dyn InventoryPlayer) -> BoxFuture<'a, bool> { + // Default implementation logic: + Box::pin(async move { + self.can_insert(&self.get_cloned_stack().await).await + && self.can_take_items(player).await + }) } /// Mojang name: `tryRemove` - async fn try_take_stack_range( - &self, + fn try_take_stack_range<'a>( + &'a self, min: u8, max: u8, - player: &dyn InventoryPlayer, - ) -> Option { - if !self.can_take_items(player).await { - return None; - } - if !self.allow_modification(player).await && self.get_cloned_stack().await.item_count > max - { - // If the slot is not allowed to be modified, we cannot take a partial stack from it. - return None; - } - let min = min.min(max); - let stack = self.take_stack(min).await; - - if stack.is_empty() { - None - } else { - if self.get_cloned_stack().await.is_empty() { - self.set_stack_prev(ItemStack::EMPTY.clone(), stack.clone()) - .await; + player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + // Default implementation logic: + Box::pin(async move { + if !self.can_take_items(player).await { + return None; } + if !self.allow_modification(player).await + && self.get_cloned_stack().await.item_count > max + { + // If the slot is not allowed to be modified, we cannot take a partial stack from it. + return None; + } + let min = min.min(max); + let stack = self.take_stack(min).await; - Some(stack) - } + if stack.is_empty() { + None + } else { + if self.get_cloned_stack().await.is_empty() { + self.set_stack_prev(ItemStack::EMPTY.clone(), stack.clone()) + .await; + } + + Some(stack) + } + }) } /// Safely tries to take a stack of items from the slot, returning `None` if the stack is empty. /// Considering such as result slots, as their stacks cannot split. /// /// Mojang name: `safeTake` - async fn safe_take(&self, min: u8, max: u8, player: &dyn InventoryPlayer) -> ItemStack { - let stack = self.try_take_stack_range(min, max, player).await; + fn safe_take<'a>( + &'a self, + min: u8, + max: u8, + player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, ItemStack> { + Box::pin(async move { + let stack = self.try_take_stack_range(min, max, player).await; - if let Some(stack) = &stack { - self.on_take_item(player, stack).await; - } - - stack.unwrap_or(ItemStack::EMPTY.clone()) - } - - async fn insert_stack(&self, stack: ItemStack) -> ItemStack { - let stack_item_count = stack.item_count; - self.insert_stack_count(stack, stack_item_count).await - } - - async fn insert_stack_count(&self, mut stack: ItemStack, count: u8) -> ItemStack { - if !stack.is_empty() && self.can_insert(&stack).await { - let stack_mutex = self.get_stack().await; - let mut stack_self = stack_mutex.lock().await; - let min_count = count - .min(stack.item_count) - .min(self.get_max_item_count_for_stack(&stack).await - stack_self.item_count); - - if min_count == 0 { - return stack; - } else { - if stack_self.is_empty() { - drop(stack_self); - self.set_stack(stack.split(min_count)).await; - } else if stack.are_items_and_components_equal(&stack_self) { - stack.decrement(min_count); - stack_self.increment(min_count); - let cloned_stack = stack_self.clone(); - drop(stack_self); - self.set_stack(cloned_stack).await; - } - - return stack; + if let Some(stack) = &stack { + self.on_take_item(player, stack).await; } - } else { - stack - } + + stack.unwrap_or(ItemStack::EMPTY.clone()) + }) + } + + fn insert_stack(&self, stack: ItemStack) -> BoxFuture<'_, ItemStack> { + // Default implementation logic: + Box::pin(async move { + let stack_item_count = stack.item_count; + self.insert_stack_count(stack, stack_item_count).await + }) + } + + fn insert_stack_count(&self, mut stack: ItemStack, count: u8) -> BoxFuture<'_, ItemStack> { + // Default implementation logic: + Box::pin(async move { + if !stack.is_empty() && self.can_insert(&stack).await { + let stack_mutex = self.get_stack().await; + let mut stack_self = stack_mutex.lock().await; + let min_count = count + .min(stack.item_count) + .min(self.get_max_item_count_for_stack(&stack).await - stack_self.item_count); + + if min_count == 0 { + stack + } else { + if stack_self.is_empty() { + drop(stack_self); + self.set_stack(stack.split(min_count)).await; + } else if stack.are_items_and_components_equal(&stack_self) { + stack.decrement(min_count); + stack_self.increment(min_count); + let cloned_stack = stack_self.clone(); + drop(stack_self); + self.set_stack(cloned_stack).await; + } + + stack + } + } else { + stack + } + }) } } @@ -212,7 +274,6 @@ impl NormalSlot { } } } -#[async_trait] impl Slot for NormalSlot { fn get_inventory(&self) -> Arc { self.inventory.clone() @@ -226,8 +287,10 @@ impl Slot for NormalSlot { self.id.store(id as u8, Ordering::Relaxed); } - async fn mark_dirty(&self) { - self.inventory.mark_dirty(); + fn mark_dirty(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.inventory.mark_dirty(); + }) } } @@ -251,7 +314,6 @@ impl ArmorSlot { } } -#[async_trait] impl Slot for ArmorSlot { fn get_inventory(&self) -> Arc { self.inventory.clone() @@ -265,33 +327,41 @@ impl Slot for ArmorSlot { self.id.store(id as u8, Ordering::Relaxed); } - async fn get_max_item_count(&self) -> u8 { - 1 + fn get_max_item_count(&self) -> BoxFuture<'_, u8> { + Box::pin(async move { 1 }) } - async fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) { - //TODO: this.entity.onEquipStack(this.equipmentSlot, previousStack, stack); - self.set_stack_no_callbacks(stack).await; + fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> { + Box::pin(async move { + //TODO: this.entity.onEquipStack(this.equipmentSlot, previousStack, stack); + self.set_stack_no_callbacks(stack).await; + }) } - async fn can_insert(&self, stack: &ItemStack) -> bool { - match self.equipment_slot { - EquipmentSlot::Head(_) => { - stack.is_helmet() || stack.is_skull() || stack.item == &Item::CARVED_PUMPKIN + fn can_insert<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, bool> { + Box::pin(async move { + match self.equipment_slot { + EquipmentSlot::Head(_) => { + stack.is_helmet() || stack.is_skull() || stack.item == &Item::CARVED_PUMPKIN + } + EquipmentSlot::Chest(_) => stack.is_chestplate() || stack.item == &Item::ELYTRA, + EquipmentSlot::Legs(_) => stack.is_leggings(), + EquipmentSlot::Feet(_) => stack.is_boots(), + _ => true, } - EquipmentSlot::Chest(_) => stack.is_chestplate() || stack.item == &Item::ELYTRA, - EquipmentSlot::Legs(_) => stack.is_leggings(), - EquipmentSlot::Feet(_) => stack.is_boots(), - _ => true, - } + }) } - async fn can_take_items(&self, _player: &dyn InventoryPlayer) -> bool { - // TODO: Check enchantments - true + fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> { + Box::pin(async move { + // TODO: Check enchantments + true + }) } - async fn mark_dirty(&self) { - self.inventory.mark_dirty(); + fn mark_dirty(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.inventory.mark_dirty(); + }) } } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 8922098d0..a8b1e3fdd 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -10,7 +10,6 @@ pumpkin-util = { path = "../pumpkin-util" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-data = { path = "../pumpkin-data" } -async-trait.workspace = true futures.workspace = true bytes.workspace = true diff --git a/pumpkin-world/benches/chunk_io.rs b/pumpkin-world/benches/chunk_io.rs index f50e65908..46e8f2893 100644 --- a/pumpkin-world/benches/chunk_io.rs +++ b/pumpkin-world/benches/chunk_io.rs @@ -1,6 +1,6 @@ // use std::{fs, path::PathBuf, sync::Arc}; // -// use async_trait::async_trait; +// // use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; // use pumpkin_data::BlockDirection; // use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; @@ -79,7 +79,6 @@ // // struct BlockRegistry; // -// #[async_trait] // impl BlockRegistryExt for BlockRegistry { // fn can_place_at( // &self, diff --git a/pumpkin-world/src/block/entities/barrel.rs b/pumpkin-world/src/block/entities/barrel.rs index 535b780d8..15cd46779 100644 --- a/pumpkin-world/src/block/entities/barrel.rs +++ b/pumpkin-world/src/block/entities/barrel.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use pumpkin_data::block_properties::{BarrelLikeProperties, BlockProperties}; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::{Block, FacingExt}; @@ -19,6 +18,7 @@ use std::{ use tokio::sync::Mutex; use crate::block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture}; +use crate::inventory::InventoryFuture; use crate::world::{BlockFlags, SimpleWorld}; use crate::{ inventory::{ @@ -175,47 +175,56 @@ impl BarrelBlockEntity { } } -#[async_trait] impl Inventory for BarrelBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } - async fn on_open(&self) { - self.viewers.open_container(); + fn on_open(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.viewers.open_container(); + }) } - async fn on_close(&self) { - self.viewers.close_container(); + fn on_close(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.viewers.close_container(); + }) } fn mark_dirty(&self) { diff --git a/pumpkin-world/src/block/entities/chest.rs b/pumpkin-world/src/block/entities/chest.rs index 8c5245e80..ed3ae7306 100644 --- a/pumpkin-world/src/block/entities/chest.rs +++ b/pumpkin-world/src/block/entities/chest.rs @@ -8,7 +8,6 @@ use std::{ }, }; -use async_trait::async_trait; use pumpkin_data::{ Block, HorizontalFacingExt, block_properties::{BlockProperties, ChestLikeProperties, ChestType}, @@ -23,7 +22,7 @@ use tokio::sync::Mutex; use crate::{ block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture}, - inventory::{Clearable, Inventory, split_stack}, + inventory::{Clearable, Inventory, InventoryFuture, split_stack}, item::ItemStack, world::SimpleWorld, }; @@ -183,47 +182,56 @@ impl ChestBlockEntity { } } -#[async_trait] impl Inventory for ChestBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } - async fn on_open(&self) { - self.viewers.open_container(); + fn on_open(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.viewers.open_container(); + }) } - async fn on_close(&self) { - self.viewers.close_container(); + fn on_close(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.viewers.close_container(); + }) } fn mark_dirty(&self) { diff --git a/pumpkin-world/src/block/entities/chiseled_bookshelf.rs b/pumpkin-world/src/block/entities/chiseled_bookshelf.rs index da423eae8..537bc3c37 100644 --- a/pumpkin-world/src/block/entities/chiseled_bookshelf.rs +++ b/pumpkin-world/src/block/entities/chiseled_bookshelf.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use log::warn; use pumpkin_data::Block; use pumpkin_data::block_properties::{BlockProperties, ChiseledBookshelfLikeProperties}; @@ -15,6 +14,7 @@ use std::{ }; use tokio::sync::Mutex; +use crate::inventory::InventoryFuture; use crate::{ block::entities::BlockEntity, inventory::{Clearable, Inventory, split_stack}, @@ -130,39 +130,44 @@ impl ChiseledBookshelfBlockEntity { } } -#[async_trait] impl Inventory for ChiseledBookshelfBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } fn mark_dirty(&self) { diff --git a/pumpkin-world/src/block/entities/dropper.rs b/pumpkin-world/src/block/entities/dropper.rs index d63de65ee..d7a95cc87 100644 --- a/pumpkin-world/src/block/entities/dropper.rs +++ b/pumpkin-world/src/block/entities/dropper.rs @@ -1,7 +1,6 @@ use crate::block::entities::BlockEntity; -use crate::inventory::{Clearable, Inventory, split_stack}; +use crate::inventory::{Clearable, Inventory, InventoryFuture, split_stack}; use crate::item::ItemStack; -use async_trait::async_trait; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use rand::{Rng, rng}; @@ -95,39 +94,44 @@ impl DropperBlockEntity { } } -#[async_trait] impl Inventory for DropperBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } fn mark_dirty(&self) { diff --git a/pumpkin-world/src/block/entities/furnace.rs b/pumpkin-world/src/block/entities/furnace.rs index 2b95d5ef2..da7f5647c 100644 --- a/pumpkin-world/src/block/entities/furnace.rs +++ b/pumpkin-world/src/block/entities/furnace.rs @@ -7,7 +7,6 @@ use std::{ }, }; -use async_trait::async_trait; use pumpkin_data::{ block_properties::{BlockProperties, FurnaceLikeProperties}, fuels::get_item_burn_ticks, @@ -20,7 +19,7 @@ use pumpkin_util::math::position::BlockPos; use tokio::sync::Mutex; use crate::{ - inventory::{Clearable, Inventory, split_stack}, + inventory::{Clearable, Inventory, InventoryFuture, split_stack}, item::ItemStack, world::{BlockFlags, SimpleWorld}, }; @@ -375,57 +374,62 @@ impl FurnaceBlockEntity { } } -#[async_trait] impl Inventory for FurnaceBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - let furnace_stack = self.get_stack(slot).await; - let mut furnace_stack = furnace_stack.lock().await; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + let furnace_stack = self.get_stack(slot).await; + let mut furnace_stack = furnace_stack.lock().await; - let is_same_item = - !stack.is_empty() && ItemStack::are_items_and_components_equal(&furnace_stack, &stack); + let is_same_item = !stack.is_empty() + && ItemStack::are_items_and_components_equal(&furnace_stack, &stack); - *furnace_stack = stack.clone(); - drop(furnace_stack); + *furnace_stack = stack.clone(); + drop(furnace_stack); - if slot == 0 && !is_same_item { - if let Some(recipe) = Self::get_furnace_cooking_recipe(stack.item) { - self.cooking_total_time - .store(recipe.cookingtime as u16, Ordering::Relaxed); - } else { - self.cooking_total_time.store(0, Ordering::Relaxed); + if slot == 0 && !is_same_item { + if let Some(recipe) = Self::get_furnace_cooking_recipe(stack.item) { + self.cooking_total_time + .store(recipe.cookingtime as u16, Ordering::Relaxed); + } else { + self.cooking_total_time.store(0, Ordering::Relaxed); + } + self.cooking_time_spent.store(0, Ordering::Relaxed); + self.mark_dirty(); } - self.cooking_time_spent.store(0, Ordering::Relaxed); - self.mark_dirty(); - } + }) } fn mark_dirty(&self) { diff --git a/pumpkin-world/src/block/entities/hopper.rs b/pumpkin-world/src/block/entities/hopper.rs index 9093a61f4..e00896e30 100644 --- a/pumpkin-world/src/block/entities/hopper.rs +++ b/pumpkin-world/src/block/entities/hopper.rs @@ -1,9 +1,8 @@ use crate::BlockStateId; use crate::block::entities::BlockEntity; -use crate::inventory::{Clearable, Inventory, split_stack}; +use crate::inventory::{Clearable, Inventory, InventoryFuture, split_stack}; use crate::item::ItemStack; use crate::world::SimpleWorld; -use async_trait::async_trait; use pumpkin_data::block_properties::{BlockProperties, HopperFacing, HopperLikeProperties}; use pumpkin_data::tag::Taggable; use pumpkin_data::{Block, tag}; @@ -294,39 +293,44 @@ impl HopperBlockEntity { } } -#[async_trait] impl Inventory for HopperBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } fn mark_dirty(&self) { diff --git a/pumpkin-world/src/block/entities/shulker_box.rs b/pumpkin-world/src/block/entities/shulker_box.rs index e481e4c4d..569fc3a3c 100644 --- a/pumpkin-world/src/block/entities/shulker_box.rs +++ b/pumpkin-world/src/block/entities/shulker_box.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; @@ -6,6 +5,7 @@ use pumpkin_util::random::xoroshiro128::Xoroshiro; use pumpkin_util::random::{RandomImpl, get_seed}; use std::any::Any; use std::pin::Pin; +use std::sync::atomic::Ordering; use std::{ array::from_fn, sync::{Arc, atomic::AtomicBool}, @@ -13,6 +13,7 @@ use std::{ use tokio::sync::Mutex; use crate::block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture}; +use crate::inventory::InventoryFuture; use crate::world::SimpleWorld; use crate::{ inventory::{ @@ -175,51 +176,60 @@ impl ShulkerBoxBlockEntity { } } -#[async_trait] impl Inventory for ShulkerBoxBlockEntity { fn size(&self) -> usize { self.items.len() } - async fn is_empty(&self) -> bool { - for slot in self.items.iter() { - if !slot.lock().await.is_empty() { - return false; + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } } - } - true + true + }) } - async fn get_stack(&self, slot: usize) -> Arc> { - self.items[slot].clone() + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) } - async fn remove_stack(&self, slot: usize) -> ItemStack { - let mut removed = ItemStack::EMPTY.clone(); - let mut guard = self.items[slot].lock().await; - std::mem::swap(&mut removed, &mut *guard); - removed + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + }) } - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { - split_stack(&self.items, slot, amount).await + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { split_stack(&self.items, slot, amount).await }) } - async fn set_stack(&self, slot: usize, stack: ItemStack) { - *self.items[slot].lock().await = stack; + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + }) } - async fn on_open(&self) { - self.viewers.open_container(); + fn on_open(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.viewers.open_container(); + }) } - async fn on_close(&self) { - self.viewers.close_container(); + fn on_close(&self) -> InventoryFuture<'_, ()> { + Box::pin(async move { + self.viewers.close_container(); + }) } fn mark_dirty(&self) { - self.dirty.store(true, std::sync::atomic::Ordering::Relaxed); + self.dirty.store(true, Ordering::Relaxed); } fn as_any(&self) -> &dyn Any { diff --git a/pumpkin-world/src/chunk/format/anvil.rs b/pumpkin-world/src/chunk/format/anvil.rs index 1b217a7d5..f17b9980e 100644 --- a/pumpkin-world/src/chunk/format/anvil.rs +++ b/pumpkin-world/src/chunk/format/anvil.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use bytes::*; use flate2::read::{GzDecoder, GzEncoder, ZlibDecoder, ZlibEncoder}; use itertools::Itertools; @@ -526,7 +525,6 @@ pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable { fn position(&self) -> &Vector2; } -#[async_trait] impl ChunkSerializer for AnvilChunkFile { type Data = S; type WriteBackend = PathBuf; @@ -827,7 +825,7 @@ impl ChunkSerializer for AnvilChunkFile { /* #[cfg(test)] mod tests { - use async_trait::async_trait; + use pumpkin_config::{AdvancedConfiguration, advanced_config, override_config_for_testing}; use pumpkin_data::BlockDirection; use pumpkin_util::math::position::BlockPos; @@ -849,7 +847,6 @@ mod tests { struct BlockRegistry; - #[async_trait] impl BlockRegistryExt for BlockRegistry { fn can_place_at( &self, diff --git a/pumpkin-world/src/chunk/format/linear.rs b/pumpkin-world/src/chunk/format/linear.rs index 864f65d25..f9e3862a1 100644 --- a/pumpkin-world/src/chunk/format/linear.rs +++ b/pumpkin-world/src/chunk/format/linear.rs @@ -6,7 +6,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::chunk::format::anvil::{AnvilChunkFile, SingleChunkDataSerializer}; use crate::chunk::io::{ChunkSerializer, LoadedData}; use crate::chunk::{ChunkReadingError, ChunkWritingError}; -use async_trait::async_trait; use bytes::{Buf, BufMut, Bytes}; use log::error; use pumpkin_util::math::vector2::Vector2; @@ -163,7 +162,6 @@ impl Default for LinearFile { } } -#[async_trait] impl ChunkSerializer for LinearFile { type Data = S; type WriteBackend = PathBuf; @@ -372,7 +370,7 @@ impl ChunkSerializer for LinearFile { /* #[cfg(test)] mod tests { - use async_trait::async_trait; + use core::panic; use pumpkin_data::BlockDirection; use pumpkin_util::math::position::BlockPos; @@ -394,7 +392,6 @@ mod tests { struct BlockRegistry; - #[async_trait] impl BlockRegistryExt for BlockRegistry { fn can_place_at( &self, diff --git a/pumpkin-world/src/chunk/io/file_manager.rs b/pumpkin-world/src/chunk/io/file_manager.rs index 3324d2e7e..19a4b7d98 100644 --- a/pumpkin-world/src/chunk/io/file_manager.rs +++ b/pumpkin-world/src/chunk/io/file_manager.rs @@ -6,7 +6,6 @@ use std::{ sync::Arc, }; -use async_trait::async_trait; use futures::future::join_all; use log::{error, trace}; use num_traits::Zero; @@ -18,7 +17,10 @@ use tokio::{ }; use crate::{ - chunk::{ChunkReadingError, ChunkWritingError, io::Dirtiable}, + chunk::{ + ChunkReadingError, ChunkWritingError, + io::{BoxFuture, Dirtiable}, + }, level::LevelFolder, }; @@ -149,7 +151,6 @@ impl> ChunkFileManager { } } -#[async_trait] impl FileIO for ChunkFileManager where P: PathFromLevelFolder + Send + Sync + Sized + Dirtiable + 'static, @@ -157,242 +158,276 @@ where { type Data = Arc>; - async fn watch_chunks(&self, folder: &LevelFolder, chunks: &[Vector2]) { - // It is intentional that regions are watched multiple times (once per chunk) - let mut watchers = self.watchers.write().await; - for chunk in chunks { - let key = S::get_chunk_key(chunk); - let map_key = P::file_path(folder, &key); - match watchers.entry(map_key) { - std::collections::btree_map::Entry::Vacant(vacant) => { - let _ = vacant.insert(1); - } - std::collections::btree_map::Entry::Occupied(mut occupied) => { - occupied.get_mut().add_assign(1); - } - } - } - } - - async fn unwatch_chunks(&self, folder: &LevelFolder, chunks: &[Vector2]) { - let mut watchers = self.watchers.write().await; - for chunk in chunks { - let key = S::get_chunk_key(chunk); - let map_key = P::file_path(folder, &key); - match watchers.entry(map_key) { - std::collections::btree_map::Entry::Vacant(_vacant) => {} - std::collections::btree_map::Entry::Occupied(mut occupied) => { - occupied.get_mut().sub_assign(1); - if occupied.get().is_zero() { - occupied.remove_entry(); + // Changed: Return BoxFuture<()> + fn watch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks: &'a [Vector2], + ) -> BoxFuture<'a, ()> { + Box::pin(async move { + // It is intentional that regions are watched multiple times (once per chunk) + let mut watchers = self.watchers.write().await; + for chunk in chunks { + let key = S::get_chunk_key(chunk); + let map_key = P::file_path(folder, &key); + match watchers.entry(map_key) { + std::collections::btree_map::Entry::Vacant(vacant) => { + let _ = vacant.insert(1); + } + std::collections::btree_map::Entry::Occupied(mut occupied) => { + occupied.get_mut().add_assign(1); } } } - } + }) } - async fn clear_watched_chunks(&self) { - self.watchers.write().await.clear(); - } - - async fn fetch_chunks( - &self, - folder: &LevelFolder, - chunk_coords: &[Vector2], - stream: tokio::sync::mpsc::Sender>, - ) { - let mut regions_chunks: BTreeMap>> = BTreeMap::new(); - - for at in chunk_coords { - let key = S::get_chunk_key(at); - - regions_chunks - .entry(key) - .and_modify(|chunks| chunks.push(*at)) - .or_insert(vec![*at]); - } - - // we use a Sync Closure with an Async Block to execute the tasks concurrently - // Also improves File Cache utilizations. - let region_read_tasks = regions_chunks.into_iter().map(async |(file_name, chunks)| { - let path = P::file_path(folder, &file_name); - let chunk_serializer = match self.get_serializer(&path).await { - Ok(chunk_serializer) => chunk_serializer, - Err(ChunkReadingError::ChunkNotExist) => { - unreachable!("Default Serializer must be created") - } - Err(err) => { - let _ = stream.send(LoadedData::Error((chunks[0], err))).await; - return; - } - }; - - // Intermediate channel for wrapping the data with the Arc - let (send, mut recv) = mpsc::channel::>(1); - - let intermediary = async { - while let Some(data) = recv.recv().await { - let wrapped_data = data.map_loaded(|data| Arc::new(RwLock::new(data))); - if stream.send(wrapped_data).await.is_err() { - // Stream is closed, so stop unneeded computation and io - return; - } - } - }; - - // We need to hold the read lock only for the duration of get_chunks - // This minimizes the time we block other operations - let reader = async move { - let serializer = chunk_serializer.read().await; - serializer.get_chunks(&chunks, send).await; - }; - - join!(intermediary, reader); - }); - - let _ = join_all(region_read_tasks).await; - } - - async fn save_chunks( - &self, - folder: &LevelFolder, - chunks_data: Vec<(Vector2, Self::Data)>, - ) -> Result<(), ChunkWritingError> { - let mut regions_chunks: BTreeMap> = BTreeMap::new(); - - for (at, chunk) in chunks_data { - let key = S::get_chunk_key(&at); - - match regions_chunks.entry(key) { - std::collections::btree_map::Entry::Occupied(mut occupied) => { - occupied.get_mut().push(chunk); - } - std::collections::btree_map::Entry::Vacant(vacant) => { - vacant.insert(vec![chunk]); - } - } - } - - // we use a Sync Closure with an Async Block to execute the tasks in parallel - // with out waiting the future. Also it improve we File Cache utilizations. - let tasks = regions_chunks - .into_iter() - .map(async |(file_name, chunk_locks)| { - let path = P::file_path(folder, &file_name); - log::trace!("Updating data for file {}", path.display()); - - let chunk_serializer = match self.get_serializer(&path).await { - Ok(file) => Ok(file), - Err(ChunkReadingError::ChunkNotExist) => { - unreachable!("Must be managed by the cache") - } - Err(ChunkReadingError::IoError(err)) => { - error!("Error reading the data before write: {err}"); - Err(ChunkWritingError::IoError(err)) - } - Err(_) => { - Err(ChunkWritingError::IoError(std::io::ErrorKind::Other)) - } - }?; - - // Create a task for each chunk_lock - let update_tasks = chunk_locks.into_iter().map(|chunk_lock| { - let chunk_serializer = chunk_serializer.clone(); - async move { - let mut chunk = chunk_lock.write().await; - let chunk_is_dirty = chunk.is_dirty(); - // Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are - // updating what we will write here - chunk.mark_dirty(false); - // It is important that we keep the lock after we mark the chunk as clean so no one else - // can modify it - let chunk = chunk.downgrade(); - - // We only need to update the chunk if it is dirty - if chunk_is_dirty { - chunk_serializer.write().await.update_chunk(&*chunk).await?; + // Changed: Return BoxFuture<()> + fn unwatch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks: &'a [Vector2], + ) -> BoxFuture<'a, ()> { + Box::pin(async move { + let mut watchers = self.watchers.write().await; + for chunk in chunks { + let key = S::get_chunk_key(chunk); + let map_key = P::file_path(folder, &key); + match watchers.entry(map_key) { + std::collections::btree_map::Entry::Vacant(_vacant) => {} + std::collections::btree_map::Entry::Occupied(mut occupied) => { + occupied.get_mut().sub_assign(1); + if occupied.get().is_zero() { + occupied.remove_entry(); } - Ok::<(), ChunkWritingError>(()) } - }); - // Run all update tasks concurrently and propagate any error - futures::future::try_join_all(update_tasks).await?; - log::trace!("Updated data for file {}", path.display()); + } + } + }) + } - let is_watched = self - .watchers - .read() - .await - .get(&path) - .is_some_and(|count| !count.is_zero()); + // Changed: Return BoxFuture<()> + fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + self.watchers.write().await.clear(); + }) + } - if !is_watched { - // With the modification done, we can drop the write lock but keep the read lock - // to avoid other threads to write/modify the data, but allow other threads to read it - let serializer = chunk_serializer.read().await; + // Changed: Return BoxFuture<()> + fn fetch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunk_coords: &'a [Vector2], + stream: tokio::sync::mpsc::Sender>, + ) -> BoxFuture<'a, ()> { + Box::pin(async move { + let mut regions_chunks: BTreeMap>> = BTreeMap::new(); - log::debug!("Writing file for {}", path.display()); - serializer - .write(path.clone()) - .await - .map_err(|err| ChunkWritingError::IoError(err.kind()))?; + for at in chunk_coords { + let key = S::get_chunk_key(at); - // Remove lock - drop(serializer); - // Decrement strong count - drop(chunk_serializer); + regions_chunks + .entry(key) + .and_modify(|chunks| chunks.push(*at)) + .or_insert(vec![*at]); + } - // If there are still no watchers, drop from the locks - let mut locks = self.file_locks.write().await; + // we use a Sync Closure with an Async Block to execute the tasks concurrently + // Also improves File Cache utilizations. + let region_read_tasks = regions_chunks.into_iter().map(move |(file_name, chunks)| { + let task_stream = stream.clone(); - if self + async move { + let path = P::file_path(folder, &file_name); + let chunk_serializer = match self.get_serializer(&path).await { + Ok(chunk_serializer) => chunk_serializer, + Err(ChunkReadingError::ChunkNotExist) => { + unreachable!("Default Serializer must be created") + } + Err(err) => { + let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await; + return; + } + }; + + // Intermediate channel for wrapping the data with the Arc + let (send, mut recv) = + mpsc::channel::>(1); + + let intermediary = async move { + // Note: 'task_stream' is captured and moved here + while let Some(data) = recv.recv().await { + let wrapped_data = data.map_loaded(|data| Arc::new(RwLock::new(data))); + if task_stream.send(wrapped_data).await.is_err() { + // Stream is closed, so stop unneeded computation and io + return; + } + } + }; + + // We need to hold the read lock only for the duration of get_chunks + // This minimizes the time we block other operations + let reader = async move { + let serializer = chunk_serializer.read().await; + serializer.get_chunks(&chunks, send).await; + }; + + join!(intermediary, reader); + } + }); + + let _ = join_all(region_read_tasks).await; + }) + } + + // Changed: Return BoxFuture> + fn save_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks_data: Vec<(Vector2, Self::Data)>, + ) -> BoxFuture<'a, Result<(), ChunkWritingError>> { + Box::pin(async move { + let mut regions_chunks: BTreeMap> = BTreeMap::new(); + + for (at, chunk) in chunks_data { + let key = S::get_chunk_key(&at); + + match regions_chunks.entry(key) { + std::collections::btree_map::Entry::Occupied(mut occupied) => { + occupied.get_mut().push(chunk); + } + std::collections::btree_map::Entry::Vacant(vacant) => { + vacant.insert(vec![chunk]); + } + } + } + + // we use a Sync Closure with an Async Block to execute the tasks in parallel + // with out waiting the future. Also it improve we File Cache utilizations. + let tasks = regions_chunks + .into_iter() + // The closure now needs `move` to capture `self` and `folder` by value + .map(move |(file_name, chunk_locks)| async move { + let path = P::file_path(folder, &file_name); + log::trace!("Updating data for file {}", path.display()); + + let chunk_serializer = match self.get_serializer(&path).await { + Ok(file) => Ok(file), + Err(ChunkReadingError::ChunkNotExist) => { + unreachable!("Must be managed by the cache") + } + Err(ChunkReadingError::IoError(err)) => { + error!("Error reading the data before write: {err}"); + Err(ChunkWritingError::IoError(err)) + } + Err(_) => Err(ChunkWritingError::IoError(std::io::ErrorKind::Other)), + }?; + + // Create a task for each chunk_lock + let update_tasks = chunk_locks.into_iter().map(|chunk_lock| { + let chunk_serializer = chunk_serializer.clone(); + async move { + let mut chunk = chunk_lock.write().await; + let chunk_is_dirty = chunk.is_dirty(); + // Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are + // updating what we will write here + chunk.mark_dirty(false); + // It is important that we keep the lock after we mark the chunk as clean so no one else + // can modify it + let chunk = chunk.downgrade(); + + // We only need to update the chunk if it is dirty + if chunk_is_dirty { + chunk_serializer.write().await.update_chunk(&*chunk).await?; + } + Ok::<(), ChunkWritingError>(()) + } + }); + // Run all update tasks concurrently and propagate any error + futures::future::try_join_all(update_tasks).await?; + log::trace!("Updated data for file {}", path.display()); + + let is_watched = self .watchers .read() .await .get(&path) - .is_none_or(|count| count.is_zero()) - { - let can_remove = if let Some(loader) = locks.get(&path) { - loader.can_remove().await - } else { - true - }; + .is_some_and(|count| !count.is_zero()); - if can_remove { - locks.remove(&path); - log::trace!("Removed lockfile cache {}", path.display()); - } else { - log::trace!("Wanted to remove lockfile cache {} but someone still holds a reference to it!", path.display()); + if !is_watched { + // With the modification done, we can drop the write lock but keep the read lock + // to avoid other threads to write/modify the data, but allow other threads to read it + let serializer = chunk_serializer.read().await; + + log::debug!("Writing file for {}", path.display()); + serializer + .write(path.clone()) + .await + .map_err(|err| ChunkWritingError::IoError(err.kind()))?; + + // Remove lock + drop(serializer); + // Decrement strong count + drop(chunk_serializer); + + // If there are still no watchers, drop from the locks + let mut locks = self.file_locks.write().await; + + if self + .watchers + .read() + .await + .get(&path) + .is_none_or(|count| count.is_zero()) + { + let can_remove = if let Some(loader) = locks.get(&path) { + loader.can_remove().await + } else { + true + }; + + if can_remove { + locks.remove(&path); + log::trace!("Removed lockfile cache {}", path.display()); + } else { + log::trace!("Wanted to remove lockfile cache {} but someone still holds a reference to it!", path.display()); + } } } - } - Ok(()) - }); + Ok(()) + }); - //TODO: we need to handle the errors and return the result - // files to save - let _test: Vec> = join_all(tasks).await; + //TODO: we need to handle the errors and return the result + // files to save + let _test: Vec> = join_all(tasks).await; - Ok(()) + Ok(()) + }) } - async fn clean_up_log(&self) { - let locks = self.file_locks.read().await; - log::debug!("{} File locks remain in cache", locks.len()); + // Changed: Return BoxFuture<()> + fn clean_up_log(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + let locks = self.file_locks.read().await; + log::debug!("{} File locks remain in cache", locks.len()); + }) } - async fn block_and_await_ongoing_tasks(&self) { - //we need to block any other operation - let serializer_cache = self.file_locks.write().await; + // Changed: Return BoxFuture<()> + fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> { + Box::pin(async move { + //we need to block any other operation + let serializer_cache = self.file_locks.write().await; - // Acquire a write lock on all entries to verify they are complete - let tasks = serializer_cache - .values() - .map(async |serializer| serializer.can_remove()); + // Acquire a write lock on all entries to verify they are complete + let tasks = serializer_cache + .values() + .map(|serializer| serializer.can_remove()); - // We need to wait to ensure that all the locks are acquired - // so there is no **operation** ongoing - let _ = join_all(tasks).await; + // We need to wait to ensure that all the locks are acquired + // so there is no **operation** ongoing + let _ = join_all(tasks).await; + }) } } diff --git a/pumpkin-world/src/chunk/io/mod.rs b/pumpkin-world/src/chunk/io/mod.rs index 9ce790b7a..ab68de3ea 100644 --- a/pumpkin-world/src/chunk/io/mod.rs +++ b/pumpkin-world/src/chunk/io/mod.rs @@ -1,6 +1,5 @@ -use std::error; +use std::{error, pin::Pin}; -use async_trait::async_trait; use bytes::Bytes; use pumpkin_util::math::vector2::Vector2; @@ -39,6 +38,8 @@ pub trait Dirtiable { fn mark_dirty(&mut self, flag: bool); } +type BoxFuture<'a, T> = Pin + Send + 'a>>; + /// Trait to handle the IO of chunks /// for loading and saving chunks data /// can be implemented for different types of IO @@ -46,7 +47,6 @@ pub trait Dirtiable { /// /// The `R` type is the type of the data that will be loaded/saved /// like ChunkData or EntityData -#[async_trait] pub trait FileIO where Self: Send + Sync, @@ -54,40 +54,47 @@ where type Data: Send + Sync + Sized; /// Load the chunks data - async fn fetch_chunks( - &self, - folder: &LevelFolder, - chunk_coords: &[Vector2], + fn fetch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunk_coords: &'a [Vector2], stream: tokio::sync::mpsc::Sender>, - ); + ) -> BoxFuture<'a, ()>; // Returns BoxFuture<()> /// Persist the chunks data - async fn save_chunks( - &self, - folder: &LevelFolder, + fn save_chunks<'a>( + &'a self, + folder: &'a LevelFolder, chunks_data: Vec<(Vector2, Self::Data)>, - ) -> Result<(), ChunkWritingError>; + ) -> BoxFuture<'a, Result<(), ChunkWritingError>>; // Returns BoxFuture /// Tells the `ChunkIO` that these chunks are currently loaded in memory - async fn watch_chunks(&self, folder: &LevelFolder, chunks: &[Vector2]); + fn watch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks: &'a [Vector2], + ) -> BoxFuture<'a, ()>; /// Tells the `ChunkIO` that these chunks are no longer loaded in memory - async fn unwatch_chunks(&self, folder: &LevelFolder, chunks: &[Vector2]); + fn unwatch_chunks<'a>( + &'a self, + folder: &'a LevelFolder, + chunks: &'a [Vector2], + ) -> BoxFuture<'a, ()>; /// Tells the `ChunkIO` that no more chunks are loaded in memory - async fn clear_watched_chunks(&self); + fn clear_watched_chunks(&self) -> BoxFuture<'_, ()>; - async fn clean_up_log(&self); + fn clean_up_log(&self) -> BoxFuture<'_, ()>; /// Ensure that all ongoing operations are finished - async fn block_and_await_ongoing_tasks(&self); + fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()>; } /// Trait to serialize and deserialize the chunk data to and from bytes. /// /// The `Data` type is the type of the data that will be updated or serialized/deserialized /// like ChunkData or EntityData -#[async_trait] pub trait ChunkSerializer: Send + Sync + Default { type Data: Send + Sync + Sized + Dirtiable; type WriteBackend; @@ -98,18 +105,24 @@ pub trait ChunkSerializer: Send + Sync + Default { fn should_write(&self, is_watched: bool) -> bool; /// Serialize the data to bytes. - async fn write(&self, backend: Self::WriteBackend) -> Result<(), std::io::Error>; + fn write( + &self, + backend: Self::WriteBackend, + ) -> impl Future> + Send; /// Create a new instance from bytes fn read(r: Bytes) -> Result; /// Add the chunk data to the serializer - async fn update_chunk(&mut self, chunk_data: &Self::Data) -> Result<(), ChunkWritingError>; + fn update_chunk( + &mut self, + chunk_data: &Self::Data, + ) -> impl Future> + Send; /// Get the chunks data from the serializer - async fn get_chunks( + fn get_chunks( &self, chunks: &[Vector2], stream: tokio::sync::mpsc::Sender>, - ); + ) -> impl Future + Send; } diff --git a/pumpkin-world/src/inventory/inventory.rs b/pumpkin-world/src/inventory/inventory.rs index ab0bfcf13..bf4487be0 100644 --- a/pumpkin-world/src/inventory/inventory.rs +++ b/pumpkin-world/src/inventory/inventory.rs @@ -1,5 +1,4 @@ use crate::item::ItemStack; -use async_trait::async_trait; use pumpkin_data::item::Item; use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; use std::any::Any; @@ -11,57 +10,114 @@ use std::{ }; use tokio::sync::{Mutex, OwnedMutexGuard}; -// Inventory.java -#[async_trait] +pub type InventoryFuture<'a, T> = Pin + Send + 'a>>; + pub trait Inventory: Send + Sync + Debug + Clearable { fn size(&self) -> usize; - async fn is_empty(&self) -> bool; + // --- Asynchronous Methods (Using BlockFuture) --- - async fn get_stack(&self, slot: usize) -> Arc>; + fn is_empty(&self) -> InventoryFuture<'_, bool>; - async fn remove_stack(&self, slot: usize) -> ItemStack; + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>>; - async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack; + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>; + + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack>; + + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()>; + + fn on_open(&self) -> InventoryFuture<'_, ()> { + Box::pin(async {}) + } + fn on_close(&self) -> InventoryFuture<'_, ()> { + Box::pin(async {}) + } + + fn count<'a>(&'a self, item: &'a Item) -> InventoryFuture<'a, u8> { + Box::pin(async move { + let mut count = 0; + + for i in 0..self.size() { + let slot = self.get_stack(i).await; + let stack = slot.lock().await; + if stack.get_item().id == item.id { + count += stack.item_count; + } + } + + count + }) + } + + fn contains_any_predicate<'a>( + &'a self, + predicate: &'a (dyn Fn(OwnedMutexGuard) -> bool + Sync), + ) -> InventoryFuture<'a, bool> { + Box::pin(async move { + for i in 0..self.size() { + let slot = self.get_stack(i).await; + let stack = slot.lock_owned().await; + if predicate(stack) { + return true; + } + } + + false + }) + } + + fn contains_any<'a>(&'a self, items: &'a [Item]) -> InventoryFuture<'a, bool> { + Box::pin(async move { + self.contains_any_predicate(&|stack| { + !stack.is_empty() && items.contains(stack.get_item()) + }) + .await + }) + } + + // --- Default Implementation: write_data (Using BlockFuture) --- + fn write_data( + &self, + nbt: &mut NbtCompound, + stacks: &[Arc>], + include_empty: bool, + ) -> InventoryFuture<'_, ()> { + // Clone for the move block (requires `nbt` and `stacks` to be cloneable/to_owned) + let nbt = nbt.to_owned(); + let stacks = stacks.to_owned(); + + Box::pin(async move { + let mut slots = Vec::new(); + let mut nbt = nbt; + + for (i, item) in stacks.iter().enumerate() { + let stack = item.lock().await; + if !stack.is_empty() { + let mut item_compound = NbtCompound::new(); + item_compound.put_byte("Slot", i as i8); + stack.write_item_stack(&mut item_compound); + slots.push(NbtTag::Compound(item_compound)); + } + } + + if !include_empty && slots.is_empty() { + return; + } + + nbt.put("Items", NbtTag::List(slots)); + }) + } + + // --- Synchronous Methods (No Change) --- fn get_max_count_per_stack(&self) -> u8 { 99 } - async fn set_stack(&self, slot: usize, stack: ItemStack); - fn mark_dirty(&self) {} - async fn write_data( - &self, - nbt: &mut pumpkin_nbt::compound::NbtCompound, - stacks: &[Arc>], - include_empty: bool, - ) { - let mut slots = Vec::new(); - - for (i, item) in stacks.iter().enumerate() { - let stack = item.lock().await; - if !stack.is_empty() { - let mut item_compound = NbtCompound::new(); - item_compound.put_byte("Slot", i as i8); - stack.write_item_stack(&mut item_compound); - slots.push(NbtTag::Compound(item_compound)); - } - } - - if !include_empty && slots.is_empty() { - return; - } - - nbt.put("Items", NbtTag::List(slots)); - } - - fn read_data( - &self, - nbt: &pumpkin_nbt::compound::NbtCompound, - stacks: &[Arc>], - ) { + fn read_data(&self, nbt: &NbtCompound, stacks: &[Arc>]) { if let Some(inventory_list) = nbt.get_list("Items") { for tag in inventory_list { if let Some(item_compound) = tag.extract_compound() @@ -71,7 +127,6 @@ pub trait Inventory: Send + Sync + Debug + Clearable { if slot < stacks.len() && let Some(item_stack) = ItemStack::read_item_stack(item_compound) { - // This won't error cause it's only called on initialization *stacks[slot].try_lock().unwrap() = item_stack; } } @@ -79,15 +134,6 @@ pub trait Inventory: Send + Sync + Debug + Clearable { } } - /* - boolean canPlayerUse(PlayerEntity player); - */ - - // TODO: Add (PlayerEntity player) - async fn on_open(&self) {} - async fn on_close(&self) {} - - /// isValid is source fn is_valid_slot_for(&self, _slot: usize, _stack: &ItemStack) -> bool { true } @@ -101,42 +147,6 @@ pub trait Inventory: Send + Sync + Debug + Clearable { true } - async fn count(&self, item: &Item) -> u8 { - let mut count = 0; - - for i in 0..self.size() { - let slot = self.get_stack(i).await; - let stack = slot.lock().await; - if stack.get_item().id == item.id { - count += stack.item_count; - } - } - - count - } - - async fn contains_any_predicate( - &self, - predicate: &(dyn Fn(OwnedMutexGuard) -> bool + Sync), - ) -> bool { - for i in 0..self.size() { - let slot = self.get_stack(i).await; - let stack = slot.lock_owned().await; - if predicate(stack) { - return true; - } - } - - false - } - - async fn contains_any(&self, items: &[Item]) -> bool { - self.contains_any_predicate(&|stack| !stack.is_empty() && items.contains(stack.get_item())) - .await - } - - // TODO: canPlayerUse - fn as_any(&self) -> &dyn Any; } diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 1a27163ba..a877c4718 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -43,7 +43,6 @@ tokio = { workspace = true, features = [ ] } rayon.workspace = true thiserror.workspace = true -async-trait.workspace = true futures.workspace = true # config diff --git a/pumpkin/src/block/blocks/barrel.rs b/pumpkin/src/block/blocks/barrel.rs index 4454974ae..5a20ed1bc 100644 --- a/pumpkin/src/block/blocks/barrel.rs +++ b/pumpkin/src/block/blocks/barrel.rs @@ -5,11 +5,13 @@ use crate::block::{ registry::BlockActionResult, {BlockBehaviour, NormalUseArgs}, }; -use async_trait::async_trait; + use pumpkin_data::block_properties::{BarrelLikeProperties, BlockProperties}; use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3; use pumpkin_inventory::player::player_inventory::PlayerInventory; -use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; use pumpkin_macros::pumpkin_block; use pumpkin_util::text::TextComponent; use pumpkin_world::BlockStateId; @@ -19,17 +21,19 @@ use tokio::sync::Mutex; struct BarrelScreenFactory(Arc); -#[async_trait] impl ScreenHandlerFactory for BarrelScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new( - create_generic_9x3(sync_id, player_inventory, self.0.clone()).await, - ))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone()).await; + let concrete_arc = Arc::new(Mutex::new(handler)); + + Some(concrete_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/block/blocks/chests.rs b/pumpkin/src/block/blocks/chests.rs index a08cc9224..1aa8763f7 100644 --- a/pumpkin/src/block/blocks/chests.rs +++ b/pumpkin/src/block/blocks/chests.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use async_trait::async_trait; use futures::future::join; use pumpkin_data::block_properties::{ BlockProperties, ChestLikeProperties, ChestType, HorizontalFacing, @@ -11,7 +10,9 @@ use pumpkin_data::{Block, BlockDirection}; use pumpkin_inventory::double::DoubleInventory; use pumpkin_inventory::generic_container_screen_handler::{create_generic_9x3, create_generic_9x6}; use pumpkin_inventory::player::player_inventory::PlayerInventory; -use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; use pumpkin_util::math::position::BlockPos; use pumpkin_util::text::TextComponent; use pumpkin_world::BlockStateId; @@ -34,19 +35,24 @@ use crate::{ struct ChestScreenFactory(Arc); -#[async_trait] impl ScreenHandlerFactory for ChestScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new(if self.0.size() > 27 { - create_generic_9x6(sync_id, player_inventory, self.0.clone()).await - } else { - create_generic_9x3(sync_id, player_inventory, self.0.clone()).await - }))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let concrete_handler = if self.0.size() > 27 { + create_generic_9x6(sync_id, player_inventory, self.0.clone()).await + } else { + create_generic_9x3(sync_id, player_inventory, self.0.clone()).await + }; + + let concrete_arc = Arc::new(Mutex::new(concrete_handler)); + + Some(concrete_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 36c094f85..3c09e8ec3 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -1,9 +1,11 @@ use crate::block::registry::BlockActionResult; use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs}; -use async_trait::async_trait; + use pumpkin_inventory::crafting::crafting_screen_handler::CraftingTableScreenHandler; use pumpkin_inventory::player::player_inventory::PlayerInventory; -use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; use pumpkin_macros::pumpkin_block; use pumpkin_util::text::TextComponent; use std::sync::Arc; @@ -26,17 +28,19 @@ impl BlockBehaviour for CraftingTableBlock { struct CraftingTableScreenFactory; -#[async_trait] impl ScreenHandlerFactory for CraftingTableScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new( - CraftingTableScreenHandler::new(sync_id, player_inventory).await, - ))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let handler = CraftingTableScreenHandler::new(sync_id, player_inventory).await; + let concrete_arc = Arc::new(Mutex::new(handler)); + + Some(concrete_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/block/blocks/ender_chest.rs b/pumpkin/src/block/blocks/ender_chest.rs index 18c4a0698..39cee4908 100644 --- a/pumpkin/src/block/blocks/ender_chest.rs +++ b/pumpkin/src/block/blocks/ender_chest.rs @@ -4,12 +4,11 @@ use crate::block::{ BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs, registry::BlockActionResult, }; -use async_trait::async_trait; use pumpkin_data::block_properties::{BlockProperties, LadderLikeProperties}; use pumpkin_inventory::{ generic_container_screen_handler::create_generic_9x3, player::player_inventory::PlayerInventory, - screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}, + screen_handler::{BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler}, }; use pumpkin_macros::pumpkin_block; use pumpkin_util::text::TextComponent; @@ -20,17 +19,19 @@ use tokio::sync::Mutex; struct EnderChestScreenFactory(Arc); -#[async_trait] impl ScreenHandlerFactory for EnderChestScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new( - create_generic_9x3(sync_id, player_inventory, self.0.clone()).await, - ))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone()).await; + let concrete_arc = Arc::new(Mutex::new(handler)); + + Some(concrete_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index 1a8997c81..6c38af17e 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -1,9 +1,10 @@ use std::sync::Arc; -use async_trait::async_trait; use pumpkin_data::block_properties::{BlockProperties, FurnaceLikeProperties}; use pumpkin_inventory::{ - furnace::furnace_screen_handler::FurnaceScreenHandler, screen_handler::ScreenHandlerFactory, + furnace::furnace_screen_handler::FurnaceScreenHandler, + player::player_inventory::PlayerInventory, + screen_handler::{BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler}, }; use pumpkin_macros::pumpkin_block; use pumpkin_util::text::TextComponent; @@ -33,22 +34,26 @@ impl FurnaceScreenFactory { } } -#[async_trait] impl ScreenHandlerFactory for FurnaceScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn pumpkin_inventory::screen_handler::InventoryPlayer, - ) -> Option>> { - let furnace_screen_handler = FurnaceScreenHandler::new( - sync_id, - player_inventory, - self.inventory.clone(), - self.block_entity.clone(), - ) - .await; - Some(Arc::new(Mutex::new(furnace_screen_handler))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let concrete_handler = FurnaceScreenHandler::new( + sync_id, + player_inventory, + self.inventory.clone(), + self.block_entity.clone(), + ) + .await; + + let concrete_arc = Arc::new(Mutex::new(concrete_handler)); + + Some(concrete_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> pumpkin_util::text::TextComponent { diff --git a/pumpkin/src/block/blocks/hopper.rs b/pumpkin/src/block/blocks/hopper.rs index 50df90814..21dc3f7a4 100644 --- a/pumpkin/src/block/blocks/hopper.rs +++ b/pumpkin/src/block/blocks/hopper.rs @@ -7,12 +7,14 @@ use crate::block::{ {BlockBehaviour, NormalUseArgs}, }; use crate::world::World; -use async_trait::async_trait; + use pumpkin_data::block_properties::{BlockProperties, HopperFacing}; use pumpkin_data::{Block, BlockDirection}; use pumpkin_inventory::generic_container_screen_handler::create_hopper; use pumpkin_inventory::player::player_inventory::PlayerInventory; -use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; use pumpkin_macros::pumpkin_block; use pumpkin_util::math::position::BlockPos; use pumpkin_util::text::TextComponent; @@ -24,17 +26,20 @@ use tokio::sync::Mutex; struct HopperBlockScreenFactory(Arc); -#[async_trait] impl ScreenHandlerFactory for HopperBlockScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new( - create_hopper(sync_id, player_inventory, self.0.clone()).await, - ))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let concrete_handler = create_hopper(sync_id, player_inventory, self.0.clone()).await; + + let concrete_arc = Arc::new(Mutex::new(concrete_handler)); + + Some(concrete_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/block/blocks/plant/segmented.rs b/pumpkin/src/block/blocks/plant/segmented.rs index 2dca16afa..bec1013c9 100644 --- a/pumpkin/src/block/blocks/plant/segmented.rs +++ b/pumpkin/src/block/blocks/plant/segmented.rs @@ -1,8 +1,7 @@ -use async_trait::async_trait; use pumpkin_data::block_properties::{BlockProperties, HorizontalFacing, Integer1To4}; use pumpkin_world::BlockStateId; -use crate::block::{BlockBehaviour, CanUpdateAtArgs}; +use crate::block::{BlockBehaviour, BlockFuture, CanUpdateAtArgs}; use crate::block::{BlockIsReplacing, OnPlaceArgs}; pub trait SegmentProperties { @@ -43,7 +42,6 @@ impl_segment_properties!( segment_amount ); -#[async_trait] pub trait Segmented: BlockBehaviour { type Properties: BlockProperties + SegmentProperties; @@ -80,30 +78,34 @@ pub trait Segmented: BlockBehaviour { } } - async fn can_update_at(&self, ctx: CanUpdateAtArgs<'_>) -> bool { - let current_props = Self::Properties::from_state_id(ctx.state_id, ctx.block); - self.can_add_segment(¤t_props) + fn can_update_at<'a>(&'a self, ctx: CanUpdateAtArgs<'a>) -> BlockFuture<'a, bool> { + Box::pin(async move { + let current_props = Self::Properties::from_state_id(ctx.state_id, ctx.block); + self.can_add_segment(¤t_props) + }) } - async fn on_place(&self, ctx: OnPlaceArgs<'_>) -> BlockStateId { - if let BlockIsReplacing::Itself(existing_state_id) = ctx.replacing { - let mut props = Self::Properties::from_state_id(existing_state_id, ctx.block); + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { + Box::pin(async move { + if let BlockIsReplacing::Itself(existing_state_id) = args.replacing { + let mut props = Self::Properties::from_state_id(existing_state_id, args.block); - if self.can_add_segment(&props) { - let current_amount = props.get_segment_amount(); - let next_amount = self.get_next_segment_amount(current_amount); - props.set_segment_amount(next_amount); - props.to_state_id(ctx.block) + if self.can_add_segment(&props) { + let current_amount = props.get_segment_amount(); + let next_amount = self.get_next_segment_amount(current_amount); + props.set_segment_amount(next_amount); + props.to_state_id(args.block) + } else { + existing_state_id + } } else { - existing_state_id + // Set first segment orientation based on player direction + let player_facing = args.player.living_entity.entity.get_horizontal_facing(); + let mut props = Self::Properties::default(args.block); + props.set_segment_amount(Integer1To4::L1); + props.set_facing(self.get_facing_for_segment(player_facing, Integer1To4::L1)); + props.to_state_id(args.block) } - } else { - // Set first segment orientation based on player direction - let player_facing = ctx.player.living_entity.entity.get_horizontal_facing(); - let mut props = Self::Properties::default(ctx.block); - props.set_segment_amount(Integer1To4::L1); - props.set_facing(self.get_facing_for_segment(player_facing, Integer1To4::L1)); - props.to_state_id(ctx.block) - } + }) } } diff --git a/pumpkin/src/block/blocks/redstone/dropper.rs b/pumpkin/src/block/blocks/redstone/dropper.rs index ce5db08b0..033ce2c18 100644 --- a/pumpkin/src/block/blocks/redstone/dropper.rs +++ b/pumpkin/src/block/blocks/redstone/dropper.rs @@ -6,14 +6,16 @@ use crate::block::{ }; use crate::entity::Entity; use crate::entity::item::ItemEntity; -use async_trait::async_trait; + use pumpkin_data::FacingExt; use pumpkin_data::block_properties::{BlockProperties, Facing}; use pumpkin_data::entity::EntityType; use pumpkin_data::world::WorldEvent; use pumpkin_inventory::generic_container_screen_handler::create_generic_3x3; use pumpkin_inventory::player::player_inventory::PlayerInventory; -use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; use pumpkin_macros::pumpkin_block; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::text::TextComponent; @@ -30,17 +32,19 @@ use uuid::Uuid; struct DropperScreenFactory(Arc); -#[async_trait] impl ScreenHandlerFactory for DropperScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new( - create_generic_3x3(sync_id, player_inventory, self.0.clone()).await, - ))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone()).await; + let screen_handler_arc = Arc::new(Mutex::new(handler)); + + Some(screen_handler_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/block/blocks/shulker_box.rs b/pumpkin/src/block/blocks/shulker_box.rs index 90b2a9695..7fa84b437 100644 --- a/pumpkin/src/block/blocks/shulker_box.rs +++ b/pumpkin/src/block/blocks/shulker_box.rs @@ -5,12 +5,14 @@ use crate::block::{ registry::BlockActionResult, {BlockBehaviour, NormalUseArgs}, }; -use async_trait::async_trait; + use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::tag::{RegistryKey, get_tag_values}; use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3; use pumpkin_inventory::player::player_inventory::PlayerInventory; -use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; use pumpkin_util::text::TextComponent; use pumpkin_world::BlockStateId; use pumpkin_world::block::entities::shulker_box::ShulkerBoxBlockEntity; @@ -19,17 +21,19 @@ use tokio::sync::Mutex; struct ShulkerBoxScreenFactory(Arc); -#[async_trait] impl ScreenHandlerFactory for ShulkerBoxScreenFactory { - async fn create_screen_handler( - &self, + fn create_screen_handler<'a>( + &'a self, sync_id: u8, - player_inventory: &Arc, - _player: &dyn InventoryPlayer, - ) -> Option>> { - Some(Arc::new(Mutex::new( - create_generic_9x3(sync_id, player_inventory, self.0.clone()).await, - ))) + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone()).await; + let screen_handler_arc = Arc::new(Mutex::new(handler)); + + Some(screen_handler_arc as SharedScreenHandler) + }) } fn get_display_name(&self) -> TextComponent { diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index 7b0ca7ee4..45c160e07 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -1,9 +1,8 @@ pub mod context; pub mod events; -use std::sync::Arc; +use std::{pin::Pin, sync::Arc}; -use async_trait::async_trait; pub use context::*; pub use events::*; @@ -24,12 +23,12 @@ pub struct PluginMetadata<'s> { pub description: &'s str, } +type PluginFuture<'a, T> = Pin + Send + 'a>>; + /// Trait representing a plugin with asynchronous lifecycle methods. /// /// This trait defines the required methods for a plugin, including hooks for when -/// the plugin is loaded and unloaded. It is marked with `async_trait` to allow -/// for asynchronous implementations. -#[async_trait] +/// the plugin is loaded and unloaded. pub trait Plugin: Send + Sync + 'static { /// Asynchronous method called when the plugin is loaded. /// @@ -40,8 +39,8 @@ pub trait Plugin: Send + Sync + 'static { /// /// # Returns /// - `Ok(())` on success, or `Err(String)` on failure. - async fn on_load(&mut self, _server: Arc) -> Result<(), String> { - Ok(()) + fn on_load(&mut self, _server: Arc) -> PluginFuture<'_, Result<(), String>> { + Box::pin(async move { Ok(()) }) } /// Asynchronous method called when the plugin is unloaded. @@ -53,7 +52,7 @@ pub trait Plugin: Send + Sync + 'static { /// /// # Returns /// - `Ok(())` on success, or `Err(String)` on failure. - async fn on_unload(&mut self, _server: Arc) -> Result<(), String> { - Ok(()) + fn on_unload(&mut self, _server: Arc) -> PluginFuture<'_, Result<(), String>> { + Box::pin(async move { Ok(()) }) } } diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 6c63be187..1e790e728 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,10 +1,10 @@ -use async_trait::async_trait; use futures::future::join_all; use loader::{LoaderError, PluginLoader, native::NativePluginLoader}; use std::{ any::Any, collections::{HashMap, HashSet}, path::{Path, PathBuf}, + pin::Pin, sync::Arc, }; use thiserror::Error; @@ -16,26 +16,31 @@ pub mod loader; use crate::{PERMISSION_MANAGER, server::Server}; pub use api::*; +type BoxFuture<'a, T> = Pin + Send + 'a>>; + /// A trait for handling events dynamically. /// /// This trait allows for handling events of any type that implements the `Event` trait. -#[async_trait] pub trait DynEventHandler: Send + Sync { /// Asynchronously handles a dynamic event. /// /// # Arguments /// - `event`: A reference to the event to handle. - async fn handle_dyn(&self, _server: &Arc, event: &(dyn Payload + Send + Sync)); + fn handle_dyn<'a>( + &'a self, + _server: &'a Arc, + event: &'a (dyn Payload + Send + Sync), + ) -> BoxFuture<'a, ()>; /// Asynchronously handles a blocking dynamic event. /// /// # Arguments /// - `event`: A mutable reference to the event to handle. - async fn handle_blocking_dyn( - &self, - _server: &Arc, - _event: &mut (dyn Payload + Send + Sync), - ); + fn handle_blocking_dyn<'a>( + &'a self, + _server: &'a Arc, + _event: &'a mut (dyn Payload + Send + Sync), + ) -> BoxFuture<'a, ()>; /// Checks if the event handler is blocking. /// @@ -53,19 +58,22 @@ pub trait DynEventHandler: Send + Sync { /// A trait for handling specific events. /// /// This trait allows for handling events of a specific type that implements the `Event` trait. -#[async_trait] pub trait EventHandler: Send + Sync { /// Asynchronously handles an event of type `E`. /// /// # Arguments /// - `event`: A reference to the event to handle. - async fn handle(&self, _server: &Arc, _event: &E) {} + fn handle(&self, _server: &Arc, _event: &E) -> BoxFuture<'_, ()> { + Box::pin(async {}) + } /// Asynchronously handles a blocking event of type `E`. /// /// # Arguments /// - `event`: A mutable reference to the event to handle. - async fn handle_blocking(&self, _server: &Arc, _event: &mut E) {} + fn handle_blocking(&self, _server: &Arc, _event: &mut E) -> BoxFuture<'_, ()> { + Box::pin(async move {}) + } } /// A struct representing a typed event handler. @@ -82,28 +90,37 @@ where _phantom: std::marker::PhantomData, } -#[async_trait] impl DynEventHandler for TypedEventHandler where E: Payload + Send + Sync + 'static, H: EventHandler + Send + Sync, { /// Asynchronously handles a blocking dynamic event. - async fn handle_blocking_dyn( - &self, - server: &Arc, - event: &mut (dyn Payload + Send + Sync), - ) { - if let Some(typed_event) = ::downcast_mut(event) { - self.handler.handle_blocking(server, typed_event).await; - } + fn handle_blocking_dyn<'a>( + &'a self, + server: &'a Arc, + event: &'a mut (dyn Payload + Send + Sync), + ) -> BoxFuture<'a, ()> { + Box::pin(async move { + if let Some(typed_event) = ::downcast_mut(event) { + // The handler.handle_blocking call now returns a Future, which we await. + self.handler.handle_blocking(server, typed_event).await; + } + }) } /// Asynchronously handles a dynamic event. - async fn handle_dyn(&self, server: &Arc, event: &(dyn Payload + Send + Sync)) { - if let Some(typed_event) = ::downcast_ref(event) { - self.handler.handle(server, typed_event).await; - } + fn handle_dyn<'a>( + &'a self, + server: &'a Arc, + event: &'a (dyn Payload + Send + Sync), + ) -> BoxFuture<'a, ()> { + Box::pin(async move { + if let Some(typed_event) = ::downcast_ref(event) { + // The handler.handle call now returns a Future, which we await. + self.handler.handle(server, typed_event).await; + } + }) } /// Checks if the handler is blocking.