Skip to content

Commit

Permalink
Added a mechanism for exposing argument metadata to Fornjot
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael-F-Bryan committed Jul 17, 2022
1 parent 09406f2 commit 3c99976
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 152 deletions.
29 changes: 10 additions & 19 deletions crates/plugins/src/abi/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use once_cell::sync::Lazy;

use crate::{abi::fornjot_plugin_init, ModelConstructor};
use crate::{abi::fornjot_plugin_init, Model};

static HOST: Lazy<Host> = Lazy::new(|| {
let mut host = Host::default();
Expand Down Expand Up @@ -33,30 +33,21 @@ static HOST: Lazy<Host> = Lazy::new(|| {
#[no_mangle]
pub extern "C" fn model(args: &HashMap<String, String>) -> fj::Shape {
let ctx = crate::abi::Context(args);
let model = (HOST.constructor)(&ctx).expect("Unable to initialize the model");
let model = HOST
.models
.first()
.expect("No models were registered inside the register_plugin!() initializer");

model.shape()
model.shape(&ctx).expect("Unable to generate the shape")
}

#[derive(Default)]
struct Host {
constructor: ModelConstructor,
}

impl Default for Host {
fn default() -> Self {
Self {
constructor: Box::new(|_| {
Err(
"No model registered. Did you forget to call the register_plugin!() macro?"
.into(),
)
}),
}
}
models: Vec<Box<dyn Model>>,
}

impl crate::Host for Host {
fn register_model_constructor(&mut self, constructor: ModelConstructor) {
self.constructor = constructor;
fn register_boxed_model(&mut self, model: Box<dyn crate::Model>) {
self.models.push(model);
}
}
79 changes: 51 additions & 28 deletions crates/plugins/src/abi/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::{ModelConstructor, PluginMetadata};
use crate::{ArgumentMetadata, Context, ModelMetadata, PluginMetadata};
use chrono::{DateTime, NaiveDateTime, Utc};
use std::{
fmt,
io::{self, Write},
sync::Arc,
};
use tracing_subscriber::fmt::{format::Writer, time::FormatTime};
use wit_bindgen_rust::Handle;
Expand All @@ -24,36 +25,20 @@ impl guest::Guest for Guest {

let metadata = unsafe { crate::abi::fornjot_plugin_init(&mut host)? };

let Host { constructor } = host;
let model_constructor = match constructor {
Some(c) => c,
None => {
let err = crate::Error::from(
"No model registered. Did you forget to call the register_plugin!() macro?",
);
return Err(err.into());
}
};
let Host { models } = host;

Ok(Handle::new(Plugin {
model_constructor,
metadata,
}))
Ok(Handle::new(Plugin { models, metadata }))
}
}

pub struct Plugin {
model_constructor: ModelConstructor,
models: Vec<Handle<Model>>,
metadata: PluginMetadata,
}

impl guest::Plugin for Plugin {
fn load_model(&self, args: Vec<(String, String)>) -> Result<Handle<Model>, guest::Error> {
let args = args.into_iter().collect();
let ctx = crate::abi::Context(&args);
let model = (self.model_constructor)(&ctx)?;

Ok(Handle::new(Model(model)))
fn models(&self) -> Vec<Handle<Model>> {
self.models.clone()
}

fn metadata(&self) -> guest::PluginMetadata {
Expand All @@ -63,20 +48,28 @@ impl guest::Plugin for Plugin {

#[derive(Default)]
struct Host {
constructor: Option<ModelConstructor>,
models: Vec<Handle<Model>>,
}

impl crate::Host for Host {
fn register_model_constructor(&mut self, constructor: ModelConstructor) {
self.constructor = Some(constructor);
fn register_boxed_model(&mut self, model: Box<dyn crate::Model>) {
let model = Model(Arc::from(model));
self.models.push(Handle::new(model));
}
}

pub struct Model(Box<dyn crate::Model>);
pub struct Model(Arc<dyn crate::Model>);

impl guest::Model for Model {
fn shape(&self) -> guest::Shape {
self.0.shape().into()
fn metadata(&self) -> guest::ModelMetadata {
self.0.metadata().into()
}

fn shape(&self, args: Vec<(String, String)>) -> Result<guest::Shape, guest::Error> {
let args = args.into_iter().collect();
let ctx = crate::abi::Context(&args);

self.0.shape(&ctx).map(Into::into).map_err(Into::into)
}
}

Expand Down Expand Up @@ -229,3 +222,33 @@ impl From<PluginMetadata> for guest::PluginMetadata {
}
}
}

impl From<ModelMetadata> for guest::ModelMetadata {
fn from(m: ModelMetadata) -> guest::ModelMetadata {
let ModelMetadata {
name,
description,
arguments,
} = m;
guest::ModelMetadata {
name,
description,
arguments: arguments.into_iter().map(Into::into).collect(),
}
}
}

impl From<ArgumentMetadata> for guest::ArgumentMetadata {
fn from(m: ArgumentMetadata) -> guest::ArgumentMetadata {
let ArgumentMetadata {
name,
description,
default_value,
} = m;
guest::ArgumentMetadata {
name,
description,
default_value,
}
}
}
29 changes: 11 additions & 18 deletions crates/plugins/src/host.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use crate::{model::ModelFromContext, Error, Model};

/// A type-erased function that is called to construct a [`Model`].
pub type ModelConstructor =
Box<dyn Fn(&dyn crate::Context) -> Result<Box<dyn Model>, Error> + Send + Sync>;
use crate::Model;

/// An abstract interface to the Fornjot host.
pub trait Host {
Expand All @@ -11,37 +7,34 @@ pub trait Host {
/// This is mainly for more advanced use cases (e.g. when you need to close
/// over extra state to load the model). For simpler models, you probably
/// want to use [`HostExt::register_model()`] instead.
fn register_model_constructor(&mut self, constructor: ModelConstructor);
fn register_boxed_model(&mut self, model: Box<dyn Model>);
}

impl<H: Host + ?Sized> Host for &'_ mut H {
fn register_model_constructor(&mut self, constructor: ModelConstructor) {
(*self).register_model_constructor(constructor);
fn register_boxed_model(&mut self, model: Box<dyn Model>) {
(*self).register_boxed_model(model);
}
}

impl<H: Host + ?Sized> Host for Box<H> {
fn register_model_constructor(&mut self, constructor: ModelConstructor) {
(**self).register_model_constructor(constructor);
fn register_boxed_model(&mut self, model: Box<dyn Model>) {
(**self).register_boxed_model(model);
}
}

/// Extension methods to augment the [`Host`] API.
pub trait HostExt {
/// Register a model with the Fornjot runtime.
fn register_model<M>(&mut self)
fn register_model<M>(&mut self, model: M)
where
M: Model + ModelFromContext + 'static;
M: Model + 'static;
}

impl<H: Host + ?Sized> HostExt for H {
fn register_model<M>(&mut self)
fn register_model<M>(&mut self, model: M)
where
M: Model + ModelFromContext + 'static,
M: Model + 'static,
{
self.register_model_constructor(Box::new(|ctx| {
let model = M::from_context(ctx)?;
Ok(Box::new(model))
}))
self.register_boxed_model(Box::new(model));
}
}
54 changes: 29 additions & 25 deletions crates/plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,32 @@
//! The typical workflow is to first define a [`Model`] type. This comes with
//! two main methods,
//!
//! 1. Use the [`ModelFromContext`] trait to make the model loadable from the
//! host context, and
//! 2. Calculate the model's shape with the [`Model`] trait
//! 1. Getting metadata about the model, and
//! 2. Calculating the model's geometry using arguments from the host context
//!
//! ```rust
//! use fj_plugins::{Model, Context, Error, ModelFromContext};
//! use fj_plugins::{Model, Context, ContextExt, Error, ModelMetadata, ArgumentMetadata};
//! use fj::{Shape, Circle, Sketch, syntax::*};
//!
//! struct MyModel;
//! struct Cylinder;
//!
//! impl ModelFromContext for MyModel {
//! fn from_context(ctx: &dyn Context) -> Result<Self, Error>
//! where
//! Self: Sized,
//! {
//! todo!("Load arguments from the context and initialize the model");
//! impl Model for Cylinder {
//! fn metadata(&self) -> ModelMetadata {
//! ModelMetadata::new("Cylinder")
//! .with_argument("radius")
//! .with_argument(
//! ArgumentMetadata::new("height")
//! .with_default_value("10.0"),
//! )
//! }
//! }
//!
//! impl Model for MyModel {
//! fn shape(&self) -> fj::Shape { todo!("Calcualte the model's geometry") }
//! fn shape(&self, ctx: &dyn Context) -> Result<Shape, Error>
//! {
//! let radius = ctx.parse_argument("radius")?;
//! let height = ctx.parse_optional_argument("height")?.unwrap_or(10.0);
//! let circle = Circle::from_radius(radius);
//! Ok(Sketch::from_circle(circle).sweep([height, 0.0, 0.0]).into())
//! }
//! }
//! ```
//!
Expand All @@ -32,22 +38,20 @@
//!
//! ```rust
//! use fj_plugins::{Host, HostExt, PluginMetadata};
//! # use fj_plugins::{Model, Context, Error, ModelFromContext};
//! # use fj_plugins::{Model, Context, Error, ModelMetadata};
//!
//! fj_plugins::register_plugin!(|host: &mut dyn Host| {
//! host.register_model::<MyModel>();
//! host.register_model(Cylinder);
//!
//! Ok(PluginMetadata::new(
//! env!("CARGO_PKG_NAME"),
//! env!("CARGO_PKG_VERSION"),
//! ))
//! });
//! # struct MyModel;
//! # impl Model for MyModel {
//! # fn shape(&self) -> fj::Shape { todo!("Calcualte the model's geometry") }
//! # }
//! # impl ModelFromContext for MyModel {
//! # fn from_context(ctx: &dyn Context) -> Result<Self, Error> where Self: Sized { todo!(); }
//! # struct Cylinder;
//! # impl Model for Cylinder {
//! # fn metadata(&self) -> ModelMetadata { unimplemented!() }
//! # fn shape(&self, ctx: &dyn Context) -> Result<fj::Shape, Error> { unimplemented!() }
//! # }
//! ```
Expand All @@ -63,9 +67,9 @@ mod metadata;
mod model;

pub use crate::{
host::{Host, HostExt, ModelConstructor},
metadata::PluginMetadata,
model::{Context, ContextExt, MissingArgument, Model, ModelFromContext},
host::{Host, HostExt},
metadata::{ArgumentMetadata, ModelMetadata, PluginMetadata},
model::{Context, ContextExt, MissingArgument, Model},
};

/// The common error type used by this crate.
Expand Down
64 changes: 64 additions & 0 deletions crates/plugins/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,67 @@ impl PluginMetadata {
}
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct ModelMetadata {
pub name: String,
pub description: Option<String>,
pub arguments: Vec<ArgumentMetadata>,
}

impl ModelMetadata {
pub fn new(name: impl Into<String>) -> Self {
ModelMetadata {
name: name.into(),
description: None,
arguments: Vec::new(),
}
}

pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}

pub fn with_argument(mut self, arg: impl Into<ArgumentMetadata>) -> Self {
self.arguments.push(arg.into());
self
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct ArgumentMetadata {
/// The name used to refer to this argument.
pub name: String,
/// A short description of this argument that could be shown to the user
/// in something like a tooltip.
pub description: Option<String>,
/// Something that could be used as a default if no value was provided.
pub default_value: Option<String>,
}

impl ArgumentMetadata {
pub fn new(name: impl Into<String>) -> Self {
ArgumentMetadata {
name: name.into(),
description: None,
default_value: None,
}
}

pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}

pub fn with_default_value(mut self, default_value: impl Into<String>) -> Self {
self.default_value = Some(default_value.into());
self
}
}

impl From<&str> for ArgumentMetadata {
fn from(name: &str) -> Self {
ArgumentMetadata::new(name)
}
}
Loading

0 comments on commit 3c99976

Please sign in to comment.