Skip to content

Commit

Permalink
Merge pull request #885 from Michael-F-Bryan/model-driven-api
Browse files Browse the repository at this point in the history
Model driven api
  • Loading branch information
hannobraun authored Aug 9, 2022
2 parents 4e4dbec + 38c77d4 commit 1fd82ee
Show file tree
Hide file tree
Showing 16 changed files with 1,991 additions and 207 deletions.
51 changes: 48 additions & 3 deletions crates/fj-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use std::{
thread,
};

use fj::abi;
use notify::Watcher as _;
use thiserror::Error;

Expand Down Expand Up @@ -131,8 +132,24 @@ impl Model {
// https://github.com/hannobraun/Fornjot/issues/71
let shape = unsafe {
let lib = libloading::Library::new(&self.lib_path)?;
let model: libloading::Symbol<ModelFn> = lib.get(b"model")?;
model(arguments)
let init: libloading::Symbol<abi::InitFunction> =
lib.get(abi::INIT_FUNCTION_NAME.as_bytes())?;

let mut host = Host {
args: arguments,
model: None,
};

match init(&mut abi::Host::from(&mut host)) {
abi::ffi_safe::Result::Ok(_metadata) => {}
abi::ffi_safe::Result::Err(e) => {
return Err(Error::InitializeModel(e.into()));
}
}

let model = host.model.take().ok_or(Error::NoModelRegistered)?;

model.shape(&host).map_err(Error::Shape)?
};

Ok(shape)
Expand Down Expand Up @@ -363,6 +380,19 @@ pub enum Error {
#[error("Error loading model from dynamic library")]
LibLoading(#[from] libloading::Error),

/// Initializing a model failed.
#[error("Unable to initialize the model")]
InitializeModel(#[source] Box<dyn std::error::Error + Send + Sync>),

/// The user forgot to register a model when calling
/// [`fj::register_model!()`].
#[error("No model was registered")]
NoModelRegistered,

/// An error was returned from [`fj::Model::shape()`].
#[error("Unable to determine the model's geometry")]
Shape(#[source] Box<dyn std::error::Error + Send + Sync>),

/// Error while watching the model code for changes
#[error("Error watching model for changes")]
Notify(#[from] notify::Error),
Expand Down Expand Up @@ -390,4 +420,19 @@ pub enum Error {
},
}

type ModelFn = unsafe extern "C" fn(args: &Parameters) -> fj::Shape;
struct Host<'a> {
args: &'a Parameters,
model: Option<Box<dyn fj::Model>>,
}

impl<'a> fj::Host for Host<'a> {
fn register_boxed_model(&mut self, model: Box<dyn fj::Model>) {
self.model = Some(model);
}
}

impl<'a> fj::Context for Host<'a> {
fn get_argument(&self, name: &str) -> Option<&str> {
self.args.get(name).map(|s| s.as_str())
}
}
185 changes: 185 additions & 0 deletions crates/fj-proc/src/expand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};

use crate::parse::{
ArgumentMetadata, Constraint, ConstraintKind, ExtractedArgument,
GeometryFunction, Initializer, Metadata, Model,
};

impl Initializer {
fn register(&self) -> TokenStream {
quote! {
const _: () = {
fj::register_model!(|host| {
fj::HostExt::register_model(host, Model);

Ok(
fj::Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
.with_short_description(env!("CARGO_PKG_DESCRIPTION"))
.with_homepage(env!("CARGO_PKG_HOMEPAGE"))
.with_repository(env!("CARGO_PKG_REPOSITORY"))
.with_license(env!("CARGO_PKG_LICENSE")),
)
});
};
}
}
}

impl ToTokens for Initializer {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Initializer { model } = self;

tokens.extend(self.register());
model.to_tokens(tokens);
}
}

impl Model {
fn definition(&self) -> TokenStream {
quote! { struct Model; }
}

fn trait_implementation(&self) -> TokenStream {
let Model { metadata, geometry } = self;

quote! {
impl fj::Model for Model {
#metadata
#geometry
}
}
}
}

impl ToTokens for Model {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.definition());
tokens.extend(self.trait_implementation());
}
}

impl ToTokens for Metadata {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Metadata { name, arguments } = self;

tokens.extend(quote! {
fn metadata(&self) -> fj::ModelMetadata {
fj::ModelMetadata::new(#name)
#( .with_argument(#arguments) )*
}
});
}
}

impl ToTokens for ArgumentMetadata {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ArgumentMetadata {
name,
default_value,
} = self;

tokens.extend(quote! { fj::ArgumentMetadata::new(#name) });

if let Some(default_value) = default_value {
tokens.extend(quote! {
.with_default_value(stringify!(#default_value))
});
}
}
}

impl ToTokens for GeometryFunction {
fn to_tokens(&self, tokens: &mut TokenStream) {
let GeometryFunction {
geometry_function,
arguments,
constraints,
fallible,
} = self;

let argument_names = arguments.iter().map(|a| &a.ident);

let invocation = quote! {
#geometry_function(#( #argument_names ),*)
};
let invocation = if *fallible {
quote! { #invocation.map(fj::Shape::from).map_err(Into::into) }
} else {
quote! { Ok(#invocation.into()) }
};

tokens.extend(quote! {
fn shape(
&self,
ctx: &dyn fj::Context,
) -> Result<fj::Shape, Box<dyn std::error::Error + Send + Sync>> {
#( #arguments )*
#( #constraints )*
#invocation
}
});
}
}

impl ToTokens for ExtractedArgument {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ExtractedArgument {
ident,
ty,
default_value,
} = self;

let name = ident.to_string();
let t = match default_value {
Some(default) => quote! {
let #ident: #ty = match ctx.get_argument(#name) {
Some(value) => value.parse()?,
None => #default
};
},
None => {
let error_message = format!("Expected {name}");
quote! {
let #ident: #ty = match ctx.get_argument(#name) {
Some(value) => value.parse()?,
None => return Err(#error_message.into()),
};
}
}
};

tokens.extend(t);
}
}

impl ToTokens for Constraint {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Constraint { target, expr, kind } = self;

let operator = match kind {
ConstraintKind::Max => quote!(<=),
ConstraintKind::Min => quote!(>=),
};
let predicate = quote! { #target #operator #expr };
// Note: this will cause `expr` to be evaluated twice. Predicates should
// be pure functions, so in theory this shouldn't be an issue.
let error_message = quote! {
format!(
"Expected {} {} {} (i.e. {} {} {})",
stringify!(#target),
stringify!(#operator),
stringify!(#expr),
#target,
stringify!(#operator),
#expr,
)
};

tokens.extend(quote! {
if !(#predicate) {
return Err(#error_message.into());
}
});
}
}
Loading

0 comments on commit 1fd82ee

Please sign in to comment.