Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: context sharable with plugins #11

Merged
merged 3 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
546 changes: 534 additions & 12 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ repository = "https://github.com/geofmureithi/plugy"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
plugy-core = { path = "./crates/plugy-core", version = "0.1.1" }
plugy-macros = { path = "./crates/plugy-macros", version = "0.1.1" }
plugy-runtime = { path = "./crates/plugy-runtime", version = "0.1.1" }
plugy-core = { path = "./crates/plugy-core", version = "0.1.1", optional = true }
plugy-macros = { path = "./crates/plugy-macros", version = "0.1.1", optional = true }
plugy-runtime = { path = "./crates/plugy-runtime", version = "0.1.1", optional = true }

[features]
default = ["core", "macros"]
core = ["plugy-core"]
macros = ["plugy-macros"]
runtime = ["plugy-runtime"]

[workspace]
members = [
Expand All @@ -24,7 +29,6 @@ members = [
"crates/plugy-runtime",
"examples/runner",
"examples/foo-plugin",
"examples/shared",
]


Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Check out the [examples](./examples/) directory for sample usage of plugy.
| Status | Goal | Labels |
| :----: | :-------------------------------------------------------------------------------------------------- | ---------- |
| ✅ | [accept multiple arity (n-ary) in plugin functions](https://github.com/geofmureithi/plugy/issues/2) | `complete` |
| | [pass down context between host and guest](https://github.com/geofmureithi/plugy/issues/3) | `pending` |
| | [pass down context between host and guest](https://github.com/geofmureithi/plugy/issues/3) | `pending` |

## Functionality

Expand Down
2 changes: 1 addition & 1 deletion crates/plugy-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
//! - [`guest`](guest/index.html): A module that facilitates communication between the host application and Wasm plugins.
//!
pub mod bitwise;
pub mod guest;
pub mod guest;
174 changes: 164 additions & 10 deletions crates/plugy-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
//! working with plugins written in WebAssembly (Wasm) within your Rust applications.
//!
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, DeriveInput, ImplItem, ImplItemFn, ItemImpl, ItemTrait, MetaNameValue};
use syn::{
parse_macro_input, DeriveInput, FnArg, ImplItem, ImplItemFn, ItemImpl, ItemTrait, MetaNameValue,
};

/// A procedural macro attribute for generating an asynchronous and callable version of a trait on the host side.
///
Expand Down Expand Up @@ -76,17 +78,17 @@ fn generate_async_trait(trait_item: &ItemTrait) -> proc_macro2::TokenStream {
quote! {
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Clone)]
pub struct #callable_trait_ident<P> {
pub handle: plugy::runtime::PluginHandle<P>
pub struct #callable_trait_ident<P, D> {
pub handle: plugy::runtime::PluginHandle<P, D>
}
#[cfg(not(target_arch = "wasm32"))]
impl<P> #callable_trait_ident<P> {
impl<P, D: Send + Clone> #callable_trait_ident<P, D> {
#(#async_methods)*
}
#[cfg(not(target_arch = "wasm32"))]
impl<P> plugy::runtime::IntoCallable<P> for Box<dyn #trait_name> {
type Output = #callable_trait_ident<P>;
fn into_callable(handle: plugy::runtime::PluginHandle<P>) -> Self::Output {
impl<P, D> plugy::runtime::IntoCallable<P, D> for Box<dyn #trait_name> {
type Output = #callable_trait_ident<P, D>;
fn into_callable(handle: plugy::runtime::PluginHandle<P, D>) -> Self::Output {
#callable_trait_ident { handle }
}
}
Expand Down Expand Up @@ -166,8 +168,8 @@ pub fn plugin_impl(_metadata: TokenStream, input: TokenStream) -> TokenStream {
quote! {
#[no_mangle]
pub unsafe extern "C" fn #expose_name_ident(value: u64) -> u64 {
let (value, #(#values),*): (#ty, #(#types),*) = plugy_core::guest::read_msg(value);
plugy_core::guest::write_msg(&value.#method_name(#(#values),*))
let (value, #(#values),*): (#ty, #(#types),*) = plugy::core::guest::read_msg(value);
plugy::core::guest::write_msg(&value.#method_name(#(#values),*))
}
}
})
Expand Down Expand Up @@ -200,3 +202,155 @@ pub fn plugin_import(args: TokenStream, input: TokenStream) -> TokenStream {
}
}.into()
}

#[proc_macro_attribute]
pub fn context(_: TokenStream, input: TokenStream) -> TokenStream {
// Parse the input as an ItemImpl
let input = parse_macro_input!(input as ItemImpl);

// Get the name of the struct being implemented
let struct_name = &input.self_ty.to_token_stream();

let struct_name_sync = Ident::new(&format!("{struct_name}Sync"), Span::call_site());

let mut externs = Vec::new();

let mut links = Vec::new();

// Iterate over the items in the impl block to find methods
let generated_methods = input
.items
.iter()
.filter_map(|item| {
if let syn::ImplItem::Fn(method) = item {
let method_name = &method.sig.ident;
let method_args: Vec<_> = method
.sig
.inputs
.iter()
.skip(2) // Skip &self, &caller
.map(|arg| {
if let FnArg::Typed(pat_type) = arg {
pat_type.to_token_stream()
} else {
panic!("Unsupported function argument type");
}
})
.collect();
let method_pats: Vec<_> = method
.sig
.inputs
.iter()
.skip(2) // Skip &self, &caller
.map(|arg| {
if let FnArg::Typed(pat_type) = arg {
pat_type.pat.to_token_stream()
} else {
panic!("Unsupported function argument type");
}
})
.collect();
let return_type = &method.sig.output;
let extern_method_name = Ident::new(
&format!("_plugy_context_{}", method_name),
Span::call_site(),
);

externs.push(quote::quote! {
extern "C" {
fn #extern_method_name(ptr: u64) -> u64;
}
});

let extern_method_name_str = extern_method_name.to_string();

links.push(quote! {
linker
.func_wrap1_async(
"env",
#extern_method_name_str,
move |mut caller: plugy::runtime::Caller<#struct_name>,
ptr: u64|
-> Box<dyn std::future::Future<Output = u64> + Send> {
use plugy::core::bitwise::{from_bitwise, into_bitwise};
Box::new(async move {
let store = caller.data().clone().unwrap();
let plugy::runtime::RuntimeCaller {
memory,
alloc_fn,
dealloc_fn,
data: ctx,
} = store;

let (ptr, len) = from_bitwise(ptr);
let mut buffer = vec![0u8; len as _];
memory.read(&mut caller, ptr as _, &mut buffer).unwrap();
dealloc_fn
.call_async(&mut caller, into_bitwise(ptr, len))
.await
.unwrap();
let message: (String,) = bincode::deserialize(&buffer).unwrap();
let buffer =
bincode::serialize(&ctx.fetch(&caller, message.0).await)
.unwrap();
let ptr = alloc_fn
.call_async(&mut caller, buffer.len() as _)
.await
.unwrap();
memory.write(&mut caller, ptr as _, &buffer).unwrap();
into_bitwise(ptr, buffer.len() as _)
})
},
)
.unwrap();
});

Some(quote! {
#[allow(unused_variables)]
pub fn #method_name(&self, #(#method_args),*) #return_type {
#[cfg(target_arch = "wasm32")]
{
let args = (#(#method_pats),*);
let ptr = plugy::core::guest::write_msg(&args);
unsafe { plugy::core::guest::read_msg(#extern_method_name(ptr)) }
}
#[cfg(not(target_arch = "wasm32"))]
panic!("You are trying to call wasm methods outside of wasm32")
}
})
} else {
None
}
})
.collect::<Vec<_>>();

// Generate the code for the context methods
let generated = quote::quote! {
#[cfg(not(target_arch = "wasm32"))]
#input

impl #struct_name {
pub fn current() -> #struct_name_sync {
#struct_name_sync
}
}
#[cfg(not(target_arch = "wasm32"))]
impl plugy::runtime::Context for #struct_name {
fn link(&self, linker: &mut plugy::runtime::Linker<Self>) {
#(#links)*
}
}

pub struct #struct_name_sync;

impl #struct_name_sync {
#(#generated_methods)*
}

#[cfg(target_arch = "wasm32")]
#(#externs)*
};

// Return the generated code as a TokenStream
generated.into()
}
2 changes: 1 addition & 1 deletion crates/plugy-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ async-lock = "2.7.0"

[dev-dependencies]
plugy-macros = { path = "../plugy-macros" }
plugy = { path = "../../" }
plugy = { path = "../../", features = ["runtime"]}

Loading