wasm_link/plugin_tree_head.rs
1//! Entry point for dispatching function calls to loaded plugins.
2//!
3//! The [`PluginTreeHead`] represents a fully loaded and linked plugin tree.
4//! It provides access to the root socket - the entry point interface that
5//! the host application calls into.
6
7use std::sync::{ Arc, Mutex };
8use wasmtime::component::Val ;
9
10use crate::interface::InterfaceData ;
11use crate::plugin::PluginData ;
12use crate::socket::Socket ;
13use crate::plugin_instance::PluginInstance ;
14use crate::DispatchError ;
15
16
17
18/// The root node of a loaded plugin tree.
19///
20/// Obtained from [`PluginTree::load`]( crate::PluginTree::load ). This is the host application's entry point
21/// for calling into the plugin system. The root socket represents the interface
22/// that the host has access to - all other interfaces are internal and can only
23/// be called by other plugins.
24///
25/// The host acts as a pseudo-plugin: it doesn't need to be implemented in WASM
26/// and has access to system capabilities that plugins don't.
27pub struct PluginTreeHead<I: InterfaceData, P: PluginData + 'static> {
28 /// Retained for future hot-loading support (adding/removing plugins at runtime).
29 pub(crate) _interface: Arc<I>,
30 pub(crate) socket: Arc<Socket<Mutex<PluginInstance<P>>, P::Id>>,
31}
32
33impl<I: InterfaceData, P: PluginData> PluginTreeHead<I, P> {
34 /// Invokes a function on all plugins in the root socket.
35 ///
36 /// Dispatches the function call to every plugin implementing the root interface.
37 /// The return type is a [`Socket`] whose variant matches the interface's cardinality:
38 ///
39 /// - `ExactlyOne` cardinality → `Socket::ExactlyOne(result)`
40 /// - `AtMostOne` cardinality → `Socket::AtMostOne(Option<result>)`
41 /// - `AtLeastOne` / `Any` cardinality → `Socket::AtLeastOne/Any(HashMap<PluginId, result>)`
42 ///
43 /// # Arguments
44 /// * `interface_path` - Full WIT interface path (e.g., `"my:package/interface-name"`)
45 /// * `function` - Function name to call as declared in the interface
46 /// * `has_return` - Whether you expect to receive a return value
47 /// * `data` - Arguments to pass to the function as wasmtime [`Val`]s
48 ///
49 /// # Example
50 ///
51 /// ```
52 /// use wasm_link::{
53 /// InterfaceData, InterfaceCardinality, FunctionData, ReturnKind,
54 /// PluginData, PluginCtxView, PluginTree, Socket,
55 /// Engine, Component, Linker, ResourceTable, Val,
56 /// };
57 ///
58 /// #[derive( Clone )]
59 /// struct Func { name: String, return_kind: ReturnKind }
60 /// impl FunctionData for Func {
61 /// /* .. */
62 /// # fn name( &self ) -> &str { self.name.as_str() }
63 /// # fn return_kind( &self ) -> ReturnKind { self.return_kind.clone() }
64 /// # fn is_method( &self ) -> bool { false }
65 /// }
66 ///
67 /// struct Interface { id: &'static str, funcs: Vec<Func> }
68 /// impl InterfaceData for Interface {
69 /// /* ... */
70 /// # type Id = &'static str ;
71 /// # type Error = std::convert::Infallible ;
72 /// # type Function = Func ;
73 /// # type FunctionIter<'a> = std::slice::Iter<'a, Func> ;
74 /// # type ResourceIter<'a> = std::iter::Empty<&'a String> ;
75 /// # fn id( &self ) -> Result<&Self::Id, Self::Error> { Ok( &self.id ) }
76 /// # fn cardinality( &self ) -> Result<&InterfaceCardinality, Self::Error> {
77 /// # Ok( &InterfaceCardinality::ExactlyOne )
78 /// # }
79 /// # fn package_name( &self ) -> Result<&str, Self::Error> { Ok( "my:package/example" ) }
80 /// # fn functions( &self ) -> Result<Self::FunctionIter<'_>, Self::Error> {
81 /// # Ok( self.funcs.iter())
82 /// # }
83 /// # fn resources( &self ) -> Result<Self::ResourceIter<'_>, Self::Error> {
84 /// # Ok( std::iter::empty())
85 /// # }
86 /// }
87 ///
88 /// struct Plugin { id: &'static str, plug: &'static str, resource_table: ResourceTable }
89 /// # impl PluginCtxView for Plugin {
90 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
91 /// # }
92 /// impl PluginData for Plugin {
93 /// /* ... */
94 /// # type Id = &'static str ;
95 /// # type InterfaceId = &'static str ;
96 /// # type Error = std::convert::Infallible ;
97 /// # type SocketIter<'a> = std::iter::Empty<&'a Self::InterfaceId> ;
98 /// # fn id( &self ) -> Result<&Self::Id, Self::Error> { Ok( &self.id ) }
99 /// # fn plug( &self ) -> Result<&Self::InterfaceId, Self::Error> { Ok( &self.plug ) }
100 /// # fn sockets( &self ) -> Result<Self::SocketIter<'_>, Self::Error> {
101 /// # Ok( std::iter::empty())
102 /// # }
103 /// # fn component( &self, engine: &Engine ) -> Result<Component, Self::Error> {
104 /// # Ok( Component::new( engine, r#"(component
105 /// # (core module $m (func (export "f") (result i32) i32.const 42))
106 /// # (core instance $i (instantiate $m))
107 /// # (func $f (export "get-value") (result u32) (canon lift (core func $i "f")))
108 /// # (instance $inst (export "get-value" (func $f)))
109 /// # (export "my:package/example" (instance $inst))
110 /// # )"# ).unwrap())
111 /// # }
112 /// }
113 ///
114 /// let root_interface_id = "root" ;
115 /// let plugins = [ Plugin { id: "foo", plug: root_interface_id, resource_table: ResourceTable::new() }];
116 /// let interfaces = [ Interface { id: root_interface_id, funcs: vec![
117 /// Func { name: "get-value".to_string(), return_kind: ReturnKind::MayContainResources }
118 /// ]}];
119 ///
120 /// let ( tree, build_errors ) = PluginTree::new( root_interface_id, interfaces, plugins );
121 /// assert!( build_errors.is_empty() );
122 ///
123 /// let engine = Engine::default();
124 /// let linker = Linker::new( &engine );
125 /// let ( tree_head, load_errors ) = tree.load( &engine, &linker ).unwrap();
126 /// assert!( load_errors.is_empty() );
127 ///
128 /// // Dispatch returns a Socket matching the interface's cardinality
129 /// let result = tree_head.dispatch( "my:package/example", "get-value", true, &[] );
130 ///
131 /// match result {
132 /// Socket::ExactlyOne( Ok( Val::U32( n ))) => assert_eq!( n, 42 ),
133 /// Socket::ExactlyOne( Err( e )) => panic!( "dispatch error: {e}" ),
134 /// _ => panic!( "unexpected cardinality" ),
135 /// }
136 /// ```
137 pub fn dispatch(
138 &self,
139 interface_path: &str,
140 function: &str,
141 has_return: bool,
142 data: &[Val],
143 ) -> Socket<Result<Val, DispatchError<I>>, P::Id> {
144 self.socket.dispatch_function( interface_path, function, has_return, data )
145 }
146}