wasm_link/plugin.rs
1//! Plugin metadata types.
2//!
3//! A plugin is a WASM component that implements one [`Binding`]( crate::Binding )
4//! (its **plug**) and may depend on zero or more other [`Binding`]( crate::Binding )s
5//! (its **sockets**). The plug declares what the plugin exports; sockets declare what
6//! the plugin expects to import from other plugins.
7
8use wasmtime::{ Engine, Store };
9use wasmtime::component::{ Component, ResourceTable, Linker, Val };
10
11use crate::BindingAny ;
12use crate::plugin_instance::PluginInstance ;
13use crate::Function ;
14
15/// Trait for accessing a [`ResourceTable`] from the store's data type.
16///
17/// Resources that flow between plugins need to be wrapped to track ownership.
18/// This trait provides access to the table where those wrapped resources are stored.
19/// [`ResourceTable`] is part of the wasmtime component model; see the
20/// [wasmtime docs](https://docs.rs/wasmtime/latest/wasmtime/component/) for details.
21///
22/// # Example
23///
24/// ```
25/// use wasmtime::component::ResourceTable ;
26/// use wasm_link::PluginContext ;
27///
28/// struct MyPluginData {
29/// resource_table: ResourceTable,
30/// // ... other fields
31/// }
32///
33/// impl PluginContext for MyPluginData {
34/// fn resource_table( &mut self ) -> &mut ResourceTable {
35/// &mut self.resource_table
36/// }
37/// }
38/// ```
39pub trait PluginContext: Send {
40 /// Returns a mutable reference to a resource table.
41 fn resource_table( &mut self ) -> &mut ResourceTable ;
42}
43
44/// A WASM component bundled with its runtime context, ready for instantiation.
45///
46/// The component's exports (its **plug**) and imports (its **sockets**) are defined through
47/// the [`crate::Binding`], not by this struct.
48///
49/// The `context` is consumed during linking to become the wasmtime [`Store`]( wasmtime::Store )'s data.
50///
51/// # Type Parameters
52/// - `Ctx`: User context type that will be stored in the wasmtime [`Store`]( wasmtime::Store )
53///
54/// # Example
55///
56/// ```
57/// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine, Linker };
58/// # struct Ctx { resource_table: ResourceTable }
59/// # impl PluginContext for Ctx {
60/// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
61/// # }
62/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
63/// let engine = Engine::default();
64/// let linker = Linker::new( &engine );
65///
66/// let plugin = Plugin::new(
67/// Component::new( &engine, "(component)" )?,
68/// Ctx { resource_table: ResourceTable::new() },
69/// ).instantiate( &engine, &linker )?;
70/// # let _ = plugin;
71/// # Ok(())
72/// # }
73/// ```
74#[must_use = "call .instantiate() or .link() to create a PluginInstance"]
75pub struct Plugin<Ctx: 'static> {
76 /// Compiled WASM component
77 component: Component,
78 /// User context consumed at load time to become `Store<Ctx>`
79 context: Ctx,
80 /// Closure that determines fuel for each function call
81 #[allow( clippy::type_complexity )]
82 fuel_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
83 /// Closure that determines epoch deadline for each function call
84 #[allow( clippy::type_complexity )]
85 epoch_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
86 /// Closure that returns a mutable reference to the `ResourceLimiter` in the context
87 #[allow( clippy::type_complexity )]
88 memory_limiter: Option<Box<dyn (FnMut( &mut Ctx ) -> &mut dyn wasmtime::ResourceLimiter) + Send + Sync>>,
89}
90
91impl<Ctx> Plugin<Ctx>
92where
93 Ctx: PluginContext + 'static,
94{
95
96 /// Creates a new plugin declaration.
97 ///
98 /// Note that the plugin ID is not specified here - it's provided when constructing
99 /// the cardinality wrapper that holds this plugin. This is done to prevent duplicate ids.
100 pub fn new(
101 component: Component,
102 context: Ctx,
103 ) -> Self {
104 Self {
105 component,
106 context,
107 fuel_limiter: None,
108 epoch_limiter: None,
109 memory_limiter: None,
110 }
111 }
112
113 /// Sets a closure that determines the fuel limit for each function call.
114 ///
115 /// The closure receives the store, the interface path (e.g., `"my:package/api"`),
116 /// the function name, and the [`Function`] metadata. It returns the fuel to set.
117 ///
118 /// **Warning:** Fuel consumption must be enabled in the [`Engine`]( wasmtime::Engine )
119 /// via [`Config::consume_fuel`]( wasmtime::Config::consume_fuel ). If not enabled,
120 /// dispatch will fail with a [`RuntimeException`]( crate::DispatchError::RuntimeException )
121 /// at call time.
122 ///
123 /// ```
124 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
125 /// # struct Ctx { resource_table: ResourceTable }
126 /// # impl PluginContext for Ctx {
127 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
128 /// # }
129 /// # fn example( component: Component ) {
130 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
131 /// .with_fuel_limiter(| _store, _interface, _function, _metadata | 100_000 );
132 /// # }
133 /// ```
134 pub fn with_fuel_limiter( mut self, limiter: impl FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send + 'static ) -> Self {
135 self.fuel_limiter = Some( Box::new( limiter ));
136 self
137 }
138
139 /// Sets a closure that determines the epoch deadline for each function call.
140 ///
141 /// The closure receives the store, the interface path (e.g., `"my:package/api"`),
142 /// the function name, and the [`Function`] metadata. It returns the epoch deadline
143 /// in ticks.
144 ///
145 /// **Warning:** Epoch interruption must be enabled in the [`Engine`]( wasmtime::Engine )
146 /// via [`Config::epoch_interruption`]( wasmtime::Config::epoch_interruption ). If not
147 /// enabled, the deadline is silently ignored.
148 ///
149 /// ```
150 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
151 /// # struct Ctx { resource_table: ResourceTable }
152 /// # impl PluginContext for Ctx {
153 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
154 /// # }
155 /// # fn example( component: Component ) {
156 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
157 /// .with_epoch_limiter(| _store, _interface, _function, _metadata | 5 );
158 /// # }
159 /// ```
160 pub fn with_epoch_limiter( mut self, limiter: impl FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send + 'static ) -> Self {
161 self.epoch_limiter = Some( Box::new( limiter ));
162 self
163 }
164
165 /// Sets a closure that returns a mutable reference to a [`ResourceLimiter`]( wasmtime::ResourceLimiter )
166 /// embedded in the plugin context.
167 ///
168 /// The limiter is installed into the wasmtime [`Store`]( wasmtime::Store ) once at instantiation
169 /// and controls memory and table growth for the lifetime of the plugin.
170 ///
171 /// The [`ResourceLimiter`]( wasmtime::ResourceLimiter ) must be stored inside the context type `Ctx`
172 /// so that wasmtime can access it through a `&mut Ctx` reference.
173 ///
174 /// ```
175 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
176 /// # struct Ctx { resource_table: ResourceTable, limiter: MyLimiter }
177 /// # impl PluginContext for Ctx {
178 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
179 /// # }
180 /// # struct MyLimiter;
181 /// # impl wasmtime::ResourceLimiter for MyLimiter {
182 /// # fn memory_growing( &mut self, _: usize, _: usize, _: Option<usize> ) -> wasmtime::Result<bool> { Ok( true ) }
183 /// # fn table_growing( &mut self, _: usize, _: usize, _: Option<usize> ) -> wasmtime::Result<bool> { Ok( true ) }
184 /// # }
185 /// # fn example( component: Component ) {
186 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new(), limiter: MyLimiter })
187 /// .with_memory_limiter(| ctx | &mut ctx.limiter );
188 /// # }
189 /// ```
190 pub fn with_memory_limiter(
191 mut self,
192 limiter: impl (FnMut( &mut Ctx ) -> &mut dyn wasmtime::ResourceLimiter) + Send + Sync + 'static,
193 ) -> Self {
194 self.memory_limiter = Some( Box::new( limiter ));
195 self
196 }
197
198 /// Links this plugin with its socket bindings and instantiates it.
199 ///
200 /// Takes ownership of the `linker` because socket bindings are added to it. If you need
201 /// to reuse the same linker for multiple plugins, clone it before passing it in.
202 ///
203 /// # Type Parameters
204 /// - `PluginId`: Must implement `Into<Val>` so plugin IDs can be passed to WASM when
205 /// dispatching to multi-plugin sockets (the ID identifies which plugin produced each result).
206 ///
207 /// # Errors
208 /// Returns an error if linking or instantiation fails.
209 pub fn link<PluginId, Sockets>(
210 self,
211 engine: &Engine,
212 mut linker: Linker<Ctx>,
213 sockets: Sockets,
214 ) -> Result<PluginInstance<Ctx>, wasmtime::Error>
215 where
216 PluginId: Eq + std::hash::Hash + Clone + std::fmt::Debug + Send + Sync + Into<Val> + 'static,
217 Sockets: IntoIterator,
218 Sockets::Item: Into<BindingAny<PluginId, Ctx>>,
219 {
220 sockets.into_iter()
221 .map( Into::into )
222 .try_for_each(| binding | binding.add_to_linker( &mut linker ))?;
223 Self::instantiate( self, engine, &linker )
224 }
225
226 /// A convenience alias for [`Plugin::link`] with 0 sockets
227 ///
228 /// # Errors
229 /// Returns an error if instantiation fails.
230 pub fn instantiate(
231 self,
232 engine: &Engine,
233 linker: &Linker<Ctx>
234 ) -> Result<PluginInstance<Ctx>, wasmtime::Error> {
235 let mut store = Store::new( engine, self.context );
236 if let Some( limiter ) = self.memory_limiter { store.limiter( limiter ); }
237 let instance = linker.instantiate( &mut store, &self.component )?;
238 Ok( PluginInstance {
239 store,
240 instance,
241 fuel_limiter: self.fuel_limiter,
242 epoch_limiter: self.epoch_limiter,
243 })
244 }
245
246}
247
248impl<Ctx: std::fmt::Debug + 'static> std::fmt::Debug for Plugin<Ctx> {
249 fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result {
250 f.debug_struct( "Plugin" )
251 .field( "component", &"<Component>" )
252 .field( "context", &self.context )
253 .field( "fuel_limiter", &self.fuel_limiter.as_ref().map(| _ | "<closure>" ))
254 .field( "epoch_limiter", &self.epoch_limiter.as_ref().map(| _ | "<closure>" ))
255 .field( "memory_limiter", &self.memory_limiter.as_ref().map(| _ | "<closure>" ))
256 .finish_non_exhaustive()
257 }
258}