From f79f186dc5ce105d644035cf818e91958a8167c0 Mon Sep 17 00:00:00 2001 From: Avril Date: Thu, 10 Sep 2020 13:08:19 +0100 Subject: [PATCH] fix bitflags with janky hack --- README.org | 53 ++++++++-- src/error.rs | 152 +++++++++++++++++++++++++++++ src/handle.rs | 7 +- src/lib.rs | 45 +++++++-- src/types.rs | 261 +++++++++++++++++++++++++++++++++++++------------- 5 files changed, 434 insertions(+), 84 deletions(-) create mode 100644 src/error.rs diff --git a/README.org b/README.org index 8677968..d7d12f8 100644 --- a/README.org +++ b/README.org @@ -5,7 +5,16 @@ ABI-compatable types and functions are all prefixed with `GHOST_` and are exported in the `c` module (TODO). I've attempted to provide more Rust-idiomatic API variants as well, which have non-prefixed names. -** Implementation log +** Notes + Eventually I intend to have /most/ (if not all) C compatable types either reimplemented or aliased to non-prefixed Rust types. + At present though this is not the case, and prefixed names that can double as ergonomic Rust types are not aliased to them, so you'll have to use both often. + The reimplementation is undergoing, the aliasing not yet. + + I've elected to leave out implementing ~Copy~ for /most/ FFI types, because I think the ownership model will work well here. + This may be changed in the future, as (virtually) all implement ~Clone~ anyway. + I did this basically just because I want people to think about it when and why they copy something, instead of it being default behaviour. + +** Implementation *** Implemented C compatable ABI Note: List is in-progress and doesn't show everything @@ -49,17 +58,18 @@ - [X] GHOST_DialogOptions - [X] GHOST_TabletData - [X] /Constants/ - - [X] =GHOST_TABLET_DATA_NONE= - * reimpl as ~const fn~ (~GHOST_TabletData::none()~) + - [X] =GHOST_TABLET_DATA_NONE= (reimpl as ~const fn GHOST_TabletData::none()~) *** Native Rust API - I provide more Rust idiomatic API for some things. They may or may not share ABI, if they do they will have type aliases to the corresponding ~GHOST_~ identifier, so always use those when ABI compatability is desired. + I provide more Rust idiomatic API for some things. + They may or may not share ABI, if they do they will have type aliases to the corresponding ~GHOST_~ identifier, so always use those when ABI compatability is desired. **** Overview List and implementation status of features - [-] Types - [X] [[Handles]]: ~handle.rs~ + - [X] [[Error handling]]: ~error.rs~ - [ ] Events: ~event.rs~ **** Handles @@ -70,12 +80,37 @@ Since handles are just types pointers, they are only ever used as such and shouldn't exist themselves. ***** Example #+BEGIN_SRC rust - extern "C" unsafe fn internal_call(window: GHOST_WindowHandle) -> GHOST_TSuccess; + extern "C" {fn some_internal_call(window: GHOST_WindowHandle) -> GHOST_TSuccess;} - fn do_something_to_window(window: &mut Handle) -> Result<()> + fn do_something_to_window(window: &mut Handle) -> GhostResult<()> { - unsafe { - internal_call(window as GHOST_WindowHandle).into() - } + some_internal_call(window as GHOST_WindowHandle).into() } #+END_SRC + +**** Error handling + I have provided an idiomatic Rust error handling module, with conversion and interop between the C ABI ~GHOST_TSuccess~ and the Rust ~error::GhostError~. + There is also the type alias ~GhostResult~ provided. + + On =nightly= Rust versions, values of type ~GHOST_TSuccess~ can be propagated directly with ~?~, but on stable they must be propagated through ~GhostResult~: + #+BEGIN_SRC rust + extern "C" {fn returns_tsuccess() -> GHOST_TSuccess;} + + fn try_things_internal() -> GhostResult // On nightly, we can propagage `GHOST_TSuccess` directly here, but it's still more desireable for us to have a `Result` instead of an integer in Rust, so this is still preferrable. + { + returns_tsuccess()?; + + Ok(()) + } + + fn caller() + { + match try_things_internal() { + Ok(_) => println!("Yay!"), + Err(_) => panic!("Fug"), + } + } + #+END_SRC + Keeping such a style is /usually/ preferred, anyhow. + + See [[./src/error.rs][error.rs]] for more details. diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6274a1e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,152 @@ +//! Error handling, reporting and propagating. +//! +//! Allows us to take advantage of `Result` and interop idiomatic Rust error reporing with the ffi C functions' error reporting. +//! Functions returning or constructing `GHOST_TSuccess` will be able to be taken advantage of to be propagated and reported better. +//! +//! See [`GhostResult`] for more information. +use super::*; +use std::{ + error, + fmt, +}; + +/// A result that returns potentially an error. In the form of `GHOST_TSuccess`. +/// +/// # Usage +/// Its function is to provide more ergonomic error handling for Rust code, while still being able to easily interface with the C code. +/// # Propagating +/// Can be used to use `?` operator on functions returning `GHOST_TSuccess`. +/// +/// When building on nightly, the following is possible. +/// ``` +/// # use ghost::{error::GhostResult, c::GHOST_TSuccess}; +/// extern "C" {fn returns_tsuccess() -> GHOST_TSuccess;} +/// +/// fn try_things_internal() -> GHOST_TSuccess +/// { +/// unsafe { +/// returns_tsuccess()?; +/// } +/// +/// GHOST_TSuccess::Success +/// } +/// +/// fn caller() +/// { +/// match try_things_internal() { +/// GHOST_TSuccess::Success => println!("Yay!"), +/// GHOST_TSuccess::Failure => panic!("Fug"), +/// } +/// } +/// ``` +/// +/// On stable, it must be rewritten a bit differently, however: +/// +/// ``` +/// # use ghost::{error::GhostResult, c::GHOST_TSuccess}; +/// extern "C" {fn returns_tsuccess() -> GHOST_TSuccess;} +/// +/// fn try_things_internal() -> GhostResult +/// { +/// unsafe { +/// returns_tsuccess()?; +/// } +/// +/// Ok(()) +/// } +/// +/// fn caller() +/// { +/// match try_things_internal() { +/// Ok(_) => println!("Yay!"), +/// Err(_) => panic!("Fug"), +/// } +/// } +/// ``` +/// The bottom scenario is preferrable for Rust, as we are using `Result` instead of an integer. +/// # When to use +/// This type should be preferred in Rust code, and `GHOST_TSuccess` preferred when doing a lot of FFI at once. +/// # Notes +/// *Usually* the internal result will be `()`. But idk if that will always be the case so it's still possible to set. +pub type GhostResult = Result; + +/// Error type for failed GHOST calls. +/// As of yet it doesn't contain any information and is just an opaque zero-sized structure. +#[derive(Debug)] +pub struct GhostError(()); +impl error::Error for GhostError{} +impl fmt::Display for GhostError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "GHOST function call failed") + } +} + +impl From for GhostResult +{ + #[inline] fn from(from: types::GHOST_TSuccess) -> Self + { + from.into_result() + } +} + +impl From for types::GHOST_TSuccess +{ + #[inline] fn from(from: GhostResult) -> Self + { + Self::from_result(from) + } +} + +impl fmt::Display for types::GHOST_TSuccess +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::Failure => write!(f, "Failure"), + Self::Success => write!(f, "Success"), + } + } +} + +#[cfg(nightly)] +impl std::ops::Try for types::GHOST_TSuccess +{ + type Ok = (); + type Error = GhostError; + + fn into_result(self) -> Result + { + self.into() + } + fn from_error(_: Self::Error) -> Self + { + Self::Failure + } + fn from_ok(_: Self::Ok) -> Self + { + Self::Success + } +} +impl types::GHOST_TSuccess +{ + /// Consume this instance into `Result<(), GhostError>`. + /// + pub const fn into_result(self) -> GhostResult + { + match self { + types::GHOST_TSuccess::Success => Ok(()), + types::GHOST_TSuccess::Failure => Err(GhostError(())), + } + } + + /// Convert a `GhostResult` into `GHOST_TSuccess`. + pub const fn from_result(from: GhostResult) -> Self + { + match from { + Ok(_) => Self::Success, + Err(_) => Self::Failure, + } + } +} diff --git a/src/handle.rs b/src/handle.rs index d159bd2..b5b8629 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -1,12 +1,15 @@ -//! GHOST_Types.h +//! Strongly typed safe opaque pointers to GHOST handles. +//! +//! We define opaque ZST structures for each handle type, these then implement the [`GhostHandle`] trait, which allows them to be used for type arguments to [`Handle`](Handle). use super::*; -use types::{Handle,GhostHandle}; +pub use types::{Handle,GhostHandle}; macro_rules! handle { ($name:ident, $inner_name:ident) => { #[cfg(nightly)] pub struct $inner_name(!); #[cfg(not(nightly))] pub struct $inner_name(()); + impl private::Sealed for $inner_name{} impl GhostHandle for $inner_name{} pub type $name = *mut Handle<$inner_name>; }; diff --git a/src/lib.rs b/src/lib.rs index b1fcebd..d3ebab1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,34 @@ //! Rust bindings and interop for GHOST. //! //! # Layout -//! ABI-compatable types and functions are all prefixed with `GHOST_` and are exported in the `c` module (TODO). +//! ABI-compatable types and functions are all prefixed with `GHOST_` and are exported in the `c` module. //! I've attempted to provide more Rust-idiomatic API variants as well, which have non-prefixed names. +//! +//! # Namespace polution and interface stablility +//! Eventually I intend to have /most/ (if not all) C compatable types either reimplemented or aliased to non-prefixed Rust types. +//! At present though this is not the case, and prefixed names that can double as ergonomic Rust types are not aliased to them, so you'll have to use both often. +//! The reimplementation is undergoing, the aliasing not yet. -#![cfg_attr(nightly, feature(never_type))] +#![cfg_attr(nightly, feature(never_type))] +#![cfg_attr(nightly, feature(try_trait))] #![allow(dead_code)] #![allow(non_camel_case_types)] -#[cfg(nightly)] pub type PVoid = *const !; -#[cfg(not(nightly))] pub type PVoid = *const libc::c_void; -#[cfg(nightly)] pub type PVoidMut = *mut !; -#[cfg(not(nightly))] pub type PVoidMut = *mut libc::c_void; +#[cfg(nightly)] pub(crate) type PVoid = *const !; +#[cfg(not(nightly))] pub(crate) type PVoid = *const libc::c_void; +#[cfg(nightly)] pub(crate) type PVoidMut = *mut !; +#[cfg(not(nightly))] pub(crate) type PVoidMut = *mut libc::c_void; + +mod private +{ + pub trait Sealed{} +} pub mod handle; pub mod event; pub mod types; +pub mod error; pub mod c; @@ -27,4 +39,25 @@ mod tests { fn it_works() { // nothing yet. } + + macro_rules! static_assert { + ($expression:expr $(, $message:literal)?) => { + const _: [u8; 1] = [0u8; (!!($expression)) as usize]; + }; + } + + #[repr(C)] + enum DummyCEnum + { + One = 0, + Two, + Three, + } + + // validate the dumb bitflags hack + static_assert!(std::mem::size_of::() == std::mem::size_of::(), + "C enum is not of type `int`. Either that or #[repr(transparent)] is broken. Aborting."); + static_assert!(std::mem::align_of::() == std::mem::align_of::(), + "C enum has unexpected alignment differing from type `int`. Either that or #[repr(transparent)] is broken. Aborting."); + // ..are there others? } diff --git a/src/types.rs b/src/types.rs index 33d98e3..d1500cc 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -//! GHOST_Types.h +//! `GHOST_Types.h` use super::*; use libc::{ c_int, @@ -9,12 +9,31 @@ use std::{ }, }; -/// Trait for opaque types that represent GHOST handles -/// API maps vaguely to `GHOST_DECLARE_HANDLE`. -pub trait GhostHandle{} - -/// Opaque handle type for GHOST C++ object. +/// Trait for opaque types that represent GHOST handles. +/// +/// API maps to `GHOST_DECLARE_HANDLE`, and ABI maps exactly for `Handle` where `T` is an implementor of this. +/// +/// See [`handle`](../handle/index.html) for implementors. +pub trait GhostHandle: private::Sealed{} + +/// Opaque handle type for GHOST C++ object pointers. +/// +/// These have the same ABI as structs defined with `GHOST_DECLARE_HANDLE`, but it is irrelevant as its use is just for typing pointers. This field should not be instantiated itself, but instead passed as `&Handle` or `&mut Handle` to provide safe interfaces for these C APIs. +/// +/// The following is safe: +/// ``` +/// # use ghost::{error::GhostResult, c::*, handle::*}; +/// extern "C" {fn some_internal_call(window: GHOST_WindowHandle) -> GHOST_TSuccess;} +/// +/// fn do_something_to_window(window: &mut Handle) -> GhostResult +/// { +/// unsafe { +/// some_internal_call(window as GHOST_WindowHandle).into() // `GHOST_WindowHandle` is an alias for `*mut Handle` +/// } +/// } +/// ``` #[repr(C)] +#[derive(Debug)] pub struct Handle(c_int, PhantomData) where T: GhostHandle; @@ -29,29 +48,117 @@ pub type GHOST_TUns64 = libc::c_ulonglong; pub type GHOST_TUserDataPtr = PVoidMut; #[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct GHOST_GLSettings { - pub flags: c_int, + pub flags: GHOST_GLFlags, } -#[repr(C)] -pub enum GHOST_GLFlags -{ +/// dumb hack for bitflags +macro_rules! enum_flags { + ($name:ident { + $($const_name:ident = $value:expr,)* + }) => { + + #[repr(transparent)] //this seems dodgy to me + #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Default)] + pub struct $name(libc::c_int); + + impl $name { + $( + #[allow(non_upper_case_globals)] pub const $const_name: $name = $name($value); + )* + + pub const fn from_int(int: libc::c_int) -> Self + { + Self(int) + } + pub const fn as_int(&self) -> libc::c_int + { + self.0 + } + + pub const fn has(&self, other: &Self) -> bool + { + (self.0 & other.0) == other.0 + } + } + + impl From for $name + { + #[inline] fn from(from: libc::c_int) -> Self + { + Self(from) + } + } + + impl From<$name> for libc::c_int + { + #[inline] fn from(from: $name) -> Self + { + from.0 + } + } + + + impl ::std::ops::BitAnd for $name + { + type Output = Self; + #[inline] fn bitand(self, rhs: Self) -> Self { + Self(rhs.0 & self.0) + } + } + + impl ::std::ops::BitOr for $name + { + type Output = Self; + #[inline] fn bitor(self, rhs: Self) -> Self { + Self(rhs.0 | self.0) + } + } + + impl ::std::ops::BitXor for $name + { + type Output = Self; + #[inline] fn bitxor(self, rhs: Self) -> Self { + Self(rhs.0 ^ self.0) + } + } + + impl ::std::ops::Not for $name + { + type Output = Self; + #[inline] fn not(self,) -> Self { + Self(!self.0) + } + } + + impl ::std::ops::BitAndAssign for $name { + #[inline] fn bitand_assign(&mut self, rhs: Self) { self.0 &= rhs.0; } + } + impl ::std::ops::BitOrAssign for $name { + #[inline] fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0; } + } + impl ::std::ops::BitXorAssign for $name{ + #[inline] fn bitxor_assign(&mut self, rhs: Self) { self.0 ^= rhs.0; } + } + }; +} + +enum_flags!(GHOST_GLFlags { StereoVisual = 1<<0, DebugContext = 1<<1, AlphaBackground = 1<<2, -} +}); -#[repr(C)] -pub enum GHOST_DialogOptions +enum_flags!(GHOST_DialogOptions { DialogWarning = 1<<0, DialogError = 1<<1, -} - -pub type DialogOptions = GHOST_DialogOptions; +}); #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TSuccess { Failure = 0, @@ -59,6 +166,7 @@ pub enum GHOST_TSuccess } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TTabletMode { None = 0, @@ -77,6 +185,7 @@ impl Default for GHOST_TTabletMode #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TTabletAPI { None = 0, @@ -95,6 +204,7 @@ impl Default for GHOST_TTabletAPI #[repr(C)] +#[derive(Debug, Clone, PartialEq)] pub struct GHOST_TabletData { pub active: GHOST_TTabletMode, @@ -105,6 +215,7 @@ pub struct GHOST_TabletData impl GHOST_TabletData { + /// We use `const fn` initialiser here instead of `GHOST_TABLET_DATA_NONE` constant. pub const fn none() -> Self{ Self{active: GHOST_TTabletMode::None, pressure: 1.0, xtilt: 0.0, ytilt: 0.0} } @@ -119,6 +230,7 @@ impl Default for GHOST_TabletData } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TVisibility { Not =0, @@ -136,13 +248,23 @@ impl Default for GHOST_TVisibility } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Copy)] pub enum GHOST_TFireTimeConstant { Never = 0xFFFFFFFF, } +impl Default for GHOST_TFireTimeConstant +{ + #[inline] + fn default() -> Self + { + Self::Never + } +} #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TModifierKeyMask { LeftShift = 0, RightShift, @@ -155,17 +277,19 @@ pub enum GHOST_TModifierKeyMask { } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TWindowState { - Normal = 0, - Maximized, - Minimized, - FullScreen, - Embedded, - // Modified, - // UnModified, + Normal = 0, + Maximized, + Minimized, + FullScreen, + Embedded, + // Modified, + // UnModified, } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TWindowOrder { Top =0, @@ -173,67 +297,70 @@ pub enum GHOST_TWindowOrder } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TDrawingContextType { - None = 0, - OpenGL, - //D3D, //lol nope + None = 0, + OpenGL, + //D3D, //lol nope } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TButtonMask{ - Left = 0, - Middle, - Right, - Button4, - Button5, - /* Trackballs and programmable buttons */ - Button6, - Button7, - NumMasks // Should we even keep this? I guess yeah for ABI compatability + Left = 0, + Middle, + Right, + Button4, + Button5, + /* Trackballs and programmable buttons */ + Button6, + Button7, + NumMasks // Should we even keep this? I guess yeah for ABI compatability } #[repr(C)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum GHOST_TEventType { - Unknown = 0, + Unknown = 0, - CursorMove, - ButtonDown, - ButtonUp, - Wheel, - Trackpad, + CursorMove, + ButtonDown, + ButtonUp, + Wheel, + Trackpad, -//#ifdef WITH_INPUT_NDOF -// NDOFMotion, /// N degree of freedom device motion event -// NDOFButton, /// N degree of freedom device button event -//#endif + //#ifdef WITH_INPUT_NDOF + // NDOFMotion, /// N degree of freedom device motion event + // NDOFButton, /// N degree of freedom device button event + //#endif - KeyDown, - KeyUp, - // KeyAuto, + KeyDown, + KeyUp, + // KeyAuto, - QuitRequest, + QuitRequest, - WindowClose, - WindowActivate, - WindowDeactivate, - WindowUpdate, - WindowSize, - WindowMove, - WindowDPIHintChanged, + WindowClose, + WindowActivate, + WindowDeactivate, + WindowUpdate, + WindowSize, + WindowMove, + WindowDPIHintChanged, - DraggingEntered, - DraggingUpdated, - DraggingExited, - DraggingDropDone, + DraggingEntered, + DraggingUpdated, + DraggingExited, + DraggingDropDone, - OpenMainFile, // Needed for Cocoa to open double-clicked .blend file at startup - NativeResolutionChange, // Needed for Cocoa when window moves to other display + OpenMainFile, // Needed for Cocoa to open double-clicked .blend file at startup + NativeResolutionChange, // Needed for Cocoa when window moves to other display - Timer, + Timer, - ImeCompositionStart, - ImeComposition, - ImeCompositionEnd, + ImeCompositionStart, + ImeComposition, + ImeCompositionEnd, - NumEventTypes + NumEventTypes }