Documentation pass

Isaac Clayton created

Change summary

crates/plugin_runtime/src/plugin.rs | 128 +++++++++---------------------
plugins/json_language/src/lib.rs    |   2 
2 files changed, 40 insertions(+), 90 deletions(-)

Detailed changes

crates/plugin_runtime/src/plugin.rs 🔗

@@ -13,6 +13,7 @@ use wasmtime::{
 };
 use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder};
 
+/// Represents a resource currently managed by the plugin, like a file descriptor.
 pub struct PluginResource(u32);
 
 #[repr(C)]
@@ -34,6 +35,7 @@ impl WasiBuffer {
     }
 }
 
+/// Represents a typed WebAssembly function.
 pub struct WasiFn<A: Serialize, R: DeserializeOwned> {
     function: TypedFunc<u64, u64>,
     _function_type: PhantomData<fn(A) -> R>,
@@ -50,6 +52,10 @@ impl<A: Serialize, R: DeserializeOwned> Clone for WasiFn<A, R> {
     }
 }
 
+/// This struct is used to build a new [`Plugin`], using the builder pattern.
+/// Create a new default plugin with `PluginBuilder::new_with_default_ctx`,
+/// and add host-side exported functions using `host_function` and `host_function_async`.
+/// Finalize the plugin by calling [`init`].
 pub struct PluginBuilder {
     wasi_ctx: WasiCtx,
     engine: Engine,
@@ -57,6 +63,8 @@ pub struct PluginBuilder {
 }
 
 impl PluginBuilder {
+    /// Create a new [`PluginBuilder`] with the given WASI context.
+    /// Using the default context is a safe bet, see [`new_with_default_context`].
     pub fn new(wasi_ctx: WasiCtx) -> Result<Self, Error> {
         let mut config = Config::default();
         config.async_support(true);
@@ -71,89 +79,17 @@ impl PluginBuilder {
         })
     }
 
+    /// Create a new `PluginBuilder` that inherits the
+    /// host processes' access to `stdout` and `stderr`.
     pub fn new_with_default_ctx() -> Result<Self, Error> {
         let wasi_ctx = WasiCtxBuilder::new()
-            .inherit_stdin()
+            .inherit_stdout()
             .inherit_stderr()
             .build();
         Self::new(wasi_ctx)
     }
 
-    // pub fn host_function_async<A: DeserializeOwned + Send, R: Serialize, F, Fut>(
-    //     mut self,
-    //     name: &str,
-    //     function: impl Fn(A) -> Pin<Box<dyn Future<Output = R> + Send + Sync>> + Sync + Send + 'static,
-    // ) -> Result<Self, Error>
-    // where
-    //     A: DeserializeOwned + Send,
-    //     R: Serialize + Send,
-    // {
-    //     self.linker.func_wrap1_async(
-    //         "env",
-    //         &format!("__{}", name),
-    //         move |caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| {
-    //             // let function = &function;
-    //             Box::new(async move {
-    //                 // grab a handle to the memory
-    //                 let mut plugin_memory = match caller.get_export("memory") {
-    //                     Some(Extern::Memory(mem)) => mem,
-    //                     _ => return Err(Trap::new("Could not grab slice of plugin memory"))?,
-    //                 };
-
-    //                 let buffer = WasiBuffer::from_u64(packed_buffer);
-
-    //                 // get the args passed from Guest
-    //                 let args = Wasi::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?;
-
-    //                 // Call the Host-side function
-    //                 let result: R = function(args).await;
-
-    //                 // Serialize the result back to guest
-    //                 let result = Wasi::serialize_to_bytes(result).map_err(|_| {
-    //                     Trap::new("Could not serialize value returned from function")
-    //                 })?;
-
-    //                 // Ok((buffer, plugin_memory, result))
-    //                 Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?;
-
-    //                 let buffer = Wasi::bytes_to_buffer(
-    //                     caller.data().alloc_buffer(),
-    //                     &mut plugin_memory,
-    //                     &mut caller,
-    //                     result,
-    //                 )
-    //                 .await?;
-
-    //                 Ok(buffer.into_u64())
-    //             })
-    //         },
-    //     )?;
-    //     Ok(self)
-    // }
-
-    // pub fn host_function_async<F>(mut self, name: &str, function: F) -> Result<Self, Error>
-    // where
-    //     F: Fn(u64) -> Pin<Box<dyn Future<Output = u64> + Send + Sync + 'static>>
-    //         + Send
-    //         + Sync
-    //         + 'static,
-    // {
-    //     self.linker.func_wrap1_async(
-    //         "env",
-    //         &format!("__{}", name),
-    //         move |_: Caller<'_, WasiCtxAlloc>, _: u64| {
-    //             // let function = &function;
-    //             Box::new(async {
-    //                 // let function = function;
-    //                 // Call the Host-side function
-    //                 let result: u64 = function(7).await;
-    //                 Ok(result)
-    //             })
-    //         },
-    //     )?;
-    //     Ok(self)
-    // }
-
+    /// Add an `async` host function. See [`host_function`] for details.
     pub fn host_function_async<F, A, R, Fut>(
         mut self,
         name: &str,
@@ -218,6 +154,22 @@ impl PluginBuilder {
         Ok(self)
     }
 
+    /// Add a new host function to the given `PluginBuilder`.
+    /// A host function is a function defined host-side, in Rust,
+    /// that is accessible guest-side, in WebAssembly.
+    /// You can specify host-side functions to import using
+    /// the `#[input]` macro attribute:
+    /// ```ignore
+    /// #[input]
+    /// fn total(counts: Vec<f64>) -> f64;
+    /// ```
+    /// When loading a plugin, you need to provide all host functions the plugin imports:
+    /// ```ignore
+    /// let plugin = PluginBuilder::new_with_default_context()
+    ///     .host_function("total", |counts| counts.iter().fold(0.0, |tot, n| tot + n))
+    ///     // and so on...
+    /// ```
+    /// And that's a wrap!
     pub fn host_function<A, R>(
         mut self,
         name: &str,
@@ -276,6 +228,8 @@ impl PluginBuilder {
         Ok(self)
     }
 
+    /// Initializes a [`Plugin`] from a given compiled Wasm module.
+    /// Both binary (`.wasm`) and text (`.wat`) module formats are supported.
     pub async fn init<T: AsRef<[u8]>>(self, module: T) -> Result<Plugin, Error> {
         Plugin::init(module.as_ref().to_vec(), self).await
     }
@@ -310,6 +264,8 @@ impl WasiCtxAlloc {
     }
 }
 
+/// Represents a WebAssembly plugin, with access to the WebAssembly System Inferface.
+/// Build a new plugin using [`PluginBuilder`].
 pub struct Plugin {
     engine: Engine,
     module: Module,
@@ -318,6 +274,8 @@ pub struct Plugin {
 }
 
 impl Plugin {
+    /// Dumps the *entirety* of Wasm linear memory to `stdout`.
+    /// Don't call this unless you're debugging a memory issue!
     pub fn dump_memory(data: &[u8]) {
         for (i, byte) in data.iter().enumerate() {
             if i % 32 == 0 {
@@ -400,6 +358,8 @@ impl Plugin {
     }
 
     /// Returns `true` if the resource existed and was removed.
+    /// Currently the only resource we support is adding scoped paths (e.g. folders and files)
+    /// to plugins using [`attach_path`].
     pub fn remove_resource(&mut self, resource: PluginResource) -> Result<(), Error> {
         self.store
             .data_mut()
@@ -410,16 +370,6 @@ impl Plugin {
         Ok(())
     }
 
-    // pub fn with_resource<T>(
-    //     &mut self,
-    //     resource: WasiResource,
-    //     callback: fn(&mut Self) -> Result<T, Error>,
-    // ) -> Result<T, Error> {
-    //     let result = callback(self);
-    //     self.remove_resource(resource)?;
-    //     return result;
-    // }
-
     // So this call function is kinda a dance, I figured it'd be a good idea to document it.
     // the high level is we take a serde type, serialize it to a byte array,
     // (we're doing this using bincode for now)
@@ -463,12 +413,14 @@ impl Plugin {
     // This isn't a problem because Wasm stops executing after the function returns,
     // so the heap is still valid for our inspection when we want to pull things out.
 
+    /// Serializes a given type to bytes.
     fn serialize_to_bytes<A: Serialize>(item: A) -> Result<Vec<u8>, Error> {
         // serialize the argument using bincode
         let bytes = bincode::serialize(&item)?;
         Ok(bytes)
     }
 
+    /// Deserializes a given type from bytes.
     fn deserialize_to_type<R: DeserializeOwned>(bytes: &[u8]) -> Result<R, Error> {
         // serialize the argument using bincode
         let bytes = bincode::deserialize(bytes)?;
@@ -522,6 +474,7 @@ impl Plugin {
         Ok(result)
     }
 
+    // TODO: don't allocate a new `Vec`!
     /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer.
     fn buffer_to_bytes<'a>(
         plugin_memory: &'a Memory,
@@ -570,9 +523,6 @@ impl Plugin {
         handle: &WasiFn<A, R>,
         arg: A,
     ) -> Result<R, Error> {
-        // dbg!(&handle.name);
-        // dbg!(serde_json::to_string(&arg)).unwrap();
-
         let mut plugin_memory = self
             .instance
             .get_memory(&mut self.store, "memory")

plugins/json_language/src/lib.rs 🔗

@@ -5,7 +5,7 @@ use std::fs;
 use std::path::PathBuf;
 
 #[import]
-fn command(string: &str) -> Option<String>;
+fn command(string: &str) -> Option<Vec<u8>>;
 
 // #[no_mangle]
 // // TODO: switch len from usize to u32?