feat: more plugin entity stuff

This commit is contained in:
Alexander Medvedev
2026-08-27 12:36:26 +02:00
parent 3d69190a37
commit 800f7d70e5
68 changed files with 4366 additions and 1700 deletions

21
Cargo.lock generated
View File

@@ -1179,19 +1179,6 @@ version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crossfire"
version = "3.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111ce8f7abfbac38b4bc4f32a3a2dda1a8034b873c90c7899f302cc9dbbc05ec"
dependencies = [
"crossbeam-utils",
"futures-core",
"parking_lot",
"pointers",
"smallvec",
]
[[package]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.4" version = "0.2.4"
@@ -3098,12 +3085,6 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "pointers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcdc93847ad24990939cce6e1804361e903efcb5f99daa5abd87943a9d6d7ba"
[[package]] [[package]]
name = "poly1305" name = "poly1305"
version = "0.8.0" version = "0.8.0"
@@ -3464,7 +3445,6 @@ dependencies = [
"pumpkin-world", "pumpkin-world",
"rand", "rand",
"thiserror 2.0.20", "thiserror 2.0.20",
"tokio",
"tracing", "tracing",
] ]
@@ -3581,7 +3561,6 @@ dependencies = [
"bytes", "bytes",
"criterion", "criterion",
"crossbeam", "crossbeam",
"crossfire",
"dashmap", "dashmap",
"flate2", "flate2",
"futures", "futures",

View File

@@ -131,7 +131,7 @@ thiserror = { version = "2.0", default-features = false }
bytes = { version = "1.12", default-features = false, features = ["std"] } bytes = { version = "1.12", default-features = false, features = ["std"] }
# Concurrency/Parallelism and Synchronization # Concurrency/Parallelism and Synchronization
futures = { version = "0.3", default-features = false, features = ["executor"] } futures = { version = "0.3", default-features = false, features = ["std"] }
rayon = { version = "1.12", default-features = false } rayon = { version = "1.12", default-features = false }
crossbeam = { version = "0.8", default-features = false, features = ["std"] } crossbeam = { version = "0.8", default-features = false, features = ["std"] }
@@ -153,7 +153,6 @@ console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = { version = "1.10.0", default-features = false, features = ["std"] } crc-fast = { version = "1.10.0", default-features = false, features = ["std"] }
criterion = { version = "0.8", default-features = false } criterion = { version = "0.8", default-features = false }
crossbeam-utils = { version = "0.8.22", default-features = false, features = ["std"] } crossbeam-utils = { version = "0.8.22", default-features = false, features = ["std"] }
crossfire = { version = "3.1.19", default-features = false, features = ["compat"] }
crypto-bigint = { version = "0.7.5", default-features = false } crypto-bigint = { version = "0.7.5", default-features = false }
dashmap = { version = "6.2", default-features = false } dashmap = { version = "6.2", default-features = false }
ecdsa = { version = "0.17.0", default-features = false, features = ["std"] } ecdsa = { version = "0.17.0", default-features = false, features = ["std"] }

View File

@@ -24,7 +24,6 @@ pumpkin-util.workspace = true
rand.workspace = true rand.workspace = true
tracing.workspace = true tracing.workspace = true
tokio.workspace = true
thiserror.workspace = true thiserror.workspace = true
[lints] [lints]

View File

@@ -0,0 +1,31 @@
//! Plugin datapack management and querying utilities.
//!
//! This module provides interfaces to inspect, query, enable, disable, and reload
//! datapacks on the server.
//!
//! # Examples
//!
//! ## Listing Datapacks
//! ```rust,ignore
//! use pumpkin_plugin_api::Server;
//!
//! fn log_datapacks(server: &Server) {
//! let manager = server.get_datapack_manager();
//! for pack in manager.list_all_packs() {
//! println!("Datapack {}: enabled = {}", pack.name, pack.is_enabled);
//! }
//! }
//! ```
//!
//! ## Enabling and Reloading Datapacks
//! ```rust,ignore
//! use pumpkin_plugin_api::{Server, datapack::EnablePosition};
//!
//! fn enable_custom_pack(server: &Server) -> Result<(), String> {
//! let manager = server.get_datapack_manager();
//! manager.enable_pack("my_custom_pack", EnablePosition::Last)?;
//! Ok(())
//! }
//! ```
pub use crate::wit::pumpkin::plugin::datapack::{DatapackInfo, DatapackManager, EnablePosition};

View File

@@ -0,0 +1,32 @@
//! Plugin inventory and container management utilities.
//!
//! This module provides a unified API for inspecting, modifying, and interacting
//! with player inventories, ender chests, custom GUIs, and container block entities.
//!
//! # Examples
//!
//! ## Inspecting and Modifying a Player's Inventory
//! ```rust,ignore
//! use pumpkin_plugin_api::{Player, ItemStack};
//!
//! fn equip_player(player: &Player) {
//! let inv = player.get_inventory();
//! inv.set_helmet(Some(ItemStack::new("minecraft:diamond_helmet", 1)));
//! inv.set_boots(Some(ItemStack::new("minecraft:diamond_boots", 1)));
//!
//! let storage = inv.as_inventory();
//! storage.set_item(0, Some(ItemStack::new("minecraft:diamond_sword", 1)));
//! }
//! ```
//!
//! ## Interacting with Custom GUIs
//! ```rust,ignore
//! use pumpkin_plugin_api::gui::Gui;
//!
//! fn setup_gui(gui: &Gui) {
//! let inv = gui.get_inventory();
//! inv.clear();
//! }
//! ```
pub use crate::wit::pumpkin::plugin::inventory::{Inventory, PlayerInventory};

View File

@@ -76,6 +76,8 @@ use crate::{
/// Plugin command registration and handling utilities. /// Plugin command registration and handling utilities.
pub mod commands; pub mod commands;
/// Datapack management and query utilities.
pub mod datapack;
/// Display and interaction entity utilities and builders. /// Display and interaction entity utilities and builders.
pub mod display; pub mod display;
/// Custom enchantment registration and builder utilities. /// Custom enchantment registration and builder utilities.
@@ -85,6 +87,10 @@ pub mod events;
mod ext; mod ext;
/// Bedrock UI form builders. /// Bedrock UI form builders.
pub mod forms; pub mod forms;
/// Unified inventory and container management utilities.
pub mod inventory;
/// Specialized mob entity wrappers and helpers.
pub mod mobs;
/// Constants for plugin permissions. /// Constants for plugin permissions.
/// ///
/// Use these in your `PluginMetadata` to request access to specific host features. /// Use these in your `PluginMetadata` to request access to specific host features.
@@ -110,18 +116,19 @@ pub use wit::pumpkin::plugin::{
advancement as advancement_wit, bedrock_packets, block_entity, boss_bar, advancement as advancement_wit, bedrock_packets, block_entity, boss_bar,
command as command_wit, common, command as command_wit, common,
context::{self, Context, MarketplaceMetadata, Server}, context::{self, Context, MarketplaceMetadata, Server},
damage_types as damage_types_wit, data_components, display as display_wit, damage_types as damage_types_wit, data_components, datapack as datapack_wit,
enchantments as enchantments_wit, entity, display as display_wit, enchantments as enchantments_wit, entity,
entity_types::EntityType, entity_types::EntityType,
event::{self as events_wit, EventType}, event::{self as events_wit, EventType},
gui, i18n, ipc, item_stack, java_dialogs, java_packets, particles, permission, player, gui, i18n, inventory as inventory_wit, ipc, item_stack, java_dialogs, java_packets, particles,
recipe as recipe_wit, scoreboard, screens as screens_wit, server, statistics as statistics_wit, permission, player, recipe as recipe_wit, scoreboard, screens as screens_wit, server,
text, uuid, world, statistics as statistics_wit, text, uuid, world,
}; };
// Convenience re-exports of commonly-used plugin types so plugin authors can // Convenience re-exports of commonly-used plugin types so plugin authors can
// name them directly (e.g. build an `ItemStack` for a GUI or `/give`). // name them directly (e.g. build an `ItemStack` for a GUI or `/give`).
pub use damage_types_wit::DamageType; pub use damage_types_wit::DamageType;
pub use datapack::{DatapackInfo, DatapackManager, EnablePosition};
pub use display::{ pub use display::{
BillboardMode, BlockDisplayEntity, DisplayEntity, DisplayEntityExt, DisplayTransformation, BillboardMode, BlockDisplayEntity, DisplayEntity, DisplayEntityExt, DisplayTransformation,
EntityDisplayExt, InteractionEntity, ItemDisplayEntity, ItemDisplayEntityExt, ItemDisplayMode, EntityDisplayExt, InteractionEntity, ItemDisplayEntity, ItemDisplayEntityExt, ItemDisplayMode,
@@ -134,6 +141,13 @@ pub use enchantment::{
}; };
pub use events::{EventHandler, FromIntoEvent}; pub use events::{EventHandler, FromIntoEvent};
pub use ext::player::PlayerEnderChestExt; pub use ext::player::PlayerEnderChestExt;
pub use inventory::{Inventory, PlayerInventory};
pub use mobs::{
Ageable, AgeableData, Cat, CatData, Creeper, CreeperData, DyeColor, Enderman, EndermanData,
EntityCastExt, Fox, FoxData, IronGolem, IronGolemData, MobCast, MobData, Sheep, SheepData,
Shulker, ShulkerData, Slime, SlimeData, Villager, VillagerData, VillagerProfession, Wolf,
WolfData, Zombie, ZombieData,
};
pub use recipe::{ pub use recipe::{
CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager, CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager,
RegistrableRecipe, ShapedRecipeBuilder, ShapelessRecipeBuilder, RegistrableRecipe, ShapedRecipeBuilder, ShapelessRecipeBuilder,
@@ -146,8 +160,8 @@ pub use wit::pumpkin::plugin::player::Player;
pub use wit::pumpkin::plugin::scoreboard::{CollisionRule, NametagVisibility, TeamSettings}; pub use wit::pumpkin::plugin::scoreboard::{CollisionRule, NametagVisibility, TeamSettings};
pub use wit::pumpkin::plugin::server::Dimension; pub use wit::pumpkin::plugin::server::Dimension;
pub use wit::pumpkin::plugin::world::{ pub use wit::pumpkin::plugin::world::{
Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, RayTraceBlockResult, Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, LivingEntity, Mob,
RayTraceEntityResult, RaycastResult, World, WorldBorder, PathNodeType, RayTraceBlockResult, RayTraceEntityResult, RaycastResult, World, WorldBorder,
}; };
pub use worldgen::{ChunkBuffer, ChunkGenerator, GenerationPhase, GeneratorManager}; pub use worldgen::{ChunkBuffer, ChunkGenerator, GenerationPhase, GeneratorManager};

View File

@@ -0,0 +1,689 @@
//! Specialized typed wrappers for major Minecraft mob entities.
//!
//! Provides typed accessors and ergonomic helpers (`Sheep::from_entity`, `Wolf::from_mob`, etc.)
//! without needing dozens of WIT resources.
use std::ops::Deref;
pub use crate::wit::pumpkin::plugin::uuid::Uuid;
pub use crate::wit::pumpkin::plugin::world::{
AgeableData, BlockDirection, CatData, CreeperData, DyeColor, EndermanData, Entity, FoxData,
IronGolemData, LivingEntity, Mob, MobData, SheepData, ShulkerData, SlimeData, VillagerData,
VillagerProfession, WolfData, ZombieData,
};
/// Trait implemented by all specialized mob wrappers to allow generic downcasting via `.cast::<T>()`.
pub trait MobCast<'a>: Sized {
/// Attempts to wrap a [`Mob`] reference if the underlying entity data matches.
fn from_mob(mob: &'a Mob) -> Option<Self>;
/// Attempts to wrap an [`Entity`] reference if it is an AI mob matching this type.
fn from_entity(entity: &'a Entity) -> Option<Self> {
let mob = entity.as_mob()?;
// Note: as_mob() returns a new Resource handle, so we extract via MobData check.
Self::from_mob_owned(mob)
}
/// Attempts to wrap a [`LivingEntity`] reference if it is an AI mob matching this type.
fn from_living(living: &'a LivingEntity) -> Option<Self> {
let mob = living.as_mob()?;
Self::from_mob_owned(mob)
}
#[doc(hidden)]
fn from_mob_owned(mob: Mob) -> Option<Self>;
}
macro_rules! define_mob_wrapper {
(
$(#[$meta:meta])*
$name:ident, $variant:ident, $data_ty:ident
) => {
$(#[$meta])*
pub struct $name<'a> {
mob: &'a Mob,
_owned: Option<Mob>,
}
impl<'a> $name<'a> {
/// Wraps a borrowed [`Mob`] reference if this mob is of the matching type.
#[must_use]
pub fn from_mob(mob: &'a Mob) -> Option<Self> {
if matches!(mob.get_mob_data(), MobData::$variant(_)) {
Some(Self { mob, _owned: None })
} else {
None
}
}
/// Wraps a borrowed [`Entity`] reference if it is an AI mob of the matching type.
#[must_use]
pub fn from_entity(entity: &'a Entity) -> Option<Self> {
let mob = entity.as_mob()?;
Self::from_mob_owned(mob)
}
/// Wraps a borrowed [`LivingEntity`] reference if it is an AI mob of the matching type.
#[must_use]
pub fn from_living(living: &'a LivingEntity) -> Option<Self> {
let mob = living.as_mob()?;
Self::from_mob_owned(mob)
}
fn from_mob_owned(mob: Mob) -> Option<Self> {
if matches!(mob.get_mob_data(), MobData::$variant(_)) {
// Safe reference extension to the owned Mob
let mob_ref = unsafe { &*(&mob as *const Mob) };
Some(Self {
mob: mob_ref,
_owned: Some(mob),
})
} else {
None
}
}
/// Retrieves the underlying data record for this mob.
#[must_use]
pub fn get_data(&self) -> Option<$data_ty> {
match self.mob.get_mob_data() {
MobData::$variant(data) => Some(data),
_ => None,
}
}
/// Updates the underlying data record for this mob.
pub fn set_data(&self, data: $data_ty) -> bool {
self.mob.set_mob_data(MobData::$variant(data))
}
}
impl<'a> Deref for $name<'a> {
type Target = Mob;
fn deref(&self) -> &Self::Target {
self.mob
}
}
impl<'a> TryFrom<&'a Mob> for $name<'a> {
type Error = ();
fn try_from(mob: &'a Mob) -> Result<Self, Self::Error> {
Self::from_mob(mob).ok_or(())
}
}
impl<'a> TryFrom<&'a Entity> for $name<'a> {
type Error = ();
fn try_from(entity: &'a Entity) -> Result<Self, Self::Error> {
Self::from_entity(entity).ok_or(())
}
}
impl<'a> TryFrom<&'a LivingEntity> for $name<'a> {
type Error = ();
fn try_from(living: &'a LivingEntity) -> Result<Self, Self::Error> {
Self::from_living(living).ok_or(())
}
}
impl<'a> MobCast<'a> for $name<'a> {
fn from_mob(mob: &'a Mob) -> Option<Self> {
Self::from_mob(mob)
}
fn from_living(living: &'a LivingEntity) -> Option<Self> {
Self::from_living(living)
}
fn from_mob_owned(mob: Mob) -> Option<Self> {
Self::from_mob_owned(mob)
}
}
};
}
define_mob_wrapper!(
/// Specialized wrapper for Sheep entities.
Sheep, Sheep, SheepData
);
impl<'a> Sheep<'a> {
/// Gets the fleece dye color of this sheep.
#[must_use]
pub fn get_color(&self) -> DyeColor {
self.get_data().map_or(DyeColor::White, |d| d.color)
}
/// Sets the fleece dye color of this sheep.
pub fn set_color(&self, color: DyeColor) {
if let Some(mut data) = self.get_data() {
data.color = color;
self.set_data(data);
}
}
/// Returns whether this sheep has been sheared.
#[must_use]
pub fn is_sheared(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sheared)
}
/// Sets whether this sheep is sheared.
pub fn set_sheared(&self, sheared: bool) {
if let Some(mut data) = self.get_data() {
data.is_sheared = sheared;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Wolf entities.
Wolf, Wolf, WolfData
);
impl<'a> Wolf<'a> {
/// Returns whether this wolf is tamed.
#[must_use]
pub fn is_tamed(&self) -> bool {
self.get_data().is_some_and(|d| d.is_tamed)
}
/// Sets whether this wolf is tamed.
pub fn set_tamed(&self, tamed: bool) {
if let Some(mut data) = self.get_data() {
data.is_tamed = tamed;
self.set_data(data);
}
}
/// Gets the UUID of the player who owns this wolf, if any.
#[must_use]
pub fn get_owner(&self) -> Option<Uuid> {
self.get_data().and_then(|d| d.owner)
}
/// Sets the owner of this wolf by UUID.
pub fn set_owner(&self, owner: Option<Uuid>) {
if let Some(mut data) = self.get_data() {
data.owner = owner;
self.set_data(data);
}
}
/// Returns whether this wolf is currently in a sitting pose.
#[must_use]
pub fn is_sitting(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sitting)
}
/// Orders this wolf to sit or stand.
pub fn set_sitting(&self, sitting: bool) {
if let Some(mut data) = self.get_data() {
data.is_sitting = sitting;
self.set_data(data);
}
}
/// Gets the collar dye color of this wolf.
#[must_use]
pub fn get_collar_color(&self) -> DyeColor {
self.get_data().map_or(DyeColor::Red, |d| d.collar_color)
}
/// Sets the collar dye color of this wolf.
pub fn set_collar_color(&self, color: DyeColor) {
if let Some(mut data) = self.get_data() {
data.collar_color = color;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Cat entities.
Cat, Cat, CatData
);
impl<'a> Cat<'a> {
/// Returns whether this cat is tamed.
#[must_use]
pub fn is_tamed(&self) -> bool {
self.get_data().is_some_and(|d| d.is_tamed)
}
/// Sets whether this cat is tamed.
pub fn set_tamed(&self, tamed: bool) {
if let Some(mut data) = self.get_data() {
data.is_tamed = tamed;
self.set_data(data);
}
}
/// Gets the owner UUID of this cat.
#[must_use]
pub fn get_owner(&self) -> Option<Uuid> {
self.get_data().and_then(|d| d.owner)
}
/// Sets the owner UUID of this cat.
pub fn set_owner(&self, owner: Option<Uuid>) {
if let Some(mut data) = self.get_data() {
data.owner = owner;
self.set_data(data);
}
}
/// Returns whether this cat is in a sitting pose.
#[must_use]
pub fn is_sitting(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sitting)
}
/// Orders this cat to sit or stand.
pub fn set_sitting(&self, sitting: bool) {
if let Some(mut data) = self.get_data() {
data.is_sitting = sitting;
self.set_data(data);
}
}
/// Gets the collar dye color of this cat.
#[must_use]
pub fn get_collar_color(&self) -> DyeColor {
self.get_data().map_or(DyeColor::Red, |d| d.collar_color)
}
/// Sets the collar dye color of this cat.
pub fn set_collar_color(&self, color: DyeColor) {
if let Some(mut data) = self.get_data() {
data.collar_color = color;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Villager entities.
Villager, Villager, VillagerData
);
impl<'a> Villager<'a> {
/// Gets the profession of this villager.
#[must_use]
pub fn get_profession(&self) -> VillagerProfession {
self.get_data()
.map_or(VillagerProfession::None, |d| d.profession)
}
/// Sets the profession of this villager.
pub fn set_profession(&self, profession: VillagerProfession) {
if let Some(mut data) = self.get_data() {
data.profession = profession;
self.set_data(data);
}
}
/// Gets the trading level of this villager (1-5).
#[must_use]
pub fn get_level(&self) -> u8 {
self.get_data().map_or(1, |d| d.level)
}
/// Sets the trading level of this villager (1-5).
pub fn set_level(&self, level: u8) {
if let Some(mut data) = self.get_data() {
data.level = level;
self.set_data(data);
}
}
/// Gets the trading experience points of this villager.
#[must_use]
pub fn get_experience(&self) -> u32 {
self.get_data().map_or(0, |d| d.experience)
}
/// Sets the trading experience points of this villager.
pub fn set_experience(&self, experience: u32) {
if let Some(mut data) = self.get_data() {
data.experience = experience;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Creeper entities.
Creeper, Creeper, CreeperData
);
impl<'a> Creeper<'a> {
/// Returns whether this creeper is charged (struck by lightning).
#[must_use]
pub fn is_powered(&self) -> bool {
self.get_data().is_some_and(|d| d.is_powered)
}
/// Sets whether this creeper is powered/charged.
pub fn set_powered(&self, powered: bool) {
if let Some(mut data) = self.get_data() {
data.is_powered = powered;
self.set_data(data);
}
}
/// Gets the fuse duration of this creeper in ticks.
#[must_use]
pub fn get_fuse(&self) -> i32 {
self.get_data().map_or(30, |d| d.fuse)
}
/// Sets the fuse duration of this creeper in ticks.
pub fn set_fuse(&self, fuse: i32) {
if let Some(mut data) = self.get_data() {
data.fuse = fuse;
self.set_data(data);
}
}
/// Returns whether this creeper has been manually ignited with flint and steel.
#[must_use]
pub fn is_ignited(&self) -> bool {
self.get_data().is_some_and(|d| d.is_ignited)
}
/// Sets whether this creeper is ignited.
pub fn set_ignited(&self, ignited: bool) {
if let Some(mut data) = self.get_data() {
data.is_ignited = ignited;
self.set_data(data);
}
}
/// Gets the explosion radius of this creeper.
#[must_use]
pub fn get_explosion_radius(&self) -> u8 {
self.get_data().map_or(3, |d| d.explosion_radius)
}
/// Sets the explosion radius of this creeper.
pub fn set_explosion_radius(&self, radius: u8) {
if let Some(mut data) = self.get_data() {
data.explosion_radius = radius;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Slime and Magma Cube entities.
Slime, Slime, SlimeData
);
impl<'a> Slime<'a> {
/// Gets the size scale of this slime.
#[must_use]
pub fn get_size(&self) -> i32 {
self.get_data().map_or(1, |d| d.size)
}
/// Sets the size scale of this slime.
pub fn set_size(&self, size: i32) {
if let Some(mut data) = self.get_data() {
data.size = size;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Enderman entities.
Enderman, Enderman, EndermanData
);
impl<'a> Enderman<'a> {
/// Gets the numerical block state ID carried by this enderman, if any.
#[must_use]
pub fn get_carried_block(&self) -> Option<u16> {
self.get_data().and_then(|d| d.carried_block_state)
}
/// Sets the carried block state ID for this enderman.
pub fn set_carried_block(&self, block_state: Option<u16>) {
if let Some(mut data) = self.get_data() {
data.carried_block_state = block_state;
self.set_data(data);
}
}
/// Returns whether this enderman is currently screaming (angry).
#[must_use]
pub fn is_screaming(&self) -> bool {
self.get_data().is_some_and(|d| d.is_screaming)
}
/// Returns whether this enderman is staring at a player.
#[must_use]
pub fn is_staring(&self) -> bool {
self.get_data().is_some_and(|d| d.is_staring)
}
}
define_mob_wrapper!(
/// Specialized wrapper for Iron Golem entities.
IronGolem, IronGolem, IronGolemData
);
impl<'a> IronGolem<'a> {
/// Returns whether this iron golem was created by a player.
#[must_use]
pub fn is_player_created(&self) -> bool {
self.get_data().is_some_and(|d| d.is_player_created)
}
/// Sets whether this iron golem is considered player-created.
pub fn set_player_created(&self, created: bool) {
if let Some(mut data) = self.get_data() {
data.is_player_created = created;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Fox entities.
Fox, Fox, FoxData
);
impl<'a> Fox<'a> {
/// Returns whether this fox is sitting.
#[must_use]
pub fn is_sitting(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sitting)
}
/// Sets whether this fox is sitting.
pub fn set_sitting(&self, sitting: bool) {
if let Some(mut data) = self.get_data() {
data.is_sitting = sitting;
self.set_data(data);
}
}
/// Returns whether this fox is sleeping.
#[must_use]
pub fn is_sleeping(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sleeping)
}
/// Sets whether this fox is sleeping.
pub fn set_sleeping(&self, sleeping: bool) {
if let Some(mut data) = self.get_data() {
data.is_sleeping = sleeping;
self.set_data(data);
}
}
/// Returns whether this fox is crouching.
#[must_use]
pub fn is_crouching(&self) -> bool {
self.get_data().is_some_and(|d| d.is_crouching)
}
/// Sets whether this fox is crouching.
pub fn set_crouching(&self, crouching: bool) {
if let Some(mut data) = self.get_data() {
data.is_crouching = crouching;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Shulker entities.
Shulker, Shulker, ShulkerData
);
impl<'a> Shulker<'a> {
/// Gets the attached block direction of this shulker.
#[must_use]
pub fn get_attached_face(&self) -> BlockDirection {
self.get_data()
.map_or(BlockDirection::Down, |d| d.attached_face)
}
/// Sets the attached block direction of this shulker.
pub fn set_attached_face(&self, face: BlockDirection) {
if let Some(mut data) = self.get_data() {
data.attached_face = face;
self.set_data(data);
}
}
/// Gets the raw peek amount of this shulker (0-100).
#[must_use]
pub fn get_peek_amount(&self) -> u8 {
self.get_data().map_or(0, |d| d.peek_amount)
}
/// Sets the raw peek amount of this shulker (0-100).
pub fn set_peek_amount(&self, amount: u8) {
if let Some(mut data) = self.get_data() {
data.peek_amount = amount;
self.set_data(data);
}
}
/// Gets the custom dye color of this shulker, if dyed.
#[must_use]
pub fn get_color(&self) -> Option<DyeColor> {
self.get_data().and_then(|d| d.color)
}
/// Sets or removes the custom dye color of this shulker.
pub fn set_color(&self, color: Option<DyeColor>) {
if let Some(mut data) = self.get_data() {
data.color = color;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Zombie entities.
Zombie, Zombie, ZombieData
);
impl<'a> Zombie<'a> {
/// Returns whether this zombie is a baby.
#[must_use]
pub fn is_baby(&self) -> bool {
self.get_data().is_some_and(|d| d.is_baby)
}
/// Sets whether this zombie is a baby.
pub fn set_baby(&self, baby: bool) {
if let Some(mut data) = self.get_data() {
data.is_baby = baby;
self.set_data(data);
}
}
/// Returns whether this zombie is capable of breaking doors.
#[must_use]
pub fn can_break_doors(&self) -> bool {
self.get_data().is_some_and(|d| d.can_break_doors)
}
/// Sets whether this zombie can break doors.
pub fn set_can_break_doors(&self, can_break: bool) {
if let Some(mut data) = self.get_data() {
data.can_break_doors = can_break;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Ageable animal mobs (cows, pigs, chickens, rabbits, etc.).
Ageable, Ageable, AgeableData
);
impl<'a> Ageable<'a> {
/// Returns whether this animal is a baby.
#[must_use]
pub fn is_baby(&self) -> bool {
self.get_data().is_some_and(|d| d.is_baby)
}
/// Sets whether this animal is a baby.
pub fn set_baby(&self, baby: bool) {
if let Some(mut data) = self.get_data() {
data.is_baby = baby;
self.set_data(data);
}
}
/// Gets the age of this animal in ticks (negative for babies).
#[must_use]
pub fn get_age(&self) -> i32 {
self.get_data().map_or(0, |d| d.age)
}
/// Sets the age of this animal in ticks.
pub fn set_age(&self, age: i32) {
if let Some(mut data) = self.get_data() {
data.age = age;
self.set_data(data);
}
}
}
/// Extension trait providing generic `.cast::<T>()` downcasting on [`Entity`] and [`Mob`].
pub trait EntityCastExt {
/// Attempts to cast this entity or mob reference to a specialized mob wrapper type.
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T>;
}
impl EntityCastExt for Mob {
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T> {
T::from_mob(self)
}
}
impl EntityCastExt for Entity {
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T> {
T::from_entity(self)
}
}
impl EntityCastExt for LivingEntity {
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T> {
T::from_living(self)
}
}

View File

@@ -55,7 +55,6 @@ tokio-util = { workspace = true, features = ["rt"] }
rand.workspace = true rand.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
slotmap.workspace = true slotmap.workspace = true
crossfire.workspace = true
[dev-dependencies] [dev-dependencies]
tempfile.workspace = true tempfile.workspace = true

View File

@@ -73,11 +73,8 @@ fn bench_chunk_deserialization(c: &mut Criterion) {
let Chunk::Level(chunk) = chunk else { let Chunk::Level(chunk) = chunk else {
panic!("full generation must return a level chunk"); panic!("full generation must return a level chunk");
}; };
let runtime = tokio::runtime::Builder::new_current_thread() let bytes = chunk
.build() .to_bytes()
.expect("failed to create benchmark runtime");
let bytes = runtime
.block_on(chunk.to_bytes())
.expect("failed to serialize benchmark chunk"); .expect("failed to serialize benchmark chunk");
let position = Vector2::new(chunk.x, chunk.z); let position = Vector2::new(chunk.x, chunk.z);

View File

@@ -8,7 +8,6 @@ use std::{
io::{Read, SeekFrom, Write}, io::{Read, SeekFrom, Write},
marker::PhantomData, marker::PhantomData,
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use tokio::{ use tokio::{
@@ -308,7 +307,7 @@ impl AnvilChunkData {
} }
} }
async fn from_chunk<S>( fn from_chunk<S>(
chunk: &S, chunk: &S,
compression: Option<Compression>, compression: Option<Compression>,
chunk_config: &AnvilChunkConfig, chunk_config: &AnvilChunkConfig,
@@ -318,7 +317,6 @@ impl AnvilChunkData {
{ {
let raw_bytes = chunk let raw_bytes = chunk
.to_bytes() .to_bytes()
.await
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?; .map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
let compression = compression.unwrap_or_else(|| chunk_config.compression.algorithm.into()); let compression = compression.unwrap_or_else(|| chunk_config.compression.algorithm.into());
@@ -498,9 +496,7 @@ impl<S: SingleChunkDataSerializer> Default for AnvilChunkFile<S> {
} }
pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable + 'static { pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable + 'static {
fn to_bytes( fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError>;
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>;
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError>; fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError>;
fn position(&self) -> (i32, i32); fn position(&self) -> (i32, i32);
} }
@@ -620,8 +616,7 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
let compression_type = self.chunks_data[index] let compression_type = self.chunks_data[index]
.as_ref() .as_ref()
.and_then(|chunk_data| chunk_data.serialized_data.compression); .and_then(|chunk_data| chunk_data.serialized_data.compression);
let new_chunk_data = let new_chunk_data = AnvilChunkData::from_chunk(chunk, compression_type, chunk_config)?;
AnvilChunkData::from_chunk(chunk, compression_type, chunk_config).await?;
let mut write_action = self.write_action.lock().await; let mut write_action = self.write_action.lock().await;
if !chunk_config.write_in_place { if !chunk_config.write_in_place {

View File

@@ -572,7 +572,6 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for LinearV2File<S>
let index = Self::get_chunk_index(chunk.position().0, chunk.position().1); let index = Self::get_chunk_index(chunk.position().0, chunk.position().1);
let chunk_raw: Bytes = chunk let chunk_raw: Bytes = chunk
.to_bytes() .to_bytes()
.await
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?; .map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
self.timestamps[index] = SystemTime::now() self.timestamps[index] = SystemTime::now()

View File

@@ -1,6 +1,5 @@
use std::{ use std::{
path::PathBuf, path::PathBuf,
pin::Pin,
str::FromStr, str::FromStr,
sync::{ sync::{
RwLock, RwLock,
@@ -13,7 +12,6 @@ use pumpkin_data::{Block, BlockStateId, chunk::ChunkStatus, fluid::Fluid};
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation}; use pumpkin_util::resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tokio::sync::Mutex;
use crate::{ use crate::{
chunk::{ chunk::{
@@ -43,10 +41,8 @@ impl SingleChunkDataSerializer for ChunkData {
} }
#[inline] #[inline]
fn to_bytes( fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
&self, Ok(self.internal_to_bytes())
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
Box::pin(async move { Ok(self.internal_to_bytes()) })
} }
#[inline] #[inline]
@@ -707,10 +703,8 @@ impl SingleChunkDataSerializer for ChunkEntityData {
} }
#[inline] #[inline]
fn to_bytes( fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
&self, Ok(self.internal_to_bytes())
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
Box::pin(async move { self.internal_to_bytes().await })
} }
#[inline] #[inline]
@@ -775,12 +769,12 @@ impl ChunkEntityData {
Ok(Self { Ok(Self {
x: position.x, x: position.x,
z: position.y, z: position.y,
data: Mutex::new(entities), data: std::sync::Mutex::new(entities),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
}) })
} }
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> { fn internal_to_bytes(&self) -> Bytes {
let mut root = NbtCompound::new(); let mut root = NbtCompound::new();
root.put_int("DataVersion", WORLD_DATA_VERSION); root.put_int("DataVersion", WORLD_DATA_VERSION);
root.put( root.put(
@@ -790,14 +784,14 @@ impl ChunkEntityData {
let entities_tag: Vec<pumpkin_nbt::tag::NbtTag> = self let entities_tag: Vec<pumpkin_nbt::tag::NbtTag> = self
.data .data
.lock() .lock()
.await .unwrap_or_else(std::sync::PoisonError::into_inner)
.iter() .iter()
.map(|c| pumpkin_nbt::tag::NbtTag::Compound(c.clone())) .map(|c| pumpkin_nbt::tag::NbtTag::Compound(c.clone()))
.collect(); .collect();
root.put_list("Entities", entities_tag); root.put_list("Entities", entities_tag);
let nbt = pumpkin_nbt::Nbt::from(root); let nbt = pumpkin_nbt::Nbt::from(root);
Ok(nbt.write()) nbt.write()
} }
} }

View File

@@ -108,7 +108,6 @@ where
let bytes = chunk_data let bytes = chunk_data
.to_bytes() .to_bytes()
.await
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?; .map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
let compressed = compress_to_vec(&bytes[..], CompressionLevel::Fastest); let compressed = compress_to_vec(&bytes[..], CompressionLevel::Fastest);
@@ -165,9 +164,6 @@ mod tests {
use crate::chunk::io::{ChunkSerializer, LoadedData}; use crate::chunk::io::{ChunkSerializer, LoadedData};
use bytes::Bytes; use bytes::Bytes;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::pin::Pin;
use tempfile::TempDir; use tempfile::TempDir;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -185,17 +181,14 @@ mod tests {
} }
impl SingleChunkDataSerializer for MockChunk { impl SingleChunkDataSerializer for MockChunk {
fn to_bytes( fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>
{
let mut root = pumpkin_nbt::compound::NbtCompound::new(); let mut root = pumpkin_nbt::compound::NbtCompound::new();
root.put_int("x", self.x); root.put_int("x", self.x);
root.put_int("z", self.z); root.put_int("z", self.z);
let i8_vec: Vec<i8> = self.data.iter().map(|&b| b as i8).collect(); let i8_vec: Vec<i8> = self.data.iter().map(|&b| b as i8).collect();
root.put("data", pumpkin_nbt::tag::NbtTag::ByteArray(i8_vec.into())); root.put("data", pumpkin_nbt::tag::NbtTag::ByteArray(i8_vec.into()));
let bytes = pumpkin_nbt::Nbt::from(root).write_unnamed(); let bytes = pumpkin_nbt::Nbt::from(root).write_unnamed();
Box::pin(async move { Ok(bytes) }) Ok(bytes)
} }
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError> { fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError> {
let mut cursor = std::io::Cursor::new(bytes); let mut cursor = std::io::Cursor::new(bytes);

View File

@@ -13,10 +13,7 @@ use tokio::{
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
use crate::{ use crate::{
chunk::{ chunk::{ChunkReadingError, ChunkWritingError, io::Dirtiable},
ChunkReadingError, ChunkWritingError,
io::{BoxFuture, Dirtiable},
},
level::LevelFolder, level::LevelFolder,
}; };
@@ -201,230 +198,209 @@ where
{ {
type Data = Arc<S::Data>; type Data = Arc<S::Data>;
fn watch_chunks<'a>( async fn watch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
&'a self, let paths: Vec<_> = chunks
folder: &'a LevelFolder, .iter()
chunks: &'a [Vector2<i32>], .map(|c| P::file_path(folder, &S::get_chunk_key(c)))
) -> BoxFuture<'a, ()> { .collect();
Box::pin(async move {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for path in paths { for path in paths {
*watchers.entry(path).or_insert(0) += 1; *watchers.entry(path).or_insert(0) += 1;
} }
})
} }
fn unwatch_chunks<'a>( async fn unwatch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
&'a self, let paths: Vec<_> = chunks
folder: &'a LevelFolder, .iter()
chunks: &'a [Vector2<i32>], .map(|c| P::file_path(folder, &S::get_chunk_key(c)))
) -> BoxFuture<'a, ()> { .collect();
Box::pin(async move {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
let mut paths_to_evict = Vec::new(); let mut paths_to_evict = Vec::new();
{ {
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for path in paths { for path in paths {
if let std::collections::btree_map::Entry::Occupied(mut e) = if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) {
watchers.entry(path) let count = e.get_mut();
{ *count = count.saturating_sub(1);
let count = e.get_mut(); if *count == 0 {
*count = count.saturating_sub(1); let (path, _) = e.remove_entry();
if *count == 0 { paths_to_evict.push(path);
let (path, _) = e.remove_entry();
paths_to_evict.push(path);
}
} }
} }
} }
}
for path in paths_to_evict { for path in paths_to_evict {
self.maybe_evict(&path).await; self.maybe_evict(&path).await;
} }
})
} }
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { async fn clear_watched_chunks(&self) {
Box::pin(async move { let paths: Vec<PathBuf> = {
let paths: Vec<PathBuf> = { let mut watchers = self.watchers.write().await;
let mut watchers = self.watchers.write().await; let keys: Vec<_> = watchers.keys().cloned().collect();
let keys: Vec<_> = watchers.keys().cloned().collect(); watchers.clear();
watchers.clear(); keys
keys };
}; for path in paths {
for path in paths { self.maybe_evict(&path).await;
self.maybe_evict(&path).await; }
}
})
} }
fn fetch_chunks<'a>( async fn fetch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>], chunk_coords: &'a [Vector2<i32>],
stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>, stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()> { ) {
Box::pin(async move { // Group requested chunk coords by their region file.
// Group requested chunk coords by their region file. let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new(); for at in chunk_coords {
for at in chunk_coords { regions_chunks
regions_chunks .entry(S::get_chunk_key(at))
.entry(S::get_chunk_key(at)) .or_default()
.or_default() .push(*at);
.push(*at); }
let region_tasks = regions_chunks.into_iter().map(|(file_name, chunks)| {
let task_stream = stream.clone();
async move {
let path = P::file_path(folder, &file_name);
let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
return;
}
Err(err) => {
// Best-effort: report the error for the first coord in the batch.
let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await;
return;
}
};
// A bounded channel of 1 keeps backpressure between the
// serializer and the caller without unbounded buffering.
let (send, mut recv) = mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
// Forward received chunks, wrapping them in `Arc`.
// Captured move is intentional — `task_stream` is consumed here.
let forward = async move {
while let Some(data) = recv.recv().await {
let wrapped = data.map_loaded(Arc::new);
if task_stream.send(wrapped).await.is_err() {
// Receiver dropped; abort early to avoid wasted work.
return;
}
}
};
// Hold the read lock only for the duration of `get_chunks`.
let read = async move {
let serializer = chunk_serializer.read().await;
serializer.get_chunks(chunks, send).await;
};
join!(forward, read);
// Evict if not watched and references are dropped
self.maybe_evict(&path).await;
} }
});
let region_tasks = regions_chunks.into_iter().map(|(file_name, chunks)| { join_all(region_tasks).await;
let task_stream = stream.clone();
async move {
let path = P::file_path(folder, &file_name);
let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
return;
}
Err(err) => {
// Best-effort: report the error for the first coord in the batch.
let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await;
return;
}
};
// A bounded channel of 1 keeps backpressure between the
// serializer and the caller without unbounded buffering.
let (send, mut recv) =
mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
// Forward received chunks, wrapping them in `Arc`.
// Captured move is intentional — `task_stream` is consumed here.
let forward = async move {
while let Some(data) = recv.recv().await {
let wrapped = data.map_loaded(Arc::new);
if task_stream.send(wrapped).await.is_err() {
// Receiver dropped; abort early to avoid wasted work.
return;
}
}
};
// Hold the read lock only for the duration of `get_chunks`.
let read = async move {
let serializer = chunk_serializer.read().await;
serializer.get_chunks(chunks, send).await;
};
join!(forward, read);
// Evict if not watched and references are dropped
self.maybe_evict(&path).await;
}
});
join_all(region_tasks).await;
})
} }
fn save_chunks<'a>( async fn save_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks_data: Vec<(Vector2<i32>, Self::Data)>, chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>> { ) -> Result<(), ChunkWritingError> {
Box::pin(async move { // Group chunks by region file.
// Group chunks by region file. let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new(); for (at, chunk) in chunks_data {
for (at, chunk) in chunks_data { regions_chunks
regions_chunks .entry(S::get_chunk_key(&at))
.entry(S::get_chunk_key(&at)) .or_default()
.or_default() .push(chunk);
.push(chunk); }
}
let tasks = regions_chunks let tasks = regions_chunks
.into_iter() .into_iter()
.map(|(file_name, chunk_locks)| async move { .map(|(file_name, chunk_locks)| async move {
let path = P::file_path(folder, &file_name); let path = P::file_path(folder, &file_name);
trace!("Saving chunks into {}", path.display()); trace!("Saving chunks into {}", path.display());
let chunk_serializer = match self.get_serializer(&path).await { let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s, Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => { Err(ChunkReadingError::ChunkNotExist) => {
return Err(ChunkWritingError::IoError(std::io::Error::other( return Err(ChunkWritingError::IoError(std::io::Error::other(
"get_serializer returned ChunkNotExist", "get_serializer returned ChunkNotExist",
))); )));
} }
Err(ChunkReadingError::IoError(err)) => { Err(ChunkReadingError::IoError(err)) => {
error!("I/O error reading region before write: {err}"); error!("I/O error reading region before write: {err}");
return Err(ChunkWritingError::IoError(err)); return Err(ChunkWritingError::IoError(err));
} }
Err(err) => { Err(err) => {
return Err(ChunkWritingError::IoError(std::io::Error::other( return Err(ChunkWritingError::IoError(std::io::Error::other(
err.to_string(), err.to_string(),
))); )));
} }
}; };
{
let mut writer = chunk_serializer.write().await;
for chunk in &chunk_locks {
// Atomically snapshot and clear the dirty flag before we
// write so that any mutation that races in *during* this
// serialisation round will mark dirty again correctly.
let was_dirty = chunk.is_dirty();
chunk.mark_dirty(false);
if was_dirty {
writer.update_chunk(&**chunk, &self.chunk_config).await?;
}
}
// Write-lock released here — flush can proceed under a read-lock.
}
trace!("Chunk data updated for {}", path.display());
// We check watchers *after* releasing the write-lock to honour
// lock ordering (serializer lock → watchers, never the reverse).
let is_watched = {
let watchers = self.watchers.read().await;
watchers.get(&path).is_some_and(|&c| c > 0)
};
if !is_watched {
// A read-lock suffices for `write()` since we have already
// applied all mutations above.
{ {
let mut writer = chunk_serializer.write().await; let serializer = chunk_serializer.read().await;
for chunk in &chunk_locks { debug!("Flushing {} to disk", path.display());
// Atomically snapshot and clear the dirty flag before we serializer
// write so that any mutation that races in *during* this .write(&path)
// serialisation round will mark dirty again correctly. .await
let was_dirty = chunk.is_dirty(); .map_err(ChunkWritingError::IoError)?;
chunk.mark_dirty(false); // Read-lock released here.
if was_dirty {
writer.update_chunk(&**chunk, &self.chunk_config).await?;
}
}
// Write-lock released here — flush can proceed under a read-lock.
}
trace!("Chunk data updated for {}", path.display());
// We check watchers *after* releasing the write-lock to honour
// lock ordering (serializer lock → watchers, never the reverse).
let is_watched = {
let watchers = self.watchers.read().await;
watchers.get(&path).is_some_and(|&c| c > 0)
}; };
if !is_watched { // Drop our handle so `can_remove` may succeed.
// A read-lock suffices for `write()` since we have already drop(chunk_serializer);
// applied all mutations above.
{
let serializer = chunk_serializer.read().await;
debug!("Flushing {} to disk", path.display());
serializer
.write(&path)
.await
.map_err(ChunkWritingError::IoError)?;
// Read-lock released here.
};
// Drop our handle so `can_remove` may succeed. // Evict the cache entry when no longer needed.
drop(chunk_serializer); self.maybe_evict(&path).await;
}
// Evict the cache entry when no longer needed. Ok(())
self.maybe_evict(&path).await; });
}
Ok(()) // Collect all region results; surface the first error encountered.
}); let results: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
results.into_iter().find(Result::is_err).unwrap_or(Ok(()))
// Collect all region results; surface the first error encountered.
let results: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
results.into_iter().find(Result::is_err).unwrap_or(Ok(()))
})
} }
/// Blocks until all in-flight serialiser operations have completed by /// Blocks until all in-flight serialiser operations have completed by
@@ -433,27 +409,25 @@ where
/// ///
/// This is a linearisation point: after this future resolves no mutation /// This is a linearisation point: after this future resolves no mutation
/// started before the call is still running. /// started before the call is still running.
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> { async fn block_and_await_ongoing_tasks(&self) {
Box::pin(async move { // Snapshot the current set of loaders under a read-lock so we do
// Snapshot the current set of loaders under a read-lock so we do // not block new insertions longer than necessary.
// not block new insertions longer than necessary. let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> =
let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> = { self.file_locks.read().await.values().cloned().collect() };
{ self.file_locks.read().await.values().cloned().collect() };
// For each loader that has been initialised, acquire a write-lock // For each loader that has been initialised, acquire a write-lock
// and release it immediately. This guarantees that any concurrent // and release it immediately. This guarantees that any concurrent
// read or write operation that was in progress has finished. // read or write operation that was in progress has finished.
let drain_tasks = loaders.into_iter().map(|loader| async move { let drain_tasks = loaders.into_iter().map(|loader| async move {
if let Some(serializer_arc) = loader.internal.get() { if let Some(serializer_arc) = loader.internal.get() {
// Acquiring + immediately dropping the write-lock acts as a // Acquiring + immediately dropping the write-lock acts as a
// barrier: it can only succeed once all current lock holders // barrier: it can only succeed once all current lock holders
// have released their guards. // have released their guards.
let _guard = serializer_arc.write().await; let _guard = serializer_arc.write().await;
} }
}); });
join_all(drain_tasks).await; join_all(drain_tasks).await;
})
} }
} }
@@ -480,68 +454,60 @@ where
{ {
type Data = Arc<P>; type Data = Arc<P>;
fn fetch_chunks<'a>( async fn fetch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>], chunk_coords: &'a [Vector2<i32>],
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>, stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()> { ) {
match self { match self {
Self::Linear(io) => io.fetch_chunks(folder, chunk_coords, stream), Self::Linear(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
Self::Anvil(io) => io.fetch_chunks(folder, chunk_coords, stream), Self::Anvil(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream), Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
} }
} }
fn save_chunks<'a>( async fn save_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks_data: Vec<(Vector2<i32>, Self::Data)>, chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>> { ) -> Result<(), ChunkWritingError> {
match self { match self {
Self::Linear(io) => io.save_chunks(folder, chunks_data), Self::Linear(io) => io.save_chunks(folder, chunks_data).await,
Self::Anvil(io) => io.save_chunks(folder, chunks_data), Self::Anvil(io) => io.save_chunks(folder, chunks_data).await,
Self::Pump(io) => io.save_chunks(folder, chunks_data), Self::Pump(io) => io.save_chunks(folder, chunks_data).await,
} }
} }
fn watch_chunks<'a>( async fn watch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
match self { match self {
Self::Linear(io) => io.watch_chunks(folder, chunks), Self::Linear(io) => io.watch_chunks(folder, chunks).await,
Self::Anvil(io) => io.watch_chunks(folder, chunks), Self::Anvil(io) => io.watch_chunks(folder, chunks).await,
Self::Pump(io) => io.watch_chunks(folder, chunks), Self::Pump(io) => io.watch_chunks(folder, chunks).await,
} }
} }
fn unwatch_chunks<'a>( async fn unwatch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
match self { match self {
Self::Linear(io) => io.unwatch_chunks(folder, chunks), Self::Linear(io) => io.unwatch_chunks(folder, chunks).await,
Self::Anvil(io) => io.unwatch_chunks(folder, chunks), Self::Anvil(io) => io.unwatch_chunks(folder, chunks).await,
Self::Pump(io) => io.unwatch_chunks(folder, chunks), Self::Pump(io) => io.unwatch_chunks(folder, chunks).await,
} }
} }
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { async fn clear_watched_chunks(&self) {
match self { match self {
Self::Linear(io) => io.clear_watched_chunks(), Self::Linear(io) => io.clear_watched_chunks().await,
Self::Anvil(io) => io.clear_watched_chunks(), Self::Anvil(io) => io.clear_watched_chunks().await,
Self::Pump(io) => io.clear_watched_chunks(), Self::Pump(io) => io.clear_watched_chunks().await,
} }
} }
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> { async fn block_and_await_ongoing_tasks(&self) {
match self { match self {
Self::Linear(io) => io.block_and_await_ongoing_tasks(), Self::Linear(io) => io.block_and_await_ongoing_tasks().await,
Self::Anvil(io) => io.block_and_await_ongoing_tasks(), Self::Anvil(io) => io.block_and_await_ongoing_tasks().await,
Self::Pump(io) => io.block_and_await_ongoing_tasks(), Self::Pump(io) => io.block_and_await_ongoing_tasks().await,
} }
} }
} }

View File

@@ -1,4 +1,4 @@
use std::{error, pin::Pin}; use std::error;
use bytes::Bytes; use bytes::Bytes;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
@@ -38,8 +38,6 @@ pub trait Dirtiable {
fn mark_dirty(&self, flag: bool); fn mark_dirty(&self, flag: bool);
} }
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
/// Trait to handle the IO of chunks /// Trait to handle the IO of chunks
/// for loading and saving chunks data /// for loading and saving chunks data
/// can be implemented for different types of IO /// can be implemented for different types of IO
@@ -59,34 +57,34 @@ where
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>], chunk_coords: &'a [Vector2<i32>],
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>, stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()>; // Returns BoxFuture<()> ) -> impl Future<Output = ()> + Send + 'a;
/// Persist the chunks data /// Persist the chunks data
fn save_chunks<'a>( fn save_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks_data: Vec<(Vector2<i32>, Self::Data)>, chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>>; // Returns BoxFuture<Result> ) -> impl Future<Output = Result<(), ChunkWritingError>> + Send + 'a;
/// Tells the `ChunkIO` that these chunks are currently loaded in memory /// Tells the `ChunkIO` that these chunks are currently loaded in memory
fn watch_chunks<'a>( fn watch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>], chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()>; ) -> impl Future<Output = ()> + Send + 'a;
/// Tells the `ChunkIO` that these chunks are no longer loaded in memory /// Tells the `ChunkIO` that these chunks are no longer loaded in memory
fn unwatch_chunks<'a>( fn unwatch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>], chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()>; ) -> impl Future<Output = ()> + Send + 'a;
/// Tells the `ChunkIO` that no more chunks are loaded in memory /// Tells the `ChunkIO` that no more chunks are loaded in memory
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()>; fn clear_watched_chunks(&self) -> impl Future<Output = ()> + Send + '_;
/// Ensure that all ongoing operations are finished /// Ensure that all ongoing operations are finished
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()>; fn block_and_await_ongoing_tasks(&self) -> impl Future<Output = ()> + Send + '_;
} }
/// Trait to serialize and deserialize the chunk data to and from bytes. /// Trait to serialize and deserialize the chunk data to and from bytes.

View File

@@ -14,7 +14,6 @@ use std::sync::RwLock;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex;
pub mod format; pub mod format;
pub mod io; pub mod io;
@@ -90,7 +89,7 @@ pub struct ChunkEntityData {
pub x: i32, pub x: i32,
/// Chunk Z /// Chunk Z
pub z: i32, pub z: i32,
pub data: Mutex<Vec<NbtCompound>>, pub data: std::sync::Mutex<Vec<NbtCompound>>,
pub dirty: AtomicBool, pub dirty: AtomicBool,
} }

View File

@@ -3,7 +3,7 @@ use super::chunk_holder::ChunkHolder;
use super::chunk_state::{Chunk, StagedChunkEnum}; use super::chunk_state::{Chunk, StagedChunkEnum};
use super::dag::{DAG, EdgeKey, Node, NodeKey}; use super::dag::{DAG, EdgeKey, Node, NodeKey};
use super::generation_cache::Cache; use super::generation_cache::Cache;
use super::worker_logic::{RecvChunk, generation_work, io_read_work, io_write_work}; use super::worker_logic::{RecvChunk, io_read_work, io_write_work};
use super::{ use super::{
ChunkLevel, ChunkListener, ChunkLoading, ChunkPos, HashMapType, HashSetType, IOLock, ChunkLevel, ChunkListener, ChunkLoading, ChunkPos, HashMapType, HashSetType, IOLock,
LevelChannel, LevelChannel,
@@ -69,12 +69,10 @@ pub struct GenerationSchedule {
running_task_count: u16, running_task_count: u16,
max_in_flight: u16, max_in_flight: u16,
queue_dirty: bool, queue_dirty: bool,
recv_chunk: crossfire::compat::MRx<(ChunkPos, RecvChunk)>, recv_chunk: crossbeam::channel::Receiver<(ChunkPos, RecvChunk)>,
io_read: crossfire::compat::MTx<Vec<ChunkPos>>, io_read: tokio::sync::mpsc::Sender<Vec<ChunkPos>>,
io_write: crossfire::compat::Tx<Vec<(ChunkPos, Chunk)>>, io_write: tokio::sync::mpsc::Sender<Vec<(ChunkPos, Chunk)>>,
generate: crossfire::compat::MTx<(ChunkPos, Cache, StagedChunkEnum)>, send_chunk: crossbeam::channel::Sender<(ChunkPos, RecvChunk)>,
send_chunk: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
gen_pool: Option<Arc<rayon::ThreadPool>>,
listener: Arc<ChunkListener>, listener: Arc<ChunkListener>,
lighting_config: LightingEngineConfig, lighting_config: LightingEngineConfig,
last_unload: std::time::Instant, last_unload: std::time::Instant,
@@ -83,22 +81,17 @@ pub struct GenerationSchedule {
impl GenerationSchedule { impl GenerationSchedule {
pub fn create( pub fn create(
io_read_thread_count: usize, io_read_thread_count: usize,
gen_thread_count: usize,
level: Arc<Level>, level: Arc<Level>,
level_channel: Arc<LevelChannel>, level_channel: Arc<LevelChannel>,
listener: Arc<ChunkListener>, listener: Arc<ChunkListener>,
thread_tracker: &mut Vec<thread::JoinHandle<()>>, thread_tracker: &mut Vec<thread::JoinHandle<()>>,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) { ) {
let (send_chunk, recv_chunk) = crossfire::compat::mpmc::unbounded_blocking(); let (send_chunk, recv_chunk) = crossbeam::channel::unbounded();
let (send_read_io, recv_read_io) = let (send_read_io, recv_read_io) = tokio::sync::mpsc::channel(io_read_thread_count + 5);
crossfire::compat::mpmc::bounded_tx_blocking_rx_async(io_read_thread_count + 5); let recv_read_io = Arc::new(tokio::sync::Mutex::new(recv_read_io));
let (send_write_io, recv_write_io) = let (send_write_io, recv_write_io) = tokio::sync::mpsc::channel(500);
crossfire::compat::spsc::bounded_tx_blocking_rx_async(500);
let (send_gen, recv_gen) = crossfire::compat::mpmc::bounded_blocking(gen_thread_count + 5);
let io_lock = Arc::new(( let io_lock = Arc::new((
Mutex::new(HashMapType::default()), Mutex::new(HashMapType::default()),
@@ -120,28 +113,8 @@ impl GenerationSchedule {
io_lock.clone(), io_lock.clone(),
)); ));
if gen_pool.is_none() { let max_in_flight =
for i in 0..gen_thread_count { (thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16;
let recv_gen = recv_gen.clone();
let send_chunk = send_chunk.clone();
let level_clone = level.clone();
let handle = thread::Builder::new()
.name(format!("Gen-{i}"))
.spawn(move || {
generation_work(&recv_gen, &send_chunk, &level_clone);
})
.expect("Failed to spawn Generation Thread");
thread_tracker.push(handle);
}
}
let max_in_flight = if gen_pool.is_some() {
(thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16
} else {
gen_thread_count as u16
};
let level_sched = level; let level_sched = level;
let lighting_config = level_sched.lighting_config; let lighting_config = level_sched.lighting_config;
@@ -164,9 +137,7 @@ impl GenerationSchedule {
recv_chunk, recv_chunk,
io_read: send_read_io, io_read: send_read_io,
io_write: send_write_io, io_write: send_write_io,
generate: send_gen,
send_chunk, send_chunk,
gen_pool,
listener, listener,
chunk_map: HashMap::default(), chunk_map: HashMap::default(),
lighting_config, lighting_config,
@@ -446,8 +417,12 @@ impl GenerationSchedule {
}) })
}); });
if all_ready { if all_ready {
now_ready.push(node_key); if node.in_degree == 0 {
false now_ready.push(node_key);
false
} else {
true
}
} else { } else {
true true
} }
@@ -463,7 +438,6 @@ impl GenerationSchedule {
Self::calc_priority(&self.last_level, &self.last_high_priority, n.pos, n.stage); Self::calc_priority(&self.last_level, &self.last_high_priority, n.pos, n.stage);
self.queue.push(TaskHeapNode(priority, node_key)); self.queue.push(TaskHeapNode(priority, node_key));
} }
// If in_degree > 0, drop_node will re-queue when unblocked
} }
} }
@@ -796,7 +770,7 @@ impl GenerationSchedule {
*data.entry(*pos).or_insert(0) += 1; *data.entry(*pos).or_insert(0) += 1;
} }
drop(data); drop(data);
if let Err(e) = self.io_write.send(chunks) { if let Err(e) = self.io_write.blocking_send(chunks) {
error!( error!(
"Failed to send chunks to io write thread during save (may have shut down): {:?}", "Failed to send chunks to io write thread during save (may have shut down): {:?}",
e e
@@ -851,7 +825,7 @@ impl GenerationSchedule {
} }
drop(data); drop(data);
if let Err(e) = self.io_write.send(chunks) { if let Err(e) = self.io_write.blocking_send(chunks) {
error!("Failed to send chunks to io write thread: {:?}", e); error!("Failed to send chunks to io write thread: {:?}", e);
} }
} }
@@ -1311,7 +1285,10 @@ impl GenerationSchedule {
io_batch.push(node.pos); io_batch.push(node.pos);
if io_batch.len() >= 16 if io_batch.len() >= 16
&& self.io_read.send(std::mem::take(&mut io_batch)).is_err() && self
.io_read
.blocking_send(std::mem::take(&mut io_batch))
.is_err()
{ {
info!("IO read thread closed, saving remaining chunks..."); info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true); self.save_all_chunk(true);
@@ -1320,7 +1297,10 @@ impl GenerationSchedule {
} else { } else {
// Send any pending IO batch before starting generation // Send any pending IO batch before starting generation
if !io_batch.is_empty() if !io_batch.is_empty()
&& self.io_read.send(std::mem::take(&mut io_batch)).is_err() && self
.io_read
.blocking_send(std::mem::take(&mut io_batch))
.is_err()
{ {
info!("IO read thread closed, saving remaining chunks..."); info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true); self.save_all_chunk(true);
@@ -1432,41 +1412,37 @@ impl GenerationSchedule {
} }
self.running_task_count += 1; self.running_task_count += 1;
if let Some(pool) = &self.gen_pool { let pos = node.pos;
let pos = node.pos; let stage = node.stage;
let stage = node.stage; let send_chunk = self.send_chunk.clone();
let send_chunk = self.send_chunk.clone(); let level = level.clone();
let level = level.clone(); let settings =
let settings = GenerationSettings::from_dimension( GenerationSettings::from_dimension(level.world_gen.load().dimension());
level.world_gen.load().dimension(),
);
pool.spawn(move || { rayon::spawn(move || {
let result = crate::chunk_system::worker_logic::run_generation( let result = crate::chunk_system::worker_logic::run_generation(
pos, cache, stage, &level, settings, pos, cache, stage, &level, settings,
); );
let _ = send_chunk.send((pos, result)); let _ = send_chunk.send((pos, result));
}); });
} else if self.generate.send((node.pos, cache, node.stage)).is_err() {
self.running_task_count = self.running_task_count.saturating_sub(1);
info!("Generation thread closed, saving remaining chunks...");
self.save_all_chunk(true);
break 'out2;
}
} }
} }
} }
// Flush any remaining IO batch // Flush any remaining IO batch
if !io_batch.is_empty() && self.io_read.send(std::mem::take(&mut io_batch)).is_err() { if !io_batch.is_empty()
&& self
.io_read
.blocking_send(std::mem::take(&mut io_batch))
.is_err()
{
info!("IO read thread closed, saving remaining chunks..."); info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true); self.save_all_chunk(true);
} }
// 3. If queue is empty, wait for work or results // 5. Wait for work or results
if self.queue.is_empty() { if self.queue.is_empty() {
// If we have tasks in flight, wait for them with timeout if self.running_task_count > 0 {
if self.running_task_count > 0 || !self.waiting_for_chunks.is_empty() {
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) { match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
Ok((pos, data)) => { Ok((pos, data)) => {
self.receive_chunk(pos, data); self.receive_chunk(pos, data);
@@ -1474,16 +1450,17 @@ impl GenerationSchedule {
self.garbage_collect_dependencies(); self.garbage_collect_dependencies();
} }
} }
Err(crossfire::compat::RecvTimeoutError::Timeout) => { Err(crossbeam::channel::RecvTimeoutError::Timeout) => {
// Periodically check LevelChannel for new requests // Periodically check LevelChannel for new requests
if self.resort_work(self.send_level.get()) { if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies(); self.garbage_collect_dependencies();
} }
} }
Err(crossfire::compat::RecvTimeoutError::Disconnected) => break, Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
} }
} else { } else {
// No tasks in flight, wait indefinitely for LevelChannel changes // No tasks in flight, check for any unblocked waiting tasks or stranded ready tasks
self.check_waiting_tasks();
let restored = Self::restore_ready_tasks( let restored = Self::restore_ready_tasks(
&mut self.graph, &mut self.graph,
&mut self.queue, &mut self.queue,
@@ -1492,10 +1469,14 @@ impl GenerationSchedule {
&self.last_high_priority, &self.last_high_priority,
&self.waiting_for_chunks, &self.waiting_for_chunks,
); );
if restored > 0 { if restored > 0 || !self.queue.is_empty() {
debug!( debug!(
"Restored {restored} stranded ready chunk tasks to generation queue" "Restored {restored} stranded ready chunk tasks to generation queue"
); );
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
continue; continue;
} }
debug_assert!(self.debug_check()); debug_assert!(self.debug_check());
@@ -1508,6 +1489,27 @@ impl GenerationSchedule {
self.sort_queue(); self.sort_queue();
self.queue_dirty = false; self.queue_dirty = false;
} }
} else if self.running_task_count >= self.max_in_flight {
// Queue has tasks, but we are at maximum in-flight capacity.
// Wait for an in-flight worker to finish instead of busy-spinning.
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
Ok((pos, data)) => {
self.receive_chunk(pos, data);
if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
}
Err(crossbeam::channel::RecvTimeoutError::Timeout) => {
if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
}
Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
}
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
} }
} }
info!( info!(

View File

@@ -6,7 +6,6 @@ use crate::chunk::format::LightContainer;
use crate::chunk::io::LoadedData::Loaded; use crate::chunk::io::LoadedData::Loaded;
use crate::chunk::io::{FileIO, LoadedData}; use crate::chunk::io::{FileIO, LoadedData};
use crate::level::Level; use crate::level::Level;
use crossfire::compat::AsyncRx;
use pumpkin_config::lighting::LightingEngineConfig; use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::chunk::ChunkStatus; use pumpkin_data::chunk::ChunkStatus;
use pumpkin_data::chunk_gen_settings::GenerationSettings; use pumpkin_data::chunk_gen_settings::GenerationSettings;
@@ -57,19 +56,15 @@ fn needs_relighting(chunk: &crate::chunk::ChunkData, config: LightingEngineConfi
} }
async fn load_proto_chunk(chunk: &Arc<crate::chunk::ChunkData>, level: &Level) -> ProtoChunk { async fn load_proto_chunk(chunk: &Arc<crate::chunk::ChunkData>, level: &Level) -> ProtoChunk {
if let Some(pool) = &level.gen_pool { let (tx, rx) = tokio::sync::oneshot::channel();
let (tx, rx) = tokio::sync::oneshot::channel(); let world_gen = level.world_gen.load();
let world_gen = level.world_gen.load(); let chunk_clone = chunk.clone();
let chunk_clone = chunk.clone(); rayon::spawn(move || {
pool.spawn(move || { let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen);
let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen); let _ = tx.send(p);
let _ = tx.send(p); });
}); rx.await
rx.await .unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
.unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
} else {
ProtoChunk::from_chunk_data(chunk, &level.world_gen.load())
}
} }
async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level) -> Chunk { async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level) -> Chunk {
@@ -91,10 +86,7 @@ async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level
proto.light.block_light = (0..section_count) proto.light.block_light = (0..section_count)
.map(|_| LightContainer::new_empty(0)) .map(|_| LightContainer::new_empty(0))
.collect(); .collect();
// Set stage to Features
proto.stage = StagedChunkEnum::Features; proto.stage = StagedChunkEnum::Features;
Chunk::Proto(Box::new(proto)) Chunk::Proto(Box::new(proto))
} else { } else {
Chunk::Level(chunk) Chunk::Level(chunk)
@@ -106,15 +98,22 @@ async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level
} }
pub async fn io_read_work( pub async fn io_read_work(
recv: crossfire::compat::MAsyncRx<Vec<ChunkPos>>, recv: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Vec<ChunkPos>>>>,
send: crossfire::compat::MTx<(ChunkPos, RecvChunk)>, send: crossbeam::channel::Sender<(ChunkPos, RecvChunk)>,
level: Arc<Level>, level: Arc<Level>,
lock: IOLock, lock: IOLock,
) { ) {
debug!("io read thread start"); debug!("io read thread start");
// Cleaner loop and async recv // Cleaner loop and async recv
while let Ok(batch) = recv.recv().await { loop {
let batch = {
let mut lock_rx = recv.lock().await;
lock_rx.recv().await
};
let Some(batch) = batch else {
break;
};
for pos in &batch { for pos in &batch {
// Lock handling // Lock handling
loop { loop {
@@ -178,10 +177,14 @@ pub async fn io_read_work(
debug!("io read thread stop"); debug!("io read thread stop");
} }
pub async fn io_write_work(recv: AsyncRx<Vec<(ChunkPos, Chunk)>>, level: Arc<Level>, lock: IOLock) { pub async fn io_write_work(
mut recv: tokio::sync::mpsc::Receiver<Vec<(ChunkPos, Chunk)>>,
level: Arc<Level>,
lock: IOLock,
) {
loop { loop {
// Don't check cancel_token here (keep saving chunks) // Don't check cancel_token here (keep saving chunks)
let Ok(data) = recv.recv().await else { break }; let Some(data) = recv.recv().await else { break };
// debug!("io write thread receive chunks size {}", data.len()); // debug!("io write thread receive chunks size {}", data.len());
let mut vec = Vec::with_capacity(data.len()); let mut vec = Vec::with_capacity(data.len());
let mut positions = Vec::with_capacity(data.len()); let mut positions = Vec::with_capacity(data.len());
@@ -287,23 +290,3 @@ pub fn run_generation(
} }
} }
} }
pub fn generation_work(
recv: &crossfire::compat::MRx<(ChunkPos, Cache, StagedChunkEnum)>,
send: &crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
level: &Arc<Level>,
) {
let settings = GenerationSettings::from_dimension(level.world_gen.load().dimension());
loop {
let Ok((pos, cache, stage)) = recv.recv() else {
debug!("generation channel closed, exiting");
break;
};
let result = run_generation(pos, cache, stage, level, settings);
if send.send((pos, result)).is_err() {
break;
}
}
}

View File

@@ -11,7 +11,6 @@ pub fn into_level(
level_config: &LevelConfig, level_config: &LevelConfig,
base_directory: PathBuf, base_directory: PathBuf,
seed: i64, seed: i64,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Level> { ) -> Arc<Level> {
Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool) Level::from_root_folder(level_config, base_directory, seed, dimension)
} }

View File

@@ -114,7 +114,6 @@ pub struct Level {
pub level_channel: Arc<LevelChannel>, pub level_channel: Arc<LevelChannel>,
pub thread_tracker: Mutex<Vec<thread::JoinHandle<()>>>, pub thread_tracker: Mutex<Vec<thread::JoinHandle<()>>>,
pub chunk_listener: Arc<ChunkListener>, pub chunk_listener: Arc<ChunkListener>,
pub gen_pool: Option<Arc<rayon::ThreadPool>>,
} }
pub struct TickData { pub struct TickData {
@@ -146,7 +145,6 @@ impl Level {
root_folder: PathBuf, root_folder: PathBuf,
seed: i64, seed: i64,
dimension: Dimension, dimension: Dimension,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Self> { ) -> Arc<Self> {
let (namespace, name) = match dimension.minecraft_name.split_once(':') { let (namespace, name) = match dimension.minecraft_name.split_once(':') {
Some((ns, n)) => (ns, n), Some((ns, n)) => (ns, n),
@@ -281,19 +279,10 @@ impl Level {
level_channel: level_channel.clone(), level_channel: level_channel.clone(),
thread_tracker, thread_tracker,
chunk_listener: listener.clone(), chunk_listener: listener.clone(),
gen_pool: gen_pool.clone(),
}); });
// TODO
let total_cores = thread::available_parallelism()
.map_or(1, std::num::NonZero::get)
.saturating_sub(2)
.max(1);
let threads_per_dimension = (total_cores / 2).max(1);
GenerationSchedule::create( GenerationSchedule::create(
4, 4,
threads_per_dimension,
level_ref.clone(), level_ref.clone(),
level_channel, level_channel,
listener, listener,
@@ -302,7 +291,6 @@ impl Level {
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) .unwrap_or_else(std::sync::PoisonError::into_inner)
.as_mut(), .as_mut(),
gen_pool,
); );
level_ref level_ref
@@ -319,48 +307,22 @@ impl Level {
pub fn spawn_entity_generation(self: &Arc<Self>, pos: Vector2<i32>) { pub fn spawn_entity_generation(self: &Arc<Self>, pos: Vector2<i32>) {
let level = self.clone(); let level = self.clone();
if let Some(pool) = &self.gen_pool { rayon::spawn(move || {
pool.spawn(move || { let arc_chunk = Arc::new(ChunkEntityData {
let arc_chunk = Arc::new(ChunkEntityData { x: pos.x,
x: pos.x, z: pos.y,
z: pos.y, data: std::sync::Mutex::new(Vec::new()),
data: tokio::sync::Mutex::new(Vec::new()), dirty: AtomicBool::new(false),
dirty: AtomicBool::new(false),
});
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
if let Some((_, waiters)) = level.pending_entity_generations.remove(&pos) {
for tx in waiters {
let _ = tx.send(arc_chunk.clone());
}
}
}); });
} else {
// Fallback to spawning a new thread if no pool is available (should not happen in production)
let level_clone = level;
let _ = thread::Builder::new()
.name(format!("Entity Gen {pos:?}"))
.spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
data: tokio::sync::Mutex::new(Vec::new()),
dirty: AtomicBool::new(false),
});
level_clone level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
.loaded_entity_chunks
.insert(pos, arc_chunk.clone());
if let Some((_, waiters)) = level_clone.pending_entity_generations.remove(&pos) if let Some((_, waiters)) = level.pending_entity_generations.remove(&pos) {
{ for tx in waiters {
for tx in waiters { let _ = tx.send(arc_chunk.clone());
let _ = tx.send(arc_chunk.clone()); }
} }
} });
});
}
} }
/// Spawns a task associated with this world. All tasks spawned with this method are awaited /// Spawns a task associated with this world. All tasks spawned with this method are awaited
@@ -750,20 +712,18 @@ impl Level {
let _ = sender.send((Arc::downgrade(&chunk), true)).await; let _ = sender.send((Arc::downgrade(&chunk), true)).await;
} }
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => { LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
let sender_clone = sender.clone(); let (tx, rx) = oneshot::channel();
let level_clone = level.clone(); match level.pending_entity_generations.entry(pos) {
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
tokio::spawn(async move { entry.get_mut().push(tx);
let (tx, rx) = oneshot::channel();
match level_clone.pending_entity_generations.entry(pos) {
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
entry.get_mut().push(tx);
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(vec![tx]);
level_clone.spawn_entity_generation(pos);
}
} }
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(vec![tx]);
level.spawn_entity_generation(pos);
}
}
let sender_clone = sender.clone();
tokio::spawn(async move {
if let Ok(chunk) = rx.await { if let Ok(chunk) = rx.await {
let _ = let _ =
sender_clone.send((Arc::downgrade(&chunk), true)).await; sender_clone.send((Arc::downgrade(&chunk), true)).await;
@@ -807,7 +767,7 @@ impl Level {
Arc::new(ChunkEntityData { Arc::new(ChunkEntityData {
x: pos.x, x: pos.x,
z: pos.y, z: pos.y,
data: tokio::sync::Mutex::new(Vec::new()), data: std::sync::Mutex::new(Vec::new()),
dirty: AtomicBool::new(false), dirty: AtomicBool::new(false),
}) })
}) })
@@ -1025,7 +985,7 @@ mod tests {
let config = LevelConfig::default(); let config = LevelConfig::default();
let overworld_level = let overworld_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD, None); Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD);
assert_eq!( assert_eq!(
overworld_level.level_folder.dim_folder, overworld_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("overworld") root.join("dimensions").join("minecraft").join("overworld")
@@ -1038,14 +998,13 @@ mod tests {
.join("region") .join("region")
); );
let nether_level = let nether_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER);
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
assert_eq!( assert_eq!(
nether_level.level_folder.dim_folder, nether_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("the_nether") root.join("dimensions").join("minecraft").join("the_nether")
); );
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END, None); let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END);
assert_eq!( assert_eq!(
end_level.level_folder.dim_folder, end_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("the_end") root.join("dimensions").join("minecraft").join("the_end")
@@ -1064,14 +1023,13 @@ mod tests {
std::fs::create_dir_all(root.join("DIM1").join("region")).unwrap(); std::fs::create_dir_all(root.join("DIM1").join("region")).unwrap();
let overworld_level = let overworld_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD, None); Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD);
assert_eq!(overworld_level.level_folder.dim_folder, root); assert_eq!(overworld_level.level_folder.dim_folder, root);
let nether_level = let nether_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER);
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
assert_eq!(nether_level.level_folder.dim_folder, root.join("DIM-1")); assert_eq!(nether_level.level_folder.dim_folder, root.join("DIM-1"));
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END, None); let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END);
assert_eq!(end_level.level_folder.dim_folder, root.join("DIM1")); assert_eq!(end_level.level_folder.dim_folder, root.join("DIM1"));
} }
} }

View File

@@ -1,4 +1,3 @@
use std::pin::Pin;
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES}; use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
@@ -126,19 +125,10 @@ pub trait BlockEntity: Any + Send + Sync {
None None
} }
fn set_block_state(&mut self, _block_state: BlockStateId) {} fn set_block_state(&mut self, _block_state: BlockStateId) {}
fn on_block_replaced<'a>( fn on_block_replaced(self: Arc<Self>, world: &Arc<World>, position: &BlockPos) {
self: Arc<Self>, if let Some(inventory) = self.get_inventory() {
world: Arc<World>, world.scatter_inventory(position, &inventory);
position: BlockPos, }
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
Self: 'a,
{
Box::pin(async move {
if let Some(inventory) = self.get_inventory() {
world.scatter_inventory(&position, &inventory);
}
})
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
false false

View File

@@ -3,8 +3,6 @@ use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use std::any::Any; use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::RwLock; use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::{array::from_fn, sync::Arc}; use std::{array::from_fn, sync::Arc};
@@ -63,17 +61,8 @@ impl BlockEntity for ShulkerBoxBlockEntity {
.update_viewer_count::<Self>(self, world, &self.position); .update_viewer_count::<Self>(self, world, &self.position);
} }
fn on_block_replaced<'a>( fn on_block_replaced(self: Arc<Self>, _world: &Arc<World>, _position: &BlockPos) {
self: Arc<Self>, // Shulker boxes retain items when broken
_world: Arc<World>,
_position: BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
Self: 'a,
{
Box::pin(async move {
// Do nothing
})
} }
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> { fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {

View File

@@ -50,84 +50,16 @@ static ERROR_CREATE_IO_FAILURE: CommandErrorType<1> = CommandErrorType::new(
translation::java::COMMANDS_DATAPACK_CREATE_IO_FAILURE, translation::java::COMMANDS_DATAPACK_CREATE_IO_FAILURE,
); );
fn get_all_known_packs(server: &Server) -> Vec<String> {
let mut packs = Vec::new();
packs.push("vanilla".to_string());
// Bundled feature packs
for bundled in [
"trade_rebalance",
"minecart_improvements",
"redstone_experiments",
] {
if !packs.iter().any(|p| p == bundled) {
packs.push(bundled.to_string());
}
}
// World datapacks directory
let datapacks_dir = server.basic_config.get_world_path().join("datapacks");
if let Ok(entries) = fs::read_dir(datapacks_dir) {
for entry in entries.flatten() {
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.starts_with('.') {
continue;
}
if path.is_dir()
|| path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
let pack_name = format!("file/{file_name}");
if !packs.iter().any(|p| p == &pack_name) {
packs.push(pack_name);
}
}
}
}
let level_info = server.level_info.load();
for pack in &level_info.data_packs.enabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
for pack in &level_info.data_packs.disabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
packs
}
fn get_enabled_packs(server: &Server) -> Vec<String> { fn get_enabled_packs(server: &Server) -> Vec<String> {
server.level_info.load().data_packs.enabled.clone() crate::data::datapack::DatapackManager::get_enabled_packs(server)
} }
fn get_available_packs(server: &Server) -> Vec<String> { fn get_available_packs(server: &Server) -> Vec<String> {
let enabled = get_enabled_packs(server); crate::data::datapack::DatapackManager::get_available_packs(server)
let all = get_all_known_packs(server);
all.into_iter().filter(|p| !enabled.contains(p)).collect()
} }
fn find_pack_name(server: &Server, input: &str) -> Option<String> { fn find_pack_name(server: &Server, input: &str) -> Option<String> {
let known = get_all_known_packs(server); crate::data::datapack::DatapackManager::find_pack_name(server, input)
if let Some(p) = known.iter().find(|p| *p == input) {
return Some(p.clone());
}
let file_input = format!("file/{input}");
if let Some(p) = known.iter().find(|p| **p == file_input) {
return Some(p.clone());
}
if let Some(p) = known
.iter()
.find(|p| p.strip_prefix("file/") == Some(input))
{
return Some(p.clone());
}
None
} }
fn format_pack(name: &str) -> TextComponent { fn format_pack(name: &str) -> TextComponent {

View File

@@ -267,28 +267,32 @@ struct ResolveNameExecutor;
impl CommandExecutor for ResolveNameExecutor { impl CommandExecutor for ResolveNameExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let name = StringArgumentType::get(context, ARG_NAME)?; let name = StringArgumentType::get(context, ARG_NAME)?;
let server = context.server(); let server = context.server().clone();
let source = context.source.clone();
let name_owned = name.to_string(); let name_owned = name.to_string();
let name_component = TextComponent::text(name_owned.clone()); let name_component = TextComponent::text(name_owned.clone());
let result = futures::executor::block_on(fetch_profile_by_name_helper(server, &name_owned)); let runtime = server.runtime.clone();
match result { runtime.spawn(async move {
Some(profile) => { let result = fetch_profile_by_name_helper(&server, &name_owned).await;
report_resolved_profile( match result {
&context.source, Some(profile) => {
&profile, report_resolved_profile(
translation::java::COMMANDS_FETCHPROFILE_NAME_SUCCESS, &source,
name_component, &profile,
); translation::java::COMMANDS_FETCHPROFILE_NAME_SUCCESS,
name_component,
);
}
None => {
source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
[name_component],
));
}
} }
None => { });
context.source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
[name_component],
));
}
}
Ok(1) Ok(1)
} }
@@ -299,27 +303,31 @@ struct ResolveIdExecutor;
impl CommandExecutor for ResolveIdExecutor { impl CommandExecutor for ResolveIdExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let id = UuidArgumentType::get(context, ARG_ID)?; let id = UuidArgumentType::get(context, ARG_ID)?;
let server = context.server(); let server = context.server().clone();
let source = context.source.clone();
let id_component = TextComponent::text(id.to_string()); let id_component = TextComponent::text(id.to_string());
let result = futures::executor::block_on(fetch_profile_by_id_helper(server, id)); let runtime = server.runtime.clone();
match result { runtime.spawn(async move {
Some(profile) => { let result = fetch_profile_by_id_helper(&server, id).await;
report_resolved_profile( match result {
&context.source, Some(profile) => {
&profile, report_resolved_profile(
translation::java::COMMANDS_FETCHPROFILE_ID_SUCCESS, &source,
id_component, &profile,
); translation::java::COMMANDS_FETCHPROFILE_ID_SUCCESS,
id_component,
);
}
None => {
source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
[id_component],
));
}
} }
None => { });
context.source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
[id_component],
));
}
}
Ok(1) Ok(1)
} }

View File

@@ -30,7 +30,6 @@ struct FillBiomeExecutor {
} }
impl CommandExecutor for FillBiomeExecutor { impl CommandExecutor for FillBiomeExecutor {
#[expect(clippy::too_many_lines)]
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let from_pos = BlockPosArgumentType::get_block_pos(context, "from")?; let from_pos = BlockPosArgumentType::get_block_pos(context, "from")?;
let to_pos = BlockPosArgumentType::get_block_pos(context, "to")?; let to_pos = BlockPosArgumentType::get_block_pos(context, "to")?;
@@ -96,51 +95,30 @@ impl CommandExecutor for FillBiomeExecutor {
let mut changed_count = 0; let mut changed_count = 0;
for (chunk_pos, mods) in chunk_modifications { for (chunk_pos, mods) in chunk_modifications {
let (has_replaced, count) = let result = world.level.read_chunk_sync(&chunk_pos, |chunk| {
futures::executor::block_on(world.level.get_or_fetch_chunk(chunk_pos, |chunk| { let mut local_count = 0;
let mut local_count = 0; let mut modified = false;
let mut modified = false; for &(rel_x, rel_y, rel_z) in &mods {
for &(rel_x, rel_y, rel_z) in &mods { let section_index = rel_y / 4;
let section_index = rel_y / 4; let scale_y = rel_y % 4;
let scale_y = rel_y % 4; if let Some(current_id) =
if let Some(current_id) = chunk
chunk .section
.section .get_noise_biome(section_index, rel_x, scale_y, rel_z)
.get_noise_biome(section_index, rel_x, scale_y, rel_z) && replace_biome_id.is_none_or(|rep| current_id == rep)
{ {
if let Some(replace_id) = replace_biome_id { chunk
if current_id == replace_id { .section
chunk.section.set_relative_biome( .set_relative_biome(rel_x, rel_y, rel_z, target_biome_id);
rel_x, local_count += 1;
rel_y, modified = true;
rel_z,
target_biome_id,
);
local_count += 1;
modified = true;
}
} else {
chunk.section.set_relative_biome(
rel_x,
rel_y,
rel_z,
target_biome_id,
);
local_count += 1;
modified = true;
}
}
} }
(modified, local_count) }
})); (local_count, modified.then(|| chunk.clone()))
});
if has_replaced { if let Some((count, Some(chunk))) = result {
changed_count += count; changed_count += count;
let chunk = futures::executor::block_on(
world
.level
.get_or_fetch_chunk(chunk_pos, std::clone::Clone::clone),
);
world.broadcast_to_chunk_except(chunk_pos, &[], &CChunkData(&chunk)); world.broadcast_to_chunk_except(chunk_pos, &[], &CChunkData(&chunk));
} }
} }

View File

@@ -34,9 +34,8 @@ impl CommandExecutor for ListExecutor {
))); )));
}; };
let plugins = futures::executor::block_on(server_arc.plugin_manager.active_plugins()); let plugins = server_arc.plugin_manager.active_plugins();
let loaded_plugins = let loaded_plugins = server_arc.plugin_manager.loaded_plugins();
futures::executor::block_on(server_arc.plugin_manager.loaded_plugins());
let mut message = TextComponent::text(format!("Plugins ({}):", loaded_plugins.len())) let mut message = TextComponent::text(format!("Plugins ({}):", loaded_plugins.len()))
.color_named(NamedColor::Gold) .color_named(NamedColor::Gold)
@@ -59,11 +58,18 @@ impl CommandExecutor for ListExecutor {
metadata.authors.join(", "), metadata.authors.join(", "),
metadata.description metadata.description
); );
let component = TextComponent::text(line) let mut plugin_component = TextComponent::text(line)
.color_named(NamedColor::Green) .color_named(NamedColor::Green)
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text))); .hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
message = message.add_child(component); if !metadata.permissions.is_empty() {
plugin_component = plugin_component.add_child(
TextComponent::text(format!(" (Permissions: {:?})", metadata.permissions))
.color_named(NamedColor::Gray),
);
}
message = message.add_child(plugin_component);
} }
sender.send_message(message); sender.send_message(message);
@@ -95,32 +101,38 @@ impl CommandExecutor for LoadExecutor {
}; };
let plugin_name = plugin_name.to_string(); let plugin_name = plugin_name.to_string();
if futures::executor::block_on(server_arc.plugin_manager.is_plugin_active(&plugin_name)) { if server_arc.plugin_manager.is_plugin_active(&plugin_name) {
sender.send_message(TextComponent::text(format!( sender.send_message(TextComponent::text(format!(
"Plugin {plugin_name} is already loaded" "Plugin {plugin_name} is already loaded"
))); )));
return Ok(1); return Ok(1);
} }
let result = futures::executor::block_on( let sender_clone = sender.clone();
server_arc let plugin_name_clone = plugin_name;
let server_clone = server_arc.clone();
server_arc.runtime.spawn(async move {
let result = server_clone
.plugin_manager .plugin_manager
.try_load_plugin(&server_arc, Path::new(&plugin_name)), .try_load_plugin(&server_clone, Path::new(&plugin_name_clone))
); .await;
match result { match result {
Ok(()) => { Ok(()) => {
sender.send_message( sender_clone.send_message(
TextComponent::text(format!("Plugin {plugin_name} loaded successfully")) TextComponent::text(format!(
"Plugin {plugin_name_clone} loaded successfully"
))
.color_named(NamedColor::Green), .color_named(NamedColor::Green),
); );
}
Err(e) => {
sender_clone.send_message(TextComponent::text(format!(
"Failed to load plugin {plugin_name_clone}: {e}"
)));
}
} }
Err(e) => { });
sender.send_message(TextComponent::text(format!(
"Failed to load plugin {plugin_name}: {e}"
)));
}
}
Ok(1) Ok(1)
} }
@@ -149,29 +161,38 @@ impl CommandExecutor for UnloadExecutor {
}; };
let plugin_name = plugin_name.to_string(); let plugin_name = plugin_name.to_string();
if !futures::executor::block_on(server_arc.plugin_manager.is_plugin_active(&plugin_name)) { if !server_arc.plugin_manager.is_plugin_active(&plugin_name) {
sender.send_message(TextComponent::text(format!( sender.send_message(TextComponent::text(format!(
"Plugin {plugin_name} is not loaded" "Plugin {plugin_name} is not loaded"
))); )));
return Ok(1); return Ok(1);
} }
let result = let sender_clone = sender.clone();
futures::executor::block_on(server_arc.plugin_manager.unload_plugin(&plugin_name)); let plugin_name_clone = plugin_name;
let server_clone = server_arc.clone();
server_arc.runtime.spawn(async move {
let result = server_clone
.plugin_manager
.unload_plugin(&plugin_name_clone)
.await;
match result { match result {
Ok(()) => { Ok(()) => {
sender.send_message( sender_clone.send_message(
TextComponent::text(format!("Plugin {plugin_name} unloaded successfully")) TextComponent::text(format!(
"Plugin {plugin_name_clone} unloaded successfully"
))
.color_named(NamedColor::Green), .color_named(NamedColor::Green),
); );
}
Err(e) => {
sender_clone.send_message(TextComponent::text(format!(
"Failed to unload plugin {plugin_name_clone}: {e}"
)));
}
} }
Err(e) => { });
sender.send_message(TextComponent::text(format!(
"Failed to unload plugin {plugin_name}: {e}"
)));
}
}
Ok(1) Ok(1)
} }
@@ -197,32 +218,36 @@ impl CommandExecutor for HotReloadExecutor {
))); )));
}; };
let sender_clone = sender.clone();
let server_clone = server_arc.clone();
if enabled { if enabled {
if let Err(e) = server_arc.runtime.spawn(async move {
futures::executor::block_on(server_arc.plugin_manager.start_watcher(&server_arc)) if let Err(e) = server_clone.plugin_manager.start_watcher(&server_clone).await {
{ sender_clone.send_message(TextComponent::text(format!(
sender.send_message(TextComponent::text(format!( "Failed to start plugin watcher: {e}"
"Failed to start plugin watcher: {e}" )));
))); return;
return Ok(1); }
}
sender.send_message( sender_clone.send_message(
TextComponent::text("Hot reloading has been enabled.") TextComponent::text("Hot reloading has been enabled.")
.color_named(NamedColor::Green), .color_named(NamedColor::Green),
); );
sender.send_message( sender_clone.send_message(
TextComponent::text( TextComponent::text(
"WARNING: Hot reloading can impact performance and should only be enabled during plugin development.", "WARNING: Hot reloading can impact performance and should only be enabled during plugin development.",
) )
.color_named(NamedColor::Red), .color_named(NamedColor::Red),
); );
});
} else { } else {
futures::executor::block_on(server_arc.plugin_manager.stop_watcher()); server_arc.runtime.spawn(async move {
sender.send_message( server_clone.plugin_manager.stop_watcher().await;
TextComponent::text("Hot reloading has been disabled.") sender_clone.send_message(
.color_named(NamedColor::Yellow), TextComponent::text("Hot reloading has been disabled.")
); .color_named(NamedColor::Yellow),
);
});
} }
Ok(1) Ok(1)

View File

@@ -28,7 +28,7 @@ impl CommandExecutor for PluginsExecutor {
))); )));
}; };
let plugins = futures::executor::block_on(server_arc.plugin_manager.active_plugins()); let plugins = server_arc.plugin_manager.active_plugins();
let message_text = if plugins.is_empty() { let message_text = if plugins.is_empty() {
TextComponent::text("No plugins are loaded on the server.").color_named(NamedColor::Red) TextComponent::text("No plugins are loaded on the server.").color_named(NamedColor::Red)

View File

@@ -108,11 +108,9 @@ impl CommandExecutor for RideMountExecutor {
continue; continue;
} }
// Dismount first // Dismount first
futures::executor::block_on( curr_veh
curr_veh .get_entity()
.get_entity() .remove_passenger_sync(target.get_entity().entity_id);
.remove_passenger(target.get_entity().entity_id),
);
} }
vehicle vehicle
@@ -160,7 +158,7 @@ impl CommandExecutor for RideDismountExecutor {
.clone(); .clone();
if let Some(vehicle) = current_vehicle { if let Some(vehicle) = current_vehicle {
let target_id = target.get_entity().entity_id; let target_id = target.get_entity().entity_id;
futures::executor::block_on(vehicle.get_entity().remove_passenger(target_id)); vehicle.get_entity().remove_passenger_sync(target_id);
success_count += 1; success_count += 1;
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(

View File

@@ -53,7 +53,7 @@ fn get_anchor_position(entity: &crate::entity::Entity, anchor: EntityAnchor) ->
/// If relative flags are true, the values are added to current rotation. /// If relative flags are true, the values are added to current rotation.
/// If relative flags are false, the values are absolute. /// If relative flags are false, the values are absolute.
fn rotate_entity( fn rotate_entity(
target: std::sync::Arc<dyn crate::entity::EntityBase>, target: &std::sync::Arc<dyn crate::entity::EntityBase>,
yaw: f32, yaw: f32,
is_yaw_relative: bool, is_yaw_relative: bool,
pitch: f32, pitch: f32,
@@ -80,7 +80,14 @@ fn rotate_entity(
// This properly handles both players (sends CPlayerPosition) and other entities // This properly handles both players (sends CPlayerPosition) and other entities
let pos = entity.pos.load(); let pos = entity.pos.load();
let world = entity.world.load_full(); let world = entity.world.load_full();
futures::executor::block_on(target.teleport(pos, Some(final_yaw), Some(final_pitch), world)); if let Some(player) = target.get_player() {
let fut = target
.clone()
.teleport(pos, Some(final_yaw), Some(final_pitch), world);
player.spawn_task(fut);
} else {
entity.teleport(pos, Some(final_yaw), Some(final_pitch), world);
}
} }
/// Sends success message for the rotate command. /// Sends success message for the rotate command.
@@ -107,7 +114,7 @@ impl CommandExecutor for RotateToRotationExecutor {
let (yaw, yaw_rel, pitch, pitch_rel) = let (yaw, yaw_rel, pitch, pitch_rel) =
RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?; RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?;
rotate_entity(target.clone(), yaw, yaw_rel, pitch, pitch_rel); rotate_entity(&target, yaw, yaw_rel, pitch, pitch_rel);
send_success_message(sender, target.as_ref()); send_success_message(sender, target.as_ref());
Ok(1) Ok(1)
@@ -135,7 +142,7 @@ impl CommandExecutor for RotateFacingLocationExecutor {
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos); let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
// Facing uses absolute rotation // Facing uses absolute rotation
rotate_entity(target.clone(), yaw, false, pitch, false); rotate_entity(&target, yaw, false, pitch, false);
send_success_message(sender, target.as_ref()); send_success_message(sender, target.as_ref());
Ok(1) Ok(1)
@@ -165,7 +172,7 @@ impl CommandExecutor for RotateFacingEntityExecutor {
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards); let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
// Facing uses absolute rotation // Facing uses absolute rotation
rotate_entity(target.clone(), yaw, false, pitch, false); rotate_entity(&target, yaw, false, pitch, false);
send_success_message(sender, target.as_ref()); send_success_message(sender, target.as_ref());
Ok(1) Ok(1)
@@ -196,7 +203,7 @@ impl CommandExecutor for RotateFacingEntityNoAnchorExecutor {
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards); let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
// Facing uses absolute rotation // Facing uses absolute rotation
rotate_entity(target.clone(), yaw, false, pitch, false); rotate_entity(&target, yaw, false, pitch, false);
send_success_message(sender, target.as_ref()); send_success_message(sender, target.as_ref());
Ok(1) Ok(1)

View File

@@ -26,24 +26,28 @@ impl CommandExecutor for SaveAllExecutor {
false, false,
); );
let server = context.server(); let server_arc = context.server().clone();
if let Err(err) = futures::executor::block_on(server.save_all()) { let source = context.source.clone();
error!("Failed to save server data: {err}"); let runtime = server_arc.runtime.clone();
context.source.send_error(TextComponent::translate_cross( runtime.spawn(async move {
translation::java::COMMANDS_SAVE_FAILED, if let Err(err) = server_arc.save_all().await {
translation::bedrock::COMMANDS_SAVE_FAILED, error!("Failed to save server data: {err}");
[], source.send_error(TextComponent::translate_cross(
)); translation::java::COMMANDS_SAVE_FAILED,
} else { translation::bedrock::COMMANDS_SAVE_FAILED,
context.source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_SAVE_SUCCESS,
translation::bedrock::COMMANDS_SAVE_SUCCESS,
[], [],
), ));
true, } else {
); source.send_feedback(
} TextComponent::translate_cross(
translation::java::COMMANDS_SAVE_SUCCESS,
translation::bedrock::COMMANDS_SAVE_SUCCESS,
[],
),
true,
);
}
});
Ok(1) Ok(1)
} }

View File

@@ -105,7 +105,11 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
let yaw = target_entity.yaw.load(); let yaw = target_entity.yaw.load();
let pitch = target_entity.pitch.load(); let pitch = target_entity.pitch.load();
player.try_send_client_packet(&CSetCamera::new(target_id.into())); player.try_send_client_packet(&CSetCamera::new(target_id.into()));
futures::executor::block_on(player.teleport(pos, Some(yaw), Some(pitch), player_world)); player.spawn_task(
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), player_world),
);
let target_name = target.get_display_name(); let target_name = target.get_display_name();
sender.send_message(TextComponent::translate_cross( sender.send_message(TextComponent::translate_cross(
@@ -174,12 +178,11 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
let pitch = target_entity.pitch.load(); let pitch = target_entity.pitch.load();
let player_world = player.world(); let player_world = player.world();
player.try_send_client_packet(&CSetCamera::new(target_id.into())); player.try_send_client_packet(&CSetCamera::new(target_id.into()));
futures::executor::block_on(player.clone().teleport( player.spawn_task(
pos, player
Some(yaw), .clone()
Some(pitch), .teleport(pos, Some(yaw), Some(pitch), player_world),
player_world, );
));
succeeded += 1; succeeded += 1;
} }

View File

@@ -269,7 +269,7 @@ impl CommandExecutor for SpreadPlayersExecutor {
for (index, target) in targets.iter().enumerate() { for (index, target) in targets.iter().enumerate() {
let pile = piles[index % pile_count]; let pile = piles[index % pile_count];
let y = surface_ys[index % pile_count]; let y = surface_ys[index % pile_count];
futures::executor::block_on(target.clone().teleport( context.server().runtime.spawn(target.clone().teleport(
Vector3::new(pile.x.floor() + 0.5, f64::from(y), pile.z.floor() + 0.5), Vector3::new(pile.x.floor() + 0.5, f64::from(y), pile.z.floor() + 0.5),
None, None,
None, None,

View File

@@ -82,7 +82,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
fn execute( fn execute(
&self, &self,
sender: &CommandSender, sender: &CommandSender,
_server: &crate::server::Server, server: &crate::server::Server,
args: &ConsumedArgs, args: &ConsumedArgs,
) -> CommandResult { ) -> CommandResult {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
@@ -101,7 +101,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
))); )));
} }
for target in targets { for target in targets {
futures::executor::block_on(target.clone().teleport( server.runtime.spawn(target.clone().teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),
@@ -148,7 +148,7 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
let world = resolve_sender_world(sender, server)?; let world = resolve_sender_world(sender, server)?;
for target in targets { for target in targets {
futures::executor::block_on(target.clone().teleport( server.runtime.spawn(target.clone().teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),
@@ -200,7 +200,7 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
let world = resolve_sender_world(sender, server)?; let world = resolve_sender_world(sender, server)?;
for target in targets { for target in targets {
futures::executor::block_on(target.clone().teleport( server.runtime.spawn(target.clone().teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),
@@ -253,7 +253,7 @@ impl CommandExecutor for EntitiesToPosWithRotationExecutor {
let world = resolve_sender_world(sender, server)?; let world = resolve_sender_world(sender, server)?;
for target in targets { for target in targets {
futures::executor::block_on(target.clone().teleport( server.runtime.spawn(target.clone().teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),
@@ -304,7 +304,7 @@ impl CommandExecutor for EntitiesToPosExecutor {
for target in targets { for target in targets {
let yaw = target.get_entity().yaw.load(); let yaw = target.get_entity().yaw.load();
let pitch = target.get_entity().pitch.load(); let pitch = target.get_entity().pitch.load();
futures::executor::block_on(target.clone().teleport( server.runtime.spawn(target.clone().teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),
@@ -338,7 +338,7 @@ impl CommandExecutor for SelfToEntityExecutor {
fn execute( fn execute(
&self, &self,
sender: &CommandSender, sender: &CommandSender,
_server: &crate::server::Server, server: &crate::server::Server,
args: &ConsumedArgs, args: &ConsumedArgs,
) -> CommandResult { ) -> CommandResult {
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?; let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
@@ -357,12 +357,9 @@ impl CommandExecutor for SelfToEntityExecutor {
[], [],
))); )));
} }
futures::executor::block_on(player.clone().teleport( server
pos, .runtime
Some(yaw), .spawn(player.clone().teleport(pos, Some(yaw), Some(pitch), world));
Some(pitch),
world,
));
sender.send_message(TextComponent::translate_cross( sender.send_message(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE, translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
@@ -390,7 +387,7 @@ impl CommandExecutor for SelfToPosExecutor {
fn execute( fn execute(
&self, &self,
sender: &CommandSender, sender: &CommandSender,
_server: &crate::server::Server, server: &crate::server::Server,
args: &ConsumedArgs, args: &ConsumedArgs,
) -> CommandResult { ) -> CommandResult {
match sender { match sender {
@@ -406,7 +403,7 @@ impl CommandExecutor for SelfToPosExecutor {
))); )));
} }
let player_world = player.world(); let player_world = player.world();
futures::executor::block_on(player.clone().teleport( server.runtime.spawn(player.clone().teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),

View File

@@ -24,6 +24,25 @@ pub struct LoadedDatapack {
pub function_count: usize, pub function_count: usize,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DatapackInfo {
pub id: String,
pub name: String,
pub description: String,
pub pack_format: u32,
pub is_enabled: bool,
pub recipe_count: usize,
pub function_count: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DatapackEnablePosition {
First,
Last,
Before(String),
After(String),
}
pub struct DatapackManager { pub struct DatapackManager {
loaded_packs: RwLock<Vec<LoadedDatapack>>, loaded_packs: RwLock<Vec<LoadedDatapack>>,
functions: RwLock<HashMap<String, Vec<String>>>, functions: RwLock<HashMap<String, Vec<String>>>,
@@ -244,9 +263,314 @@ impl DatapackManager {
Ok(total_executed) Ok(total_executed)
} }
pub fn get_all_known_packs(server: &Server) -> Vec<String> {
let mut packs = Vec::new();
packs.push("vanilla".to_string());
// Bundled feature packs
for bundled in [
"trade_rebalance",
"minecart_improvements",
"redstone_experiments",
] {
if !packs.iter().any(|p| p == bundled) {
packs.push(bundled.to_string());
}
}
// World datapacks directory
let datapacks_dir = server.basic_config.get_world_path().join("datapacks");
if let Ok(entries) = fs::read_dir(datapacks_dir) {
for entry in entries.flatten() {
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.starts_with('.') {
continue;
}
if path.is_dir()
|| path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
let pack_name = format!("file/{file_name}");
if !packs.iter().any(|p| p == &pack_name) {
packs.push(pack_name);
}
}
}
}
let level_info = server.level_info.load();
for pack in &level_info.data_packs.enabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
for pack in &level_info.data_packs.disabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
packs
}
pub fn get_enabled_packs(server: &Server) -> Vec<String> {
server.level_info.load().data_packs.enabled.clone()
}
pub fn get_available_packs(server: &Server) -> Vec<String> {
let enabled = Self::get_enabled_packs(server);
let all = Self::get_all_known_packs(server);
all.into_iter().filter(|p| !enabled.contains(p)).collect()
}
pub fn find_pack_name(server: &Server, input: &str) -> Option<String> {
let known = Self::get_all_known_packs(server);
if let Some(p) = known.iter().find(|p| *p == input) {
return Some(p.clone());
}
let file_input = format!("file/{input}");
if let Some(p) = known.iter().find(|p| **p == file_input) {
return Some(p.clone());
}
if let Some(p) = known
.iter()
.find(|p| p.strip_prefix("file/") == Some(input))
{
return Some(p.clone());
}
None
}
pub fn get_pack_info(server: &Server, name_or_id: &str) -> Option<DatapackInfo> {
let resolved_name = Self::find_pack_name(server, name_or_id)?;
let enabled_packs = Self::get_enabled_packs(server);
let is_enabled = enabled_packs.contains(&resolved_name);
let loaded = server.datapack_manager.get_loaded_packs();
if let Some(pack) = loaded
.iter()
.find(|p| p.id == resolved_name || p.name == resolved_name)
{
return Some(DatapackInfo {
id: pack.id.clone(),
name: pack.name.clone(),
description: pack.description.clone(),
pack_format: pack.pack_format,
is_enabled,
recipe_count: pack.recipe_count,
function_count: pack.function_count,
});
}
let (id, name, description, pack_format) = if resolved_name == "vanilla" {
(
"vanilla".to_string(),
"vanilla".to_string(),
"The default data pack".to_string(),
61,
)
} else if let Some(stripped) = resolved_name.strip_prefix("file/") {
let pack_path = server
.basic_config
.get_world_path()
.join("datapacks")
.join(stripped);
let (desc, format) = read_pack_mcmeta(&pack_path);
(resolved_name.clone(), stripped.to_string(), desc, format)
} else {
(
resolved_name.clone(),
resolved_name.clone(),
format!("Bundled datapack: {resolved_name}"),
61,
)
};
Some(DatapackInfo {
id,
name,
description,
pack_format,
is_enabled,
recipe_count: 0,
function_count: 0,
})
}
pub fn list_all_packs(server: &Server) -> Vec<DatapackInfo> {
let all = Self::get_all_known_packs(server);
all.into_iter()
.filter_map(|p| Self::get_pack_info(server, &p))
.collect()
}
pub fn list_enabled_packs(server: &Server) -> Vec<DatapackInfo> {
let enabled = Self::get_enabled_packs(server);
enabled
.into_iter()
.filter_map(|p| Self::get_pack_info(server, &p))
.collect()
}
pub fn list_available_packs(server: &Server) -> Vec<DatapackInfo> {
let available = Self::get_available_packs(server);
available
.into_iter()
.filter_map(|p| Self::get_pack_info(server, &p))
.collect()
}
pub fn is_pack_enabled(server: &Server, name: &str) -> bool {
let Some(resolved) = Self::find_pack_name(server, name) else {
return false;
};
Self::get_enabled_packs(server).contains(&resolved)
}
pub fn enable_pack(
server: &Arc<Server>,
name: &str,
position: DatapackEnablePosition,
) -> Result<(), String> {
let Some(resolved_name) = Self::find_pack_name(server, name) else {
return Err(format!("Unknown datapack '{name}'"));
};
let enabled = Self::get_enabled_packs(server);
if enabled.contains(&resolved_name) {
return Err(format!("Datapack '{resolved_name}' is already enabled"));
}
let target = resolved_name;
match position {
DatapackEnablePosition::First => {
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
new_info.data_packs.enabled.insert(0, target.clone());
new_info
});
}
DatapackEnablePosition::Last => {
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
new_info.data_packs.enabled.push(target.clone());
new_info
});
}
DatapackEnablePosition::Before(existing_name) => {
let Some(existing_pack) = Self::find_pack_name(server, &existing_name) else {
return Err(format!("Unknown existing datapack '{existing_name}'"));
};
if !enabled.contains(&existing_pack) {
return Err(format!("Datapack '{existing_pack}' is not enabled"));
}
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
if let Some(idx) = new_info
.data_packs
.enabled
.iter()
.position(|p| p == &existing_pack)
{
new_info.data_packs.enabled.insert(idx, target.clone());
} else {
new_info.data_packs.enabled.push(target.clone());
}
new_info
});
}
DatapackEnablePosition::After(existing_name) => {
let Some(existing_pack) = Self::find_pack_name(server, &existing_name) else {
return Err(format!("Unknown existing datapack '{existing_name}'"));
};
if !enabled.contains(&existing_pack) {
return Err(format!("Datapack '{existing_pack}' is not enabled"));
}
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
if let Some(idx) = new_info
.data_packs
.enabled
.iter()
.position(|p| p == &existing_pack)
{
new_info.data_packs.enabled.insert(idx + 1, target.clone());
} else {
new_info.data_packs.enabled.push(target.clone());
}
new_info
});
}
}
if let Err(err) = server.save_world_info() {
tracing::error!("Failed to save world info: {err}");
}
server.reload_datapacks(server);
Ok(())
}
pub fn disable_pack(server: &Arc<Server>, name: &str) -> Result<(), String> {
let Some(target_pack) = Self::find_pack_name(server, name) else {
return Err(format!("Unknown datapack '{name}'"));
};
let enabled = Self::get_enabled_packs(server);
if !enabled.contains(&target_pack) {
return Err(format!("Datapack '{target_pack}' is not enabled"));
}
if target_pack == "vanilla" {
return Err("Cannot disable the default vanilla datapack".to_string());
}
let target = target_pack;
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.enabled.retain(|p| p != &target);
if !new_info.data_packs.disabled.contains(&target) {
new_info.data_packs.disabled.push(target.clone());
}
new_info
});
if let Err(err) = server.save_world_info() {
tracing::error!("Failed to save world info: {err}");
}
server.reload_datapacks(server);
Ok(())
}
pub fn reload(server: &Arc<Server>) -> Result<(), String> {
server.reload_datapacks(server);
Ok(())
}
pub fn execute_function_from_console(
server: &Arc<Server>,
name: &str,
) -> Result<usize, String> {
let source = crate::command::CommandSender::Console.into_source(server);
server
.datapack_manager
.execute_function(server, &source, name)
}
} }
fn read_pack_mcmeta(pack_path: &Path) -> (String, u32) { pub fn read_pack_mcmeta(pack_path: &Path) -> (String, u32) {
let mcmeta_path = pack_path.join("pack.mcmeta"); let mcmeta_path = pack_path.join("pack.mcmeta");
if let Ok(content) = fs::read_to_string(mcmeta_path) if let Ok(content) = fs::read_to_string(mcmeta_path)
&& let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) && let Ok(val) = serde_json::from_str::<serde_json::Value>(&content)

View File

@@ -94,7 +94,7 @@ impl ServerPlayerData {
} }
let storage = self.storage.clone(); let storage = self.storage.clone();
server.runtime.spawn(async move { tokio::task::spawn_blocking(move || {
for (uuid, nbt) in snapshots { for (uuid, nbt) in snapshots {
if let Err(e) = storage.save_player_data(&uuid, nbt) { if let Err(e) = storage.save_player_data(&uuid, nbt) {
error!("Failed to save player data for {uuid}: {e}"); error!("Failed to save player data for {uuid}: {e}");

View File

@@ -247,3 +247,53 @@ impl Mob for CreeperEntity {
true true
} }
} }
impl CreeperEntity {
pub fn is_charged(&self) -> bool {
self.charged.load(Ordering::Relaxed)
}
pub fn set_charged(&self, charged: bool) {
self.charged.store(charged, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::CHARGED,
charged,
)],
None,
);
}
pub fn is_ignited(&self) -> bool {
self.ignited.load(Ordering::Relaxed)
}
pub fn set_ignited(&self, ignited: bool) {
self.ignited.store(ignited, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::IS_IGNITED,
ignited,
)],
None,
);
}
pub fn get_fuse(&self) -> i32 {
self.fuse_time.load(Ordering::Relaxed)
}
pub fn set_fuse(&self, fuse: i32) {
self.fuse_time.store(fuse, Ordering::Relaxed);
}
pub fn get_explosion_radius(&self) -> i32 {
self.explosion_radius.load(Ordering::Relaxed)
}
pub fn set_explosion_radius(&self, radius: i32) {
self.explosion_radius.store(radius, Ordering::Relaxed);
}
}

View File

@@ -535,8 +535,9 @@ impl Goal for EvokerWololoSpellGoal {
for cand in candidates { for cand in candidates {
if *cand.get_entity().entity_type == EntityType::SHEEP if *cand.get_entity().entity_type == EntityType::SHEEP
&& let Some(mob) = cand.get_mob() && let Some(sheep) = cand
&& let Some(sheep) = mob.get_sheep() .cast_any()
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
{ {
// Blue color is 11 in Minecraft // Blue color is 11 in Minecraft
if sheep.get_color() == 11 { if sheep.get_color() == 11 {
@@ -609,8 +610,9 @@ impl Goal for EvokerWololoSpellGoal {
for cand in candidates { for cand in candidates {
if cand.get_entity().entity_id == target_id if cand.get_entity().entity_id == target_id
&& let Some(mob) = cand.get_mob() && let Some(sheep) = cand
&& let Some(sheep) = mob.get_sheep() .cast_any()
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
{ {
// Convert color to Red (14) // Convert color to Red (14)
sheep.set_color(14); sheep.set_color(14);

View File

@@ -957,10 +957,6 @@ pub trait Mob: EntityBase + Send + Sync {
fn mob_set_variant_name(&self, _name: &str) {} fn mob_set_variant_name(&self, _name: &str) {}
fn get_sheep(&self) -> Option<&crate::entity::passive::sheep::SheepEntity> {
None
}
fn mob_on_lightning_strike( fn mob_on_lightning_strike(
&self, &self,
caller: &dyn EntityBase, caller: &dyn EntityBase,

View File

@@ -113,7 +113,7 @@ impl ShulkerEntity {
.unwrap_or(DEFAULT_ATTACH_FACE) .unwrap_or(DEFAULT_ATTACH_FACE)
} }
fn set_attach_face(&self, face: BlockDirection) { pub fn set_attach_face(&self, face: BlockDirection) {
self.attach_face.store(face as u8, Ordering::Relaxed); self.attach_face.store(face as u8, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data( entity.send_meta_data(
@@ -125,6 +125,24 @@ impl ShulkerEntity {
); );
} }
pub fn get_color(&self) -> Option<u8> {
let c = self.color.load(Ordering::Relaxed);
if c == NO_COLOR { None } else { Some(c) }
}
pub fn set_color(&self, color: Option<u8>) {
let val = color.unwrap_or(NO_COLOR);
self.color.store(val, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::shulker::COLOR,
val as i8,
)],
None,
);
}
pub fn get_raw_peek(&self) -> u8 { pub fn get_raw_peek(&self) -> u8 {
self.peek_amount.load(Ordering::Relaxed) self.peek_amount.load(Ordering::Relaxed)
} }

View File

@@ -52,3 +52,46 @@ impl Mob for ZombieEntity {
self.entity.mob_read_nbt(nbt); self.entity.mob_read_nbt(nbt);
} }
} }
impl ZombieEntity {
#[must_use]
pub fn can_break_doors(&self) -> bool {
self.entity
.can_break_doors
.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn set_can_break_doors(&self, can_break: bool) {
self.entity
.can_break_doors
.store(can_break, std::sync::atomic::Ordering::Relaxed);
}
#[must_use]
pub fn is_baby(&self) -> bool {
self.entity
.mob_entity
.living_entity
.entity
.age
.load(std::sync::atomic::Ordering::Relaxed)
< 0
}
pub fn set_baby(&self, baby: bool) {
let age = if baby { -24000 } else { 0 };
self.entity
.mob_entity
.living_entity
.entity
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
self.entity.mob_entity.living_entity.entity.send_meta_data(
&[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::zombie::BABY,
baby,
)],
None,
);
}
}

View File

@@ -2351,29 +2351,18 @@ impl Entity {
tokio::spawn(async move { tokio::spawn(async move {
let world_for_dest = world_clone.clone(); let world_for_dest = world_clone.clone();
let caller_for_dest = caller_clone.clone(); let caller_for_dest = caller_clone.clone();
let gen_pool = world_for_dest.level.gen_pool.clone(); let (tx, rx) = tokio::sync::oneshot::channel();
let transition = if let Some(pool) = gen_pool { rayon::spawn(move || {
let (tx, rx) = tokio::sync::oneshot::channel(); let dest = portal_type.get_portal_destination(
pool.spawn(move || {
let dest = portal_type.get_portal_destination(
&world_for_dest,
dest_world_opt,
&caller_for_dest,
entry_pos,
src_portal.as_ref(),
);
let _ = tx.send(dest);
});
rx.await.ok().flatten()
} else {
portal_type.get_portal_destination(
&world_for_dest, &world_for_dest,
dest_world_opt, dest_world_opt,
&caller_for_dest, &caller_for_dest,
entry_pos, entry_pos,
src_portal.as_ref(), src_portal.as_ref(),
) );
}; let _ = tx.send(dest);
});
let transition = rx.await.ok().flatten();
if let Some(transition) = transition { if let Some(transition) = transition {
let dest_world = transition.new_world.clone(); let dest_world = transition.new_world.clone();
@@ -3164,7 +3153,7 @@ impl Entity {
} }
} }
fn teleport( pub fn teleport(
&self, &self,
position: Vector3<f64>, position: Vector3<f64>,
yaw: Option<f32>, yaw: Option<f32>,
@@ -3485,6 +3474,10 @@ impl Entity {
); );
} }
pub fn remove_passenger_sync(&self, passenger_id: i32) {
self.remove_passenger_on_disconnect(passenger_id);
}
pub async fn remove_passenger(&self, passenger_id: i32) { pub async fn remove_passenger(&self, passenger_id: i32) {
self.remove_passenger_internal(passenger_id, true).await; self.remove_passenger_internal(passenger_id, true).await;
} }

View File

@@ -119,9 +119,6 @@ impl IronGolemEntity {
} }
impl Mob for IronGolemEntity { impl Mob for IronGolemEntity {
fn as_iron_golem(&self) -> Option<&IronGolemEntity> {
Some(self)
}
fn mob_write_nbt(&self, nbt: &mut NbtCompound) { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_bool("PlayerCreated", self.is_player_created()); nbt.put_bool("PlayerCreated", self.is_player_created());
} }

View File

@@ -156,10 +156,6 @@ impl Mob for SheepEntity {
self.set_sheared(false); self.set_sheared(false);
} }
fn get_sheep(&self) -> Option<&SheepEntity> {
Some(self)
}
fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool { fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
use super::animal::Animal; use super::animal::Animal;
self.animal_interact(player, item_stack, Sound::EntitySheepAmbient) self.animal_interact(player, item_stack, Sound::EntitySheepAmbient)

View File

@@ -328,3 +328,21 @@ impl Mob for WolfEntity {
); );
} }
} }
impl WolfEntity {
pub fn get_collar_color(&self) -> u8 {
self.collar_color.load(Ordering::Relaxed)
}
pub fn set_collar_color(&self, color: u8) {
self.collar_color.store(color, Ordering::Relaxed);
let entity = self.get_entity();
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::COLLAR_COLOR,
VarInt(color as i32),
)],
None,
);
}
}

View File

@@ -411,8 +411,8 @@ pub struct ChunkManager {
} }
impl ChunkManager { impl ChunkManager {
pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 10; pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 16;
const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(250); const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(100);
#[must_use] #[must_use]
pub fn new( pub fn new(

View File

@@ -76,7 +76,9 @@ impl ItemBehaviour for ShearsItem {
} }
fn use_on_entity(&self, _item: &mut ItemStack, player: &Player, entity: Arc<dyn EntityBase>) { fn use_on_entity(&self, _item: &mut ItemStack, player: &Player, entity: Arc<dyn EntityBase>) {
if let Some(sheep) = entity.get_mob().and_then(|m| m.get_sheep()) if let Some(sheep) = entity
.cast_any()
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
&& !sheep.is_sheared() && !sheep.is_sheared()
{ {
sheep.set_sheared(true); sheep.set_sheared(true);

View File

@@ -49,6 +49,11 @@ static MAIN_THREAD: OnceLock<ThreadId> = OnceLock::new();
async fn main() { async fn main() {
let _ = MAIN_THREAD.set(thread::current().id()); let _ = MAIN_THREAD.set(thread::current().id());
// Initialize global Rayon thread pool with named worker threads
let _ = rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("Rayon-Worker-{i}"))
.build_global();
// Set the panic handler. // Set the panic handler.
std::panic::set_hook(Box::new(handle_panic)); std::panic::set_hook(Box::new(handle_panic));

View File

@@ -13,45 +13,60 @@ impl JavaClient {
if version.supports_configuration_state() { if version.supports_configuration_state() {
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()])) self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
.await; .await;
let registry = Registry::get_synced(version); let registry_packets = tokio::task::spawn_blocking(move || {
let mut sent_dimension_type = false; let registry = Registry::get_synced(version);
for reg in &registry { let mut packets = Vec::new();
if reg.registry_id == "minecraft:dimension_type" { let mut sent_dimension_type = false;
sent_dimension_type = true; for reg in &registry {
if reg.registry_id == "minecraft:dimension_type" {
sent_dimension_type = true;
}
let packet = CRegistryData::new(&reg.registry_id, &reg.registry_entries);
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
packets.push(data);
}
} }
self.send_packet(&CRegistryData::new(&reg.registry_id, &reg.registry_entries)) if !sent_dimension_type {
.await; let dims = [
} &pumpkin_data::dimension::Dimension::OVERWORLD,
if !sent_dimension_type { &pumpkin_data::dimension::Dimension::OVERWORLD_CAVES,
let dims = [ &pumpkin_data::dimension::Dimension::THE_END,
&pumpkin_data::dimension::Dimension::OVERWORLD, &pumpkin_data::dimension::Dimension::THE_NETHER,
&pumpkin_data::dimension::Dimension::OVERWORLD_CAVES, ];
&pumpkin_data::dimension::Dimension::THE_END, let dim_entries: Vec<pumpkin_data::registry::RegistryEntryData> = dims
&pumpkin_data::dimension::Dimension::THE_NETHER, .iter()
]; .map(|dim| pumpkin_data::registry::RegistryEntryData {
let dim_entries: Vec<pumpkin_data::registry::RegistryEntryData> = dims entry_id: dim.minecraft_name.to_string(),
.iter() data: Some(build_dimension_nbt(dim).into_boxed_slice()),
.map(|dim| pumpkin_data::registry::RegistryEntryData { })
entry_id: dim.minecraft_name.to_string(), .collect();
data: Some(build_dimension_nbt(dim).into_boxed_slice()), let dim_type = "minecraft:dimension_type".to_string();
}) let packet = CRegistryData::new(&dim_type, &dim_entries);
.collect(); if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
self.send_packet(&CRegistryData::new( packets.push(data);
&"minecraft:dimension_type".to_string(), }
&dim_entries, }
)) let mut tags = Vec::new();
.await; for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
if pumpkin_data::tag::get_registry_key_tags(version, key)
.is_some_and(|map| !map.is_empty())
{
tags.push(key);
}
}
let packet = CUpdateTags::new(&tags);
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
packets.push(data);
}
packets
})
.await
.unwrap_or_default();
for packet_data in registry_packets {
self.send_packet_now(packet_data).await;
} }
} }
let mut tags = Vec::new();
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
if pumpkin_data::tag::get_registry_key_tags(version, key)
.is_some_and(|map| !map.is_empty())
{
tags.push(key);
}
}
self.send_packet(&CUpdateTags::new(&tags)).await;
// We are done with configuring // We are done with configuring
self.send_packet(&CFinishConfig).await; self.send_packet(&CFinishConfig).await;

View File

@@ -344,51 +344,84 @@ impl JavaClient {
return; return;
}; };
if self.version.load() >= JavaMinecraftVersion::V_1_20_2 { let mut valid_chunks = Vec::with_capacity(chunks.len());
self.send_packet(&CChunkBatchStart).await;
}
for chunk in chunks { for chunk in chunks {
let mut event = ChunkSend::new(player.world(), chunk.clone()); let mut event = ChunkSend::new(player.world(), chunk.clone());
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled { if !event.cancelled {
continue; valid_chunks.push(chunk.clone());
} }
}
let mut buf = Vec::new(); if valid_chunks.is_empty() {
let version = self.version.load(); return;
if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) { }
error!("Failed to write chunk data id: {err:?}");
continue;
}
if let Err(err) = CChunkData(chunk).write_packet_data(&mut buf, &version) {
error!("Failed to write chunk data: {err:?}");
continue;
}
self.send_packet_now_data(buf.into()).await;
if version >= JavaMinecraftVersion::V_1_14 && version < JavaMinecraftVersion::V_1_18 { let version = self.version.load();
match CLightUpdate::from_chunk(chunk, version) { // Offload CPU-heavy packet serialization (palette packing, lighting, NBT heightmaps)
Ok(light_packet) => { // off Tokio worker threads onto blocking/Rayon worker threads
let mut light_buf = Vec::new(); let serialize_tasks: Vec<_> = valid_chunks
if let Err(err) = .into_iter()
light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version))) .map(|chunk| {
{ tokio::task::spawn_blocking(move || {
error!("Failed to write light update id: {err:?}"); let mut buf = Vec::with_capacity(32 * 1024);
} else if let Err(err) = if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) {
light_packet.write_packet_data(&mut light_buf, &version) error!("Failed to write chunk data id: {err:?}");
{ return None;
error!("Failed to write light update data: {err:?}"); }
} else { if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) {
self.send_packet_now_data(light_buf.into()).await; error!("Failed to write chunk data: {err:?}");
return None;
}
let light_buf = if version >= JavaMinecraftVersion::V_1_14
&& version < JavaMinecraftVersion::V_1_18
{
match CLightUpdate::from_chunk(&chunk, version) {
Ok(light_packet) => {
let mut light_buf = Vec::new();
if let Err(err) =
light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version)))
{
error!("Failed to write light update id: {err:?}");
None
} else if let Err(err) =
light_packet.write_packet_data(&mut light_buf, &version)
{
error!("Failed to write light update data: {err:?}");
None
} else {
Some(Bytes::from(light_buf))
}
}
Err(err) => {
error!("Failed to create light update packet: {err:?}");
None
}
} }
} } else {
Err(err) => { None
error!("Failed to create light update packet: {err:?}"); };
}
Some((Bytes::from(buf), light_buf))
})
})
.collect();
if version >= JavaMinecraftVersion::V_1_20_2 {
self.send_packet(&CChunkBatchStart).await;
}
for task in serialize_tasks {
if let Ok(Some((chunk_data, light_data))) = task.await {
self.send_packet_now_data(chunk_data).await;
if let Some(light_data) = light_data {
self.send_packet_now_data(light_data).await;
} }
} }
} }
if self.version.load() >= JavaMinecraftVersion::V_1_20_2 {
if version >= JavaMinecraftVersion::V_1_20_2 {
self.send_packet(&CChunkBatchEnd::new(chunks.len() as u16)) self.send_packet(&CChunkBatchEnd::new(chunks.len() as u16))
.await; .await;
} }

View File

@@ -145,7 +145,6 @@ async fn handle_packet(
let plugins = server let plugins = server
.plugin_manager .plugin_manager
.active_plugins() .active_plugins()
.await
.into_iter() .into_iter()
.map(|meta| meta.name) .map(|meta| meta.name)
.reduce(|acc, name| format!("{acc}, {name}")) .reduce(|acc, name| format!("{acc}, {name}"))

View File

@@ -316,9 +316,9 @@ impl Context {
&self, &self,
loader: Arc<dyn crate::plugin::loader::PluginLoader>, loader: Arc<dyn crate::plugin::loader::PluginLoader>,
) -> bool { ) -> bool {
let before_count = self.plugin_manager.loaded_plugins().await.len(); let before_count = self.plugin_manager.loaded_plugins().len();
self.plugin_manager.add_loader(&self.server, loader).await; self.plugin_manager.add_loader(&self.server, loader).await;
let after_count = self.plugin_manager.loaded_plugins().await.len(); let after_count = self.plugin_manager.loaded_plugins().len();
// Return true if any new plugins were loaded // Return true if any new plugins were loaded
after_count > before_count after_count > before_count

View File

@@ -70,8 +70,22 @@ pub type EnchantmentManagerResource =
pub type OpManagerResource = WasmResource<Arc<Server>>; pub type OpManagerResource = WasmResource<Arc<Server>>;
pub type BanManagerResource = WasmResource<Arc<Server>>; pub type BanManagerResource = WasmResource<Arc<Server>>;
pub type WhitelistManagerResource = WasmResource<Arc<Server>>; pub type WhitelistManagerResource = WasmResource<Arc<Server>>;
pub type DatapackManagerResource = WasmResource<Arc<Server>>;
pub type BlockEntityResource = WasmResource<Arc<dyn crate::block::entities::BlockEntity>>; pub type BlockEntityResource = WasmResource<Arc<dyn crate::block::entities::BlockEntity>>;
#[derive(Clone)]
pub enum InventoryProvider {
Generic(Arc<dyn pumpkin_world::inventory::Inventory>),
PlayerMain(Arc<Player>),
PlayerEnderChest(Arc<Player>),
}
pub type InventoryResource = WasmResource<InventoryProvider>;
pub type PlayerInventoryResource = WasmResource<Arc<Player>>;
pub type LivingEntityResource = WasmResource<Arc<dyn EntityBase>>;
pub type MobResource = WasmResource<Arc<dyn EntityBase>>;
#[derive(Clone)] #[derive(Clone)]
pub struct ContainerBlockEntity { pub struct ContainerBlockEntity {
pub provider: Arc<dyn crate::block::entities::BlockEntity>, pub provider: Arc<dyn crate::block::entities::BlockEntity>,
@@ -195,6 +209,24 @@ impl PluginHostState {
Ok(wasmtime::component::Resource::new_own(resource.rep())) Ok(wasmtime::component::Resource::new_own(resource.rep()))
} }
pub fn add_living_entity<T>(
&mut self,
provider: Arc<dyn EntityBase>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(LivingEntityResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_mob<T>(
&mut self,
provider: Arc<dyn EntityBase>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(MobResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_world<T>( pub fn add_world<T>(
&mut self, &mut self,
provider: Arc<World>, provider: Arc<World>,
@@ -362,6 +394,34 @@ impl PluginHostState {
Ok(wasmtime::component::Resource::new_own(resource.rep())) Ok(wasmtime::component::Resource::new_own(resource.rep()))
} }
pub fn add_datapack_manager<T>(
&mut self,
provider: Arc<Server>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(DatapackManagerResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_inventory<T>(
&mut self,
provider: InventoryProvider,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(InventoryResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_player_inventory<T>(
&mut self,
provider: Arc<Player>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(PlayerInventoryResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_block_entity<T>( pub fn add_block_entity<T>(
&mut self, &mut self,
provider: Arc<dyn crate::block::entities::BlockEntity>, provider: Arc<dyn crate::block::entities::BlockEntity>,

View File

@@ -319,6 +319,24 @@ impl HostContainerBlockEntity for PluginHostState {
self.add_block_entity(provider) self.add_block_entity(provider)
} }
async fn get_inventory(
&mut self,
res: Resource<ContainerBlockEntity>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::Inventory,
>,
>{
let container = self
.resource_table
.get::<ContainerBlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid container block entity resource handle"))?;
let inventory = container.provider.inventory.clone();
self.add_inventory(
crate::plugin::loader::wasm::wasm_host::state::InventoryProvider::Generic(inventory),
)
}
async fn get_size(&mut self, res: Resource<ContainerBlockEntity>) -> wasmtime::Result<u32> { async fn get_size(&mut self, res: Resource<ContainerBlockEntity>) -> wasmtime::Result<u32> {
let container = self let container = self
.resource_table .resource_table

View File

@@ -0,0 +1,154 @@
use crate::data::datapack::DatapackManager;
use crate::plugin::loader::wasm::wasm_host::state::PluginHostState;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::datapack::{
DatapackInfo as WitDatapackInfo, DatapackManager as WitDatapackManager,
EnablePosition as WitEnablePosition, Host as DatapackHost, HostDatapackManager,
};
use wasmtime::component::Resource;
impl DatapackHost for PluginHostState {}
impl HostDatapackManager for PluginHostState {
async fn list_all_packs(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Vec<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let packs = DatapackManager::list_all_packs(server);
Ok(packs.into_iter().map(to_wit_datapack_info).collect())
}
async fn list_enabled_packs(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Vec<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let packs = DatapackManager::list_enabled_packs(server);
Ok(packs.into_iter().map(to_wit_datapack_info).collect())
}
async fn list_available_packs(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Vec<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let packs = DatapackManager::list_available_packs(server);
Ok(packs.into_iter().map(to_wit_datapack_info).collect())
}
async fn get_pack(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<Option<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let pack = DatapackManager::get_pack_info(server, &name);
Ok(pack.map(to_wit_datapack_info))
}
async fn is_enabled(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<bool> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(DatapackManager::is_pack_enabled(server, &name))
}
async fn enable_pack(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
position: WitEnablePosition,
) -> wasmtime::Result<Result<(), String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let pos = to_data_enable_position(position);
Ok(DatapackManager::enable_pack(server, &name, pos))
}
async fn disable_pack(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<Result<(), String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(DatapackManager::disable_pack(server, &name))
}
async fn reload(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Result<(), String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(DatapackManager::reload(server))
}
async fn execute_function(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<Result<u32, String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let result = DatapackManager::execute_function_from_console(server, &name);
Ok(result.map(|count| count as u32))
}
async fn drop(&mut self, rep: Resource<WitDatapackManager>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::DatapackManagerResource>(
Resource::new_own(rep.rep()),
);
Ok(())
}
}
fn to_wit_datapack_info(info: crate::data::datapack::DatapackInfo) -> WitDatapackInfo {
WitDatapackInfo {
id: info.id,
name: info.name,
description: info.description,
pack_format: info.pack_format,
is_enabled: info.is_enabled,
recipe_count: info.recipe_count as u32,
function_count: info.function_count as u32,
}
}
fn to_data_enable_position(
pos: WitEnablePosition,
) -> crate::data::datapack::DatapackEnablePosition {
match pos {
WitEnablePosition::First => crate::data::datapack::DatapackEnablePosition::First,
WitEnablePosition::Last => crate::data::datapack::DatapackEnablePosition::Last,
WitEnablePosition::Before(s) => crate::data::datapack::DatapackEnablePosition::Before(s),
WitEnablePosition::After(s) => crate::data::datapack::DatapackEnablePosition::After(s),
}
}

View File

@@ -3,27 +3,18 @@ use wasmtime::component::Resource;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::entity::ai::goal::Goal;
use crate::entity::mob::Mob;
use crate::plugin::loader::wasm::wasm_host::{PluginInstance, WasmPlugin};
use crate::plugin::loader::wasm::wasm_host::{ use crate::plugin::loader::wasm::wasm_host::{
state::{EntityResource, PluginHostState}, state::{EntityResource, PluginHostState},
wit::v0_1::events::to_wasm_position, wit::v0_1::events::to_wasm_position,
wit::v0_1::pumpkin::plugin::{ wit::v0_1::pumpkin::plugin::{
attributes::{
Attribute, AttributeModifier as WitAttributeModifier,
ModifierOperation as WitModifierOperation,
},
common::{EntityPose, NbtTree as WitNbtTree, Position}, common::{EntityPose, NbtTree as WitNbtTree, Position},
damage_types::DamageType as WitDamageType,
entity::Host, entity::Host,
entity_types, entity_types,
item_stack::ItemStack as WitHostItemStack,
text::TextComponent, text::TextComponent,
uuid::Uuid, uuid::Uuid,
world::{ world::{
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, HostEntity,
EquipmentSlot as WitEquipmentSlot, HostEntity, LivingEntity as WitLivingEntity, Mob as WitMob,
RayTraceBlockResult as WitRayTraceBlockResult, RayTraceBlockResult as WitRayTraceBlockResult,
RayTraceEntityResult as WitRayTraceEntityResult, RaycastResult as WitRaycastResult, RayTraceEntityResult as WitRayTraceEntityResult, RaycastResult as WitRaycastResult,
World, World,
@@ -37,7 +28,7 @@ use pumpkin_data::entity::EntityPose as InternalEntityPose;
impl Host for PluginHostState {} impl Host for PluginHostState {}
impl entity_types::Host for PluginHostState {} impl entity_types::Host for PluginHostState {}
fn entity_from_resource( pub fn entity_from_resource(
state: &PluginHostState, state: &PluginHostState,
entity: &Resource<Entity>, entity: &Resource<Entity>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> { ) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
@@ -71,112 +62,6 @@ const fn map_entity_pose(pose: InternalEntityPose) -> EntityPose {
} }
} }
#[must_use]
pub const fn from_wit_attribute(attr: Attribute) -> &'static pumpkin_data::attributes::Attributes {
use pumpkin_data::attributes::Attributes;
match attr {
Attribute::AirDragModifier => &Attributes::AIR_DRAG_MODIFIER,
Attribute::Armor => &Attributes::ARMOR,
Attribute::ArmorToughness => &Attributes::ARMOR_TOUGHNESS,
Attribute::AttackDamage => &Attributes::ATTACK_DAMAGE,
Attribute::AttackKnockback => &Attributes::ATTACK_KNOCKBACK,
Attribute::AttackSpeed => &Attributes::ATTACK_SPEED,
Attribute::BelowNameDistance => &Attributes::BELOW_NAME_DISTANCE,
Attribute::BlockBreakSpeed => &Attributes::BLOCK_BREAK_SPEED,
Attribute::BlockInteractionRange => &Attributes::BLOCK_INTERACTION_RANGE,
Attribute::Bounciness => &Attributes::BOUNCINESS,
Attribute::BurningTime => &Attributes::BURNING_TIME,
Attribute::CameraDistance => &Attributes::CAMERA_DISTANCE,
Attribute::ExplosionKnockbackResistance => &Attributes::EXPLOSION_KNOCKBACK_RESISTANCE,
Attribute::EntityInteractionRange => &Attributes::ENTITY_INTERACTION_RANGE,
Attribute::FallDamageMultiplier => &Attributes::FALL_DAMAGE_MULTIPLIER,
Attribute::FlyingSpeed => &Attributes::FLYING_SPEED,
Attribute::FollowRange => &Attributes::FOLLOW_RANGE,
Attribute::FrictionModifier => &Attributes::FRICTION_MODIFIER,
Attribute::Gravity => &Attributes::GRAVITY,
Attribute::JumpStrength => &Attributes::JUMP_STRENGTH,
Attribute::KnockbackResistance => &Attributes::KNOCKBACK_RESISTANCE,
Attribute::Luck => &Attributes::LUCK,
Attribute::MaxAbsorption => &Attributes::MAX_ABSORPTION,
Attribute::MaxHealth => &Attributes::MAX_HEALTH,
Attribute::MiningEfficiency => &Attributes::MINING_EFFICIENCY,
Attribute::MovementEfficiency => &Attributes::MOVEMENT_EFFICIENCY,
Attribute::MovementSpeed => &Attributes::MOVEMENT_SPEED,
Attribute::NameTagDistance => &Attributes::NAME_TAG_DISTANCE,
Attribute::OxygenBonus => &Attributes::OXYGEN_BONUS,
Attribute::SafeFallDistance => &Attributes::SAFE_FALL_DISTANCE,
Attribute::Scale => &Attributes::SCALE,
Attribute::SneakingSpeed => &Attributes::SNEAKING_SPEED,
Attribute::SpawnReinforcements => &Attributes::SPAWN_REINFORCEMENTS,
Attribute::StepHeight => &Attributes::STEP_HEIGHT,
Attribute::SubmergedMiningSpeed => &Attributes::SUBMERGED_MINING_SPEED,
Attribute::SweepingDamageRatio => &Attributes::SWEEPING_DAMAGE_RATIO,
Attribute::TemptRange => &Attributes::TEMPT_RANGE,
Attribute::WaterMovementEfficiency => &Attributes::WATER_MOVEMENT_EFFICIENCY,
Attribute::WaypointTransmitRange => &Attributes::WAYPOINT_TRANSMIT_RANGE,
Attribute::WaypointReceiveRange => &Attributes::WAYPOINT_RECEIVE_RANGE,
}
}
#[must_use]
pub const fn from_wit_modifier_op(
op: WitModifierOperation,
) -> crate::entity::attributes::ModifierOperation {
match op {
WitModifierOperation::Add => crate::entity::attributes::ModifierOperation::Add,
WitModifierOperation::MultiplyBase => {
crate::entity::attributes::ModifierOperation::MultiplyBase
}
WitModifierOperation::MultiplyTotal => {
crate::entity::attributes::ModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn to_wit_modifier_op(
op: crate::entity::attributes::ModifierOperation,
) -> WitModifierOperation {
match op {
crate::entity::attributes::ModifierOperation::Add => WitModifierOperation::Add,
crate::entity::attributes::ModifierOperation::MultiplyBase => {
WitModifierOperation::MultiplyBase
}
crate::entity::attributes::ModifierOperation::MultiplyTotal => {
WitModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn from_wit_equipment_slot(
slot: WitEquipmentSlot,
) -> pumpkin_data::data_component_impl::EquipmentSlot {
use pumpkin_data::data_component_impl::EquipmentSlot;
match slot {
WitEquipmentSlot::MainHand => EquipmentSlot::MAIN_HAND,
WitEquipmentSlot::OffHand => EquipmentSlot::OFF_HAND,
WitEquipmentSlot::Feet => EquipmentSlot::FEET,
WitEquipmentSlot::Legs => EquipmentSlot::LEGS,
WitEquipmentSlot::Chest => EquipmentSlot::CHEST,
WitEquipmentSlot::Head => EquipmentSlot::HEAD,
WitEquipmentSlot::Body => EquipmentSlot::BODY,
WitEquipmentSlot::Saddle => EquipmentSlot::SADDLE,
}
}
#[must_use]
pub const fn to_wit_damage_type(damage_type: &pumpkin_data::damage::DamageType) -> WitDamageType {
// SAFETY: WIT enum is generated in the same order as the internal enum / id
unsafe { std::mem::transmute(damage_type.id) }
}
#[must_use]
pub fn from_wit_damage_type(wit: WitDamageType) -> pumpkin_data::damage::DamageType {
pumpkin_data::damage::DamageType::from_id(wit as u8)
.unwrap_or(pumpkin_data::damage::DamageType::GENERIC)
}
impl HostEntity for PluginHostState { impl HostEntity for PluginHostState {
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> { async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
@@ -526,300 +411,6 @@ impl HostEntity for PluginHostState {
Ok(()) Ok(())
} }
async fn get_health(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.health.load()))
}
async fn set_health(&mut self, entity: Resource<Entity>, health: f32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.health.store(health);
}
Ok(())
}
async fn get_max_health(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, crate::entity::living::LivingEntity::get_max_health))
}
async fn damage(
&mut self,
entity: Resource<Entity>,
amount: f32,
damage_type: WitDamageType,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.damage(&*entity, amount, from_wit_damage_type(damage_type));
Ok(())
}
async fn is_dead(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_living_entity().map_or_else(
|| entity.get_entity().removal_reason.load().is_some(),
|living| living.dead.load(std::sync::atomic::Ordering::Relaxed),
))
}
async fn get_absorption(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.absorption.load()))
}
async fn set_absorption(
&mut self,
entity: Resource<Entity>,
amount: f32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.absorption.store(amount);
}
Ok(())
}
async fn get_attribute_value(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_value(attribute)
}))
}
async fn get_attribute_base(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_base(attribute)
}))
}
async fn set_attribute_base(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
value: f64,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.set_attribute_base(attribute, value);
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn add_attribute_modifier(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
modifier: WitAttributeModifier,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let internal_mod = crate::entity::attributes::Modifier {
id: modifier.id,
amount: modifier.amount,
operation: from_wit_modifier_op(modifier.operation),
};
living.update_attribute(attribute, |inst| inst.add_or_replace_modifier(internal_mod));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn remove_attribute_modifier(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
id: String,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.update_attribute(attribute, |inst| inst.remove_modifier(&id));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn get_attribute_modifiers(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<Vec<WitAttributeModifier>> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let map = living
.attributes
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inst) = map.get(&attribute.id) {
return Ok(inst
.modifiers
.iter()
.map(|m| WitAttributeModifier {
id: m.id.clone(),
amount: m.amount,
operation: to_wit_modifier_op(m.operation),
})
.collect());
}
}
Ok(Vec::new())
}
async fn reset_attribute(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
{
let mut map = living
.attributes
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.remove(&attribute.id);
};
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn reset_all_attributes(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.reset_effects_and_attributes();
}
Ok(())
}
async fn get_equipment(
&mut self,
entity: Resource<Entity>,
slot: WitEquipmentSlot,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let stack = equipment.get(&slot);
if !stack.is_empty() {
return Ok(Some(
self.add_item_stack(Arc::new(tokio::sync::Mutex::new(stack)))?,
));
}
}
Ok(None)
}
async fn set_equipment(
&mut self,
entity: Resource<Entity>,
slot: WitEquipmentSlot,
stack: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let item_stack = if let Some(stack_res) = stack {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
{
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment.put(&slot, item_stack.clone());
};
living.send_equipment_changes(&[(slot, item_stack)]);
}
Ok(())
}
async fn clear_equipment(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let slots_to_clear: Vec<(
pumpkin_data::data_component_impl::EquipmentSlot,
pumpkin_data::item_stack::ItemStack,
)> = equipment
.equipment
.drain()
.map(|(slot, _)| (slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone()))
.collect();
drop(equipment);
living.send_equipment_changes(&slots_to_clear);
}
Ok(())
}
async fn get_age(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.age
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn set_age(&mut self, entity: Resource<Entity>, age: i32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity
.get_entity()
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
async fn get_fall_distance(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> { async fn get_fall_distance(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
Ok(entity Ok(entity
@@ -1155,144 +746,12 @@ impl HostEntity for PluginHostState {
Ok(crate::entity::breath::MAX_AIR) Ok(crate::entity::breath::MAX_AIR)
} }
async fn send_system_message(
&mut self,
entity: Resource<Entity>,
message: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(player) = entity.get_player() {
let text_res = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::TextComponentResource>(
&Resource::new_own(message.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid text component resource handle"))?;
player.send_system_message(&text_res.provider);
}
Ok(())
}
async fn remove(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> { async fn remove(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
entity.get_entity().remove(); entity.get_entity().remove();
Ok(()) Ok(())
} }
async fn add_ai_goal(
&mut self,
entity: Resource<Entity>,
priority: u8,
goal: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
match goal {
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::Swim => {
mob_entity.add_goal(priority, crate::entity::ai::goal::swim::SwimGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::WanderAround(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::wander_around::WanderAroundGoal::new(f64::from(speed)));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::MeleeAttack(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::melee_attack::MeleeAttackGoal::new(f64::from(speed), false));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAtPlayer(range) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_at_entity::LookAtEntityGoal::new(
std::sync::Weak::<crate::entity::mob::zombie::zombie::ZombieEntity>::new() as std::sync::Weak<dyn crate::entity::mob::Mob>,
&pumpkin_data::entity::EntityType::PLAYER,
range,
0.02,
false,
));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAround => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_around::RandomLookAroundGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::EscapeDanger(speed) => {
mob_entity.add_goal(priority, *crate::entity::ai::goal::escape_danger::EscapeDangerGoal::new(f64::from(speed)));
}
_ => {} // Remaining goals
}
}
Ok(())
}
async fn add_custom_ai_goal(
&mut self,
entity: Resource<Entity>,
priority: u8,
goal_id: u32,
) -> wasmtime::Result<()> {
let Some(plugin) = self.plugin.as_ref().and_then(std::sync::Weak::upgrade) else {
return Err(wasmtime::Error::msg("Plugin not active"));
};
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
mob_entity.add_goal(priority, CustomWasmGoal { plugin, goal_id });
}
Ok(())
}
async fn clear_ai_goals(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().clear_ai_goals(mob);
}
Ok(())
}
async fn set_ai_disabled(
&mut self,
entity: Resource<Entity>,
disabled: bool,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_no_ai(disabled);
}
Ok(())
}
async fn is_ai_disabled(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_mob()
.is_none_or(|mob| mob.get_mob_entity().is_no_ai()))
}
async fn set_target(
&mut self,
entity: Resource<Entity>,
target: Option<Resource<Entity>>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let target_entity = if let Some(t) = target {
Some(entity_from_resource(self, &t)?)
} else {
None
};
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_target(target_entity);
}
Ok(())
}
async fn get_target(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<Entity>>> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob()
&& let Some(target) = mob.get_mob_entity().get_target()
{
return Ok(Some(self.add_entity(target)?));
}
Ok(None)
}
async fn raycast( async fn raycast(
&mut self, &mut self,
entity: Resource<Entity>, entity: Resource<Entity>,
@@ -1429,6 +888,40 @@ impl HostEntity for PluginHostState {
Ok(base_entity.has_custom_data(&namespace, &key)) Ok(base_entity.has_custom_data(&namespace, &key))
} }
async fn as_living(
&mut self,
this: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<WitLivingEntity>>> {
let entity = entity_from_resource(self, &this)?;
if entity.get_living_entity().is_some() {
Ok(Some(self.add_living_entity(entity)?))
} else {
Ok(None)
}
}
async fn as_mob(
&mut self,
this: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<WitMob>>> {
let entity = entity_from_resource(self, &this)?;
if entity.get_mob().is_some() {
Ok(Some(self.add_mob(entity)?))
} else {
Ok(None)
}
}
async fn is_living(&mut self, this: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &this)?;
Ok(entity.get_living_entity().is_some())
}
async fn is_mob(&mut self, this: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &this)?;
Ok(entity.get_mob().is_some())
}
async fn drop(&mut self, rep: Resource<Entity>) -> wasmtime::Result<()> { async fn drop(&mut self, rep: Resource<Entity>) -> wasmtime::Result<()> {
let _ = self let _ = self
.resource_table .resource_table
@@ -1436,161 +929,3 @@ impl HostEntity for PluginHostState {
Ok(()) Ok(())
} }
} }
pub struct CustomWasmGoal {
pub plugin: Arc<WasmPlugin>,
pub goal_id: u32,
}
fn current_mob_entity(mob: &dyn Mob) -> Option<Arc<dyn crate::entity::EntityBase>> {
let entity = mob.get_entity();
entity.world.load().get_entity_by_id(entity.entity_id)
}
impl Goal for CustomWasmGoal {
fn can_start(&mut self, _mob: &dyn Mob) -> bool {
false
}
fn should_continue(&self, _mob: &dyn Mob) -> bool {
false
}
fn start(&mut self, mob: &dyn Mob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_start(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn tick(&mut self, mob: &dyn Mob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_tick(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn stop(&mut self, mob: &dyn Mob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_stop(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
}

View File

@@ -67,12 +67,12 @@ use crate::plugin::{
loader::wasm::wasm_host::{ loader::wasm::wasm_host::{
state::PluginHostState, state::PluginHostState,
wit::v0_1::{ wit::v0_1::{
entity::{from_wit_damage_type, to_wit_damage_type},
events::{ events::{
ToFromWasmEvent, cleanup_event, consume_player, consume_text_component, ToFromWasmEvent, cleanup_event, consume_player, consume_text_component,
consume_world, from_wasm_block_position, from_wasm_position, consume_world, from_wasm_block_position, from_wasm_position,
to_wasm_block_position, to_wasm_position, to_wasm_block_position, to_wasm_position,
}, },
living_entity::{from_wit_damage_type, to_wit_damage_type},
pumpkin::plugin::event::{ pumpkin::plugin::event::{
AreaEffectCloudApplyEventData, ArrowBodyCountChangeEventData, AreaEffectCloudApplyEventData, ArrowBodyCountChangeEventData,
BatToggleSleepEventData, CreatureSpawnEventData, CreeperPowerEventData, BatToggleSleepEventData, CreatureSpawnEventData, CreeperPowerEventData,

View File

@@ -118,6 +118,23 @@ impl gui::HostGui for PluginHostState {
self.add_gui(gui) self.add_gui(gui)
} }
async fn get_inventory(
&mut self,
res: Resource<Gui>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::Inventory,
>,
>{
let inv = {
let gui = self.get_gui_res(&res)?.provider.lock().await;
gui.inventory.clone()
};
self.add_inventory(
crate::plugin::loader::wasm::wasm_host::state::InventoryProvider::Generic(inv),
)
}
async fn set_item( async fn set_item(
&mut self, &mut self,
res: Resource<Gui>, res: Resource<Gui>,

View File

@@ -0,0 +1,568 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use wasmtime::component::Resource;
use crate::entity::player::Player;
use crate::plugin::loader::wasm::wasm_host::{
state::{InventoryProvider, InventoryResource, PlayerInventoryResource, PluginHostState},
wit::v0_1::pumpkin::plugin::{
common::Hand as WitHand,
inventory::{
Host as InventoryHost, HostInventory, HostPlayerInventory, Inventory as WitInventory,
PlayerInventory as WitPlayerInventory,
},
item_stack::ItemStack as WitHostItemStack,
},
};
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_protocol::java::client::play::CSetContainerSlot;
use pumpkin_world::inventory::{Clearable, Inventory};
const fn from_wasm_hand(hand: WitHand) -> pumpkin_util::Hand {
match hand {
WitHand::Right => pumpkin_util::Hand::Right,
WitHand::Left => pumpkin_util::Hand::Left,
}
}
impl InventoryHost for PluginHostState {}
impl PluginHostState {
fn get_inventory_provider(
&self,
res: &Resource<WitInventory>,
) -> wasmtime::Result<InventoryProvider> {
let r = self
.resource_table
.get::<InventoryResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)?;
Ok(r.provider.clone())
}
fn get_player_inventory_player(
&self,
res: &Resource<WitPlayerInventory>,
) -> wasmtime::Result<Arc<Player>> {
let r = self
.resource_table
.get::<PlayerInventoryResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)?;
Ok(r.provider.clone())
}
}
impl HostInventory for PluginHostState {
async fn get_size(&mut self, res: Resource<WitInventory>) -> wasmtime::Result<u32> {
let provider = self.get_inventory_provider(&res)?;
let size = match provider {
InventoryProvider::Generic(inv) => inv.size() as u32,
InventoryProvider::PlayerMain(_) => 36,
InventoryProvider::PlayerEnderChest(_) => 27,
};
Ok(size)
}
async fn is_empty(&mut self, res: Resource<WitInventory>) -> wasmtime::Result<bool> {
let provider = self.get_inventory_provider(&res)?;
let empty = match provider {
InventoryProvider::Generic(inv) => inv.is_empty(),
InventoryProvider::PlayerMain(player) => {
let inv = player.inventory();
(0..36).all(|slot| inv.get_stack(slot).is_empty())
}
InventoryProvider::PlayerEnderChest(player) => {
let ec = player.ender_chest_inventory();
(0..27).all(|slot| ec.get_stack(slot).is_empty())
}
};
Ok(empty)
}
async fn get_item(
&mut self,
res: Resource<WitInventory>,
slot: u32,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let provider = self.get_inventory_provider(&res)?;
let stack = match provider {
InventoryProvider::Generic(inv) => {
let s = inv.get_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
}
InventoryProvider::PlayerMain(player) => {
if slot < 36 {
let s = player.inventory().get_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
InventoryProvider::PlayerEnderChest(player) => {
if slot < 27 {
let s = player.ender_chest_inventory().get_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
};
if let Some(stack) = stack {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
} else {
Ok(None)
}
}
async fn set_item(
&mut self,
res: Resource<WitInventory>,
slot: u32,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
let provider = self.get_inventory_provider(&res)?;
match provider {
InventoryProvider::Generic(inv) => {
inv.set_stack(slot as usize, stack);
}
InventoryProvider::PlayerMain(player) => {
if slot < 36 {
player.inventory().set_stack(slot as usize, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer);
player.send_client_packet(&packet).await;
}
}
InventoryProvider::PlayerEnderChest(player) => {
if slot < 27 {
player
.ender_chest_inventory()
.set_stack(slot as usize, stack);
}
}
}
Ok(())
}
async fn remove_item(
&mut self,
res: Resource<WitInventory>,
slot: u32,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let provider = self.get_inventory_provider(&res)?;
let old_stack = match provider {
InventoryProvider::Generic(inv) => {
let s = inv.remove_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
}
InventoryProvider::PlayerMain(player) => {
if slot < 36 {
let s = player.inventory().get_stack(slot as usize);
player.inventory().set_stack(
slot as usize,
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
let empty_serializer = ItemStackSerializer::from(
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &empty_serializer);
player.send_client_packet(&packet).await;
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
InventoryProvider::PlayerEnderChest(player) => {
if slot < 27 {
let s = player.ender_chest_inventory().get_stack(slot as usize);
player.ender_chest_inventory().set_stack(
slot as usize,
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
};
if let Some(stack) = old_stack {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
} else {
Ok(None)
}
}
async fn clear(&mut self, res: Resource<WitInventory>) -> wasmtime::Result<()> {
let provider = self.get_inventory_provider(&res)?;
match provider {
InventoryProvider::Generic(inv) => {
inv.clear();
}
InventoryProvider::PlayerMain(player) => {
for slot in 0..36 {
player
.inventory()
.set_stack(slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone());
let empty_serializer = ItemStackSerializer::from(
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &empty_serializer);
player.send_client_packet(&packet).await;
}
}
InventoryProvider::PlayerEnderChest(player) => {
player.ender_chest_inventory().clear();
}
}
Ok(())
}
async fn get_all_items(
&mut self,
res: Resource<WitInventory>,
) -> wasmtime::Result<Vec<Option<Resource<WitHostItemStack>>>> {
let size = self.get_size(Resource::new_own(res.rep())).await?;
let mut items = Vec::with_capacity(size as usize);
for slot in 0..size {
let item = self.get_item(Resource::new_own(res.rep()), slot).await?;
items.push(item);
}
Ok(items)
}
async fn set_all_items(
&mut self,
res: Resource<WitInventory>,
items: Vec<Option<Resource<WitHostItemStack>>>,
) -> wasmtime::Result<()> {
let size = self.get_size(Resource::new_own(res.rep())).await?;
for (slot, item) in items.into_iter().take(size as usize).enumerate() {
self.set_item(Resource::new_own(res.rep()), slot as u32, item)
.await?;
}
Ok(())
}
async fn count_item(
&mut self,
res: Resource<WitInventory>,
item_id: String,
) -> wasmtime::Result<u32> {
let provider = self.get_inventory_provider(&res)?;
let mut total = 0u32;
let is_matching =
|key: &str| key == item_id || key.strip_prefix("minecraft:") == Some(&item_id);
match provider {
InventoryProvider::Generic(inv) => {
for slot in 0..inv.size() {
let s = inv.get_stack(slot);
if !s.is_empty() && is_matching(s.item.registry_key) {
total += u32::from(s.item_count);
}
}
}
InventoryProvider::PlayerMain(player) => {
let inv = player.inventory();
for slot in 0..36 {
let s = inv.get_stack(slot);
if !s.is_empty() && is_matching(s.item.registry_key) {
total += u32::from(s.item_count);
}
}
}
InventoryProvider::PlayerEnderChest(player) => {
let ec = player.ender_chest_inventory();
for slot in 0..27 {
let s = ec.get_stack(slot);
if !s.is_empty() && is_matching(s.item.registry_key) {
total += u32::from(s.item_count);
}
}
}
}
Ok(total)
}
async fn contains_item(
&mut self,
res: Resource<WitInventory>,
item_id: String,
) -> wasmtime::Result<bool> {
let count = self.count_item(res, item_id).await?;
Ok(count > 0)
}
async fn drop(&mut self, rep: Resource<WitInventory>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<InventoryResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostPlayerInventory for PluginHostState {
async fn as_inventory(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Resource<WitInventory>> {
let player = self.get_player_inventory_player(&res)?;
self.add_inventory(InventoryProvider::PlayerMain(player))
}
async fn get_item_in_hand(
&mut self,
res: Resource<WitPlayerInventory>,
hand: WitHand,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let hand = from_wasm_hand(hand);
let stack = player.inventory().get_stack_in_hand(hand);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_item_in_hand(
&mut self,
res: Resource<WitPlayerInventory>,
hand: WitHand,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
let hand = from_wasm_hand(hand);
let slot = match hand {
pumpkin_util::Hand::Right => player.inventory().get_selected_slot() as usize,
pumpkin_util::Hand::Left => PlayerInventory::OFF_HAND_SLOT,
};
player.inventory().set_stack(slot, stack.clone());
// Sync to client
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_selected_slot(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<u8> {
let player = self.get_player_inventory_player(&res)?;
Ok(player.inventory().get_selected_slot())
}
async fn set_selected_slot(
&mut self,
res: Resource<WitPlayerInventory>,
slot: u8,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
if slot < 9 {
player.inventory().set_selected_slot(slot);
}
Ok(())
}
async fn get_helmet(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(39);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_helmet(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(39, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 5, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_chestplate(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(38);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_chestplate(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(38, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 6, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_leggings(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(37);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_leggings(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(37, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 7, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_boots(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(36);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_boots(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(36, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 8, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_off_hand(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(40);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_off_hand(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(40, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 45, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn clear_armor(&mut self, res: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
self.set_helmet(Resource::new_own(res.rep()), None).await?;
self.set_chestplate(Resource::new_own(res.rep()), None)
.await?;
self.set_leggings(Resource::new_own(res.rep()), None)
.await?;
self.set_boots(Resource::new_own(res.rep()), None).await?;
Ok(())
}
async fn clear_main(&mut self, res: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
let inv = self.as_inventory(res).await?;
self.clear(inv).await
}
async fn clear_all(&mut self, res: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
self.clear_main(Resource::new_own(res.rep())).await?;
self.clear_armor(Resource::new_own(res.rep())).await?;
self.set_off_hand(Resource::new_own(res.rep()), None)
.await?;
Ok(())
}
async fn drop(&mut self, rep: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<PlayerInventoryResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@@ -0,0 +1,502 @@
use std::sync::Arc;
use wasmtime::component::Resource;
use crate::plugin::loader::wasm::wasm_host::{
state::{LivingEntityResource, PluginHostState},
wit::v0_1::pumpkin::plugin::{
attributes::{
Attribute, AttributeModifier as WitAttributeModifier,
ModifierOperation as WitModifierOperation,
},
damage_types::DamageType as WitDamageType,
item_stack::ItemStack as WitHostItemStack,
text::TextComponent,
world::{
Entity, EquipmentSlot as WitEquipmentSlot, HostLivingEntity,
LivingEntity as WitLivingEntity, Mob as WitMob,
},
},
};
pub fn living_entity_from_resource(
state: &PluginHostState,
entity: &Resource<WitLivingEntity>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
state
.resource_table
.get::<LivingEntityResource>(&Resource::new_own(entity.rep()))
.map_err(|_| wasmtime::Error::msg("invalid living entity resource handle"))
.map(|resource| resource.provider.clone())
}
#[must_use]
pub const fn from_wit_attribute(attr: Attribute) -> &'static pumpkin_data::attributes::Attributes {
use pumpkin_data::attributes::Attributes;
match attr {
Attribute::AirDragModifier => &Attributes::AIR_DRAG_MODIFIER,
Attribute::Armor => &Attributes::ARMOR,
Attribute::ArmorToughness => &Attributes::ARMOR_TOUGHNESS,
Attribute::AttackDamage => &Attributes::ATTACK_DAMAGE,
Attribute::AttackKnockback => &Attributes::ATTACK_KNOCKBACK,
Attribute::AttackSpeed => &Attributes::ATTACK_SPEED,
Attribute::BelowNameDistance => &Attributes::BELOW_NAME_DISTANCE,
Attribute::BlockBreakSpeed => &Attributes::BLOCK_BREAK_SPEED,
Attribute::BlockInteractionRange => &Attributes::BLOCK_INTERACTION_RANGE,
Attribute::Bounciness => &Attributes::BOUNCINESS,
Attribute::BurningTime => &Attributes::BURNING_TIME,
Attribute::CameraDistance => &Attributes::CAMERA_DISTANCE,
Attribute::ExplosionKnockbackResistance => &Attributes::EXPLOSION_KNOCKBACK_RESISTANCE,
Attribute::EntityInteractionRange => &Attributes::ENTITY_INTERACTION_RANGE,
Attribute::FallDamageMultiplier => &Attributes::FALL_DAMAGE_MULTIPLIER,
Attribute::FlyingSpeed => &Attributes::FLYING_SPEED,
Attribute::FollowRange => &Attributes::FOLLOW_RANGE,
Attribute::FrictionModifier => &Attributes::FRICTION_MODIFIER,
Attribute::Gravity => &Attributes::GRAVITY,
Attribute::JumpStrength => &Attributes::JUMP_STRENGTH,
Attribute::KnockbackResistance => &Attributes::KNOCKBACK_RESISTANCE,
Attribute::Luck => &Attributes::LUCK,
Attribute::MaxAbsorption => &Attributes::MAX_ABSORPTION,
Attribute::MaxHealth => &Attributes::MAX_HEALTH,
Attribute::MiningEfficiency => &Attributes::MINING_EFFICIENCY,
Attribute::MovementEfficiency => &Attributes::MOVEMENT_EFFICIENCY,
Attribute::MovementSpeed => &Attributes::MOVEMENT_SPEED,
Attribute::NameTagDistance => &Attributes::NAME_TAG_DISTANCE,
Attribute::OxygenBonus => &Attributes::OXYGEN_BONUS,
Attribute::SafeFallDistance => &Attributes::SAFE_FALL_DISTANCE,
Attribute::Scale => &Attributes::SCALE,
Attribute::SneakingSpeed => &Attributes::SNEAKING_SPEED,
Attribute::SpawnReinforcements => &Attributes::SPAWN_REINFORCEMENTS,
Attribute::StepHeight => &Attributes::STEP_HEIGHT,
Attribute::SubmergedMiningSpeed => &Attributes::SUBMERGED_MINING_SPEED,
Attribute::SweepingDamageRatio => &Attributes::SWEEPING_DAMAGE_RATIO,
Attribute::TemptRange => &Attributes::TEMPT_RANGE,
Attribute::WaterMovementEfficiency => &Attributes::WATER_MOVEMENT_EFFICIENCY,
Attribute::WaypointTransmitRange => &Attributes::WAYPOINT_TRANSMIT_RANGE,
Attribute::WaypointReceiveRange => &Attributes::WAYPOINT_RECEIVE_RANGE,
}
}
#[must_use]
pub const fn from_wit_modifier_op(
op: WitModifierOperation,
) -> crate::entity::attributes::ModifierOperation {
match op {
WitModifierOperation::Add => crate::entity::attributes::ModifierOperation::Add,
WitModifierOperation::MultiplyBase => {
crate::entity::attributes::ModifierOperation::MultiplyBase
}
WitModifierOperation::MultiplyTotal => {
crate::entity::attributes::ModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn to_wit_modifier_op(
op: crate::entity::attributes::ModifierOperation,
) -> WitModifierOperation {
match op {
crate::entity::attributes::ModifierOperation::Add => WitModifierOperation::Add,
crate::entity::attributes::ModifierOperation::MultiplyBase => {
WitModifierOperation::MultiplyBase
}
crate::entity::attributes::ModifierOperation::MultiplyTotal => {
WitModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn from_wit_equipment_slot(
slot: WitEquipmentSlot,
) -> pumpkin_data::data_component_impl::EquipmentSlot {
use pumpkin_data::data_component_impl::EquipmentSlot;
match slot {
WitEquipmentSlot::MainHand => EquipmentSlot::MAIN_HAND,
WitEquipmentSlot::OffHand => EquipmentSlot::OFF_HAND,
WitEquipmentSlot::Feet => EquipmentSlot::FEET,
WitEquipmentSlot::Legs => EquipmentSlot::LEGS,
WitEquipmentSlot::Chest => EquipmentSlot::CHEST,
WitEquipmentSlot::Head => EquipmentSlot::HEAD,
WitEquipmentSlot::Body => EquipmentSlot::BODY,
WitEquipmentSlot::Saddle => EquipmentSlot::SADDLE,
}
}
#[must_use]
pub const fn to_wit_damage_type(damage_type: &pumpkin_data::damage::DamageType) -> WitDamageType {
// SAFETY: WIT enum is generated in the same order as the internal enum / id
unsafe { std::mem::transmute(damage_type.id) }
}
#[must_use]
pub fn from_wit_damage_type(wit: WitDamageType) -> pumpkin_data::damage::DamageType {
pumpkin_data::damage::DamageType::from_id(wit as u8)
.unwrap_or(pumpkin_data::damage::DamageType::GENERIC)
}
impl HostLivingEntity for PluginHostState {
async fn as_entity(
&mut self,
this: Resource<WitLivingEntity>,
) -> wasmtime::Result<Resource<Entity>> {
let entity = living_entity_from_resource(self, &this)?;
self.add_entity(entity)
}
async fn as_mob(
&mut self,
this: Resource<WitLivingEntity>,
) -> wasmtime::Result<Option<Resource<WitMob>>> {
let entity = living_entity_from_resource(self, &this)?;
if entity.get_mob().is_some() {
Ok(Some(self.add_mob(entity)?))
} else {
Ok(None)
}
}
async fn is_mob(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<bool> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity.get_mob().is_some())
}
async fn get_health(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<f32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.health.load()))
}
async fn set_health(
&mut self,
this: Resource<WitLivingEntity>,
health: f32,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.health.store(health);
}
Ok(())
}
async fn get_max_health(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<f32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity
.get_living_entity()
.map_or(0.0, crate::entity::living::LivingEntity::get_max_health))
}
async fn set_max_health(
&mut self,
this: Resource<WitLivingEntity>,
max_health: f32,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.set_max_health(max_health);
}
Ok(())
}
async fn damage(
&mut self,
this: Resource<WitLivingEntity>,
amount: f32,
damage_type: WitDamageType,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
entity.damage(&*entity, amount, from_wit_damage_type(damage_type));
Ok(())
}
async fn is_dead(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<bool> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity.get_living_entity().map_or_else(
|| entity.get_entity().removal_reason.load().is_some(),
|living| living.dead.load(std::sync::atomic::Ordering::Relaxed),
))
}
async fn get_absorption(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<f32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.absorption.load()))
}
async fn set_absorption(
&mut self,
this: Resource<WitLivingEntity>,
amount: f32,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.absorption.store(amount);
}
Ok(())
}
async fn get_attribute_value(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_value(attribute)
}))
}
async fn get_attribute_base(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_base(attribute)
}))
}
async fn set_attribute_base(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
value: f64,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.set_attribute_base(attribute, value);
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn add_attribute_modifier(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
modifier: WitAttributeModifier,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let internal_mod = crate::entity::attributes::Modifier {
id: modifier.id,
amount: modifier.amount,
operation: from_wit_modifier_op(modifier.operation),
};
living.update_attribute(attribute, |inst| inst.add_or_replace_modifier(internal_mod));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn remove_attribute_modifier(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
id: String,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.update_attribute(attribute, |inst| inst.remove_modifier(&id));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn get_attribute_modifiers(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<Vec<WitAttributeModifier>> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let map = living
.attributes
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inst) = map.get(&attribute.id) {
return Ok(inst
.modifiers
.iter()
.map(|m| WitAttributeModifier {
id: m.id.clone(),
amount: m.amount,
operation: to_wit_modifier_op(m.operation),
})
.collect());
}
}
Ok(Vec::new())
}
async fn reset_attribute(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
{
let mut map = living
.attributes
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.remove(&attribute.id);
};
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn reset_all_attributes(
&mut self,
this: Resource<WitLivingEntity>,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.reset_effects_and_attributes();
}
Ok(())
}
async fn get_equipment(
&mut self,
this: Resource<WitLivingEntity>,
slot: WitEquipmentSlot,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let stack = equipment.get(&slot);
if !stack.is_empty() {
return Ok(Some(
self.add_item_stack(Arc::new(tokio::sync::Mutex::new(stack)))?,
));
}
}
Ok(None)
}
async fn set_equipment(
&mut self,
this: Resource<WitLivingEntity>,
slot: WitEquipmentSlot,
stack: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let item_stack = if let Some(stack_res) = stack {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
{
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment.put(&slot, item_stack.clone());
};
living.send_equipment_changes(&[(slot, item_stack)]);
}
Ok(())
}
async fn clear_equipment(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let slots_to_clear: Vec<(
pumpkin_data::data_component_impl::EquipmentSlot,
pumpkin_data::item_stack::ItemStack,
)> = equipment
.equipment
.drain()
.map(|(slot, _)| (slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone()))
.collect();
drop(equipment);
living.send_equipment_changes(&slots_to_clear);
}
Ok(())
}
async fn get_age(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<i32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity.get_living_entity().map_or(0, |living| {
living.entity.age.load(std::sync::atomic::Ordering::Relaxed)
}))
}
async fn set_age(&mut self, this: Resource<WitLivingEntity>, age: i32) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living
.entity
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
}
Ok(())
}
async fn send_system_message(
&mut self,
this: Resource<WitLivingEntity>,
message: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(player) = entity.get_player() {
let text_res = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::TextComponentResource>(
&Resource::new_own(message.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid text component resource handle"))?;
player.send_system_message(&text_res.provider);
}
Ok(())
}
async fn drop(&mut self, rep: Resource<WitLivingEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<LivingEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@@ -0,0 +1,941 @@
use std::sync::Arc;
use wasmtime::component::Resource;
use pumpkin_util::math::vector3::Vector3;
use crate::entity::ai::goal::Goal;
use crate::entity::mob::Mob as InternalMob;
use crate::entity::passive::tamable::TamableAnimal;
use crate::plugin::loader::wasm::wasm_host::{
PluginInstance, WasmPlugin,
state::{MobResource, PluginHostState},
wit::v0_1::entity::entity_from_resource,
wit::v0_1::pumpkin::plugin::{
common::Position,
uuid::Uuid,
world::{
AgeableData as WitAgeableData, BlockDirection as WitBlockDirection,
CatData as WitCatData, CreeperData as WitCreeperData, DyeColor as WitDyeColor,
EndermanData as WitEndermanData, Entity, FoxData as WitFoxData, HostMob,
IronGolemData as WitIronGolemData, LivingEntity as WitLivingEntity, Mob as WitMob,
MobData as WitMobData, PathNodeType as WitPathNodeType, SheepData as WitSheepData,
ShulkerData as WitShulkerData, SlimeData as WitSlimeData,
VillagerData as WitVillagerData, VillagerProfession as WitVillagerProfession,
WolfData as WitWolfData, ZombieData as WitZombieData,
},
},
wit::v0_1::uuid::UuidExt,
};
pub fn mob_from_resource(
state: &PluginHostState,
entity: &Resource<WitMob>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
state
.resource_table
.get::<MobResource>(&Resource::new_own(entity.rep()))
.map_err(|_| wasmtime::Error::msg("invalid mob resource handle"))
.map(|resource| resource.provider.clone())
}
#[must_use]
pub const fn from_wit_path_node_type(
t: WitPathNodeType,
) -> crate::entity::ai::pathfinder::node::PathType {
use crate::entity::ai::pathfinder::node::PathType;
match t {
WitPathNodeType::Blocked => PathType::Blocked,
WitPathNodeType::Open => PathType::Open,
WitPathNodeType::Walkable => PathType::Walkable,
WitPathNodeType::WalkableDoor => PathType::WalkableDoor,
WitPathNodeType::Trapdoor => PathType::Trapdoor,
WitPathNodeType::PowderSnow => PathType::PowderSnow,
WitPathNodeType::DangerPowderSnow => PathType::DangerPowderSnow,
WitPathNodeType::Fence => PathType::Fence,
WitPathNodeType::Lava => PathType::Lava,
WitPathNodeType::Water => PathType::Water,
WitPathNodeType::WaterBorder => PathType::WaterBorder,
WitPathNodeType::Rail => PathType::Rail,
WitPathNodeType::UnpassableRail => PathType::UnpassableRail,
WitPathNodeType::DangerFire => PathType::DangerFire,
WitPathNodeType::DamageFire => PathType::DamageFire,
WitPathNodeType::DangerOther => PathType::DangerOther,
WitPathNodeType::DamageOther => PathType::DamageOther,
WitPathNodeType::DoorOpen => PathType::DoorOpen,
WitPathNodeType::DoorWoodClosed => PathType::DoorWoodClosed,
WitPathNodeType::DoorIronClosed => PathType::DoorIronClosed,
WitPathNodeType::Breach => PathType::Breach,
WitPathNodeType::Leaves => PathType::Leaves,
WitPathNodeType::StickyHoney => PathType::StickyHoney,
WitPathNodeType::Cocoa => PathType::Cocoa,
WitPathNodeType::DamageCautious => PathType::DamageCautious,
WitPathNodeType::DangerTrapdoor => PathType::DangerTrapdoor,
}
}
#[must_use]
pub const fn to_wit_dye_color(color: u8) -> WitDyeColor {
match color {
0 => WitDyeColor::White,
1 => WitDyeColor::Orange,
2 => WitDyeColor::Magenta,
3 => WitDyeColor::LightBlue,
4 => WitDyeColor::Yellow,
5 => WitDyeColor::Lime,
6 => WitDyeColor::Pink,
7 => WitDyeColor::Gray,
8 => WitDyeColor::LightGray,
9 => WitDyeColor::Cyan,
10 => WitDyeColor::Purple,
11 => WitDyeColor::Blue,
12 => WitDyeColor::Brown,
13 => WitDyeColor::Green,
14 => WitDyeColor::Red,
_ => WitDyeColor::Black,
}
}
#[must_use]
pub const fn from_wit_dye_color(color: WitDyeColor) -> u8 {
match color {
WitDyeColor::White => 0,
WitDyeColor::Orange => 1,
WitDyeColor::Magenta => 2,
WitDyeColor::LightBlue => 3,
WitDyeColor::Yellow => 4,
WitDyeColor::Lime => 5,
WitDyeColor::Pink => 6,
WitDyeColor::Gray => 7,
WitDyeColor::LightGray => 8,
WitDyeColor::Cyan => 9,
WitDyeColor::Purple => 10,
WitDyeColor::Blue => 11,
WitDyeColor::Brown => 12,
WitDyeColor::Green => 13,
WitDyeColor::Red => 14,
WitDyeColor::Black => 15,
}
}
#[must_use]
pub const fn to_wit_villager_profession(prof_id: i32) -> WitVillagerProfession {
let prof = match pumpkin_data::villager::VillagerProfession::from_i32(prof_id) {
Some(p) => p,
None => pumpkin_data::villager::VillagerProfession::None,
};
match prof {
pumpkin_data::villager::VillagerProfession::None => WitVillagerProfession::None,
pumpkin_data::villager::VillagerProfession::Armorer => WitVillagerProfession::Armorer,
pumpkin_data::villager::VillagerProfession::Butcher => WitVillagerProfession::Butcher,
pumpkin_data::villager::VillagerProfession::Cartographer => {
WitVillagerProfession::Cartographer
}
pumpkin_data::villager::VillagerProfession::Cleric => WitVillagerProfession::Cleric,
pumpkin_data::villager::VillagerProfession::Farmer => WitVillagerProfession::Farmer,
pumpkin_data::villager::VillagerProfession::Fisherman => WitVillagerProfession::Fisherman,
pumpkin_data::villager::VillagerProfession::Fletcher => WitVillagerProfession::Fletcher,
pumpkin_data::villager::VillagerProfession::Leatherworker => {
WitVillagerProfession::Leatherworker
}
pumpkin_data::villager::VillagerProfession::Librarian => WitVillagerProfession::Librarian,
pumpkin_data::villager::VillagerProfession::Mason => WitVillagerProfession::Mason,
pumpkin_data::villager::VillagerProfession::Nitwit => WitVillagerProfession::Nitwit,
pumpkin_data::villager::VillagerProfession::Shepherd => WitVillagerProfession::Shepherd,
pumpkin_data::villager::VillagerProfession::Toolsmith => WitVillagerProfession::Toolsmith,
pumpkin_data::villager::VillagerProfession::Weaponsmith => {
WitVillagerProfession::Weaponsmith
}
}
}
#[must_use]
pub const fn from_wit_villager_profession(
prof: WitVillagerProfession,
) -> pumpkin_data::villager::VillagerProfession {
match prof {
WitVillagerProfession::None => pumpkin_data::villager::VillagerProfession::None,
WitVillagerProfession::Armorer => pumpkin_data::villager::VillagerProfession::Armorer,
WitVillagerProfession::Butcher => pumpkin_data::villager::VillagerProfession::Butcher,
WitVillagerProfession::Cartographer => {
pumpkin_data::villager::VillagerProfession::Cartographer
}
WitVillagerProfession::Cleric => pumpkin_data::villager::VillagerProfession::Cleric,
WitVillagerProfession::Farmer => pumpkin_data::villager::VillagerProfession::Farmer,
WitVillagerProfession::Fisherman => pumpkin_data::villager::VillagerProfession::Fisherman,
WitVillagerProfession::Fletcher => pumpkin_data::villager::VillagerProfession::Fletcher,
WitVillagerProfession::Leatherworker => {
pumpkin_data::villager::VillagerProfession::Leatherworker
}
WitVillagerProfession::Librarian => pumpkin_data::villager::VillagerProfession::Librarian,
WitVillagerProfession::Mason => pumpkin_data::villager::VillagerProfession::Mason,
WitVillagerProfession::Nitwit => pumpkin_data::villager::VillagerProfession::Nitwit,
WitVillagerProfession::Shepherd => pumpkin_data::villager::VillagerProfession::Shepherd,
WitVillagerProfession::Toolsmith => pumpkin_data::villager::VillagerProfession::Toolsmith,
WitVillagerProfession::Weaponsmith => {
pumpkin_data::villager::VillagerProfession::Weaponsmith
}
}
}
#[must_use]
pub const fn to_wit_block_direction(dir: pumpkin_data::BlockDirection) -> WitBlockDirection {
match dir {
pumpkin_data::BlockDirection::Down => WitBlockDirection::Down,
pumpkin_data::BlockDirection::Up => WitBlockDirection::Up,
pumpkin_data::BlockDirection::North => WitBlockDirection::North,
pumpkin_data::BlockDirection::South => WitBlockDirection::South,
pumpkin_data::BlockDirection::West => WitBlockDirection::West,
pumpkin_data::BlockDirection::East => WitBlockDirection::East,
}
}
#[must_use]
pub const fn from_wit_block_direction(dir: WitBlockDirection) -> pumpkin_data::BlockDirection {
match dir {
WitBlockDirection::Down => pumpkin_data::BlockDirection::Down,
WitBlockDirection::Up => pumpkin_data::BlockDirection::Up,
WitBlockDirection::North => pumpkin_data::BlockDirection::North,
WitBlockDirection::South => pumpkin_data::BlockDirection::South,
WitBlockDirection::West => pumpkin_data::BlockDirection::West,
WitBlockDirection::East => pumpkin_data::BlockDirection::East,
}
}
pub struct CustomWasmGoal {
pub plugin: Arc<WasmPlugin>,
pub goal_id: u32,
}
fn current_mob_entity(mob: &dyn InternalMob) -> Option<Arc<dyn crate::entity::EntityBase>> {
let entity = mob.get_entity();
entity.world.load().get_entity_by_id(entity.entity_id)
}
impl Goal for CustomWasmGoal {
fn can_start(&mut self, _mob: &dyn InternalMob) -> bool {
false
}
fn should_continue(&self, _mob: &dyn InternalMob) -> bool {
false
}
fn start(&mut self, mob: &dyn InternalMob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_start(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn tick(&mut self, mob: &dyn InternalMob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_tick(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn stop(&mut self, mob: &dyn InternalMob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_stop(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
}
impl HostMob for PluginHostState {
async fn as_entity(&mut self, this: Resource<WitMob>) -> wasmtime::Result<Resource<Entity>> {
let entity = mob_from_resource(self, &this)?;
self.add_entity(entity)
}
async fn as_living(
&mut self,
this: Resource<WitMob>,
) -> wasmtime::Result<Resource<WitLivingEntity>> {
let entity = mob_from_resource(self, &this)?;
self.add_living_entity(entity)
}
async fn add_ai_goal(
&mut self,
this: Resource<WitMob>,
priority: u8,
goal: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
match goal {
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::Swim => {
mob_entity.add_goal(priority, crate::entity::ai::goal::swim::SwimGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::WanderAround(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::wander_around::WanderAroundGoal::new(f64::from(speed)));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::MeleeAttack(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::melee_attack::MeleeAttackGoal::new(f64::from(speed), false));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAtPlayer(range) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_at_entity::LookAtEntityGoal::new(
std::sync::Weak::<crate::entity::mob::zombie::zombie::ZombieEntity>::new() as std::sync::Weak<dyn crate::entity::mob::Mob>,
&pumpkin_data::entity::EntityType::PLAYER,
range,
0.02,
false,
));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAround => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_around::RandomLookAroundGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::EscapeDanger(speed) => {
mob_entity.add_goal(priority, *crate::entity::ai::goal::escape_danger::EscapeDangerGoal::new(f64::from(speed)));
}
_ => {}
}
}
Ok(())
}
async fn add_custom_ai_goal(
&mut self,
this: Resource<WitMob>,
priority: u8,
goal_id: u32,
) -> wasmtime::Result<()> {
let Some(plugin) = self.plugin.as_ref().and_then(std::sync::Weak::upgrade) else {
return Err(wasmtime::Error::msg("Plugin not active"));
};
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
mob_entity.add_goal(priority, CustomWasmGoal { plugin, goal_id });
}
Ok(())
}
async fn clear_ai_goals(&mut self, this: Resource<WitMob>) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().clear_ai_goals(mob);
}
Ok(())
}
async fn set_ai_disabled(
&mut self,
this: Resource<WitMob>,
disabled: bool,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_no_ai(disabled);
}
Ok(())
}
async fn is_ai_disabled(&mut self, this: Resource<WitMob>) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity
.get_mob()
.is_none_or(|mob| mob.get_mob_entity().is_no_ai()))
}
async fn set_target(
&mut self,
this: Resource<WitMob>,
target: Option<Resource<Entity>>,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
let target_entity = target.map(|t| entity_from_resource(self, &t)).transpose()?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_target(target_entity);
}
Ok(())
}
async fn get_target(
&mut self,
this: Resource<WitMob>,
) -> wasmtime::Result<Option<Resource<Entity>>> {
let entity = mob_from_resource(self, &this)?;
if let Some(target) = entity
.get_mob()
.and_then(|mob| mob.get_mob_entity().get_target())
{
return Ok(Some(self.add_entity(target)?));
}
Ok(None)
}
async fn navigate_to_pos(
&mut self,
this: Resource<WitMob>,
pos: Position,
speed: f64,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_some_and(|mob| {
let mob_pos = entity.get_entity().pos.load();
let dest = Vector3::new(pos.0, pos.1, pos.2);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_progress(crate::entity::ai::pathfinder::NavigatorGoal::new(
mob_pos, dest, speed,
));
true
}))
}
async fn navigate_to_entity(
&mut self,
this: Resource<WitMob>,
target: Resource<Entity>,
speed: f64,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
let target_entity = entity_from_resource(self, &target)?;
Ok(entity.get_mob().is_some_and(|mob| {
let mob_pos = entity.get_entity().pos.load();
let target_pos = target_entity.get_entity().pos.load();
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_progress(crate::entity::ai::pathfinder::NavigatorGoal::new(
mob_pos, target_pos, speed,
));
mob.get_mob_entity()
.look_control
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.look_at_entity(mob, &target_entity);
true
}))
}
async fn stop_navigation(&mut self, this: Resource<WitMob>) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.stop();
}
Ok(())
}
async fn is_navigating(&mut self, this: Resource<WitMob>) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_some_and(|mob| {
let is_idle = mob
.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_idle
.load(std::sync::atomic::Ordering::Relaxed);
!is_idle
}))
}
async fn has_reached_destination(&mut self, this: Resource<WitMob>) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_none_or(|mob| {
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_idle
.load(std::sync::atomic::Ordering::Relaxed)
}))
}
async fn set_navigation_speed(
&mut self,
this: Resource<WitMob>,
speed: f64,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_speed(speed);
}
Ok(())
}
async fn can_reach(
&mut self,
this: Resource<WitMob>,
pos: Position,
max_distance: f32,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_some_and(|mob| {
let living = &mob.get_mob_entity().living_entity;
let dest = Vector3::new(pos.0, pos.1, pos.2);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.can_reach_within(living, dest, max_distance)
}))
}
async fn set_pathfinding_malus(
&mut self,
this: Resource<WitMob>,
node_type: WitPathNodeType,
malus: f32,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
let internal_type = from_wit_path_node_type(node_type);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_pathfinding_malus(internal_type, malus);
}
Ok(())
}
async fn get_pathfinding_malus(
&mut self,
this: Resource<WitMob>,
node_type: WitPathNodeType,
) -> wasmtime::Result<f32> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().map_or(0.0, |mob| {
let internal_type = from_wit_path_node_type(node_type);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get_pathfinding_malus(internal_type)
}))
}
async fn look_at(&mut self, this: Resource<WitMob>, pos: Position) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.look_control
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.look_at(mob, pos.0, pos.1, pos.2);
}
Ok(())
}
async fn look_at_entity(
&mut self,
this: Resource<WitMob>,
target: Resource<Entity>,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
let target_entity = entity_from_resource(self, &target)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.look_control
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.look_at_entity(mob, &target_entity);
}
Ok(())
}
#[allow(clippy::too_many_lines)]
async fn get_mob_data(&mut self, this: Resource<WitMob>) -> wasmtime::Result<WitMobData> {
let entity = mob_from_resource(self, &this)?;
let any = entity.cast_any();
if let Some(sheep) = any.downcast_ref::<crate::entity::passive::sheep::SheepEntity>() {
return Ok(WitMobData::Sheep(WitSheepData {
color: to_wit_dye_color(sheep.get_color()),
is_sheared: sheep.is_sheared(),
}));
}
if let Some(wolf) = any.downcast_ref::<crate::entity::passive::wolf::WolfEntity>() {
return Ok(WitMobData::Wolf(WitWolfData {
is_tamed: wolf.is_tame(),
owner: wolf.get_owner().map(|u| Uuid::to_wit(&u)),
is_sitting: wolf.is_in_sitting_pose(),
collar_color: to_wit_dye_color(wolf.get_collar_color()),
is_angry: false,
is_begging: false,
}));
}
if let Some(cat) = any.downcast_ref::<crate::entity::passive::cat::CatEntity>() {
return Ok(WitMobData::Cat(WitCatData {
is_tamed: cat.is_tame(),
owner: cat.get_owner().map(|u| Uuid::to_wit(&u)),
is_sitting: cat.is_in_sitting_pose(),
collar_color: to_wit_dye_color(cat.get_collar_color()),
}));
}
if let Some(villager) =
any.downcast_ref::<crate::entity::passive::villager::VillagerEntity>()
{
let data = *villager
.villager_data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
return Ok(WitMobData::Villager(WitVillagerData {
profession: to_wit_villager_profession(data.profession.0),
level: (data.level.0).clamp(0, 255) as u8,
experience: villager
.xp
.load(std::sync::atomic::Ordering::Relaxed)
.max(0) as u32,
}));
}
if let Some(creeper) = any.downcast_ref::<crate::entity::mob::creeper::CreeperEntity>() {
return Ok(WitMobData::Creeper(WitCreeperData {
is_powered: creeper.is_charged(),
fuse: creeper.get_fuse(),
is_ignited: creeper.is_ignited(),
explosion_radius: creeper.get_explosion_radius().clamp(0, 255) as u8,
}));
}
if let Some(slime) = any.downcast_ref::<crate::entity::mob::slime::SlimeEntity>() {
return Ok(WitMobData::Slime(WitSlimeData {
size: slime.get_size(),
}));
}
if let Some(enderman) = any.downcast_ref::<crate::entity::mob::enderman::EndermanEntity>() {
return Ok(WitMobData::Enderman(WitEndermanData {
carried_block_state: enderman
.get_carried_block()
.map(pumpkin_data::BlockStateId::as_u16),
is_screaming: enderman.is_angry(),
is_staring: enderman.is_angry(),
}));
}
if let Some(iron_golem) =
any.downcast_ref::<crate::entity::passive::iron_golem::IronGolemEntity>()
{
return Ok(WitMobData::IronGolem(WitIronGolemData {
is_player_created: iron_golem.is_player_created(),
}));
}
if let Some(fox) = any.downcast_ref::<crate::entity::passive::fox::FoxEntity>() {
return Ok(WitMobData::Fox(WitFoxData {
is_sitting: fox.is_sitting(),
is_sleeping: fox.is_sleeping(),
is_crouching: fox.is_crouching(),
}));
}
if let Some(shulker) = any.downcast_ref::<crate::entity::mob::shulker::ShulkerEntity>() {
return Ok(WitMobData::Shulker(WitShulkerData {
attached_face: to_wit_block_direction(shulker.get_attach_face()),
peek_amount: shulker.get_raw_peek(),
color: shulker.get_color().map(to_wit_dye_color),
}));
}
if let Some(zombie) = any.downcast_ref::<crate::entity::mob::zombie::zombie::ZombieEntity>()
{
return Ok(WitMobData::Zombie(WitZombieData {
is_baby: zombie.is_baby(),
can_break_doors: zombie.can_break_doors(),
}));
}
if let Some(living) = entity.get_living_entity() {
let age = living.entity.age.load(std::sync::atomic::Ordering::Relaxed);
return Ok(WitMobData::Ageable(WitAgeableData {
is_baby: age < 0,
age,
in_love_ticks: 0,
}));
}
Ok(WitMobData::Generic)
}
#[allow(clippy::too_many_lines)]
async fn set_mob_data(
&mut self,
this: Resource<WitMob>,
data: WitMobData,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
let any = entity.cast_any();
match data {
WitMobData::Sheep(sheep_data) => {
if let Some(sheep) =
any.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
{
sheep.set_color(from_wit_dye_color(sheep_data.color));
sheep.set_sheared(sheep_data.is_sheared);
return Ok(true);
}
}
WitMobData::Wolf(wolf_data) => {
if let Some(wolf) = any.downcast_ref::<crate::entity::passive::wolf::WolfEntity>() {
wolf.set_tame(wolf_data.is_tamed);
wolf.set_owner(wolf_data.owner.map(|u| Uuid::from_wit(&u)));
wolf.set_in_sitting_pose(wolf_data.is_sitting);
wolf.set_collar_color(from_wit_dye_color(wolf_data.collar_color));
return Ok(true);
}
}
WitMobData::Cat(cat_data) => {
if let Some(cat) = any.downcast_ref::<crate::entity::passive::cat::CatEntity>() {
cat.set_tame(
cat_data.is_tamed,
cat_data.owner.map(|u| Uuid::from_wit(&u)),
);
cat.set_sitting(cat_data.is_sitting);
cat.set_collar_color(from_wit_dye_color(cat_data.collar_color));
return Ok(true);
}
}
WitMobData::Villager(villager_data) => {
if let Some(villager) =
any.downcast_ref::<crate::entity::passive::villager::VillagerEntity>()
{
{
let mut vdata = villager
.villager_data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
vdata.profession = pumpkin_protocol::codec::var_int::VarInt(
from_wit_villager_profession(villager_data.profession) as i32,
);
vdata.level = pumpkin_protocol::codec::var_int::VarInt(i32::from(
villager_data.level,
));
};
villager.xp.store(
villager_data.experience as i32,
std::sync::atomic::Ordering::Relaxed,
);
return Ok(true);
}
}
WitMobData::Creeper(creeper_data) => {
if let Some(creeper) =
any.downcast_ref::<crate::entity::mob::creeper::CreeperEntity>()
{
creeper.set_charged(creeper_data.is_powered);
creeper.set_fuse(creeper_data.fuse);
creeper.set_ignited(creeper_data.is_ignited);
creeper.set_explosion_radius(i32::from(creeper_data.explosion_radius));
return Ok(true);
}
}
WitMobData::Slime(slime_data) => {
if let Some(slime) = any.downcast_ref::<crate::entity::mob::slime::SlimeEntity>() {
slime.set_size(slime_data.size, false);
return Ok(true);
}
}
WitMobData::Enderman(enderman_data) => {
if let Some(enderman) =
any.downcast_ref::<crate::entity::mob::enderman::EndermanEntity>()
{
enderman.set_carried_block(
enderman_data
.carried_block_state
.and_then(pumpkin_data::BlockStateId::new),
);
enderman.set_angry(enderman_data.is_screaming || enderman_data.is_staring);
return Ok(true);
}
}
WitMobData::IronGolem(iron_golem_data) => {
if let Some(iron_golem) =
any.downcast_ref::<crate::entity::passive::iron_golem::IronGolemEntity>()
{
iron_golem.set_player_created(iron_golem_data.is_player_created);
return Ok(true);
}
}
WitMobData::Fox(fox_data) => {
if let Some(fox) = any.downcast_ref::<crate::entity::passive::fox::FoxEntity>() {
fox.set_sitting(fox_data.is_sitting);
fox.set_sleeping(fox_data.is_sleeping);
fox.set_crouching(fox_data.is_crouching);
return Ok(true);
}
}
WitMobData::Shulker(shulker_data) => {
if let Some(shulker) =
any.downcast_ref::<crate::entity::mob::shulker::ShulkerEntity>()
{
shulker.set_attach_face(from_wit_block_direction(shulker_data.attached_face));
shulker.set_raw_peek(shulker_data.peek_amount);
shulker.set_color(shulker_data.color.map(from_wit_dye_color));
return Ok(true);
}
}
WitMobData::Zombie(zombie_data) => {
if let Some(zombie) =
any.downcast_ref::<crate::entity::mob::zombie::zombie::ZombieEntity>()
{
zombie.set_baby(zombie_data.is_baby);
zombie.set_can_break_doors(zombie_data.can_break_doors);
return Ok(true);
}
}
WitMobData::Ageable(ageable_data) => {
if let Some(living) = entity.get_living_entity() {
let age = if ageable_data.is_baby && ageable_data.age >= 0 {
-24000
} else {
ageable_data.age
};
living
.entity
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
return Ok(true);
}
}
WitMobData::Generic => return Ok(true),
}
Ok(false)
}
async fn drop(&mut self, rep: Resource<WitMob>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<MobResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@@ -22,6 +22,8 @@ pub mod common;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod context; pub mod context;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod datapack;
#[allow(clippy::unused_async_trait_impl)]
pub mod display; pub mod display;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod enchantment; pub mod enchantment;
@@ -34,12 +36,18 @@ pub mod generated_packets;
pub mod gui; pub mod gui;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod i18n; pub mod i18n;
#[allow(clippy::unused_async_trait_impl)]
pub mod inventory;
pub mod ipc; pub mod ipc;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod item_stack; pub mod item_stack;
pub mod java_dialogs; pub mod java_dialogs;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod living_entity;
#[allow(clippy::unused_async_trait_impl)]
pub mod logging; pub mod logging;
#[allow(clippy::unused_async_trait_impl)]
pub mod mob;
pub mod permission; pub mod permission;
#[allow(clippy::unused_async_trait_impl)] #[allow(clippy::unused_async_trait_impl)]
pub mod player; pub mod player;

View File

@@ -15,10 +15,10 @@ use crate::{
GuiResource, PlayerResource, PluginHostState, TextComponentResource, WorldResource, GuiResource, PlayerResource, PluginHostState, TextComponentResource, WorldResource,
}, },
wit::v0_1::{ wit::v0_1::{
entity::from_wit_damage_type,
events::{ events::{
from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position, from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position,
}, },
living_entity::from_wit_damage_type,
pumpkin::{ pumpkin::{
self, self,
plugin::damage_types::DamageType as WitDamageType, plugin::damage_types::DamageType as WitDamageType,
@@ -1136,6 +1136,34 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(()) Ok(())
} }
async fn get_inventory(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::PlayerInventory,
>,
>{
let player = player_from_resource(self, &player)?;
self.add_player_inventory(player)
}
async fn get_ender_chest(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::Inventory,
>,
>{
let player = player_from_resource(self, &player)?;
self.add_inventory(
crate::plugin::loader::wasm::wasm_host::state::InventoryProvider::PlayerEnderChest(
player,
),
)
}
async fn get_inventory_item( async fn get_inventory_item(
&mut self, &mut self,
player: Resource<Player>, player: Resource<Player>,

View File

@@ -19,6 +19,7 @@ use crate::plugin::{
wit::v0_1::pumpkin::{ wit::v0_1::pumpkin::{
self, self,
plugin::{ plugin::{
datapack::DatapackManager as WitDatapackManager,
player::{BanIpOptions, BanPlayerOptions, Player}, player::{BanIpOptions, BanPlayerOptions, Player},
server::{ server::{
BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty, BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty,
@@ -606,6 +607,17 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
Ok(ids) Ok(ids)
} }
async fn get_datapack_manager(
&mut self,
_rep: Resource<Server>,
) -> wasmtime::Result<Resource<WitDatapackManager>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
self.add_datapack_manager(server.clone())
}
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> { async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
self.resource_table self.resource_table
.delete::<ServerResource>(Resource::new_own(rep.rep())) .delete::<ServerResource>(Resource::new_own(rep.rep()))

View File

@@ -2231,7 +2231,7 @@ impl WasmChunkGenerator {
proto_chunk, proto_chunk,
}; };
futures::executor::block_on(async { let run = async {
let mut store = self.plugin.store.lock().await; let mut store = self.plugin.store.lock().await;
let Ok(buffer_res) = store.data_mut().add_chunk_buffer(chunk_buffer) else { let Ok(buffer_res) = store.data_mut().add_chunk_buffer(chunk_buffer) else {
return; return;
@@ -2257,7 +2257,13 @@ impl WasmChunkGenerator {
); );
} }
} }
}); };
if let Ok(handle) = tokio::runtime::Handle::try_current() {
tokio::task::block_in_place(|| {
handle.block_on(run);
});
}
} }
} }

View File

@@ -1009,8 +1009,8 @@ impl PluginManager {
/// Checks if plugin active /// Checks if plugin active
#[must_use] #[must_use]
pub async fn is_plugin_active(&self, name: &str) -> bool { pub fn is_plugin_active(&self, name: &str) -> bool {
let plugins = self.plugins.read().await; let plugins = self.plugins.blocking_read();
plugins plugins
.iter() .iter()
.any(|p| p.metadata.name == name && p.is_active && p.instance.is_some()) .any(|p| p.metadata.name == name && p.is_active && p.instance.is_some())
@@ -1018,8 +1018,8 @@ impl PluginManager {
/// Get list of active plugins /// Get list of active plugins
#[must_use] #[must_use]
pub async fn active_plugins(&self) -> Vec<PluginMetadata> { pub fn active_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.read().await; let plugins = self.plugins.blocking_read();
plugins plugins
.iter() .iter()
.filter(|p| p.is_active && p.instance.is_some()) .filter(|p| p.is_active && p.instance.is_some())
@@ -1029,15 +1029,15 @@ impl PluginManager {
/// Checks if plugin loaded /// Checks if plugin loaded
#[must_use] #[must_use]
pub async fn is_plugin_loaded(&self, name: &str) -> bool { pub fn is_plugin_loaded(&self, name: &str) -> bool {
let plugins = self.plugins.read().await; let plugins = self.plugins.blocking_read();
plugins.iter().any(|p| p.metadata.name == name) plugins.iter().any(|p| p.metadata.name == name)
} }
/// Get list of loaded plugins /// Get list of loaded plugins
#[must_use] #[must_use]
pub async fn loaded_plugins(&self) -> Vec<PluginMetadata> { pub fn loaded_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.read().await; let plugins = self.plugins.blocking_read();
plugins.iter().map(|p| p.metadata.clone()).collect() plugins.iter().map(|p| p.metadata.clone()).collect()
} }

View File

@@ -315,16 +315,6 @@ impl Server {
}; };
let server = Arc::new(server); let server = Arc::new(server);
let gen_pool = Arc::new(
rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("Gen-Pool-{i}"))
.build()
.unwrap_or_else(|err| {
error!("Failed to build generation thread pool: {err}");
std::process::exit(1);
}),
);
// Fetch / generate keys in background tasks to avoid blocking startup // Fetch / generate keys in background tasks to avoid blocking startup
let server_clone = server.clone(); let server_clone = server.clone();
tokio::spawn(async move { tokio::spawn(async move {
@@ -400,7 +390,6 @@ impl Server {
let l_info = server.level_info.clone(); // Access from struct let l_info = server.level_info.clone(); // Access from struct
let weak = Arc::downgrade(&server); let weak = Arc::downgrade(&server);
let config = Arc::new(server.advanced_config.world.clone()); let config = Arc::new(server.advanced_config.world.clone());
let pool = gen_pool.clone();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
info!( info!(
@@ -409,7 +398,7 @@ impl Server {
.color_named(NamedColor::DarkGreen) .color_named(NamedColor::DarkGreen)
.to_pretty_console() .to_pretty_console()
); );
let level = into_level(dim.clone(), &config, path, seed, Some(pool)); let level = into_level(dim.clone(), &config, path, seed);
let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak)); let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak));
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone())); let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
level.world_portal.store(Arc::new(Some(portal))); level.world_portal.store(Arc::new(Some(portal)));
@@ -508,14 +497,8 @@ impl Server {
let config = Arc::new(server.advanced_config.world.clone()); let config = Arc::new(server.advanced_config.world.clone());
let seed = server.level_info.load().world_gen_settings.seed; let seed = server.level_info.load().world_gen_settings.seed;
// TODO: gen_pool should be reused let level =
let level = pumpkin_world::dimension::into_level( pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed);
dimension.clone(),
&config,
world_path,
seed,
None,
);
let world: World = World::load(level.clone(), l_info, dimension, registry, weak); let world: World = World::load(level.clone(), l_info, dimension, registry, weak);
let world = Arc::new(world); let world = Arc::new(world);
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone())); let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));

View File

@@ -84,8 +84,8 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData}; use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData}; use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
use pumpkin_protocol::java::client::play::{ use pumpkin_protocol::java::client::play::{
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion, CBlockUpdate, CDisguisedChatMessage, CExplosion, CRespawn, CSetBlockDestroyStage, CWorldEvent,
CLightUpdate, CRespawn, CSetBlockDestroyStage, CWorldEvent, PlayerSpawnData, PlayerSpawnData,
}; };
use pumpkin_protocol::java::client::play::{ use pumpkin_protocol::java::client::play::{
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage, CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
@@ -511,7 +511,11 @@ impl World {
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
entity.write_nbt(&mut nbt); entity.write_nbt(&mut nbt);
let chunk = self.level.get_entity_chunk(current_chunk).await; let chunk = self.level.get_entity_chunk(current_chunk).await;
chunk.data.lock().await.push(nbt); chunk
.data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(nbt);
chunk.mark_dirty(true); chunk.mark_dirty(true);
} }
@@ -3144,20 +3148,23 @@ impl World {
if client.version.load() < JavaMinecraftVersion::V_1_20_2 if client.version.load() < JavaMinecraftVersion::V_1_20_2
&& client.version.load() >= JavaMinecraftVersion::V_1_13 && client.version.load() >= JavaMinecraftVersion::V_1_13
{ {
let mut tags = Vec::new();
let version = client.version.load(); let version = client.version.load();
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS { if let Ok(Ok(packet_data)) = tokio::task::spawn_blocking(move || {
if pumpkin_data::tag::get_registry_key_tags(version, key) let mut tags = Vec::new();
.is_some_and(|map| !map.is_empty()) for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
{ if pumpkin_data::tag::get_registry_key_tags(version, key)
tags.push(key); .is_some_and(|map| !map.is_empty())
{
tags.push(key);
}
} }
let packet = pumpkin_protocol::java::client::play::CUpdateTagsPlay::new(&tags);
JavaClient::serialize_packet_for_version(&packet, version)
})
.await
{
client.send_packet_now(packet_data).await;
} }
client
.send_packet(&pumpkin_protocol::java::client::play::CUpdateTagsPlay::new(
&tags,
))
.await;
} }
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) { let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
@@ -3205,19 +3212,7 @@ impl World {
return; return;
} }
} }
if client.version.load() >= JavaMinecraftVersion::V_1_20_2 { client.send_chunks(&[chunk]).await;
client.send_packet(&CChunkBatchStart).await;
}
client.send_packet(&CChunkData(&chunk)).await;
if client.version.load() >= JavaMinecraftVersion::V_1_14
&& client.version.load() < JavaMinecraftVersion::V_1_18
&& let Ok(light_packet) = CLightUpdate::from_chunk(&chunk, client.version.load())
{
client.send_packet(&light_packet).await;
}
if client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
client.send_packet(&CChunkBatchEnd::new(1u16)).await;
}
let velocity = player.living_entity.entity.velocity.load(); let velocity = player.living_entity.entity.velocity.load();
@@ -4245,20 +4240,7 @@ impl World {
.level .level
.get_or_fetch_chunk(center_chunk, std::clone::Clone::clone) .get_or_fetch_chunk(center_chunk, std::clone::Clone::clone)
.await; .await;
if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 { java_client.send_chunks(&[chunk]).await;
java_client.send_packet(&CChunkBatchStart).await;
}
java_client.send_packet(&CChunkData(&chunk)).await;
if java_client.version.load() >= JavaMinecraftVersion::V_1_14
&& java_client.version.load() < JavaMinecraftVersion::V_1_18
&& let Ok(light_packet) =
CLightUpdate::from_chunk(&chunk, java_client.version.load())
{
java_client.send_packet(&light_packet).await;
}
if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
java_client.send_packet(&CChunkBatchEnd::new(1u16)).await;
}
} }
// Send teleport packet after at least the center chunk was delivered // Send teleport packet after at least the center chunk was delivered
@@ -4360,7 +4342,12 @@ impl World {
// truth, so the chunk's NBT is taken (cleared) to avoid keeping // truth, so the chunk's NBT is taken (cleared) to avoid keeping
// a duplicate copy that would be re-appended on the next unload // a duplicate copy that would be re-appended on the next unload
// and doubled on every reload. // and doubled on every reload.
let entity_nbts = std::mem::take(&mut *chunk.data.lock().await); let entity_nbts = std::mem::take(
&mut *chunk
.data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
let mut entities_to_add: Vec<Arc<dyn EntityBase>> = let mut entities_to_add: Vec<Arc<dyn EntityBase>> =
Vec::with_capacity(entity_nbts.len()); Vec::with_capacity(entity_nbts.len());
for entity_nbt in &entity_nbts { for entity_nbt in &entity_nbts {