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 <kubik.palacky@gmail.com>
This commit is contained in:
Demetrius Kanios
2026-08-10 05:39:01 -07:00
committed by GitHub
parent b17096d96b
commit cbb12b2200
9 changed files with 112 additions and 3 deletions

View File

@@ -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<wit::IpcMessage, String> {
plugin().handle_ipc_message(sender, message)
}
}
/// Convenience alias for `core::result::Result<T, String>` 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<wit::IpcMessage, String> {
Err("This plugin cannot receive messages.".to_string())
}
}
#[doc(hidden)]

View File

@@ -55,4 +55,16 @@ pub trait Plugin: Send + Sync + 'static {
fn on_unload(&self, server: Arc<Context>) -> 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<Vec<u8>, String>> {
Box::pin(async move { Err("This plugin cannot receive messages.".to_string()) })
}
}

View File

@@ -29,6 +29,21 @@ impl Plugin for WasmPlugin {
.flatten()
})
}
fn on_ipc_message(
&self,
sender: &str,
message: &[u8],
) -> PluginFuture<'_, Result<Vec<u8>, 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;

View File

@@ -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<u8>,
) -> Result<Result<Vec<u8>, 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<E> {

View File

@@ -69,6 +69,7 @@ pub struct PluginHostState {
pub plugin: Option<Weak<WasmPlugin>>,
pub server: Option<Arc<Server>>,
pub permissions: Vec<String>,
pub name: Option<String>,
}
impl Default for PluginHostState {
@@ -92,6 +93,7 @@ impl PluginHostState {
plugin: None,
server: None,
permissions: Vec::new(),
name: None,
}
}

View File

@@ -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<Result<Result<IpcMessage, String>, ()>> {
Ok(self
.server
.as_ref()
.unwrap()
.plugin_manager
.send_message(self.name.as_ref().unwrap(), &recipient, &message)
.await)
}
}

View File

@@ -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;

View File

@@ -1032,6 +1032,28 @@ impl PluginManager {
}
}
}
pub async fn send_message(
&self,
sender: &str,
recipient: &str,
message: &[u8],
) -> Result<Result<Vec<u8>, 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)]