Skip to main content

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}