1use std::collections::HashMap ;
2use thiserror::Error ;
3use wasmtime::component::{ Instance, Val };
4use wasmtime::Store ;
5
6use crate::{ Function, PluginContext, Remap, ReturnKind };
7use crate::resource_wrapper::{ ResourceCreationError, ResourceReceiveError };
8
9
10
11pub struct PluginInstance<Ctx: 'static> {
17 pub(crate) store: Store<Ctx>,
18 pub(crate) instance: Instance,
19 pub(crate) interface_remaps: HashMap<String, Remap>,
20 #[allow( clippy::type_complexity )]
21 pub(crate) fuel_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
22 #[allow( clippy::type_complexity )]
23 pub(crate) epoch_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
24}
25
26impl<Ctx: std::fmt::Debug + 'static> std::fmt::Debug for PluginInstance<Ctx> {
27 fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::result::Result<(), std::fmt::Error> {
28 f.debug_struct( "PluginInstance" )
29 .field( "data", &self.store.data() )
30 .field( "store", &self.store )
31 .field( "interface_remaps", &self.interface_remaps )
32 .field( "fuel_limiter", &self.fuel_limiter.as_ref().map(| _ | "<closure>" ))
33 .field( "epoch_limiter", &self.epoch_limiter.as_ref().map(| _ | "<closure>" ))
34 .finish_non_exhaustive()
35 }
36}
37
38#[derive( Error, Debug )]
44pub enum DispatchError {
45 #[error( "Lock Rejected" )] LockRejected,
47 #[error( "Invalid Interface Path: {0}" )] InvalidInterfacePath( String ),
49 #[error( "Invalid Function: {0}" )] InvalidFunction( String ),
51 #[error( "Missing Response" )] MissingResponse,
53 #[error( "Runtime Exception" )] RuntimeException( wasmtime::Error ),
55 #[error( "Invalid Argument List" )] InvalidArgumentList,
57 #[error( "Unsupported type: {0}" )] UnsupportedType( String ),
59 #[error( "Resource Create Error: {0}" )] ResourceCreationError( #[from] ResourceCreationError ),
61 #[error( "Resource Receive Error: {0}" )] ResourceReceiveError( #[from] ResourceReceiveError ),
63}
64
65impl From<DispatchError> for Val {
66 fn from( error: DispatchError ) -> Val { match error {
67 DispatchError::LockRejected => Val::Variant( "lock-rejected".to_string(), None ),
68 DispatchError::InvalidInterfacePath( package ) => Val::Variant( "invalid-interface-path".to_string(), Some( Box::new( Val::String( package )))),
69 DispatchError::InvalidFunction( function ) => Val::Variant( "invalid-function".to_string(), Some( Box::new( Val::String( function )))),
70 DispatchError::MissingResponse => Val::Variant( "missing-response".to_string(), None ),
71 DispatchError::RuntimeException( exception ) => Val::Variant( "runtime-exception".to_string(), Some( Box::new( Val::String( exception.to_string() )))),
72 DispatchError::InvalidArgumentList => Val::Variant( "invalid-argument-list".to_string(), None ),
73 DispatchError::UnsupportedType( name ) => Val::Variant( "unsupported-type".to_string(), Some( Box::new( Val::String( name )))),
74 DispatchError::ResourceCreationError( err ) => err.into(),
75 DispatchError::ResourceReceiveError( err ) => err.into(),
76 }}
77}
78
79impl<Ctx: PluginContext + 'static> PluginInstance<Ctx> {
80
81 const PLACEHOLDER_VAL: Val = Val::Option( None );
82 const VOID_RETURN_VAL: Val = Val::Option( None );
83
84 pub(crate) fn dispatch(
85 &mut self,
86 package_name: &str,
87 interface_name: &str,
88 function_name: &str,
89 function: &Function,
90 data: &[Val],
91 ) -> Result<Val, DispatchError> {
92
93 let mut buffer = match function.return_kind() != ReturnKind::Void {
94 true => vec![ Self::PLACEHOLDER_VAL ],
95 false => Vec::with_capacity( 0 ),
96 };
97
98 let canonical_interface_path = format!( "{}/{}", package_name, interface_name );
99 let ( exported_interface_path, exported_function_name ) = self.resolve_export( package_name, interface_name, function_name );
100
101 let fuel_was_set = if let Some( mut limiter ) = self.fuel_limiter.take() {
102 let fuel = limiter( &mut self.store, &canonical_interface_path, function_name, function );
103 self.fuel_limiter = Some( limiter );
104 self.store.set_fuel( fuel ).map_err( DispatchError::RuntimeException )?;
105 true
106 } else { false };
107
108 if let Some( mut limiter ) = self.epoch_limiter.take() {
109 let ticks = limiter( &mut self.store, &canonical_interface_path, function_name, function );
110 self.epoch_limiter = Some( limiter );
111 self.store.set_epoch_deadline( ticks );
112 }
113
114 let interface_index = self.instance
115 .get_export_index( &mut self.store, None, &exported_interface_path )
116 .ok_or( DispatchError::InvalidInterfacePath( exported_interface_path.clone() ))?;
117 let func_index = self.instance
118 .get_export_index( &mut self.store, Some( &interface_index ), &exported_function_name )
119 .ok_or( DispatchError::InvalidFunction( format!( "{}:{}", exported_interface_path, exported_function_name )))?;
120 let func = self.instance
121 .get_func( &mut self.store, func_index )
122 .ok_or( DispatchError::InvalidFunction( format!( "{}:{}", exported_interface_path, exported_function_name )))?;
123 let call_result = func.call( &mut self.store, data, &mut buffer );
124
125 if fuel_was_set { let _ = self.store.set_fuel( 0 ); }
127
128 call_result.map_err( DispatchError::RuntimeException )?;
129 Ok( match function.return_kind() != ReturnKind::Void {
130 true => buffer.pop().ok_or( DispatchError::MissingResponse )?,
131 false => Self::VOID_RETURN_VAL,
132 })
133
134 }
135
136 fn resolve_export( &self, package_name: &str, interface_name: &str, function_name: &str ) -> (String, String) {
137 match self.interface_remaps.get( interface_name ) {
138 Some( remap ) => (
139 format!( "{}/{}", package_name, remap.interface_name( interface_name )),
140 remap.item_name( function_name ).to_string(),
141 ),
142 None => (
143 format!( "{}/{}", package_name, interface_name ),
144 function_name.to_string(),
145 ),
146 }
147 }
148
149}