From cbb12b2200e43a6d1e8aac1fd60f9089c0275f32 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 10 Aug 2026 05:39:01 -0700 Subject: [PATCH] feat(plugin-api): Initial inter-plugin communication (#2788) * feat(plugin-api): Initial inter-plugin communication * Update wit submodule commit ref --------- Co-authored-by: Jakub Palacky --- crates/pumpkin-plugin-api/src/lib.rs | 20 ++++++++++++++-- crates/pumpkin-plugin-wit | 2 +- crates/pumpkin/src/plugin/api/mod.rs | 12 ++++++++++ crates/pumpkin/src/plugin/loader/wasm/mod.rs | 15 ++++++++++++ .../src/plugin/loader/wasm/wasm_host/mod.rs | 18 +++++++++++++++ .../src/plugin/loader/wasm/wasm_host/state.rs | 2 ++ .../loader/wasm/wasm_host/wit/v0_1/ipc.rs | 23 +++++++++++++++++++ .../loader/wasm/wasm_host/wit/v0_1/mod.rs | 1 + crates/pumpkin/src/plugin/mod.rs | 22 ++++++++++++++++++ 9 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs diff --git a/crates/pumpkin-plugin-api/src/lib.rs b/crates/pumpkin-plugin-api/src/lib.rs index de472923e..388760fcf 100644 --- a/crates/pumpkin-plugin-api/src/lib.rs +++ b/crates/pumpkin-plugin-api/src/lib.rs @@ -99,8 +99,8 @@ pub use wit::pumpkin::plugin::{ data_components, entity, entity_types::EntityType, event::{self as events_wit, EventType}, - gui, i18n, item_stack, java_dialogs, java_packets, particles, permission, player, scoreboard, - server, text, uuid, world, + gui, i18n, ipc, item_stack, java_dialogs, java_packets, particles, permission, player, + scoreboard, server, text, uuid, world, }; // Convenience re-exports of commonly-used plugin types so plugin authors can @@ -263,6 +263,13 @@ impl wit::Guest for Component { goal.stop(server, entity); } } + + fn handle_ipc_message( + sender: wit::PluginId, + message: wit::IpcMessage, + ) -> Result { + plugin().handle_ipc_message(sender, message) + } } /// Convenience alias for `core::result::Result` used throughout the plugin API. @@ -295,6 +302,15 @@ pub trait Plugin: Send + Sync { fn on_unload(&mut self, _context: Context) -> Result<()> { Ok(()) } + + /// Called when the plugin receives a message from another plugin. + fn handle_ipc_message( + &mut self, + _sender: wit::PluginId, + _message: wit::IpcMessage, + ) -> Result { + Err("This plugin cannot receive messages.".to_string()) + } } #[doc(hidden)] diff --git a/crates/pumpkin-plugin-wit b/crates/pumpkin-plugin-wit index 97c0281c8..160251fb0 160000 --- a/crates/pumpkin-plugin-wit +++ b/crates/pumpkin-plugin-wit @@ -1 +1 @@ -Subproject commit 97c0281c8e3c33741996f864c55d09e86f0d2728 +Subproject commit 160251fb0b15ad529bf7c4939140bb5fa0c07035 diff --git a/crates/pumpkin/src/plugin/api/mod.rs b/crates/pumpkin/src/plugin/api/mod.rs index ec5654f6e..e5e28ea53 100644 --- a/crates/pumpkin/src/plugin/api/mod.rs +++ b/crates/pumpkin/src/plugin/api/mod.rs @@ -55,4 +55,16 @@ pub trait Plugin: Send + Sync + 'static { fn on_unload(&self, server: Arc) -> PluginFuture<'_, Result<(), String>> { Box::pin(async move { Ok(()) }) } + + /// Asynchronous method called when the plugin receives an IPC message. + /// + /// This processes the message, and optionally returns a response + #[expect(unused)] + fn on_ipc_message( + &self, + sender: &str, + message: &[u8], + ) -> PluginFuture<'_, Result, String>> { + Box::pin(async move { Err("This plugin cannot receive messages.".to_string()) }) + } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/mod.rs b/crates/pumpkin/src/plugin/loader/wasm/mod.rs index bca42c7e5..b137a2513 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/mod.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/mod.rs @@ -29,6 +29,21 @@ impl Plugin for WasmPlugin { .flatten() }) } + + fn on_ipc_message( + &self, + sender: &str, + message: &[u8], + ) -> PluginFuture<'_, Result, String>> { + let sender_own = sender.to_owned(); + let message_own = message.to_owned(); + Box::pin(async move { + self.handle_ipc_message(&sender_own, &message_own) + .await + .map_err(|err| err.to_string()) + .flatten() + }) + } } pub struct WasmPluginLoader; diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs index 36f73e54e..306c6f758 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs @@ -266,6 +266,8 @@ impl WasmPlugin { store.data_mut().server = Some(context.server.clone()); + store.data_mut().name = Some(metadata.name.clone()); + match self.plugin_instance { PluginInstance::V0_1(ref plugin) => { let context = store.data_mut().add_context(context)?; @@ -297,6 +299,22 @@ impl WasmPlugin { } } } + + pub async fn handle_ipc_message( + &self, + sender: &String, + message: &Vec, + ) -> Result, String>, wasmtime::Error> { + let mut store = self.store.lock().await; + + match self.plugin_instance { + PluginInstance::V0_1(ref plugin) => { + plugin + .call_handle_ipc_message(&mut *store, sender, message) + .await + } + } + } } pub trait DowncastResourceExt { diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs index 166bd4f35..efb236a6c 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -69,6 +69,7 @@ pub struct PluginHostState { pub plugin: Option>, pub server: Option>, pub permissions: Vec, + pub name: Option, } impl Default for PluginHostState { @@ -92,6 +93,7 @@ impl PluginHostState { plugin: None, server: None, permissions: Vec::new(), + name: None, } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs new file mode 100644 index 000000000..55d89d91b --- /dev/null +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs @@ -0,0 +1,23 @@ +use crate::plugin::loader::wasm::wasm_host::{ + state::PluginHostState, + wit::v0_1::pumpkin::{ + self, + plugin::ipc::{IpcMessage, PluginId}, + }, +}; + +impl pumpkin::plugin::ipc::Host for PluginHostState { + async fn send_ipc_message( + &mut self, + recipient: PluginId, + message: IpcMessage, + ) -> wasmtime::Result, ()>> { + Ok(self + .server + .as_ref() + .unwrap() + .plugin_manager + .send_message(self.name.as_ref().unwrap(), &recipient, &message) + .await) + } +} diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs index 9f5096a70..52d96892c 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs @@ -19,6 +19,7 @@ pub mod forms; pub mod generated_packets; pub mod gui; pub mod i18n; +pub mod ipc; pub mod item_stack; pub mod java_dialogs; pub mod logging; diff --git a/crates/pumpkin/src/plugin/mod.rs b/crates/pumpkin/src/plugin/mod.rs index 67a5642da..30ee58776 100644 --- a/crates/pumpkin/src/plugin/mod.rs +++ b/crates/pumpkin/src/plugin/mod.rs @@ -1032,6 +1032,28 @@ impl PluginManager { } } } + + pub async fn send_message( + &self, + sender: &str, + recipient: &str, + message: &[u8], + ) -> Result, String>, ()> { + if sender == recipient { + return Err(()); + } + + let plugins = self.plugins.read().await; + let target_plugin = &plugins + .iter() + .find(|p| p.metadata.name == recipient) + .ok_or(())?; + if let Some(instance) = &target_plugin.instance { + Ok(instance.on_ipc_message(sender, message).await) + } else { + Err(()) + } + } } #[cfg(test)]