diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 99d030e..9cfd886 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -30,4 +30,5 @@ jobs: run: cargo build --verbose - name: Run tests - run: cargo test --verbose + # LVGL is not thread safe, we need to run tests sequentially + run: cargo test --verbose -- --nocapture --test-threads 1 diff --git a/examples/demo.rs b/examples/demo.rs index 206dba6..91d2d65 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -5,7 +5,7 @@ use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; use lvgl; -use lvgl::display::{DefaultDisplay, Display}; +use lvgl::display::{DefaultDisplay, Display, DrawBuffer}; use lvgl::style::Style; use lvgl::widgets::{Label, LabelAlign}; use lvgl::{Align, Color, LvError, Part, State, Widget, UI}; @@ -16,6 +16,8 @@ use std::thread; use std::thread::sleep; use std::time::{Duration, Instant}; +static DRAW_BUFFER: DrawBuffer = DrawBuffer::new(); + fn main() -> Result<(), LvError> { lvgl::init(); @@ -29,11 +31,13 @@ fn main() -> Result<(), LvError> { // Implement and register your display: let shared_native_display = SyncArc::new(Mutex::new(embedded_graphics_display)); - let _display = Display::register_shared(&shared_native_display)?; + let _display = Display::register_shared(&DRAW_BUFFER, &shared_native_display)?; // Create screen and widgets let mut screen = DefaultDisplay::get_scr_act()?; + println!("Before all widgets: {:?}", mem_info()); + let mut screen_style = Style::default(); screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0))); screen_style.set_radius(State::DEFAULT, 0); @@ -92,9 +96,12 @@ fn main() -> Result<(), LvError> { _ => {} } } + println!("During run: {:?}", mem_info()); sleep(Duration::from_secs(1)); } + println!("Final part of demo app: {:?}", mem_info()); + Ok(()) } @@ -109,3 +116,20 @@ fn main() -> Result<(), LvError> { extern "C" { pub static mut noto_sans_numeric_80: lvgl_sys::lv_font_t; } + +fn mem_info() -> lvgl_sys::lv_mem_monitor_t { + let mut info = lvgl_sys::lv_mem_monitor_t { + total_size: 0, + free_cnt: 0, + free_size: 0, + free_biggest_size: 0, + used_cnt: 0, + max_used: 0, + used_pct: 0, + frag_pct: 0, + }; + unsafe { + lvgl_sys::lv_mem_monitor(&mut info as *mut _); + } + info +} diff --git a/lvgl-codegen/src/analysis.rs b/lvgl-codegen/src/analysis.rs index 7af6991..3b9f7fe 100644 --- a/lvgl-codegen/src/analysis.rs +++ b/lvgl-codegen/src/analysis.rs @@ -3,23 +3,64 @@ /// This struct represents all relevant information we can extract from the C function declaration /// of a LVGL public interface. We can use this information to do inference for how the parameter /// should be represented in a safe Rust API. +#[derive(Clone, Debug)] pub struct CParameter { /// The name of the parameter in the C code. - name: String, + pub name: String, /// This is the raw representation of the Rust equivalent of the C type. - c_type: String, + pub c_type: String, /// Takes a pointer to a type that is referenced by the LVGL code permanently. - owned: bool, + pub scope: ParameterScope, /// The pointer is not marked as `*const` so the referenced object can be mutated. - mutable: bool, + pub mutable: bool, /// We need to check if the value is optional in the C code. We need to check /// the function comments for this information. /// - "if NULL then" /// - "if not NULL then" /// - "NULL to" - nullable: bool, + pub allow_none: bool, + + /// Comment associated with the parameter, if exists. + pub comment: Option, +} + +#[derive(Clone, Debug)] +pub enum ParameterScope { + Call, + Static, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FunctionKind { + Constructor, + Method, + Function, +} + +/// Inference from a LVGL C API function. +#[derive(Clone, Debug)] +pub struct Function { + /// Name of the function in the LVGL C API. + pub name: String, + + /// Comment associated with the function, if exists. + pub comment: Option, + + pub kind: FunctionKind, + + pub parameters: Vec, + + pub ret: Return, +} + +#[derive(Clone, Debug)] +pub enum Return { + Value(Option), + + /// If the return is a LVGL result + ResultError(CParameter), } diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index e8ca44c..355698f 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -356,7 +356,7 @@ impl Rusty for LvType { Some(name) => { let val = if self.is_str() { quote!(&cstr_core::CStr) - } else if (self.literal_name.contains("lv_")) { + } else if self.literal_name.contains("lv_") { let ident = format_ident!("{}", name); quote!(&#ident) } else { @@ -672,9 +672,8 @@ mod test { } pub fn new() -> crate::LvResult { - Ok(Self::create_at( - &mut crate::display::DefaultDisplay::get_scr_act(), - )?) + let mut parent = crate::display::DefaultDisplay::get_scr_act()?; + Ok(Self::create_at(&mut parent)?) } } }; diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index 478a9c5..4083b3d 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -19,6 +19,7 @@ cstr_core = "0.2.3" bitflags = "1.2.1" parking_lot = "0.11.1" lazy_static = "1.4.0" +heapless = "0.7.1" [features] alloc = ["cstr_core/alloc"] @@ -32,7 +33,6 @@ lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" } [dev-dependencies] embedded-graphics-simulator = "0.2.1" -heapless = "0.5.5" [[example]] name = "app" diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index 679b5c6..5e77e2c 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -2,15 +2,16 @@ use crate::functions::CoreError; use crate::Box; use crate::{disp_drv_register, disp_get_default, get_str_act}; use crate::{Color, Obj}; +use core::cell::{Cell, RefCell}; use core::marker::PhantomData; -use core::mem::{ManuallyDrop, MaybeUninit}; +use core::mem::MaybeUninit; use core::ptr::NonNull; +use core::sync::atomic::{AtomicBool, Ordering}; use core::{ptr, result}; use embedded_graphics::drawable; use embedded_graphics::prelude::*; - -#[cfg(feature = "alloc")] -use parking_lot::Mutex; // TODO: Can this really be used in no_std envs with alloc? +use parking_lot::const_mutex; +use parking_lot::Mutex; #[cfg(feature = "alloc")] use alloc::sync::Arc; @@ -20,11 +21,6 @@ const REFRESH_BUFFER_LEN: usize = 2; // Declare a buffer for the refresh rate const BUF_SIZE: usize = lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN; -static mut REFRESH_BUFFER1: [MaybeUninit; BUF_SIZE] = - [MaybeUninit::::uninit(); BUF_SIZE]; - -static mut DRAW_BUFFER: MaybeUninit = MaybeUninit::uninit(); - #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum DisplayError { NotAvailable, @@ -46,23 +42,26 @@ impl Display { Self { disp } } - pub fn register(native_display: T) -> Result + pub fn register(draw_buffer: &'static DrawBuffer, native_display: T) -> Result where T: DrawTarget, C: PixelColor + From, { - let mut display_diver = DisplayDriver::new(DisplayBuffer::new(), native_display); + let mut display_diver = DisplayDriver::new(draw_buffer, native_display)?; Ok(disp_drv_register(&mut display_diver)?) } #[cfg(feature = "alloc")] - pub fn register_shared(shared_native_display: &SharedNativeDisplay) -> Result + pub fn register_shared( + draw_buffer: &'static DrawBuffer, + shared_native_display: &SharedNativeDisplay, + ) -> Result where T: DrawTarget, C: PixelColor + From, { let mut display_diver = - DisplayDriver::new_shared(DisplayBuffer::new(), Arc::clone(shared_native_display)); + DisplayDriver::new_shared(draw_buffer, Arc::clone(shared_native_display))?; Ok(disp_drv_register(&mut display_diver)?) } @@ -73,7 +72,7 @@ impl Display { impl Default for Display { fn default() -> Self { - disp_get_default().expect("LVGL must be initialized") + disp_get_default().expect("LVGL must be INITIALIZED") } } @@ -87,21 +86,40 @@ impl DefaultDisplay { } } -pub struct DisplayBuffer {} +pub struct DrawBuffer { + initialized: AtomicBool, + refresh_buffer: Mutex>>, +} -impl DisplayBuffer { - pub fn new() -> Self { - unsafe { - lvgl_sys::lv_disp_buf_init( - DRAW_BUFFER.as_mut_ptr(), - &mut REFRESH_BUFFER1 as *mut _ as *mut cty::c_void, - // Box::into_raw(refresh_buffer2) as *mut cty::c_void, - ptr::null_mut(), - lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, - ); - }; +impl DrawBuffer { + pub const fn new() -> Self { + Self { + initialized: AtomicBool::new(false), + refresh_buffer: const_mutex(RefCell::new(heapless::Vec::new())), + } + } - Self {} + fn get_ptr(&self) -> Option> { + if self + .initialized + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + let mut inner: MaybeUninit = MaybeUninit::uninit(); + let refresh_buffer_guard = self.refresh_buffer.lock(); + let draw_buf = unsafe { + lvgl_sys::lv_disp_buf_init( + inner.as_mut_ptr(), + refresh_buffer_guard.borrow_mut().as_mut_ptr() as *mut _ as *mut cty::c_void, + ptr::null_mut(), + lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, + ); + inner.assume_init() + }; + Some(Box::new(draw_buf)) + } else { + None + } } } @@ -120,51 +138,54 @@ where T: DrawTarget, C: PixelColor + From, { - pub fn new(_display_buffer: DisplayBuffer, native_display: T) -> Self { + pub fn new(draw_buffer: &'static DrawBuffer, native_display: T) -> Result { let mut disp_drv = unsafe { let mut inner = MaybeUninit::uninit(); lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); inner.assume_init() }; - // Safety: The variable `disp_buf` is statically allocated, no need to worry about this being dropped. - unsafe { - disp_drv.buffer = DRAW_BUFFER.as_mut_ptr(); - } + // Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped. + disp_drv.buffer = draw_buffer + .get_ptr() + .map(|ptr| Box::into_raw(ptr) as *mut _) + .ok_or(DisplayError::FailedToRegister)?; - let mut native_display = ManuallyDrop::new(DisplayUserData { + let native_display = DisplayUserData { display: native_display, phantom: PhantomData, - }); - disp_drv.user_data = &mut native_display as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; + }; + disp_drv.user_data = + Box::into_raw(Box::new(native_display)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; // Sets trampoline pointer to the function implementation using the types (T, C) that // are used in this instance of `DisplayDriver`. disp_drv.flush_cb = Some(disp_flush_trampoline::); // We do not store any memory that can be accidentally deallocated by on the Rust side. - Self { + Ok(Self { disp_drv, phantom_color: PhantomData, phantom_display: PhantomData, - } + }) } #[cfg(feature = "alloc")] pub fn new_shared( - _display_buffer: DisplayBuffer, + draw_buffer: &'static DrawBuffer, shared_native_display: SharedNativeDisplay, - ) -> Self { + ) -> Result { let mut disp_drv = unsafe { let mut inner = MaybeUninit::uninit(); lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); inner.assume_init() }; - // Safety: The variable `disp_buf` is statically allocated, no need to worry about this being dropped. - unsafe { - disp_drv.buffer = DRAW_BUFFER.as_mut_ptr(); - } + // Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped. + disp_drv.buffer = draw_buffer + .get_ptr() + .map(|ptr| Box::into_raw(ptr) as *mut _) + .ok_or(DisplayError::FailedToRegister)?; let native_display = SharedDisplayUserData { display: shared_native_display, @@ -178,11 +199,11 @@ where disp_drv.flush_cb = Some(shared_disp_flush_trampoline::); // We do not store any memory that can be accidentally deallocated by on the Rust side. - Self { + Ok(Self { disp_drv, phantom_color: PhantomData, phantom_display: PhantomData, - } + }) } } diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index 5f00c41..b3beb71 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -55,20 +55,27 @@ pub mod widgets; use core::sync::atomic::{AtomicBool, Ordering}; pub use functions::*; pub use lv_core::*; -use parking_lot::Mutex; pub use support::*; pub use ui::*; -lazy_static! { - static ref MUTEX: Mutex = Mutex::new(AtomicBool::new(false)); +struct RunOnce(AtomicBool); + +impl RunOnce { + const fn new() -> Self { + Self(AtomicBool::new(false)) + } + + fn swap_and_get(&self) -> bool { + self.0 + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + } } +static LVGL_INITIALIZED: RunOnce = RunOnce::new(); + pub fn init() { - let initialized = MUTEX.lock(); - if initialized - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { + if LVGL_INITIALIZED.swap_and_get() { unsafe { lvgl_sys::lv_init(); } @@ -78,20 +85,19 @@ pub fn init() { #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::display::Display; + use crate::display::{Display, DrawBuffer}; use embedded_graphics::mock_display::MockDisplay; use embedded_graphics::pixelcolor::Rgb565; pub(crate) fn initialize_test() { init(); - static RUN_ONCE: Mutex> = parking_lot::const_mutex(None); - let mut run_once = RUN_ONCE.lock(); + static DRAW_BUFFER: DrawBuffer = DrawBuffer::new(); + static ONCE_INIT: RunOnce = RunOnce::new(); - if run_once.is_none() { + if ONCE_INIT.swap_and_get() { let embedded_graphics_display: MockDisplay = Default::default(); - let _ = Display::register(embedded_graphics_display).unwrap(); - *run_once = Some(1); + let _ = Display::register(&DRAW_BUFFER, embedded_graphics_display).unwrap(); } } }