refactor(core/rust): reorganize LayoutObj

* move most actual functionality to LayoutObjInner
* subsume features of top-level Root and Child into LayoutObjInner
(saving ~7 kB of flash because LayoutObjInner is not generic)
* make use of GcBox to drop the top-level component
pull/4026/head
matejcik 3 months ago committed by matejcik
parent 2a896c44f6
commit 863dee1a43

@ -8,7 +8,7 @@ use crate::{
ui::{
button_request::{ButtonRequest, ButtonRequestCode},
component::{maybe::PaintOverlapping, MsgMap, PageMap},
display::{self, Color},
display::Color,
geometry::{Offset, Rect},
shape::Renderer,
},
@ -207,111 +207,6 @@ where
}
}
/// Same as `Child` but also handles screen clearing when layout is first
/// painted.
pub struct Root<T> {
inner: Option<Child<T>>,
marked_for_clear: bool,
transition_out: Option<AttachType>,
}
impl<T> Root<T> {
pub fn new(component: T) -> Self {
Self {
inner: Some(Child::new(component)),
marked_for_clear: true,
transition_out: None,
}
}
pub fn inner_mut(&mut self) -> &mut Child<T> {
if let Some(ref mut c) = self.inner {
c
} else {
fatal_error!("Root object is deallocated")
}
}
pub fn inner(&self) -> &Child<T> {
if let Some(ref c) = self.inner {
c
} else {
fatal_error!("Root object is deallocated")
}
}
pub fn skip_paint(&mut self) {
self.inner_mut().skip_paint();
}
pub fn clear_screen(&mut self) {
self.marked_for_clear = true;
}
pub fn get_transition_out(&self) -> Option<AttachType> {
self.transition_out
}
pub fn delete(&mut self) {
self.inner = None;
}
}
impl<T> Component for Root<T>
where
T: Component,
{
type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect {
self.inner_mut().place(bounds)
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let msg = self.inner_mut().event(ctx, event);
if ctx.needs_repaint_root() {
self.marked_for_clear = true;
let mut dummy_ctx = EventCtx::new();
let paint_msg = self.inner_mut().event(&mut dummy_ctx, Event::RequestPaint);
assert!(paint_msg.is_none());
assert!(dummy_ctx.timers.is_empty());
}
if let Some(t) = ctx.get_transition_out() {
self.transition_out = Some(t);
}
msg
}
fn paint(&mut self) {
if self.marked_for_clear && self.inner().will_paint() {
self.marked_for_clear = false;
display::clear()
}
self.inner.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner().render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.inner().bounds(sink)
}
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Root<T>
where
T: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
self.inner().trace(t);
}
}
impl<M, T, U> Component for (T, U)
where
T: Component<Msg = M>,
@ -582,7 +477,7 @@ impl EventCtx {
/// Returns `true` if we should first perform a place traversal before
/// processing events or painting.
pub fn needs_place_before_next_event_or_paint(&self) -> bool {
pub fn needs_place(&self) -> bool {
self.place_requested
}
@ -616,6 +511,10 @@ impl EventCtx {
self.root_repaint_requested
}
pub fn needs_repaint(&self) -> bool {
self.paint_requested
}
pub fn set_page_count(&mut self, count: usize) {
// #[cfg(feature = "ui_debug")]
// assert!(self.page_count.unwrap_or(count) == count);

@ -27,7 +27,7 @@ pub mod text;
pub mod timeout;
pub use bar::Bar;
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken};
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken};
pub use border::Border;
pub use button_request::{ButtonRequestExt, OneButtonRequest};
#[cfg(all(feature = "jpeg", feature = "ui_image_buffer", feature = "micropython"))]

@ -1,6 +1,7 @@
use core::{
cell::RefCell,
cell::{RefCell, RefMut},
convert::{TryFrom, TryInto},
ops::{Deref, DerefMut},
};
use num_traits::{FromPrimitive, ToPrimitive};
@ -9,7 +10,7 @@ use crate::{
maybe_trace::MaybeTrace,
micropython::{
buffer::StrBuffer,
gc::Gc,
gc::{self, Gc, GcBox},
macros::{obj_dict, obj_fn_1, obj_fn_2, obj_fn_3, obj_fn_var, obj_map, obj_type},
map::Map,
obj::{Obj, ObjBase},
@ -20,9 +21,8 @@ use crate::{
time::Duration,
ui::{
button_request::ButtonRequest,
component::{Component, Event, EventCtx, Never, Root, TimerToken},
constant,
display::sync,
component::{Component, Event, EventCtx, Never, TimerToken},
constant, display,
geometry::Rect,
},
};
@ -77,17 +77,13 @@ pub trait ComponentMsgObj: Component {
pub trait ObjComponent: MaybeTrace {
fn obj_place(&mut self, bounds: Rect) -> Rect;
fn obj_event(&mut self, ctx: &mut EventCtx, event: Event) -> Result<Obj, Error>;
fn obj_paint(&mut self) -> bool;
fn obj_paint(&mut self);
fn obj_bounds(&self, _sink: &mut dyn FnMut(Rect)) {}
fn obj_skip_paint(&mut self) {}
fn obj_request_clear(&mut self) {}
fn obj_delete(&mut self) {}
fn obj_get_transition_out(&self) -> Result<Obj, Error>;
}
impl<T> ObjComponent for Root<T>
impl<T> ObjComponent for T
where
T: ComponentMsgObj + MaybeTrace,
T: Component + ComponentMsgObj + MaybeTrace,
{
fn obj_place(&mut self, bounds: Rect) -> Rect {
self.place(bounds)
@ -95,30 +91,23 @@ where
fn obj_event(&mut self, ctx: &mut EventCtx, event: Event) -> Result<Obj, Error> {
if let Some(msg) = self.event(ctx, event) {
self.inner().inner().msg_try_into_obj(msg)
self.msg_try_into_obj(msg)
} else {
Ok(Obj::const_none())
}
}
fn obj_paint(&mut self) -> bool {
fn obj_paint(&mut self) {
#[cfg(not(feature = "new_rendering"))]
{
let will_paint = self.inner().will_paint();
self.paint();
will_paint
}
#[cfg(feature = "new_rendering")]
{
let will_paint = self.inner().will_paint();
if will_paint {
render_on_display(None, Some(Color::black()), |target| {
self.render(target);
});
self.skip_paint();
}
will_paint
render_on_display(None, Some(Color::black()), |target| {
self.render(target);
});
}
}
@ -126,26 +115,13 @@ where
fn obj_bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.bounds(sink)
}
}
fn obj_skip_paint(&mut self) {
self.skip_paint()
}
fn obj_request_clear(&mut self) {
self.clear_screen()
}
fn obj_delete(&mut self) {
self.delete()
}
fn obj_get_transition_out(&self) -> Result<Obj, Error> {
if let Some(msg) = self.get_transition_out() {
Ok(msg.to_obj())
} else {
Ok(Obj::const_none())
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum Repaint {
None,
Partial,
Full,
}
/// `LayoutObj` is a GC-allocated object exported to MicroPython, with type
@ -158,119 +134,130 @@ pub struct LayoutObj {
}
struct LayoutObjInner {
root: Gc<dyn ObjComponent>,
root: Option<GcBox<dyn ObjComponent>>,
event_ctx: EventCtx,
timer_fn: Obj,
page_count: u16,
repaint: Repaint,
transition_out: AttachType,
}
impl LayoutObj {
impl LayoutObjInner {
/// Create a new `LayoutObj`, wrapping a root component.
#[inline(never)]
pub fn new(root: impl ComponentMsgObj + MaybeTrace + 'static) -> Result<Gc<Self>, Error> {
// Let's wrap the root component into a `Root` to maintain the top-level
// invalidation logic.
let wrapped_root = Root::new(root);
// SAFETY: We are coercing GC-allocated sized ptr into an unsized one.
let root =
unsafe { Gc::from_raw(Gc::into_raw(Gc::new(wrapped_root)?) as *mut dyn ObjComponent) };
// SAFETY: This is a Python object and hase a base as first element
unsafe {
Gc::new_with_custom_finaliser(Self {
base: Self::obj_type().as_base(),
inner: RefCell::new(LayoutObjInner {
root,
event_ctx: EventCtx::new(),
timer_fn: Obj::const_none(),
page_count: 1,
}),
})
}
pub fn new(root: impl ObjComponent + 'static) -> Result<Self, Error> {
let root = GcBox::new(root)?;
Ok(Self {
root: Some(gc::coerce!(ObjComponent, root)),
event_ctx: EventCtx::new(),
timer_fn: Obj::const_none(),
page_count: 1,
repaint: Repaint::Full,
transition_out: AttachType::Initial,
})
}
pub fn obj_delete(&self) {
let mut inner = self.inner.borrow_mut();
fn obj_delete(&mut self) {
self.root = None;
}
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_delete();
/// Timer callback is expected to be a callable object of the following
/// form: `def timer(token: int, deadline_in_ms: int)`.
fn obj_set_timer_fn(&mut self, timer_fn: Obj) {
self.timer_fn = timer_fn;
}
pub fn skip_first_paint(&self) {
let mut inner = self.inner.borrow_mut();
fn root(&self) -> &impl Deref<Target = dyn ObjComponent> {
unwrap!(self.root.as_ref())
}
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_skip_paint();
fn root_mut(&mut self) -> &mut impl DerefMut<Target = dyn ObjComponent> {
unwrap!(self.root.as_mut())
}
/// Timer callback is expected to be a callable object of the following
/// form: `def timer(token: int, deadline_in_ms: int)`.
fn obj_set_timer_fn(&self, timer_fn: Obj) {
self.inner.borrow_mut().timer_fn = timer_fn;
fn obj_request_repaint(&mut self) {
self.repaint = Repaint::Full;
let mut dummy_ctx = EventCtx::new();
let paint_msg = self
.root_mut()
.obj_event(&mut dummy_ctx, Event::RequestPaint);
// paint_msg must not be an error and it must not return a result
assert!(matches!(paint_msg, Ok(s) if s == Obj::const_none()));
// there must be no timers set
assert!(dummy_ctx.pop_timer().is_none());
}
/// Run an event pass over the component tree. After the traversal, any
/// pending timers are drained into `self.timer_callback`. Returns `Err`
/// in case the timer callback raises or one of the components returns
/// an error, `Ok` with the message otherwise.
fn obj_event(&self, event: Event) -> Result<Obj, Error> {
let inner = &mut *self.inner.borrow_mut();
fn obj_event(&mut self, event: Event) -> Result<Obj, Error> {
let root = unwrap!(self.root.as_mut());
// Place the root component on the screen in case it was previously requested.
if inner.event_ctx.needs_place_before_next_event_or_paint() {
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_place(constant::screen());
if self.event_ctx.needs_place() {
root.obj_place(constant::screen());
}
// Clear the leftover flags from the previous event pass.
inner.event_ctx.clear();
self.event_ctx.clear();
// Send the event down the component tree. Bail out in case of failure.
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
let msg = unsafe { Gc::as_mut(&mut inner.root) }.obj_event(&mut inner.event_ctx, event)?;
let msg = root.obj_event(&mut self.event_ctx, event)?;
// Check if we should repaint next time
if self.event_ctx.needs_repaint_root() {
self.obj_request_repaint();
} else if self.event_ctx.needs_repaint() {
self.repaint = Repaint::Partial;
}
// All concerning `Child` wrappers should have already marked themselves for
// painting by now, and we're prepared for a paint pass.
// Drain any pending timers into the callback.
while let Some((token, deadline)) = inner.event_ctx.pop_timer() {
while let Some((token, deadline)) = self.event_ctx.pop_timer() {
let token = token.try_into();
let deadline = deadline.try_into();
if let (Ok(token), Ok(deadline)) = (token, deadline) {
inner.timer_fn.call_with_n_args(&[token, deadline])?;
self.timer_fn.call_with_n_args(&[token, deadline])?;
} else {
// Failed to convert token or deadline into `Obj`, skip.
}
}
if let Some(count) = inner.event_ctx.page_count() {
inner.page_count = count as u16;
if let Some(count) = self.event_ctx.page_count() {
self.page_count = count as u16;
}
Ok(msg)
}
if let Some(t) = self.event_ctx.get_transition_out() {
self.transition_out = t;
}
fn obj_request_clear(&self) {
let mut inner = self.inner.borrow_mut();
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_request_clear();
Ok(msg)
}
/// Run a paint pass over the component tree. Returns true if any component
/// actually requested painting since last invocation of the function.
fn obj_paint_if_requested(&self) -> bool {
let mut inner = self.inner.borrow_mut();
fn obj_paint_if_requested(&mut self) -> bool {
if self.repaint == Repaint::Full {
display::clear();
}
// Place the root component on the screen in case it was previously requested.
if inner.event_ctx.needs_place_before_next_event_or_paint() {
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_place(constant::screen());
if self.event_ctx.needs_place() {
self.root_mut().obj_place(constant::screen());
}
sync();
display::sync();
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_paint()
if self.repaint != Repaint::None {
self.repaint = Repaint::None;
self.root_mut().obj_paint();
true
} else {
false
}
}
/// Run a tracing pass over the component tree. Passed `callback` is called
@ -293,18 +280,16 @@ impl LayoutObj {
// because trait upcasting is unstable.
// Luckily, calling `root.trace()` works perfectly fine in spite of the above.)
tracer.root(&|t| {
self.inner.borrow().root.trace(t);
self.root().trace(t);
});
}
fn obj_page_count(&self) -> Obj {
self.inner.borrow().page_count.into()
self.page_count.into()
}
fn obj_button_request(&self) -> Result<Obj, Error> {
let inner = &mut *self.inner.borrow_mut();
match inner.event_ctx.button_request() {
fn obj_button_request(&mut self) -> Result<Obj, Error> {
match self.event_ctx.button_request() {
None => Ok(Obj::const_none()),
Some(ButtonRequest { code, br_type }) => {
(code.num().into(), br_type.try_into()?).try_into()
@ -312,18 +297,12 @@ impl LayoutObj {
}
}
fn obj_get_transition_out(&self) -> Result<Obj, Error> {
let inner = &mut *self.inner.borrow_mut();
// Get transition out result
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_get_transition_out()
fn obj_get_transition_out(&self) -> Obj {
self.transition_out.to_obj()
}
#[cfg(feature = "ui_debug")]
fn obj_bounds(&self) {
use crate::ui::display;
// Sink for `Trace::bounds` that draws the boundaries using pseudorandom color.
fn wireframe(r: Rect) {
let w = r.width() as u16;
@ -334,7 +313,25 @@ impl LayoutObj {
// use crate::ui::model_tt::theme;
// wireframe(theme::borders());
self.inner.borrow().root.obj_bounds(&mut wireframe);
self.root().obj_bounds(&mut wireframe);
}
}
impl LayoutObj {
/// Create a new `LayoutObj`, wrapping a root component.
#[inline(never)]
pub fn new(root: impl ComponentMsgObj + MaybeTrace + 'static) -> Result<Gc<Self>, Error> {
// SAFETY: This is a Python object and hase a base as first element
unsafe {
Gc::new_with_custom_finaliser(Self {
base: Self::obj_type().as_base(),
inner: RefCell::new(LayoutObjInner::new(root)?),
})
}
}
fn inner_mut(&self) -> RefMut<LayoutObjInner> {
self.inner.borrow_mut()
}
fn obj_type() -> &'static Type {
@ -359,6 +356,10 @@ impl LayoutObj {
};
&TYPE
}
pub fn skip_first_paint(&self) {
self.inner_mut().repaint = Repaint::None;
}
}
impl From<Gc<LayoutObj>> for Obj {
@ -424,9 +425,11 @@ impl TryFrom<Never> for Obj {
extern "C" fn ui_layout_attach_timer_fn(this: Obj, timer_fn: Obj, attach_type: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_set_timer_fn(timer_fn);
this.inner_mut().obj_set_timer_fn(timer_fn);
let msg = this.obj_event(Event::Attach(AttachType::try_from_obj(attach_type)?))?;
let msg = this
.inner_mut()
.obj_event(Event::Attach(AttachType::try_from_obj(attach_type)?))?;
assert!(msg == Obj::const_none());
Ok(Obj::const_none())
};
@ -445,7 +448,7 @@ extern "C" fn ui_layout_touch_event(n_args: usize, args: *const Obj) -> Obj {
args[2].try_into()?,
args[3].try_into()?,
)?;
let msg = this.obj_event(Event::Touch(event))?;
let msg = this.inner_mut().obj_event(Event::Touch(event))?;
Ok(msg)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
@ -464,7 +467,7 @@ extern "C" fn ui_layout_button_event(n_args: usize, args: *const Obj) -> Obj {
}
let this: Gc<LayoutObj> = args[0].try_into()?;
let event = ButtonEvent::new(args[1].try_into()?, args[2].try_into()?)?;
let msg = this.obj_event(Event::Button(event))?;
let msg = this.inner_mut().obj_event(Event::Button(event))?;
Ok(msg)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
@ -483,7 +486,9 @@ extern "C" fn ui_layout_progress_event(n_args: usize, args: *const Obj) -> Obj {
let this: Gc<LayoutObj> = args[0].try_into()?;
let value: u16 = args[1].try_into()?;
let description: StrBuffer = args[2].try_into()?;
let msg = this.obj_event(Event::Progress(value, description.into()))?;
let msg = this
.inner_mut()
.obj_event(Event::Progress(value, description.into()))?;
Ok(msg)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
@ -496,7 +501,7 @@ extern "C" fn ui_layout_usb_event(n_args: usize, args: *const Obj) -> Obj {
}
let this: Gc<LayoutObj> = args[0].try_into()?;
let event = USBEvent::Connected(args[1].try_into()?);
let msg = this.obj_event(Event::USB(event))?;
let msg = this.inner_mut().obj_event(Event::USB(event))?;
Ok(msg)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
@ -506,7 +511,7 @@ extern "C" fn ui_layout_timer(this: Obj, token: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
let event = Event::Timer(token.try_into()?);
let msg = this.obj_event(event)?;
let msg = this.inner_mut().obj_event(event)?;
Ok(msg)
};
unsafe { util::try_or_raise(block) }
@ -515,7 +520,7 @@ extern "C" fn ui_layout_timer(this: Obj, token: Obj) -> Obj {
extern "C" fn ui_layout_paint(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
let painted = this.obj_paint_if_requested().into();
let painted = this.inner_mut().obj_paint_if_requested().into();
Ok(painted)
};
unsafe { util::try_or_raise(block) }
@ -524,15 +529,7 @@ extern "C" fn ui_layout_paint(this: Obj) -> Obj {
extern "C" fn ui_layout_request_complete_repaint(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
let event = Event::RequestPaint;
let msg = this.obj_event(event)?;
if msg != Obj::const_none() {
// Messages raised during a `RequestPaint` dispatch are not propagated, let's
// make sure we don't do that.
#[cfg(feature = "ui_debug")]
fatal_error!("Cannot raise messages during RequestPaint");
};
this.obj_request_clear();
this.inner_mut().obj_request_repaint();
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
@ -541,7 +538,8 @@ extern "C" fn ui_layout_request_complete_repaint(this: Obj) -> Obj {
extern "C" fn ui_layout_page_count(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
Ok(this.obj_page_count())
let page_count = this.inner_mut().obj_page_count();
Ok(page_count)
};
unsafe { util::try_or_raise(block) }
}
@ -549,7 +547,8 @@ extern "C" fn ui_layout_page_count(this: Obj) -> Obj {
extern "C" fn ui_layout_button_request(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_button_request()
let button_request = this.inner_mut().obj_button_request();
button_request
};
unsafe { util::try_or_raise(block) }
}
@ -557,7 +556,8 @@ extern "C" fn ui_layout_button_request(this: Obj) -> Obj {
extern "C" fn ui_layout_get_transition_out(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_get_transition_out()
let transition_out = this.inner_mut().obj_get_transition_out();
Ok(transition_out)
};
unsafe { util::try_or_raise(block) }
}
@ -572,7 +572,7 @@ pub extern "C" fn ui_debug_layout_type() -> &'static Type {
extern "C" fn ui_layout_trace(this: Obj, callback: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_trace(callback);
this.inner_mut().obj_trace(callback);
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
@ -587,7 +587,7 @@ extern "C" fn ui_layout_trace(_this: Obj, _callback: Obj) -> Obj {
extern "C" fn ui_layout_bounds(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_bounds();
this.inner_mut().obj_bounds();
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
@ -601,7 +601,7 @@ extern "C" fn ui_layout_bounds(_this: Obj) -> Obj {
extern "C" fn ui_layout_delete(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_delete();
this.inner_mut().obj_delete();
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }

Loading…
Cancel
Save