Skip to main content

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}