From 164eb8229a6f284bd218745acbde29525c6a46a9 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 3 Jan 2023 00:21:45 +0300 Subject: [PATCH] feat: implement test callback support --- crates/gui/Cargo.toml | 3 + crates/gui/src/canvas.rs | 2 +- crates/gui/src/lib.rs | 4 + crates/gui/src/view_port.rs | 270 ++++++++++++++++++++++++------------ examples/gui/Cargo.lock | 8 ++ examples/gui/Cargo.toml | 3 +- examples/gui/src/main.rs | 22 ++- 7 files changed, 206 insertions(+), 106 deletions(-) diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml index 8ce62b50..1cb45f06 100644 --- a/crates/gui/Cargo.toml +++ b/crates/gui/Cargo.toml @@ -25,3 +25,6 @@ test = false flipperzero-sys = { path = "../sys", version = "0.6.0-alpha" } # FIXME: this is only required for access to `Align` enum which may be mvoved to this crate flipperzero = { path = "../flipperzero", version = "0.6.0-alpha" } + +[features] +alloc = ["flipperzero/alloc"] diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index add4c237..1cd6f0b1 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -59,7 +59,7 @@ impl<'a> Canvas<'a> { /// # Example /// /// Converting the raw pointer back into a `Canvas` - /// with `Canvas::from_raw` for automatic cleanup: + /// with [`Canvas::from_raw`] for automatic cleanup: /// /// ``` /// use flipperzero_gui::{canvas::Canvas, gui::Gui}; diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 8e82141b..212ecfa9 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -2,6 +2,10 @@ #![no_std] +#[cfg(feature = "alloc")] +extern crate alloc; + pub mod canvas; pub mod gui; +pub mod view; pub mod view_port; diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index ce5af83f..b6453621 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -1,6 +1,12 @@ //! ViewPort APIs +#[cfg(feature = "alloc")] +pub use self::alloc_features::*; + +use core::mem::size_of_val; use core::{ + ffi::c_void, + mem::size_of, num::NonZeroU8, ptr::{null_mut, NonNull}, }; @@ -10,7 +16,9 @@ use flipperzero_sys::{ /// System ViewPort. pub struct ViewPort { - view_port: *mut SysViewPort, + raw: *mut SysViewPort, + #[cfg(feature = "alloc")] + draw_callback: Option, } impl ViewPort { @@ -25,11 +33,90 @@ impl ViewPort { /// /// let view_port = ViewPort::new(); /// ``` - pub fn new() -> ViewPort { + pub fn new() -> Self { // SAFETY: allocation either succeeds producing the valid pointer // or stops the system on OOM - let view_port = unsafe { sys::view_port_alloc() }; - Self { view_port } + let raw = unsafe { sys::view_port_alloc() }; + + Self { + raw, + draw_callback: None, + } + } + + /// Construct a `ViewPort` from a raw non-null pointer. + /// + /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. + /// Specifically, the `ViewPort` destructor will free the allocated memory. + /// + /// # Safety + /// + /// `raw` should be a valid pointer to [`SysViewPort`]. + /// + /// # Examples + /// + /// Recreate a `ViewPort` + /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let (raw, draw_callback) = view_port.into_raw(); + /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; + /// ``` + pub unsafe fn from_raw( + raw: NonNull, + draw_callback: Option, + ) -> Self { + Self { + raw: raw.as_ptr(), + #[cfg(feature = "alloc")] + draw_callback, + } + } + + /// Consumes this wrapper, returning a non-null raw pointer. + /// + /// After calling this function, the caller is responsible + /// for the memory previously managed by the `ViewPort`. + /// In particular, the caller should properly destroy `SysViewPort` and release the memory + /// such as by calling [`sys::view_port_free`]. + /// The easiest way to do this is to convert the raw pointer + /// back into a `ViewPort` with the [ViewPort::from_raw] function, + /// allowing the `ViewPort` destructor to perform the cleanup. + /// + /// # Example + /// + /// Converting the raw pointer back into a `ViewPort` + /// with [`ViewPort::from_raw`] for automatic cleanup: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let (raw, draw_callback) = view_port.into_raw(); + /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; + /// ``` + pub fn into_raw(mut self) -> (NonNull, Option) { + let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); + ( + // SAFETY: `self.raw` is guaranteed to be non-null + // since it only becomes null after call to this function + // which consumes the wrapper + unsafe { NonNull::new_unchecked(raw_pointer) }, + self.draw_callback.take(), + ) + } + + /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. + /// + /// # Safety + /// + /// Caller must ensure that the provided pointer does not outlive this wrapper. + pub unsafe fn as_raw(&self) -> NonNull { + // SAFETY: the pointer is guaranteed to be non-null + unsafe { NonNull::new_unchecked(self.raw) } } /// Sets the width of this `ViewPort`. @@ -57,9 +144,9 @@ impl ViewPort { /// ``` pub fn set_width(&mut self, width: Option) { let width = width.map_or(0u8, NonZeroU8::into); - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and there are no `width` constraints - unsafe { sys::view_port_set_width(self.view_port, width) } + unsafe { sys::view_port_set_width(self.raw, width) } } /// Gets the width of this `ViewPort`. @@ -75,8 +162,8 @@ impl ViewPort { /// let width = view_port.get_width(); /// ``` pub fn get_width(&self) -> NonZeroU8 { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_width(self.view_port) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_get_width(self.raw) } .try_into() .expect("`view_port_get_width` should produce a positive value") } @@ -106,9 +193,9 @@ impl ViewPort { /// ``` pub fn set_height(&mut self, height: Option) { let height = height.map_or(0u8, NonZeroU8::into); - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and there are no `height` constraints - unsafe { sys::view_port_set_height(self.view_port, height) } + unsafe { sys::view_port_set_height(self.raw, height) } } /// Gets the height of this `ViewPort`. @@ -124,8 +211,8 @@ impl ViewPort { /// let height = view_port.get_height(); /// ``` pub fn get_height(&self) -> NonZeroU8 { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_height(self.view_port) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_get_height(self.raw) } .try_into() .expect("`view_port_get_height` should produce a positive value") } @@ -196,9 +283,9 @@ impl ViewPort { pub fn set_orientation(&mut self, orientation: ViewPortOrientation) { let orientation = SysViewPortOrientation::from(orientation); - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and `orientation` is guaranteed to be valid by `From` implementation - unsafe { sys::view_port_set_orientation(self.view_port, orientation) } + unsafe { sys::view_port_set_orientation(self.raw, orientation) } } /// Gets the orientation of this `ViewPort`. @@ -215,8 +302,8 @@ impl ViewPort { /// let orientation = view_port.get_orientation(); /// ``` pub fn get_orientation(&self) -> ViewPortOrientation { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_orientation(self.view_port as *const _) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_get_orientation(self.raw as *const _) } .try_into() .expect("`view_port_get_orientation` should produce a valid `ViewPort`") } @@ -236,8 +323,8 @@ impl ViewPort { /// view_port.set_enabled(false); /// ``` pub fn set_enabled(&mut self, enabled: bool) { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_enabled_set(self.view_port, enabled) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_enabled_set(self.raw, enabled) } } /// Checks if this `ViewPort` is enabled. @@ -254,76 +341,28 @@ impl ViewPort { /// let enabled = view_port.is_enabled(); /// ``` pub fn is_enabled(&self) -> bool { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_is_enabled(self.view_port) } - } - - /// Construct a `ViewPort` from a raw non-null pointer. - /// - /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. - /// Specifically, the `ViewPort` destructor will free the allocated memory. - /// - /// # Safety - /// - /// `raw` should be a valid pointer to [`SysViewPort`]. - /// - /// # Examples - /// - /// Recreate a `ViewPort` - /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. - /// - /// ``` - /// use flipperzero_gui::view_port::ViewPort; - /// - /// let view_port = ViewPort::new(); - /// let ptr = view_port.into_raw(); - /// let view_port = unsafe { ViewPort::from_raw(ptr) }; - /// ``` - pub unsafe fn from_raw(raw: NonNull) -> Self { - Self { - view_port: raw.as_ptr(), - } - } - - /// Consumes this wrapper, returning a non-null raw pointer. - /// - /// After calling this function, the caller is responsible - /// for the memory previously managed by the `ViewPort`. - /// In particular, the caller should properly destroy `SysViewPort` and release the memory - /// such as by calling [`sys::view_port_free`]. - /// The easiest way to do this is to convert the raw pointer - /// back into a `ViewPort` with the [ViewPort::from_raw] function, - /// allowing the `ViewPort` destructor to perform the cleanup. - /// - /// # Example - /// - /// Converting the raw pointer back into a `ViewPort` - /// with `ViewPort::from_raw` for automatic cleanup: - /// - /// ``` - /// use flipperzero_gui::view_port::ViewPort; - /// - /// let view_port = ViewPort::new(); - /// let ptr = view_port.into_raw(); - /// let view_port = unsafe { ViewPort::from_raw(ptr) }; - /// ``` - pub fn into_raw(mut self) -> NonNull { - let raw_pointer = core::mem::replace(&mut self.view_port, null_mut()); - // SAFETY: `self.view_port` is guaranteed to be non-null - // since it only becomes null after call to this function - // which consumes the wrapper - unsafe { NonNull::new_unchecked(raw_pointer) } - } - - /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. - /// - /// # Safety - /// - /// Caller must ensure that the provided pointer does not outlive this wrapper. - pub unsafe fn as_raw(&self) -> NonNull { - // SAFETY: the pointer is guaranteed to be non-null - unsafe { NonNull::new_unchecked(self.view_port) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_is_enabled(self.raw) } } + // + // pub fn set_static_draw_callback<'a: 'b, 'b, F: Fn(*mut sys::Canvas)>( + // &'a mut self, + // callback: &'static fn(*mut sys::Canvas), + // ) { + // pub unsafe extern "C" fn dispatch( + // canvas: *mut sys::Canvas, + // context: *mut core::ffi::c_void, + // ) { + // // SAFETY: `context` is always a valid pointer + // let context = NonNull::new_unchecked(context as *const F as *mut F); + // // SAFETY: context is a valid pointer + // (unsafe { context.as_ref() })(canvas); + // } + // // FIXME: flipperzero-firmware: function pointer should be const + // let ptr = callback.as_ref().get_ref() as *const F as *mut c_void; + // + // unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch::), ptr) } + // } } impl Default for ViewPort { @@ -334,12 +373,12 @@ impl Default for ViewPort { impl Drop for ViewPort { fn drop(&mut self) { - // `self.view_port` is `null` iff it has been taken by call to `into_raw()` - if !self.view_port.is_null() { + // `self.raw` is `null` iff it has been taken by call to `into_raw()` + if !self.raw.is_null() { // FIXME: unregister from system - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and it should have been unregistered from the system by now - unsafe { sys::view_port_free(self.view_port) } + unsafe { sys::view_port_free(self.raw) } } } } @@ -398,3 +437,52 @@ impl From for SysViewPortOrientation { } } } + +/// Functionality only available when `alloc` feature is enabled. +#[cfg(feature = "alloc")] +mod alloc_features { + use super::*; + use alloc::boxed::Box; + use core::pin::Pin; + + impl ViewPort { + pub fn set_draw_callback(&mut self, mut callback: ViewPortDrawCallback) { + type CallbackPtr = *mut ViewPortDrawCallback; + + pub unsafe extern "C" fn dispatch(canvas: *mut sys::Canvas, context: *mut c_void) { + let context: CallbackPtr = context.cast(); + // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + (unsafe { NonNull::new_unchecked(context).as_mut() }).0(canvas); + } + + let mut callback = Box::pin(callback); + let ptr = callback.as_mut().get_mut() as CallbackPtr as *mut c_void; + // keep old cllback alive until the new one is written + let old_callback = self.draw_callback.replace(callback); + + const _: () = assert!( + size_of::<*const ViewPortDrawCallback>() == size_of::<*mut c_void>(), + "`ViewPortDrawCallback` should be a thin pointer" + ); + + unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch), ptr) } + + drop(old_callback); + } + } + + pub struct ViewPortDrawCallback(Box); + + impl ViewPortDrawCallback { + pub fn new(callback: Box) -> Self { + Self(callback) + } + + pub fn boxed(callback: F) -> Self { + Self::new(Box::new(callback)) + } + } + + pub(super) type PinnedViewPortDrawCallback = Pin>; +} diff --git a/examples/gui/Cargo.lock b/examples/gui/Cargo.lock index cd8b832c..36e92dc5 100644 --- a/examples/gui/Cargo.lock +++ b/examples/gui/Cargo.lock @@ -9,6 +9,13 @@ dependencies = [ "flipperzero-sys", ] +[[package]] +name = "flipperzero-alloc" +version = "0.6.0-alpha" +dependencies = [ + "flipperzero-sys", +] + [[package]] name = "flipperzero-gui" version = "0.6.0-alpha" @@ -33,6 +40,7 @@ name = "gui" version = "0.1.0" dependencies = [ "flipperzero", + "flipperzero-alloc", "flipperzero-gui", "flipperzero-rt", "flipperzero-sys", diff --git a/examples/gui/Cargo.toml b/examples/gui/Cargo.toml index 2a004b36..23fcc883 100644 --- a/examples/gui/Cargo.toml +++ b/examples/gui/Cargo.toml @@ -17,4 +17,5 @@ test = false flipperzero = { version = "0.6.0-alpha", path = "../../crates/flipperzero" } flipperzero-sys = { version = "0.6.0-alpha", path = "../../crates/sys" } flipperzero-rt = { version = "0.6.0-alpha", path = "../../crates/rt" } -flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui" } +flipperzero-alloc = { version = "0.6.0-alpha", path = "../../crates/alloc" } +flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui", features = ["alloc"] } diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index beb84830..a6a44b25 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -6,14 +6,15 @@ // Required for panic handler extern crate flipperzero_rt; +// Alloc +extern crate alloc; +extern crate flipperzero_alloc; -use core::ffi::c_void; -use core::ptr; use core::time::Duration; use flipperzero::furi::thread::sleep; use flipperzero_gui::gui::{Gui, GuiLayer}; -use flipperzero_gui::view_port::ViewPort; +use flipperzero_gui::view_port::{ViewPort, ViewPortDrawCallback}; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; @@ -21,11 +22,8 @@ manifest!(name = "Rust GUI example"); entry!(main); /// View draw handler. -/// -/// # Safety -/// -/// `canvas` should be a valid pointer to [`sys::Canvas`] -pub unsafe extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut c_void) { +fn draw_callback(canvas: *mut sys::Canvas) { + // # SAFETY: `canvas` should be a valid pointer unsafe { sys::canvas_draw_str(canvas, 39, 31, sys::c_string!("Hello, Rust!")); } @@ -34,11 +32,9 @@ pub unsafe extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut fn main(_args: *mut u8) -> i32 { // Currently there is no high level GUI bindings, // so this all has to be done using the `sys` bindings. - let view_port = ViewPort::new().into_raw(); - let view_port = unsafe { - sys::view_port_draw_callback_set(view_port.as_ptr(), Some(draw_callback), ptr::null_mut()); - ViewPort::from_raw(view_port) - }; + let mut view_port = ViewPort::new(); + + view_port.set_draw_callback(ViewPortDrawCallback::boxed(draw_callback)); let mut gui = Gui::new(); let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen);