Skip to content

Commit

Permalink
feat: implement test callback support
Browse files Browse the repository at this point in the history
  • Loading branch information
JarvisCraft committed Jan 2, 2023
1 parent 38a04c0 commit 164eb82
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 106 deletions.
3 changes: 3 additions & 0 deletions crates/gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
2 changes: 1 addition & 1 deletion crates/gui/src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
4 changes: 4 additions & 0 deletions crates/gui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
270 changes: 179 additions & 91 deletions crates/gui/src/view_port.rs
Original file line number Diff line number Diff line change
@@ -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},
};
Expand All @@ -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<PinnedViewPortDrawCallback>,
}

impl ViewPort {
Expand All @@ -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<SysViewPort>,
draw_callback: Option<PinnedViewPortDrawCallback>,
) -> 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<SysViewPort>, Option<PinnedViewPortDrawCallback>) {
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<SysViewPort> {
// SAFETY: the pointer is guaranteed to be non-null
unsafe { NonNull::new_unchecked(self.raw) }
}

/// Sets the width of this `ViewPort`.
Expand Down Expand Up @@ -57,9 +144,9 @@ impl ViewPort {
/// ```
pub fn set_width(&mut self, width: Option<NonZeroU8>) {
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`.
Expand All @@ -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")
}
Expand Down Expand Up @@ -106,9 +193,9 @@ impl ViewPort {
/// ```
pub fn set_height(&mut self, height: Option<NonZeroU8>) {
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`.
Expand All @@ -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")
}
Expand Down Expand Up @@ -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`.
Expand All @@ -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`")
}
Expand All @@ -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.
Expand All @@ -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<SysViewPort>) -> 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<SysViewPort> {
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<SysViewPort> {
// 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<F: Fn(*mut sys::Canvas)>(
// 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::<F>), ptr) }
// }
}

impl Default for ViewPort {
Expand All @@ -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) }
}
}
}
Expand Down Expand Up @@ -398,3 +437,52 @@ impl From<ViewPortOrientation> 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<dyn FnMut(*mut sys::Canvas)>);

impl ViewPortDrawCallback {
pub fn new(callback: Box<dyn FnMut(*mut sys::Canvas)>) -> Self {
Self(callback)
}

pub fn boxed<F: FnMut(*mut sys::Canvas) + 'static>(callback: F) -> Self {
Self::new(Box::new(callback))
}
}

pub(super) type PinnedViewPortDrawCallback = Pin<Box<ViewPortDrawCallback>>;
}
Loading

0 comments on commit 164eb82

Please sign in to comment.