feat(plugin-api): use trappable

This commit is contained in:
Alexander Medvedev 2026-04-07 11:28:36 +02:00
parent 8b418813f8
commit 45ed583312
12 changed files with 1094 additions and 1054 deletions

View File

@ -98,7 +98,7 @@ interface command {
}
variant command-sender-type {
rcon(list<string>),
rcon,
console,
player(player),
command-block(tuple<command-block-entity, %world>),

View File

@ -15,79 +15,79 @@ interface player {
}
resource player {
get-id: func () -> result<string, string>;
get-name: func () -> result<string, string>;
get-id: func () -> string;
get-name: func () -> string;
// Basic state
get-position: func () -> result<position, string>;
get-rotation: func () -> result<tuple<f32, f32>, string>;
get-world: func () -> result<%world, string>;
get-gamemode: func () -> result<game-mode, string>;
set-gamemode: func (mode: game-mode) -> result<bool, string>;
get-locale: func () -> result<string, string>;
get-ping: func () -> result<u32, string>;
get-position: func () -> position;
get-rotation: func () -> tuple<f32, f32>;
get-world: func () -> %world;
get-gamemode: func () -> game-mode;
set-gamemode: func (mode: game-mode) -> bool;
get-locale: func () -> string;
get-ping: func () -> u32;
// Permissions & metadata
get-permission-level: func () -> result<permission-level, string>;
set-permission-level: func (level: permission-level) -> result<_, string>;
has-permission: func (node: string) -> result<bool, string>;
get-display-name: func () -> result<text-component, string>;
get-permission-level: func () -> permission-level;
set-permission-level: func (level: permission-level);
has-permission: func (node: string) -> bool;
get-display-name: func () -> text-component;
// Messaging & titles
send-system-message: func (text: text-component, overlay: bool) -> result<_, string>;
show-title: func (text: text-component) -> result<_, string>;
show-subtitle: func (text: text-component) -> result<_, string>;
show-actionbar: func (text: text-component) -> result<_, string>;
send-title-animation: func (fade-in: s32, stay: s32, fade-out: s32) -> result<_, string>;
send-system-message: func (text: text-component, overlay: bool);
show-title: func (text: text-component);
show-subtitle: func (text: text-component);
show-actionbar: func (text: text-component);
send-title-animation: func (fade-in: s32, stay: s32, fade-out: s32);
// Teleport & kick
teleport: func (position: position, yaw: f32, pitch: f32) -> result<_, string>;
teleport-world: func (world-ref: %world, position: position, yaw: option<f32>, pitch: option<f32>) -> result<_, string>;
kick: func (message: text-component) -> result<_, string>;
respawn: func () -> result<_, string>;
teleport: func (position: position, yaw: f32, pitch: f32);
teleport-world: func (world-ref: %world, position: position, yaw: option<f32>, pitch: option<f32>);
kick: func (message: text-component);
respawn: func ();
ban: func (reason: option<text-component>) -> result<_, string>;
ban-ip: func (reason: option<text-component>) -> result<_, string>;
ban: func (reason: option<text-component>);
ban-ip: func (reason: option<text-component>);
// Inventory
get-selected-slot: func () -> result<u8, string>;
get-item-in-hand: func (hand: hand) -> result<option<item-stack>, string>;
get-inventory-item: func (slot: u8) -> result<option<item-stack>, string>;
get-selected-slot: func () -> u8;
get-item-in-hand: func (hand: hand) -> option<item-stack>;
get-inventory-item: func (slot: u8) -> option<item-stack>;
// Status
get-health: func () -> result<f32, string>;
set-health: func (health: f32) -> result<_, string>;
get-max-health: func () -> result<f32, string>;
set-max-health: func (max-health: f32) -> result<_, string>;
get-food-level: func () -> result<u8, string>;
set-food-level: func (food-level: u8) -> result<_, string>;
get-saturation: func () -> result<f32, string>;
set-saturation: func (saturation: f32) -> result<_, string>;
get-exhaustion: func () -> result<f32, string>;
set-exhaustion: func (exhaustion: f32) -> result<_, string>;
get-absorption: func () -> result<f32, string>;
set-absorption: func (absorption: f32) -> result<_, string>;
get-health: func () -> f32;
set-health: func (health: f32);
get-max-health: func () -> f32;
set-max-health: func (max-health: f32);
get-food-level: func () -> u8;
set-food-level: func (food-level: u8);
get-saturation: func () -> f32;
set-saturation: func (saturation: f32);
get-exhaustion: func () -> f32;
set-exhaustion: func (exhaustion: f32);
get-absorption: func () -> f32;
set-absorption: func (absorption: f32);
get-experience-level: func() -> result<s32, string>;
get-experience-progress: func() -> result<f32, string>;
get-experience-points: func() -> result<s32, string>;
set-experience-level: func(level: s32) -> result<_, string>;
set-experience-progress: func(progress: f32) -> result<_, string>;
set-experience-points: func(points: s32) -> result<_, string>;
add-experience-levels: func(levels: s32) -> result<_, string>;
add-experience-points: func(points: s32) -> result<_, string>;
get-experience-level: func() -> s32;
get-experience-progress: func() -> f32;
get-experience-points: func() -> s32;
set-experience-level: func(level: s32);
set-experience-progress: func(progress: f32);
set-experience-points: func(points: s32);
add-experience-levels: func(levels: s32);
add-experience-points: func(points: s32);
is-sneaking: func() -> result<bool, string>;
set-sneaking: func(sneaking: bool) -> result<_, string>;
is-sprinting: func() -> result<bool, string>;
set-sprinting: func(sprinting: bool) -> result<_, string>;
is-on-ground: func() -> result<bool, string>;
is-flying: func() -> result<bool, string>;
set-flying: func(flying: bool) -> result<_, string>;
is-sneaking: func() -> bool;
set-sneaking: func(sneaking: bool);
is-sprinting: func() -> bool;
set-sprinting: func(sprinting: bool);
is-on-ground: func() -> bool;
is-flying: func() -> bool;
set-flying: func(flying: bool);
get-abilities: func() -> result<player-abilities, string>;
set-abilities: func(abilities: player-abilities) -> result<_, string>;
get-abilities: func() -> player-abilities;
set-abilities: func(abilities: player-abilities);
get-ip: func() -> result<string, string>;
get-ip: func() -> string;
}
}
}

View File

@ -125,6 +125,21 @@ async fn register_player_event(
}
}
impl PluginHostState {
fn get_context(&self, res: &Resource<Context>) -> wasmtime::Result<&ContextResource> {
self.resource_table
.get::<ContextResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
fn take_command(&mut self, res: &Resource<Command>) -> wasmtime::Result<CommandResource> {
self.resource_table
.delete::<CommandResource>(Resource::new_own(res.rep()))
// Convert ResourceTableError -> wasmtime::Error
.map_err(wasmtime::Error::from)
}
}
async fn register_world_event(
resource: &ContextResource,
handler: &Arc<WasmPluginV0_1_0EventHandler>,
@ -237,7 +252,8 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
event_type: EventType,
event_priority: EventPriority,
blocking: bool,
) {
) -> wasmtime::Result<()> {
// Updated return type
let priority = match event_priority {
EventPriority::Highest => crate::plugin::EventPriority::Highest,
EventPriority::High => crate::plugin::EventPriority::High,
@ -246,14 +262,15 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
EventPriority::Lowest => crate::plugin::EventPriority::Lowest,
};
// Use ? to trap if the plugin was dropped or the context handle is dead
let plugin = self
.plugin
.as_ref()
.expect("plugin should always be initialized here")
.ok_or_else(|| wasmtime::Error::msg("Plugin state uninitialized"))?
.upgrade()
.expect("plugin has been dropped");
.ok_or_else(|| wasmtime::Error::msg("Plugin has been dropped"))?;
let resource = context.downcast_ref(self);
let resource = self.get_context(&context)?;
let handler = Arc::new(WasmPluginV0_1_0EventHandler { handler_id, plugin });
match event_type {
@ -275,6 +292,8 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
register_player_event(resource, &handler, priority, blocking, event_type).await;
}
}
Ok(())
}
async fn register_command(
@ -282,44 +301,37 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
context: Resource<Context>,
command: Resource<Command>,
permission: String,
) {
let command = self
.resource_table
.delete::<CommandResource>(Resource::new_own(command.rep()))
.expect("invalid command resource handle")
.provider;
) -> wasmtime::Result<()> {
// Updated return type
// Use your helpers to safely take/get resources
let command_res = self.take_command(&command)?;
let context_res = self.get_context(&context)?;
let context_resource = self
.resource_table
.get_any_mut(context.rep())
.expect("invalid context resource handle")
.downcast_ref::<ContextResource>()
.expect("resource type mismatch");
context_resource
context_res
.provider
.register_command(command, permission)
.register_command(command_res.provider, permission)
.await;
Ok(())
}
async fn register_permission(
&mut self,
context: Resource<Context>,
permission: Permission,
) -> Result<(), String> {
let mut children: HashMap<String, bool> = HashMap::with_capacity(permission.children.len());
) -> wasmtime::Result<Result<(), String>> {
let mut children = HashMap::with_capacity(permission.children.len());
for child in permission.children {
children.insert(child.node, child.value);
}
let permission = pumpkin_util::permission::Permission {
let util_permission = pumpkin_util::permission::Permission {
node: permission.node,
description: permission.description,
default: match permission.default {
PermissionDefault::Deny => pumpkin_util::permission::PermissionDefault::Deny,
PermissionDefault::Allow => pumpkin_util::permission::PermissionDefault::Allow,
PermissionDefault::Op(permission_level) => {
pumpkin_util::permission::PermissionDefault::Op(match permission_level {
PermissionDefault::Op(lvl) => {
pumpkin_util::permission::PermissionDefault::Op(match lvl {
PermissionLevel::Zero => pumpkin_util::permission::PermissionLvl::Zero,
PermissionLevel::One => pumpkin_util::permission::PermissionLvl::One,
PermissionLevel::Two => pumpkin_util::permission::PermissionLvl::Two,
@ -331,26 +343,31 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
children,
};
context
.downcast_mut(self)
let context_res = self
.resource_table
.get_mut::<ContextResource>(&Resource::new_own(context.rep()))?;
Ok(context_res
.provider
.register_permission(permission)
.await
.register_permission(util_permission)
.await)
}
async fn get_data_folder(&mut self, context: Resource<Context>) -> String {
context
.downcast_ref(self)
async fn get_data_folder(&mut self, context: Resource<Context>) -> wasmtime::Result<String> {
Ok(self
.get_context(&context)?
.provider
.get_data_folder()
.to_string_lossy()
.into_owned()
.into_owned())
}
async fn get_server(&mut self, context: Resource<Context>) -> Resource<Server> {
let server_provider = context.downcast_ref(self).provider.server.clone();
async fn get_server(
&mut self,
context: Resource<Context>,
) -> wasmtime::Result<Resource<Server>> {
let server_provider = self.get_context(&context)?.provider.server.clone();
self.add_server(server_provider)
.expect("failed to add server resource")
.map_err(|_| wasmtime::Error::msg("failed to add server resource"))
}
async fn drop(&mut self, rep: Resource<Context>) -> wasmtime::Result<()> {

View File

@ -14,68 +14,77 @@ use crate::plugin::loader::wasm::wasm_host::{
impl pumpkin::plugin::entity::Host for PluginHostState {}
impl pumpkin::plugin::entity::HostBlockEntity for PluginHostState {
async fn resource_location(&mut self, _block_entity: Resource<BlockEntity>) -> String {
todo!()
async fn resource_location(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<String> {
todo!("Implement resource_location lookup")
}
async fn get_position(&mut self, _block_entity: Resource<BlockEntity>) -> BlockPosition {
todo!()
async fn get_position(
&mut self,
_res: Resource<BlockEntity>,
) -> wasmtime::Result<BlockPosition> {
todo!("Implement get_position lookup")
}
async fn get_id(&mut self, _block_entity: Resource<BlockEntity>) -> u32 {
todo!()
async fn get_id(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement get_id lookup")
}
async fn is_dirty(&mut self, _block_entity: Resource<BlockEntity>) -> bool {
todo!()
async fn is_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement is_dirty lookup")
}
async fn clear_dirty(&mut self, _block_entity: Resource<BlockEntity>) {
todo!()
async fn clear_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<()> {
todo!("Implement clear_dirty logic")
}
async fn drop(&mut self, _rep: Resource<BlockEntity>) -> wasmtime::Result<()> {
todo!()
todo!();
}
}
impl pumpkin::plugin::entity::HostCommandBlockEntity for PluginHostState {
async fn get_block_entity(
&mut self,
_command_block_entity: Resource<CommandBlockEntity>,
) -> Resource<BlockEntity> {
todo!()
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
todo!("Implement upcasting/mapping to BlockEntity")
}
async fn last_output(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> String {
todo!()
async fn last_output(
&mut self,
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<String> {
todo!("Implement last_output lookup")
}
async fn track_output(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> bool {
todo!()
async fn track_output(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement track_output lookup")
}
async fn success_count(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> u32 {
todo!()
async fn success_count(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement success_count lookup")
}
async fn command(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> String {
todo!()
async fn command(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<String> {
todo!("Implement command lookup")
}
async fn auto(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> bool {
todo!()
async fn auto(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement auto lookup")
}
async fn condition_met(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> bool {
todo!()
async fn condition_met(
&mut self,
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<bool> {
todo!("Implement condition_met lookup")
}
async fn powered(&mut self, _command_block_entity: Resource<CommandBlockEntity>) -> bool {
todo!()
async fn powered(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement powered lookup")
}
async fn drop(&mut self, _rep: Resource<CommandBlockEntity>) -> wasmtime::Result<()> {
todo!()
todo!();
}
}

View File

@ -6,27 +6,31 @@ use pumpkin_util::translation::{Locale as UtilLocale, add_translation_file, get_
use std::str::FromStr;
impl Host for PluginHostState {
async fn translate(&mut self, key: String, locale: WitLocale) -> String {
async fn translate(&mut self, key: String, locale: WitLocale) -> wasmtime::Result<String> {
let util_locale = wit_to_util_locale(locale);
get_translation(&key, util_locale)
Ok(get_translation(&key, util_locale))
}
async fn load_translations(&mut self, namespace: String, json: String, locale: WitLocale) {
async fn load_translations(
&mut self,
namespace: String,
json: String,
locale: WitLocale,
) -> wasmtime::Result<()> {
let util_locale = wit_to_util_locale(locale);
add_translation_file(namespace, json, util_locale);
Ok(())
}
}
/// Converts a WIT Locale to a pumpkin-util Locale.
fn wit_to_util_locale(wit: WitLocale) -> UtilLocale {
// WIT names are kebab-case (e.g., AfZa -> af-za in .wit, but in generated Rust it might vary)
// We can use the debug representation or a custom mapping.
// Given the amount of variants, we use a string-based approach if possible.
// WIT variants like EnUs often debug to "EnUs".
// We convert to lowercase and handle potential format differences.
let s = format!("{wit:?}").to_lowercase();
// pumpkin-util::Locale::from_str expects "af_za" or similar.
// WIT might generate "AfZa" or "af_za" depending on bindgen config.
// Let's assume standard bindgen which might produce "AfZa".
// We'll replace kebab-case if necessary.
let s = s.replace('-', "_");
// Most translation systems expect underscores (en_us) rather than nothing or dashes.
// If the WIT Debug format is "EnUs", lowercase is "enus".
// We might need a smarter mapping if your util expects specifically "en_us".
UtilLocale::from_str(&s).unwrap_or(UtilLocale::EnUs)
}

View File

@ -3,7 +3,11 @@ use crate::plugin::loader::wasm::wasm_host::{
};
impl pumpkin::plugin::logging::Host for PluginHostState {
async fn log(&mut self, level: pumpkin::plugin::logging::Level, message: String) {
async fn log(
&mut self,
level: pumpkin::plugin::logging::Level,
message: String,
) -> wasmtime::Result<()> {
match level {
pumpkin::plugin::logging::Level::Trace => tracing::trace!("[plugin] {message}"),
pumpkin::plugin::logging::Level::Debug => tracing::debug!("[plugin] {message}"),
@ -11,9 +15,11 @@ impl pumpkin::plugin::logging::Host for PluginHostState {
pumpkin::plugin::logging::Level::Warn => tracing::warn!("[plugin] {message}"),
pumpkin::plugin::logging::Level::Error => tracing::error!("[plugin] {message}"),
}
Ok(())
}
async fn log_tracing(&mut self, event: Vec<u8>) {
async fn log_tracing(&mut self, event: Vec<u8>) -> wasmtime::Result<()> {
log_tracing(event).await;
Ok(())
}
}

View File

@ -22,8 +22,8 @@ pub mod world;
bindgen!({
path: "../pumpkin-plugin-wit/v0.1.0",
world: "plugin",
imports: { default: async },
exports: { default: async },
imports: { default: async | trappable },
exports: { default: async | trappable},
});
struct PluginHostComponent;

View File

@ -21,34 +21,36 @@ use pumpkin_util::permission::PermissionLvl;
fn player_from_resource(
state: &PluginHostState,
player: &Resource<Player>,
) -> Result<std::sync::Arc<crate::entity::player::Player>, String> {
) -> wasmtime::Result<std::sync::Arc<crate::entity::player::Player>> {
state
.resource_table
.get::<PlayerResource>(&Resource::new_own(player.rep()))
.map_err(|_| "invalid player resource handle".to_string())
.map_err(|_| wasmtime::Error::msg("invalid player resource handle"))
.map(|resource| resource.provider.clone())
}
fn text_component_from_resource(
state: &PluginHostState,
text: &Resource<pumpkin::plugin::text::TextComponent>,
) -> Result<pumpkin_util::text::TextComponent, String> {
) -> pumpkin_util::text::TextComponent {
state
.resource_table
.get::<TextComponentResource>(&Resource::new_own(text.rep()))
.map_err(|_| "invalid text-component resource handle".to_string())
.map(|resource| resource.provider.clone())
.expect("invalid text-component resource handle")
.provider
.clone()
}
fn world_from_resource(
state: &PluginHostState,
world: &Resource<pumpkin::plugin::world::World>,
) -> Result<std::sync::Arc<crate::world::World>, String> {
) -> std::sync::Arc<crate::world::World> {
state
.resource_table
.get::<WorldResource>(&Resource::new_own(world.rep()))
.map_err(|_| "invalid world resource handle".to_string())
.map(|resource| resource.provider.clone())
.expect("invalid world resource handle")
.provider
.clone()
}
fn to_wit_item_stack(
@ -124,12 +126,12 @@ impl DowncastResourceExt<PlayerResource> for Resource<Player> {
impl pumpkin::plugin::player::Host for PluginHostState {}
impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn get_id(&mut self, player: Resource<Player>) -> Result<String, String> {
async fn get_id(&mut self, player: Resource<Player>) -> wasmtime::Result<String> {
let player = player_from_resource(self, &player)?;
Ok(player.gameprofile.id.to_string())
}
async fn get_name(&mut self, player: Resource<Player>) -> Result<String, String> {
async fn get_name(&mut self, player: Resource<Player>) -> wasmtime::Result<String> {
let player = player_from_resource(self, &player)?;
Ok(player.gameprofile.name.clone())
}
@ -137,13 +139,12 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn get_position(
&mut self,
player: Resource<Player>,
) -> Result<pumpkin::plugin::common::Position, String> {
) -> wasmtime::Result<pumpkin::plugin::common::Position> {
let player = player_from_resource(self, &player)?;
let position = player.position();
Ok(to_wasm_position(position))
Ok(to_wasm_position(player.position()))
}
async fn get_rotation(&mut self, player: Resource<Player>) -> Result<(f32, f32), String> {
async fn get_rotation(&mut self, player: Resource<Player>) -> wasmtime::Result<(f32, f32)> {
let player = player_from_resource(self, &player)?;
Ok(player.rotation())
}
@ -151,39 +152,36 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn get_world(
&mut self,
player: Resource<Player>,
) -> Result<wasmtime::component::Resource<pumpkin::plugin::world::World>, String> {
) -> wasmtime::Result<wasmtime::component::Resource<pumpkin::plugin::world::World>> {
let player = player_from_resource(self, &player)?;
let world = player.world();
self.add_world(world)
.map_err(|_| "failed to add world resource".to_string())
.map_err(|_| wasmtime::Error::msg("failed to add world resource"))
}
async fn get_gamemode(
&mut self,
player: Resource<Player>,
) -> Result<pumpkin::plugin::common::GameMode, String> {
) -> wasmtime::Result<pumpkin::plugin::common::GameMode> {
let player = player_from_resource(self, &player)?;
let gamemode = player.gamemode.load();
Ok(to_wasm_game_mode(gamemode))
Ok(to_wasm_game_mode(player.gamemode.load()))
}
async fn set_gamemode(
&mut self,
player: Resource<Player>,
mode: pumpkin::plugin::common::GameMode,
) -> Result<bool, String> {
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
let mode = from_wasm_game_mode(mode);
Ok(player.set_gamemode(mode).await)
Ok(player.set_gamemode(from_wasm_game_mode(mode)).await)
}
async fn get_locale(&mut self, player: Resource<Player>) -> Result<String, String> {
async fn get_locale(&mut self, player: Resource<Player>) -> wasmtime::Result<String> {
let player = player_from_resource(self, &player)?;
let config = player.config.load();
Ok(config.locale.clone())
Ok(player.config.load().locale.clone())
}
async fn get_ping(&mut self, player: Resource<Player>) -> Result<u32, String> {
async fn get_ping(&mut self, player: Resource<Player>) -> wasmtime::Result<u32> {
let player = player_from_resource(self, &player)?;
Ok(player.ping.load(Ordering::Relaxed))
}
@ -191,7 +189,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn get_permission_level(
&mut self,
player: Resource<Player>,
) -> Result<pumpkin::plugin::permission::PermissionLevel, String> {
) -> wasmtime::Result<pumpkin::plugin::permission::PermissionLevel> {
let player = player_from_resource(self, &player)?;
Ok(to_wit_permission_level(player.permission_lvl.load()))
}
@ -200,11 +198,9 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
level: pumpkin::plugin::permission::PermissionLevel,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let Some(server) = self.server.as_ref() else {
return Err("server not available".to_string());
};
let server = self.server.as_ref().expect("server not available");
let level = from_wit_permission_level(level);
let command_dispatcher = server.command_dispatcher.read().await;
player
@ -217,22 +213,20 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
node: String,
) -> Result<bool, String> {
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
let Some(server) = self.server.as_ref() else {
return Err("server not available".to_string());
};
let server = self.server.as_ref().expect("server not available");
Ok(player.has_permission(server, &node).await)
}
async fn get_display_name(
&mut self,
player: Resource<Player>,
) -> Result<Resource<pumpkin::plugin::text::TextComponent>, String> {
) -> wasmtime::Result<Resource<pumpkin::plugin::text::TextComponent>> {
let player = player_from_resource(self, &player)?;
let display_name = player.get_display_name().await;
self.add_text_component(display_name)
.map_err(|_| "failed to add text-component resource".to_string())
.map_err(|_| wasmtime::Error::msg("failed to add text-component resource"))
}
async fn send_system_message(
@ -240,8 +234,8 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
player: Resource<Player>,
text: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
overlay: bool,
) -> Result<(), String> {
let component = text_component_from_resource(self, &text)?;
) -> wasmtime::Result<()> {
let component = text_component_from_resource(self, &text);
let player = player_from_resource(self, &player)?;
player.send_system_message_raw(&component, overlay).await;
Ok(())
@ -251,8 +245,8 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
text: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
) -> Result<(), String> {
let component = text_component_from_resource(self, &text)?;
) -> wasmtime::Result<()> {
let component = text_component_from_resource(self, &text);
let player = player_from_resource(self, &player)?;
player.show_title(&component, &TitleMode::Title).await;
Ok(())
@ -262,8 +256,8 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
text: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
) -> Result<(), String> {
let component = text_component_from_resource(self, &text)?;
) -> wasmtime::Result<()> {
let component = text_component_from_resource(self, &text);
let player = player_from_resource(self, &player)?;
player.show_title(&component, &TitleMode::SubTitle).await;
Ok(())
@ -273,8 +267,8 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
text: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
) -> Result<(), String> {
let component = text_component_from_resource(self, &text)?;
) -> wasmtime::Result<()> {
let component = text_component_from_resource(self, &text);
let player = player_from_resource(self, &player)?;
player.show_title(&component, &TitleMode::ActionBar).await;
Ok(())
@ -286,7 +280,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
fade_in: i32,
stay: i32,
fade_out: i32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.send_title_animation(fade_in, stay, fade_out).await;
Ok(())
@ -298,10 +292,11 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
position: pumpkin::plugin::common::Position,
yaw: f32,
pitch: f32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let position = from_wasm_position(position);
player.request_teleport(position, yaw, pitch).await;
player
.request_teleport(from_wasm_position(position), yaw, pitch)
.await;
Ok(())
}
@ -312,11 +307,12 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
position: pumpkin::plugin::common::Position,
yaw: Option<f32>,
pitch: Option<f32>,
) -> Result<(), String> {
let world = world_from_resource(self, &world)?;
) -> wasmtime::Result<()> {
let world = world_from_resource(self, &world);
let player = player_from_resource(self, &player)?;
let position = from_wasm_position(position);
player.teleport_world(world, position, yaw, pitch).await;
player
.teleport_world(world, from_wasm_position(position), yaw, pitch)
.await;
Ok(())
}
@ -324,14 +320,14 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
message: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
) -> Result<(), String> {
let component = text_component_from_resource(self, &message)?;
) -> wasmtime::Result<()> {
let component = text_component_from_resource(self, &message);
let player = player_from_resource(self, &player)?;
player.kick(DisconnectReason::Kicked, component).await;
Ok(())
}
async fn respawn(&mut self, player: Resource<Player>) -> Result<(), String> {
async fn respawn(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.respawn().await;
Ok(())
@ -341,15 +337,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
reason: Option<Resource<pumpkin::plugin::text::TextComponent>>,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let Some(server) = self.server.as_ref() else {
return Err("server not available".to_string());
};
let reason = match reason {
Some(r) => Some(text_component_from_resource(self, &r)?),
None => None,
};
let server = self.server.as_ref().expect("server not available");
let reason = reason.map(|r| text_component_from_resource(self, &r));
player.ban(server, reason).await;
Ok(())
}
@ -358,20 +349,15 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
reason: Option<Resource<pumpkin::plugin::text::TextComponent>>,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let Some(server) = self.server.as_ref() else {
return Err("server not available".to_string());
};
let reason = match reason {
Some(r) => Some(text_component_from_resource(self, &r)?),
None => None,
};
let server = self.server.as_ref().expect("server not available");
let reason = reason.map(|r| text_component_from_resource(self, &r));
player.ban_ip(server, reason).await;
Ok(())
}
async fn get_selected_slot(&mut self, player: Resource<Player>) -> Result<u8, String> {
async fn get_selected_slot(&mut self, player: Resource<Player>) -> wasmtime::Result<u8> {
let player = player_from_resource(self, &player)?;
Ok(player.inventory.get_selected_slot())
}
@ -380,7 +366,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
hand: pumpkin::plugin::common::Hand,
) -> Result<Option<pumpkin::plugin::common::ItemStack>, String> {
) -> wasmtime::Result<Option<pumpkin::plugin::common::ItemStack>> {
let player = player_from_resource(self, &player)?;
let inventory = player.inventory();
let item_stack = match hand {
@ -395,29 +381,28 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
slot: u8,
) -> Result<Option<pumpkin::plugin::common::ItemStack>, String> {
) -> wasmtime::Result<Option<pumpkin::plugin::common::ItemStack>> {
let player = player_from_resource(self, &player)?;
let slot = slot as usize;
if slot >= PlayerInventory::MAIN_SIZE {
return Err("invalid inventory slot".to_string());
return Ok(None);
}
let item_stack = player.inventory.main_inventory[slot].lock().await.clone();
Ok(to_wit_item_stack(&item_stack))
}
async fn get_health(&mut self, player: Resource<Player>) -> Result<f32, String> {
async fn get_health(&mut self, player: Resource<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.living_entity.health.load())
}
async fn set_health(&mut self, player: Resource<Player>, health: f32) -> Result<(), String> {
async fn set_health(&mut self, player: Resource<Player>, health: f32) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_health(health).await;
Ok(())
}
async fn get_max_health(&mut self, player: Resource<Player>) -> Result<f32, String> {
async fn get_max_health(&mut self, player: Resource<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.living_entity.get_max_health())
}
@ -426,24 +411,28 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
max_health: f32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_max_health(max_health).await;
Ok(())
}
async fn get_food_level(&mut self, player: Resource<Player>) -> Result<u8, String> {
async fn get_food_level(&mut self, player: Resource<Player>) -> wasmtime::Result<u8> {
let player = player_from_resource(self, &player)?;
Ok(player.hunger_manager.level.load())
}
async fn set_food_level(&mut self, player: Resource<Player>, level: u8) -> Result<(), String> {
async fn set_food_level(
&mut self,
player: Resource<Player>,
level: u8,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_food_level(level).await;
Ok(())
}
async fn get_saturation(&mut self, player: Resource<Player>) -> Result<f32, String> {
async fn get_saturation(&mut self, player: Resource<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.hunger_manager.saturation.load())
}
@ -452,13 +441,13 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
saturation: f32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_saturation(saturation).await;
Ok(())
}
async fn get_exhaustion(&mut self, player: Resource<Player>) -> Result<f32, String> {
async fn get_exhaustion(&mut self, player: Resource<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.get_exhaustion())
}
@ -467,13 +456,13 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
exhaustion: f32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_exhaustion(exhaustion).await;
Ok(())
}
async fn get_absorption(&mut self, player: Resource<Player>) -> Result<f32, String> {
async fn get_absorption(&mut self, player: Resource<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.get_absorption())
}
@ -482,23 +471,23 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
absorption: f32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_absorption(absorption).await;
Ok(())
}
async fn get_experience_level(&mut self, player: Resource<Player>) -> Result<i32, String> {
async fn get_experience_level(&mut self, player: Resource<Player>) -> wasmtime::Result<i32> {
let player = player_from_resource(self, &player)?;
Ok(player.experience_level.load(Ordering::Relaxed))
}
async fn get_experience_progress(&mut self, player: Resource<Player>) -> Result<f32, String> {
async fn get_experience_progress(&mut self, player: Resource<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.experience_progress.load())
}
async fn get_experience_points(&mut self, player: Resource<Player>) -> Result<i32, String> {
async fn get_experience_points(&mut self, player: Resource<Player>) -> wasmtime::Result<i32> {
let player = player_from_resource(self, &player)?;
Ok(player.experience_points.load(Ordering::Relaxed))
}
@ -507,7 +496,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
level: i32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_experience_level(level, true).await;
Ok(())
@ -517,7 +506,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
progress: f32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player
.set_experience(
@ -533,7 +522,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
points: i32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player
.set_experience(
@ -549,7 +538,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
levels: i32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.add_experience_levels(levels).await;
Ok(())
@ -559,13 +548,13 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
points: i32,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.add_experience_points(points).await;
Ok(())
}
async fn is_sneaking(&mut self, player: Resource<Player>) -> Result<bool, String> {
async fn is_sneaking(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().sneaking.load(Ordering::Relaxed))
}
@ -574,13 +563,13 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
sneaking: bool,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_sneaking(sneaking).await;
Ok(())
}
async fn is_sprinting(&mut self, player: Resource<Player>) -> Result<bool, String> {
async fn is_sprinting(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().sprinting.load(Ordering::Relaxed))
}
@ -589,23 +578,23 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
sprinting: bool,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_sprinting(sprinting).await;
Ok(())
}
async fn is_on_ground(&mut self, player: Resource<Player>) -> Result<bool, String> {
async fn is_on_ground(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().on_ground.load(Ordering::Relaxed))
}
async fn is_flying(&mut self, player: Resource<Player>) -> Result<bool, String> {
async fn is_flying(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.is_flying().await)
}
async fn set_flying(&mut self, player: Resource<Player>, flying: bool) -> Result<(), String> {
async fn set_flying(&mut self, player: Resource<Player>, flying: bool) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
{
let mut abilities = player.abilities.lock().await;
@ -618,7 +607,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn get_abilities(
&mut self,
player: Resource<Player>,
) -> Result<pumpkin::plugin::player::PlayerAbilities, String> {
) -> wasmtime::Result<pumpkin::plugin::player::PlayerAbilities> {
let player = player_from_resource(self, &player)?;
let abilities = player.abilities.lock().await;
Ok(pumpkin::plugin::player::PlayerAbilities {
@ -636,7 +625,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
&mut self,
player: Resource<Player>,
abilities: pumpkin::plugin::player::PlayerAbilities,
) -> Result<(), String> {
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
{
let mut a = player.abilities.lock().await;
@ -652,7 +641,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(())
}
async fn get_ip(&mut self, player: Resource<Player>) -> Result<String, String> {
async fn get_ip(&mut self, player: Resource<Player>) -> wasmtime::Result<String> {
let player = player_from_resource(self, &player)?;
Ok(player.get_ip().await)
}

View File

@ -8,47 +8,56 @@ use crate::plugin::loader::wasm::wasm_host::{
},
};
impl PluginHostState {
fn get_server_res(&self, res: &Resource<Server>) -> wasmtime::Result<&ServerResource> {
self.resource_table
.get::<ServerResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
}
impl pumpkin::plugin::server::Host for PluginHostState {}
impl pumpkin::plugin::server::HostServer for PluginHostState {
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<ServerResource>(Resource::new_own(rep.rep()));
Ok(())
}
async fn get_difficulty(&mut self, res: Resource<Server>) -> wasmtime::Result<Difficulty> {
let resource = self.get_server_res(&res)?;
async fn get_difficulty(&mut self, server: Resource<Server>) -> Difficulty {
let resource: &ServerResource = self
.resource_table
.get_any_mut(server.rep())
.expect("invalid server resource handle")
.downcast_ref::<ServerResource>()
.expect("resource type mismatch");
match resource.provider.get_difficulty() {
Ok(match resource.provider.get_difficulty() {
pumpkin_util::Difficulty::Peaceful => Difficulty::Peaceful,
pumpkin_util::Difficulty::Easy => Difficulty::Easy,
pumpkin_util::Difficulty::Normal => Difficulty::Normal,
pumpkin_util::Difficulty::Hard => Difficulty::Hard,
}
})
}
async fn get_player_count(
&mut self,
_server: Resource<pumpkin::plugin::server::Server>,
) -> u32 {
let server = self.server.as_ref().expect("server not available");
server.get_player_count() as u32
async fn get_player_count(&mut self, _res: Resource<Server>) -> wasmtime::Result<u32> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(server.get_player_count() as u32)
}
async fn get_mspt(&mut self, _server: Resource<pumpkin::plugin::server::Server>) -> f64 {
let server = self.server.as_ref().expect("server not available");
server.get_mspt()
async fn get_mspt(&mut self, _res: Resource<Server>) -> wasmtime::Result<f64> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(server.get_mspt())
}
async fn get_tps(&mut self, _server: Resource<pumpkin::plugin::server::Server>) -> f64 {
let server = self.server.as_ref().expect("server not available");
server.get_tps()
async fn get_tps(&mut self, _res: Resource<Server>) -> wasmtime::Result<f64> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(server.get_tps())
}
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
self.resource_table
.delete::<ServerResource>(Resource::new_own(rep.rep()))
.map_err(wasmtime::Error::from)?;
Ok(())
}
}

View File

@ -1,5 +1,4 @@
use std::borrow::Cow;
use wasmtime::component::Resource;
use crate::plugin::loader::wasm::wasm_host::{
@ -12,39 +11,309 @@ use crate::plugin::loader::wasm::wasm_host::{
};
use pumpkin_util::text::{
TextComponent as InternalTextComponent,
click::ClickEvent,
color::{self, Color},
hover::HoverEvent,
};
impl pumpkin::plugin::text::Host for PluginHostState {}
// --- Trapping Helpers ---
impl PluginHostState {
fn get_text_ref(
&self,
res: &Resource<TextComponent>,
) -> wasmtime::Result<&TextComponentResource> {
self.resource_table
.get::<TextComponentResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
// TODO - Change the pumpkin_util::text::TextComponent to use &mut self instead of self for the builder pattern.
// right now we have to do a bunch of cloning due to the fact that the builder pattern doesn't accept &mut self.
impl DowncastResourceExt<TextComponentResource> for Resource<TextComponent> {
fn get_text_mut(
&mut self,
res: &Resource<TextComponent>,
) -> wasmtime::Result<&mut TextComponentResource> {
self.resource_table
.get_mut::<TextComponentResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
fn take_text(
&mut self,
res: &Resource<TextComponent>,
) -> wasmtime::Result<TextComponentResource> {
self.resource_table
.delete::<TextComponentResource>(Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
}
impl DowncastResourceExt<TextComponentResource> for wasmtime::component::Resource<TextComponent> {
fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a TextComponentResource {
state
.resource_table
.get_any_mut(self.rep())
.expect("invalid text-component resource handle")
.downcast_ref::<TextComponentResource>()
.expect("resource type mismatch")
.expect("invalid handle")
.downcast_ref()
.expect("type mismatch")
}
fn downcast_mut<'a>(&'a self, state: &'a mut PluginHostState) -> &'a mut TextComponentResource {
state
.resource_table
.get_any_mut(self.rep())
.expect("invalid text-component resource handle")
.downcast_mut::<TextComponentResource>()
.expect("resource type mismatch")
.expect("invalid handle")
.downcast_mut()
.expect("type mismatch")
}
fn consume(self, state: &mut PluginHostState) -> TextComponentResource {
state
.resource_table
.delete::<TextComponentResource>(Resource::new_own(self.rep()))
.expect("invalid text-component resource handle")
.delete(wasmtime::component::Resource::new_own(self.rep()))
.expect("invalid handle")
}
}
impl pumpkin::plugin::text::Host for PluginHostState {}
impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
async fn text(&mut self, plain: String) -> wasmtime::Result<Resource<TextComponent>> {
let tc = InternalTextComponent::text(plain);
self.add_text_component(tc)
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
}
async fn translate(
&mut self,
key: String,
with: Vec<Resource<TextComponent>>,
) -> wasmtime::Result<Resource<TextComponent>> {
let mut components = Vec::with_capacity(with.len());
for r in with {
components.push(self.take_text(&r)?.provider);
}
let tc = InternalTextComponent::translate(key, components);
self.add_text_component(tc)
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
}
async fn add_child(
&mut self,
text_component: Resource<TextComponent>,
child: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let child_tc = self.take_text(&child)?.provider;
let parent = self.get_text_mut(&text_component)?;
// Cloning here as noted in your TODO until builder pattern supports &mut self
parent.provider = parent.provider.clone().add_child(child_tc);
Ok(())
}
async fn add_text(
&mut self,
text_component: Resource<TextComponent>,
text: String,
) -> wasmtime::Result<()> {
let parent = self.get_text_mut(&text_component)?;
parent.provider = parent.provider.clone().add_text(text);
Ok(())
}
async fn get_text(
&mut self,
text_component: Resource<TextComponent>,
) -> wasmtime::Result<String> {
Ok(self
.get_text_ref(&text_component)?
.provider
.clone()
.get_text())
}
async fn encode(
&mut self,
text_component: Resource<TextComponent>,
) -> wasmtime::Result<Vec<u8>> {
Ok(self
.get_text_ref(&text_component)?
.provider
.encode()
.into_vec())
}
async fn color_named(
&mut self,
res: Resource<TextComponent>,
color: NamedColor,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.color =
Some(Color::Named(map_named_color(color)));
Ok(())
}
async fn color_rgb(
&mut self,
res: Resource<TextComponent>,
color: RgbColor,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.color =
Some(Color::Rgb(color::RGBColor::new(color.r, color.g, color.b)));
Ok(())
}
async fn bold(&mut self, res: Resource<TextComponent>, value: bool) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.bold = Some(value);
Ok(())
}
async fn italic(&mut self, res: Resource<TextComponent>, value: bool) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.italic = Some(value);
Ok(())
}
async fn underlined(
&mut self,
res: Resource<TextComponent>,
value: bool,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.underlined = Some(value);
Ok(())
}
async fn strikethrough(
&mut self,
res: Resource<TextComponent>,
value: bool,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.strikethrough = Some(value);
Ok(())
}
async fn obfuscated(
&mut self,
res: Resource<TextComponent>,
value: bool,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.obfuscated = Some(value);
Ok(())
}
async fn insertion(
&mut self,
res: Resource<TextComponent>,
text: String,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.insertion = Some(text);
Ok(())
}
async fn font(&mut self, res: Resource<TextComponent>, font: String) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.font = Some(font);
Ok(())
}
async fn shadow_color(
&mut self,
res: Resource<TextComponent>,
color: ArgbColor,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.shadow_color =
Some(color::ARGBColor::new(color.a, color.r, color.g, color.b));
Ok(())
}
async fn click_open_url(
&mut self,
res: Resource<TextComponent>,
url: String,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.click_event = Some(ClickEvent::OpenUrl {
url: Cow::Owned(url),
});
Ok(())
}
async fn click_run_command(
&mut self,
res: Resource<TextComponent>,
command: String,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.click_event = Some(ClickEvent::RunCommand {
command: Cow::Owned(command),
});
Ok(())
}
async fn click_suggest_command(
&mut self,
res: Resource<TextComponent>,
command: String,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.click_event = Some(ClickEvent::SuggestCommand {
command: Cow::Owned(command),
});
Ok(())
}
async fn click_copy_to_clipboard(
&mut self,
res: Resource<TextComponent>,
text: String,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.click_event = Some(ClickEvent::CopyToClipboard {
value: Cow::Owned(text),
});
Ok(())
}
async fn hover_show_text(
&mut self,
res: Resource<TextComponent>,
text: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let hover_tc = self.take_text(&text)?.provider;
self.get_text_mut(&res)?.provider.0.style.hover_event = Some(HoverEvent::ShowText {
value: vec![hover_tc.0],
});
Ok(())
}
async fn hover_show_item(
&mut self,
res: Resource<TextComponent>,
item: String,
) -> wasmtime::Result<()> {
self.get_text_mut(&res)?.provider.0.style.hover_event = Some(HoverEvent::ShowItem {
id: Cow::Owned(item),
count: None,
});
Ok(())
}
async fn hover_show_entity(
&mut self,
res: Resource<TextComponent>,
entity_type: String,
id: String,
name: Option<Resource<TextComponent>>,
) -> wasmtime::Result<()> {
let name_val = match name {
Some(r) => Some(vec![self.take_text(&r)?.provider.0]),
None => None,
};
self.get_text_mut(&res)?.provider.0.style.hover_event = Some(HoverEvent::ShowEntity {
id: Cow::Owned(entity_type),
uuid: Cow::Owned(id),
name: name_val,
});
Ok(())
}
async fn drop(&mut self, rep: Resource<TextComponent>) -> wasmtime::Result<()> {
self.resource_table
.delete::<TextComponentResource>(Resource::new_own(rep.rep()))
.map_err(wasmtime::Error::from)?;
Ok(())
}
}
@ -68,225 +337,3 @@ const fn map_named_color(color: NamedColor) -> color::NamedColor {
NamedColor::White => color::NamedColor::White,
}
}
impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
async fn text(&mut self, plain: String) -> Resource<TextComponent> {
let tc = pumpkin_util::text::TextComponent::text(plain);
self.add_text_component(tc).unwrap()
}
async fn translate(
&mut self,
key: String,
with: Vec<Resource<TextComponent>>,
) -> Resource<TextComponent> {
let with: Vec<pumpkin_util::text::TextComponent> =
with.into_iter().map(|r| r.consume(self).provider).collect();
let tc = pumpkin_util::text::TextComponent::translate(key, with);
self.add_text_component(tc).unwrap()
}
async fn add_child(
&mut self,
text_component: Resource<TextComponent>,
child: Resource<TextComponent>,
) {
let child = child.consume(self).provider;
let parent = &mut text_component.downcast_mut(self).provider;
*parent = parent.clone().add_child(child);
}
async fn add_text(&mut self, text_component: Resource<TextComponent>, text: String) {
let parent = &mut text_component.downcast_mut(self).provider;
*parent = parent.clone().add_text(text);
}
async fn get_text(&mut self, text_component: Resource<TextComponent>) -> String {
text_component
.downcast_ref(self)
.provider
.clone()
.get_text()
}
async fn encode(&mut self, text_component: Resource<TextComponent>) -> Vec<u8> {
text_component
.downcast_ref(self)
.provider
.encode()
.into_vec()
}
async fn color_named(&mut self, text_component: Resource<TextComponent>, color: NamedColor) {
text_component.downcast_mut(self).provider.0.style.color =
Some(Color::Named(map_named_color(color)));
}
async fn color_rgb(&mut self, text_component: Resource<TextComponent>, color: RgbColor) {
text_component.downcast_mut(self).provider.0.style.color =
Some(Color::Rgb(color::RGBColor::new(color.r, color.g, color.b)));
}
async fn bold(&mut self, text_component: Resource<TextComponent>, value: bool) {
text_component.downcast_mut(self).provider.0.style.bold = Some(value);
}
async fn italic(&mut self, text_component: Resource<TextComponent>, value: bool) {
text_component.downcast_mut(self).provider.0.style.italic = Some(value);
}
async fn underlined(&mut self, text_component: Resource<TextComponent>, value: bool) {
text_component
.downcast_mut(self)
.provider
.0
.style
.underlined = Some(value);
}
async fn strikethrough(&mut self, text_component: Resource<TextComponent>, value: bool) {
text_component
.downcast_mut(self)
.provider
.0
.style
.strikethrough = Some(value);
}
async fn obfuscated(&mut self, text_component: Resource<TextComponent>, value: bool) {
text_component
.downcast_mut(self)
.provider
.0
.style
.obfuscated = Some(value);
}
async fn insertion(&mut self, text_component: Resource<TextComponent>, text: String) {
text_component.downcast_mut(self).provider.0.style.insertion = Some(text);
}
async fn font(&mut self, text_component: Resource<TextComponent>, font: String) {
text_component.downcast_mut(self).provider.0.style.font = Some(font);
}
async fn shadow_color(&mut self, text_component: Resource<TextComponent>, color: ArgbColor) {
text_component
.downcast_mut(self)
.provider
.0
.style
.shadow_color = Some(color::ARGBColor::new(color.a, color.r, color.g, color.b));
}
async fn click_open_url(&mut self, text_component: Resource<TextComponent>, url: String) {
text_component
.downcast_mut(self)
.provider
.0
.style
.click_event = Some(ClickEvent::OpenUrl {
url: Cow::Owned(url),
});
}
async fn click_run_command(
&mut self,
text_component: Resource<TextComponent>,
command: String,
) {
text_component
.downcast_mut(self)
.provider
.0
.style
.click_event = Some(ClickEvent::RunCommand {
command: Cow::Owned(command),
});
}
async fn click_suggest_command(
&mut self,
text_component: Resource<TextComponent>,
command: String,
) {
text_component
.downcast_mut(self)
.provider
.0
.style
.click_event = Some(ClickEvent::SuggestCommand {
command: Cow::Owned(command),
});
}
async fn click_copy_to_clipboard(
&mut self,
text_component: Resource<TextComponent>,
text: String,
) {
text_component
.downcast_mut(self)
.provider
.0
.style
.click_event = Some(ClickEvent::CopyToClipboard {
value: Cow::Owned(text),
});
}
async fn hover_show_text(
&mut self,
text_component: Resource<TextComponent>,
text: Resource<TextComponent>,
) {
let hover_tc = text.consume(self).provider;
text_component
.downcast_mut(self)
.provider
.0
.style
.hover_event = Some(HoverEvent::ShowText {
value: vec![hover_tc.0],
});
}
async fn hover_show_item(&mut self, text_component: Resource<TextComponent>, item: String) {
text_component
.downcast_mut(self)
.provider
.0
.style
.hover_event = Some(HoverEvent::ShowItem {
id: Cow::Owned(item),
count: None,
});
}
async fn hover_show_entity(
&mut self,
text_component: Resource<TextComponent>,
entity_type: String,
id: String,
name: Option<Resource<TextComponent>>,
) {
let name = name.map(|r| vec![r.consume(self).provider.0]);
text_component
.downcast_mut(self)
.provider
.0
.style
.hover_event = Some(HoverEvent::ShowEntity {
id: Cow::Owned(entity_type),
uuid: Cow::Owned(id),
name,
});
}
async fn drop(&mut self, rep: Resource<TextComponent>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<TextComponentResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@ -1,85 +1,69 @@
use pumpkin_data::block_state::PistonBehavior;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags;
use pumpkin_world::world::{BlockFlags, SimpleWorld};
use wasmtime::component::Resource;
use pumpkin_world::world::SimpleWorld;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::world::{
BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, BlockState as WitBlockState,
PistonBehavior as WitPistonBehavior,
};
use crate::plugin::loader::wasm::wasm_host::{
DowncastResourceExt,
state::{PluginHostState, TextComponentResource, WorldResource},
wit::v0_1_0::pumpkin::{self, plugin::world::World},
};
fn text_component_from_resource(
state: &PluginHostState,
text: &Resource<pumpkin::plugin::text::TextComponent>,
) -> Result<pumpkin_util::text::TextComponent, String> {
state
.resource_table
.get::<TextComponentResource>(&Resource::new_own(text.rep()))
.map_err(|_| "invalid text-component resource handle".to_string())
.map(|resource| resource.provider.clone())
}
impl DowncastResourceExt<WorldResource> for Resource<World> {
fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a WorldResource {
state
.resource_table
.get_any_mut(self.rep())
.expect("invalid world resource handle")
.downcast_ref::<WorldResource>()
.expect("resource type mismatch")
// --- Trapping Helpers ---
impl PluginHostState {
fn get_world_res(&self, res: &Resource<World>) -> wasmtime::Result<&WorldResource> {
self.resource_table
.get::<WorldResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
fn downcast_mut<'a>(&'a self, state: &'a mut PluginHostState) -> &'a mut WorldResource {
state
fn get_text_provider(
&self,
res: &Resource<pumpkin::plugin::text::TextComponent>,
) -> wasmtime::Result<pumpkin_util::text::TextComponent> {
Ok(self
.resource_table
.get_any_mut(self.rep())
.expect("invalid world resource handle")
.downcast_mut::<WorldResource>()
.expect("resource type mismatch")
}
fn consume(self, state: &mut PluginHostState) -> WorldResource {
state
.resource_table
.delete::<WorldResource>(Resource::new_own(self.rep()))
.expect("invalid world resource handle")
.get::<TextComponentResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)?
.provider
.clone())
}
}
impl pumpkin::plugin::world::Host for PluginHostState {}
impl pumpkin::plugin::world::HostWorld for PluginHostState {
async fn get_id(&mut self, world: Resource<World>) -> String {
world
.downcast_ref(self)
async fn get_id(&mut self, world: Resource<World>) -> wasmtime::Result<String> {
Ok(self
.get_world_res(&world)?
.provider
.get_world_name()
.to_string()
.to_string())
}
async fn get_block_state_id(&mut self, world: Resource<World>, pos: WitBlockPos) -> u16 {
let world_ref = world.downcast_ref(self);
async fn get_block_state_id(
&mut self,
world: Resource<World>,
pos: WitBlockPos,
) -> wasmtime::Result<u16> {
let world_ref = self.get_world_res(&world)?;
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
world_ref.provider.get_block_state_id(&internal_pos).await
Ok(world_ref.provider.get_block_state_id(&internal_pos).await)
}
async fn get_block_state(&mut self, world: Resource<World>, pos: WitBlockPos) -> WitBlockState {
let world_ref = world.downcast_ref(self);
async fn get_block_state(
&mut self,
world: Resource<World>,
pos: WitBlockPos,
) -> wasmtime::Result<WitBlockState> {
let world_ref = self.get_world_res(&world)?;
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
// Fetch the actual BlockState struct from the world
// get_block_state typically returns &'static BlockState in Pumpkin
let state = world_ref.provider.get_block_state(&internal_pos).await;
WitBlockState {
Ok(WitBlockState {
id: state.id,
luminance: state.luminance,
opacity: state.opacity,
@ -96,7 +80,7 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
PistonBehavior::Ignore => WitPistonBehavior::Ignore,
PistonBehavior::PushOnly => WitPistonBehavior::PushOnly,
},
}
})
}
async fn set_block_state(
@ -105,11 +89,10 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
pos: WitBlockPos,
state: u16,
update_flags: WitBlockFlags,
) {
let world_ref = world.downcast_ref(self);
) -> wasmtime::Result<()> {
let world_ref = self.get_world_res(&world)?;
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
// Map WIT flags to your internal bitflags
let mut internal_flags = BlockFlags::empty();
if update_flags.contains(WitBlockFlags::NOTIFY_NEIGHBORS) {
internal_flags |= BlockFlags::NOTIFY_NEIGHBORS;
@ -136,65 +119,91 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
internal_flags |= BlockFlags::SKIP_BLOCK_ADDED_CALLBACK;
}
// Update the world
world_ref
.provider
.clone()
.set_block_state(&internal_pos, state, internal_flags)
.await;
Ok(())
}
async fn get_time_of_day(&mut self, world: Resource<World>) -> u64 {
let world_ref = world.downcast_ref(self);
world_ref.provider.get_time_of_day().await as u64
async fn get_time_of_day(&mut self, world: Resource<World>) -> wasmtime::Result<u64> {
Ok(self.get_world_res(&world)?.provider.get_time_of_day().await as u64)
}
async fn set_time_of_day(&mut self, world: Resource<World>, time: u64) {
let world_ref = world.downcast_ref(self);
world_ref.provider.set_time_of_day(time as i64).await;
async fn set_time_of_day(&mut self, world: Resource<World>, time: u64) -> wasmtime::Result<()> {
self.get_world_res(&world)?
.provider
.set_time_of_day(time as i64)
.await;
Ok(())
}
async fn get_world_age(&mut self, world: Resource<World>) -> u64 {
let world_ref = world.downcast_ref(self);
world_ref.provider.get_world_age().await as u64
async fn get_world_age(&mut self, world: Resource<World>) -> wasmtime::Result<u64> {
Ok(self.get_world_res(&world)?.provider.get_world_age().await as u64)
}
async fn get_dimension(&mut self, world: Resource<World>) -> String {
let world_ref = world.downcast_ref(self);
world_ref.provider.dimension.minecraft_name.to_string()
async fn get_dimension(&mut self, world: Resource<World>) -> wasmtime::Result<String> {
Ok(self
.get_world_res(&world)?
.provider
.dimension
.minecraft_name
.to_string())
}
async fn get_top_block_y(&mut self, world: Resource<World>, x: i32, z: i32) -> i32 {
let world_ref = world.downcast_ref(self);
world_ref
async fn get_top_block_y(
&mut self,
world: Resource<World>,
x: i32,
z: i32,
) -> wasmtime::Result<i32> {
Ok(self
.get_world_res(&world)?
.provider
.get_top_block(pumpkin_util::math::vector2::Vector2::new(x, z))
.await
.await)
}
async fn get_motion_blocking_height(&mut self, world: Resource<World>, x: i32, z: i32) -> i32 {
let world_ref = world.downcast_ref(self);
world_ref.provider.get_motion_blocking_height(x, z).await
async fn get_motion_blocking_height(
&mut self,
world: Resource<World>,
x: i32,
z: i32,
) -> wasmtime::Result<i32> {
Ok(self
.get_world_res(&world)?
.provider
.get_motion_blocking_height(x, z)
.await)
}
async fn is_raining(&mut self, world: Resource<World>) -> bool {
let world_ref = world.downcast_ref(self);
world_ref.provider.is_raining().await
async fn is_raining(&mut self, world: Resource<World>) -> wasmtime::Result<bool> {
Ok(self.get_world_res(&world)?.provider.is_raining().await)
}
async fn set_raining(&mut self, world: Resource<World>, raining: bool) {
let world_ref = world.downcast_ref(self);
world_ref.provider.set_raining(raining).await;
async fn set_raining(&mut self, world: Resource<World>, raining: bool) -> wasmtime::Result<()> {
self.get_world_res(&world)?
.provider
.set_raining(raining)
.await;
Ok(())
}
async fn is_thundering(&mut self, world: Resource<World>) -> bool {
let world_ref = world.downcast_ref(self);
world_ref.provider.is_thundering().await
async fn is_thundering(&mut self, world: Resource<World>) -> wasmtime::Result<bool> {
Ok(self.get_world_res(&world)?.provider.is_thundering().await)
}
async fn set_thundering(&mut self, world: Resource<World>, thundering: bool) {
let world_ref = world.downcast_ref(self);
world_ref.provider.set_thundering(thundering).await;
async fn set_thundering(
&mut self,
world: Resource<World>,
thundering: bool,
) -> wasmtime::Result<()> {
self.get_world_res(&world)?
.provider
.set_thundering(thundering)
.await;
Ok(())
}
async fn broadcast_system_message(
@ -202,19 +211,19 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
world: Resource<World>,
message: Resource<pumpkin::plugin::text::TextComponent>,
overlay: bool,
) {
let message = text_component_from_resource(self, &message).unwrap();
let world_ref = world.downcast_ref(self);
world_ref
) -> wasmtime::Result<()> {
let msg = self.get_text_provider(&message)?;
self.get_world_res(&world)?
.provider
.broadcast_system_message(&message, overlay)
.broadcast_system_message(&msg, overlay)
.await;
Ok(())
}
async fn drop(&mut self, rep: Resource<World>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<WorldResource>(Resource::new_own(rep.rep()));
self.resource_table
.delete::<WorldResource>(Resource::new_own(rep.rep()))
.map_err(wasmtime::Error::from)?;
Ok(())
}
}