diff --git a/crates/fj-host/src/lib.rs b/crates/fj-host/src/lib.rs index e831326dc..18a2ea830 100644 --- a/crates/fj-host/src/lib.rs +++ b/crates/fj-host/src/lib.rs @@ -382,16 +382,16 @@ pub enum Error { /// Initializing a model failed. #[error("Unable to initialize the model")] - InitializeModel(#[source] Box), + InitializeModel(#[source] fj::models::Error), /// 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()`]. + /// An error was returned from [`fj::models::Model::shape()`]. #[error("Unable to determine the model's geometry")] - Shape(#[source] Box), + Shape(#[source] fj::models::Error), /// Error while watching the model code for changes #[error("Error watching model for changes")] @@ -422,16 +422,16 @@ pub enum Error { struct Host<'a> { args: &'a Parameters, - model: Option>, + model: Option>, } -impl<'a> fj::Host for Host<'a> { - fn register_boxed_model(&mut self, model: Box) { +impl<'a> fj::models::Host for Host<'a> { + fn register_boxed_model(&mut self, model: Box) { self.model = Some(model); } } -impl<'a> fj::Context for Host<'a> { +impl<'a> fj::models::Context for Host<'a> { fn get_argument(&self, name: &str) -> Option<&str> { self.args.get(name).map(|s| s.as_str()) } diff --git a/crates/fj-proc/src/expand.rs b/crates/fj-proc/src/expand.rs index 45be76958..37644bdbd 100644 --- a/crates/fj-proc/src/expand.rs +++ b/crates/fj-proc/src/expand.rs @@ -11,10 +11,10 @@ impl Initializer { quote! { const _: () = { fj::register_model!(|host| { - fj::HostExt::register_model(host, Model); + fj::models::HostExt::register_model(host, Model); Ok( - fj::Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")) + fj::models::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")) @@ -44,7 +44,7 @@ impl Model { let Model { metadata, geometry } = self; quote! { - impl fj::Model for Model { + impl fj::models::Model for Model { #metadata #geometry } @@ -64,8 +64,8 @@ impl ToTokens for Metadata { let Metadata { name, arguments } = self; tokens.extend(quote! { - fn metadata(&self) -> fj::ModelMetadata { - fj::ModelMetadata::new(#name) + fn metadata(&self) -> fj::models::ModelMetadata { + fj::models::ModelMetadata::new(#name) #( .with_argument(#arguments) )* } }); @@ -79,7 +79,7 @@ impl ToTokens for ArgumentMetadata { default_value, } = self; - tokens.extend(quote! { fj::ArgumentMetadata::new(#name) }); + tokens.extend(quote! { fj::models::ArgumentMetadata::new(#name) }); if let Some(default_value) = default_value { tokens.extend(quote! { @@ -112,8 +112,8 @@ impl ToTokens for GeometryFunction { tokens.extend(quote! { fn shape( &self, - ctx: &dyn fj::Context, - ) -> Result> { + ctx: &dyn fj::models::Context, + ) -> Result { #( #arguments )* #( #constraints )* #invocation diff --git a/crates/fj-proc/src/lib.rs b/crates/fj-proc/src/lib.rs index bdec05655..68af2576a 100644 --- a/crates/fj-proc/src/lib.rs +++ b/crates/fj-proc/src/lib.rs @@ -71,6 +71,20 @@ use syn::{parse_macro_input, FnArg, ItemFn}; /// spacer.into() /// } /// ``` +/// +/// For more complex situations, model functions are allowed to return any +/// error type that converts into a model error. +/// +/// ```rust +/// #[fj::model] +/// pub fn model() -> Result { +/// let home_dir = std::env::var("HOME")?; +/// +/// todo!("Do something with {home_dir}") +/// } +/// +/// fn assert_convertible(e: std::env::VarError) -> fj::models::Error { e.into() } +/// ``` #[proc_macro_attribute] pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as syn::ItemFn); diff --git a/crates/fj/src/abi/context.rs b/crates/fj/src/abi/context.rs index 03e641b0d..c421382f1 100644 --- a/crates/fj/src/abi/context.rs +++ b/crates/fj/src/abi/context.rs @@ -10,13 +10,13 @@ pub struct Context<'a> { _lifetime: PhantomData<&'a ()>, } -impl<'a> From<&'a &dyn crate::Context> for Context<'a> { - fn from(ctx: &'a &dyn crate::Context) -> Self { +impl<'a> From<&'a &dyn crate::models::Context> for Context<'a> { + fn from(ctx: &'a &dyn crate::models::Context) -> Self { unsafe extern "C" fn get_argument( user_data: *const c_void, name: StringSlice, ) -> StringSlice { - let ctx = &*(user_data as *const &dyn crate::Context); + let ctx = &*(user_data as *const &dyn crate::models::Context); match std::panic::catch_unwind(AssertUnwindSafe(|| { ctx.get_argument(&*name) @@ -28,14 +28,15 @@ impl<'a> From<&'a &dyn crate::Context> for Context<'a> { } Context { - user_data: ctx as *const &dyn crate::Context as *const c_void, + user_data: ctx as *const &dyn crate::models::Context + as *const c_void, get_argument, _lifetime: PhantomData, } } } -impl crate::Context for Context<'_> { +impl crate::models::Context for Context<'_> { fn get_argument(&self, name: &str) -> Option<&str> { unsafe { let Context { diff --git a/crates/fj/src/abi/ffi_safe.rs b/crates/fj/src/abi/ffi_safe.rs index ce9dabe7d..a7df4858e 100644 --- a/crates/fj/src/abi/ffi_safe.rs +++ b/crates/fj/src/abi/ffi_safe.rs @@ -7,6 +7,8 @@ use std::{ ptr::NonNull, }; +use crate::models::Error; + /// A FFI-safe version of `Vec`. #[repr(C)] pub(crate) struct Vec { @@ -312,8 +314,8 @@ impl Display for BoxedError { impl std::error::Error for BoxedError {} -impl From> for BoxedError { - fn from(err: Box) -> Self { +impl From for BoxedError { + fn from(err: Error) -> Self { // Open question: is it worth capturing the message from each source // error, too? We could have some sort of `sources: Vec` field // where `Source` is a private wrapper around String that implements diff --git a/crates/fj/src/abi/host.rs b/crates/fj/src/abi/host.rs index e129a0d66..2b61cee95 100644 --- a/crates/fj/src/abi/host.rs +++ b/crates/fj/src/abi/host.rs @@ -10,9 +10,9 @@ pub struct Host<'a> { _lifetime: PhantomData<&'a mut ()>, } -impl<'a, H: crate::Host + Sized> From<&'a mut H> for Host<'a> { +impl<'a, H: crate::models::Host + Sized> From<&'a mut H> for Host<'a> { fn from(host: &'a mut H) -> Self { - extern "C" fn register_boxed_model( + extern "C" fn register_boxed_model( user_data: *mut c_void, model: Model, ) { @@ -33,8 +33,8 @@ impl<'a, H: crate::Host + Sized> From<&'a mut H> for Host<'a> { } } -impl<'a> crate::Host for Host<'a> { - fn register_boxed_model(&mut self, model: Box) { +impl<'a> crate::models::Host for Host<'a> { + fn register_boxed_model(&mut self, model: Box) { let Host { user_data, register_boxed_model, diff --git a/crates/fj/src/abi/metadata.rs b/crates/fj/src/abi/metadata.rs index c0ac1947a..b21c0bd3a 100644 --- a/crates/fj/src/abi/metadata.rs +++ b/crates/fj/src/abi/metadata.rs @@ -8,7 +8,7 @@ pub struct ModelMetadata { arguments: ffi_safe::Vec, } -impl From for crate::ModelMetadata { +impl From for crate::models::ModelMetadata { fn from(m: ModelMetadata) -> Self { let ModelMetadata { name, @@ -16,7 +16,7 @@ impl From for crate::ModelMetadata { arguments, } = m; - crate::ModelMetadata { + crate::models::ModelMetadata { name: name.into(), description: description.map(Into::into).into(), arguments: arguments.iter().cloned().map(|a| a.into()).collect(), @@ -24,9 +24,9 @@ impl From for crate::ModelMetadata { } } -impl From for ModelMetadata { - fn from(m: crate::ModelMetadata) -> Self { - let crate::ModelMetadata { +impl From for ModelMetadata { + fn from(m: crate::models::ModelMetadata) -> Self { + let crate::models::ModelMetadata { name, description, arguments, @@ -52,7 +52,7 @@ pub struct Metadata { license: ffi_safe::Option, } -impl From for crate::Metadata { +impl From for crate::models::Metadata { fn from(m: Metadata) -> Self { let Metadata { name, @@ -64,7 +64,7 @@ impl From for crate::Metadata { license, } = m; - crate::Metadata { + crate::models::Metadata { name: name.into(), version: version.into(), short_description: short_description.map(Into::into).into(), @@ -76,9 +76,9 @@ impl From for crate::Metadata { } } -impl From for Metadata { - fn from(m: crate::Metadata) -> Self { - let crate::Metadata { +impl From for Metadata { + fn from(m: crate::models::Metadata) -> Self { + let crate::models::Metadata { name, version, short_description, @@ -108,9 +108,9 @@ pub struct ArgumentMetadata { default_value: ffi_safe::Option, } -impl From for ArgumentMetadata { - fn from(meta: crate::ArgumentMetadata) -> Self { - let crate::ArgumentMetadata { +impl From for ArgumentMetadata { + fn from(meta: crate::models::ArgumentMetadata) -> Self { + let crate::models::ArgumentMetadata { name, description, default_value, @@ -124,7 +124,7 @@ impl From for ArgumentMetadata { } } -impl From for crate::ArgumentMetadata { +impl From for crate::models::ArgumentMetadata { fn from(meta: ArgumentMetadata) -> Self { let ArgumentMetadata { name, @@ -132,7 +132,7 @@ impl From for crate::ArgumentMetadata { default_value, } = meta; - crate::ArgumentMetadata { + crate::models::ArgumentMetadata { name: name.into(), description: description.map(Into::into).into(), default_value: default_value.map(Into::into).into(), diff --git a/crates/fj/src/abi/mod.rs b/crates/fj/src/abi/mod.rs index a882592e8..132c781e4 100644 --- a/crates/fj/src/abi/mod.rs +++ b/crates/fj/src/abi/mod.rs @@ -7,7 +7,7 @@ /// If the macro generated the wrong code, this doctest would fail. /// /// ```rust -/// use fj::{abi::INIT_FUNCTION_NAME, Metadata}; +/// use fj::{abi::INIT_FUNCTION_NAME, models::Metadata}; /// /// fj::register_model!(|_| { /// Ok(Metadata::new("My Model", "1.2.3")) @@ -24,7 +24,7 @@ /// /// // We can also make sure the unmangled name is correct by calling it. /// -/// let metadata: fj::Metadata = unsafe { +/// let metadata: fj::models::Metadata = unsafe { /// let mut host = Host; /// let mut host = fj::abi::Host::from(&mut host); /// x::fj_model_init(&mut host).unwrap().into() @@ -33,8 +33,8 @@ /// assert_eq!(metadata.name, "My Model"); /// /// struct Host; -/// impl fj::Host for Host { -/// fn register_boxed_model(&mut self, model: Box) { todo!() } +/// impl fj::models::Host for Host { +/// fn register_boxed_model(&mut self, model: Box) { todo!() } /// } /// ``` mod context; @@ -52,6 +52,33 @@ pub use self::{ model::Model, }; +/// Define the initialization routine used when registering models. +/// +/// See the [`crate::model`] macro if your model can be implemented as a pure +/// function. +/// +/// # Examples +/// +/// ```rust +/// use fj::models::*; +/// +/// fj::register_model!(|host: &mut dyn Host| { +/// host.register_model(MyModel::default()); +/// +/// Ok(Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))) +/// }); +/// +/// #[derive(Default)] +/// struct MyModel { } +/// +/// impl Model for MyModel { +/// fn metadata(&self) -> ModelMetadata { todo!() } +/// +/// fn shape(&self, ctx: &dyn Context) -> Result { +/// todo!() +/// } +/// } +/// ``` #[macro_export] macro_rules! register_model { ($init:expr) => { @@ -60,10 +87,10 @@ macro_rules! register_model { mut host: *mut $crate::abi::Host<'_>, ) -> $crate::abi::InitResult { let init: fn( - &mut dyn $crate::Host, + &mut dyn $crate::models::Host, ) -> Result< - $crate::Metadata, - Box, + $crate::models::Metadata, + $crate::models::Error, > = $init; match init(&mut *host) { diff --git a/crates/fj/src/abi/model.rs b/crates/fj/src/abi/model.rs index 4dab289e9..eb7def772 100644 --- a/crates/fj/src/abi/model.rs +++ b/crates/fj/src/abi/model.rs @@ -1,6 +1,9 @@ use std::{os::raw::c_void, panic::AssertUnwindSafe}; -use crate::abi::{Context, ModelMetadata, ShapeResult}; +use crate::{ + abi::{Context, ModelMetadata, ShapeResult}, + models::Error, +}; #[repr(C)] pub struct Model { @@ -10,11 +13,11 @@ pub struct Model { free: unsafe extern "C" fn(*mut c_void), } -impl crate::Model for Model { +impl crate::models::Model for Model { fn shape( &self, - ctx: &dyn crate::Context, - ) -> Result> { + ctx: &dyn crate::models::Context, + ) -> Result { let ctx = Context::from(&ctx); let Model { ptr, shape, .. } = *self; @@ -27,17 +30,17 @@ impl crate::Model for Model { } } - fn metadata(&self) -> crate::ModelMetadata { + fn metadata(&self) -> crate::models::ModelMetadata { let Model { ptr, metadata, .. } = *self; unsafe { metadata(ptr).into() } } } -impl From> for Model { - fn from(m: Box) -> Self { +impl From> for Model { + fn from(m: Box) -> Self { unsafe extern "C" fn metadata(user_data: *mut c_void) -> ModelMetadata { - let model = &*(user_data as *mut Box); + let model = &*(user_data as *mut Box); match std::panic::catch_unwind(AssertUnwindSafe(|| { model.metadata() @@ -51,7 +54,7 @@ impl From> for Model { user_data: *mut c_void, ctx: Context<'_>, ) -> ShapeResult { - let model = &*(user_data as *mut Box); + let model = &*(user_data as *mut Box); match std::panic::catch_unwind(AssertUnwindSafe(|| { model.shape(&ctx) @@ -63,7 +66,7 @@ impl From> for Model { } unsafe extern "C" fn free(user_data: *mut c_void) { - let model = user_data as *mut Box; + let model = user_data as *mut Box; if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| { let model = Box::from_raw(model); diff --git a/crates/fj/src/lib.rs b/crates/fj/src/lib.rs index 0ca79b7ee..100a9b6b5 100644 --- a/crates/fj/src/lib.rs +++ b/crates/fj/src/lib.rs @@ -23,27 +23,14 @@ pub mod syntax; #[doc(hidden)] pub mod abi; mod angle; -mod context; mod group; -mod host; -mod metadata; -mod model; +pub mod models; mod shape_2d; mod sweep; mod transform; pub use self::{ - angle::*, - context::{ - Context, ContextError, ContextExt, MissingArgument, ParseFailed, - }, - group::Group, - host::{Host, HostExt}, - metadata::{ArgumentMetadata, Metadata, ModelMetadata}, - model::Model, - shape_2d::*, - sweep::Sweep, - transform::Transform, + angle::*, group::Group, shape_2d::*, sweep::Sweep, transform::Transform, }; pub use fj_proc::*; #[cfg(feature = "serde")] diff --git a/crates/fj/src/context.rs b/crates/fj/src/models/context.rs similarity index 97% rename from crates/fj/src/context.rs rename to crates/fj/src/models/context.rs index e893a128e..e64848102 100644 --- a/crates/fj/src/context.rs +++ b/crates/fj/src/models/context.rs @@ -4,8 +4,10 @@ use std::{ str::FromStr, }; -/// Contextual information passed to a [`Model`][crate::Model] when it is being -/// initialized. +use crate::models::Error; + +/// Contextual information passed to a [`Model`][crate::models::Model] when it +/// is being initialized. /// /// Check out the [`ContextExt`] trait for some helper methods. pub trait Context { @@ -191,7 +193,7 @@ pub struct ParseFailed { /// The actual value. pub value: String, /// The error that occurred. - pub error: Box, + pub error: Error, } impl Display for ParseFailed { diff --git a/crates/fj/src/host.rs b/crates/fj/src/models/host.rs similarity index 97% rename from crates/fj/src/host.rs rename to crates/fj/src/models/host.rs index ba712fb84..19072c881 100644 --- a/crates/fj/src/host.rs +++ b/crates/fj/src/models/host.rs @@ -1,4 +1,4 @@ -use crate::Model; +use crate::models::Model; /// An abstract interface to the Fornjot host. pub trait Host { diff --git a/crates/fj/src/metadata.rs b/crates/fj/src/models/metadata.rs similarity index 99% rename from crates/fj/src/metadata.rs rename to crates/fj/src/models/metadata.rs index 6a90a1d21..91fd2fc21 100644 --- a/crates/fj/src/metadata.rs +++ b/crates/fj/src/models/metadata.rs @@ -115,7 +115,7 @@ impl Metadata { } } -/// Metadata about a [`crate::Model`]. +/// Metadata about a [`crate::models::Model`]. #[derive(Debug, Clone, PartialEq)] pub struct ModelMetadata { /// A short, human-friendly name used to identify this model. diff --git a/crates/fj/src/models/mod.rs b/crates/fj/src/models/mod.rs new file mode 100644 index 000000000..03c033a40 --- /dev/null +++ b/crates/fj/src/models/mod.rs @@ -0,0 +1,18 @@ +//! Interfaces used when defining models. + +mod context; +mod host; +mod metadata; +mod model; + +pub use self::{ + context::{ + Context, ContextError, ContextExt, MissingArgument, ParseFailed, + }, + host::{Host, HostExt}, + metadata::{ArgumentMetadata, Metadata, ModelMetadata}, + model::Model, +}; + +/// A generic error used when defining a model. +pub type Error = Box; diff --git a/crates/fj/src/model.rs b/crates/fj/src/models/model.rs similarity index 64% rename from crates/fj/src/model.rs rename to crates/fj/src/models/model.rs index da82c65d7..6a157fd3c 100644 --- a/crates/fj/src/model.rs +++ b/crates/fj/src/models/model.rs @@ -1,12 +1,12 @@ -use crate::{Context, ModelMetadata, Shape}; +use crate::{ + models::{Context, Error, ModelMetadata}, + Shape, +}; /// A model. pub trait Model: Send + Sync { /// Calculate this model's concrete geometry. - fn shape( - &self, - ctx: &dyn Context, - ) -> Result>; + fn shape(&self, ctx: &dyn Context) -> Result; /// Get metadata for the model. fn metadata(&self) -> ModelMetadata;