Skip to content

Commit

Permalink
refactor(core): šŸ’”implement state share and split hierarchy.
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Aug 15, 2023
1 parent bdb877b commit f1405a7
Show file tree
Hide file tree
Showing 20 changed files with 877 additions and 279 deletions.
2 changes: 1 addition & 1 deletion core/src/builtin_widgets/scrollable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ mod tests {
modifiers: ModifiersState::default(),
});

wnd.layout();
wnd.draw_frame();
let pos = wnd.layout_info_by_path(&[0, 0, 0, 0]).unwrap().pos;
assert_eq!(pos.y, expect_y);
let pos = wnd.layout_info_by_path(&[0, 0, 0, 0, 0]).unwrap().pos;
Expand Down
9 changes: 7 additions & 2 deletions core/src/context/app_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct AppCtx {
typography_store: TypographyStore,
clipboard: RefCell<Box<dyn Clipboard>>,
runtime_waker: Box<dyn RuntimeWaker + Send>,
scheduler: FuturesLocalScheduler,
executor: RefCell<LocalPool>,
}

Expand Down Expand Up @@ -102,7 +103,7 @@ impl AppCtx {

/// Get the scheduler of the application.
#[track_caller]
pub fn scheduler() -> FuturesLocalScheduler { Self::shared().executor.borrow_mut().spawner() }
pub fn scheduler() -> FuturesLocalScheduler { Self::shared().scheduler.clone() }

/// Get the clipboard of the application.
#[track_caller]
Expand Down Expand Up @@ -231,14 +232,18 @@ impl AppCtx {
let reorder = TextReorder::default();
let typography_store = TypographyStore::new(reorder.clone(), font_db.clone(), shaper.clone());

let executor = LocalPool::new();
let scheduler = executor.spawner();

let ctx = AppCtx {
font_db,
app_theme,
shaper,
reorder,
typography_store,
clipboard: RefCell::new(Box::new(MockClipboard {})),
executor: <_>::default(),
executor: RefCell::new(executor),
scheduler,
runtime_waker: Box::new(MockWaker),
windows: RefCell::new(ahash::HashMap::default()),
};
Expand Down
2 changes: 1 addition & 1 deletion core/src/context/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl<'a> BuildCtx<'a> {
/// and fire mount events.
pub(crate) fn on_widget_mounted(&self, id: WidgetId) {
self.assert_get(id).query_all_type(
|notifier: &StateChangeNotifier| {
|notifier: &Modifier| {
let state_changed = self.tree.dirty_set.clone();
notifier
.raw_modifies()
Expand Down
4 changes: 2 additions & 2 deletions core/src/dynamic_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl<D: 'static> DynRender<D> {
// Place the real old render in node.
std::mem::swap(&mut tmp_render, ctx.id.assert_get_mut(&mut ctx.tree.arena));

self.dyn_widgets.modify_notifier.reset();
self.dyn_widgets.modifier.reset();

let wrap_render = |gen_info, tree: &mut WidgetTree| {
let new_render = DynRender {
Expand Down Expand Up @@ -260,7 +260,7 @@ impl<D: 'static> DynRender<D> {

let arena = &mut tree.arena;
wid.assert_get(arena).query_all_type(
|notifier: &StateChangeNotifier| {
|notifier: &Modifier| {
let state_changed = tree.dirty_set.clone();
// abandon the old subscribe
notifier.reset();
Expand Down
5 changes: 2 additions & 3 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub mod prelude {
pub use crate::dynamic_widget::*;
#[doc(no_inline)]
pub use crate::events::*;
pub use crate::path_state_splitter;
#[doc(no_inline)]
pub use crate::pipe::Pipe;
#[doc(no_inline)]
Expand All @@ -60,8 +59,8 @@ pub mod prelude {
pub use ribir_geom::*;
#[doc(no_inline)]
pub use ribir_macros::{
ctx, fn_widget, include_svg, pipe, rdl, set_build_ctx, watch, widget, Declare, Declare2, Lerp,
MultiChild, SingleChild, Template, _dollar_ą² _ą² ,
ctx, fn_widget, include_svg, orphan_partial_state, partial_state, pipe, rdl, set_build_ctx,
watch, widget, Declare, Declare2, Lerp, MultiChild, SingleChild, Template, _dollar_ą² _ą² ,
};
#[doc(no_inline)]
pub use ribir_painter::*;
Expand Down
229 changes: 204 additions & 25 deletions core/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
mod orphan_state;
mod partial_state;
mod readonly;
mod stateful;
use std::{convert::Infallible, mem::MaybeUninit, rc::Rc};

use std::{any::Any, convert::Infallible, mem::MaybeUninit, ops::DerefMut, rc::Rc};

pub use orphan_state::*;
pub use partial_state::*;
pub use readonly::*;
use rxrust::{ops::box_it::BoxOp, prelude::ObservableItem};
pub use stateful::*;
Expand All @@ -10,9 +15,91 @@ use crate::{
context::BuildCtx,
dynamic_widget::DynWidget,
prelude::{BoxMultiParent, BoxedSingleParent, MultiChild, SingleChild},
widget::{Compose, Render, RenderFul, WidgetBuilder, WidgetId},
widget::{Compose, Render, WidgetBuilder, WidgetId},
};

/// A trait that a state should need to implement.
pub trait Share {
/// The value type of the state.
type S;
/// The origin type of the state.
type Origin: Share;
/// The reference type of the state.
type Ref<'a>: RefShare<Target = Self::S>
where
Self: 'a;

/// Return the reference of the state.
fn state_ref(&'_ mut self) -> Self::Ref<'_>;

/// Clone the state and return a new one. The returned state should be the
/// same as the original one, if the original one is not shareable should
/// convert to a shareable one.
fn clone(&mut self) -> Self;

/// Create a partial state from the state by applying a function to the state
/// inner value. The return state share the modifier with the original state,
/// when modify the returned state, means the original state be modified too.
#[inline]
fn split_partial<P>(&mut self) -> PartialState<P::Target, Self, P>
where
Self: Sized,
P: StateSplitter<Origin = Self::S>,
{
PartialState::new(self.clone())
}

/// Create a partial state from the state by applying a function to the state
/// inner value. The return state has orphan modifier, when modify the
/// returned stateļ¼Œ it's not be perceived by the original state. So although
/// those two state share the same data, but the modifies notifier is
/// independent.
#[inline]
fn orphan_split_partial<P>(&mut self) -> OrphanState<P::Target, Self, P>
where
Self: Sized,
P: StateSplitter<Origin = Self::S>,
{
OrphanState::new(self.clone())
}

/// Return the origin state of the state if this state is a partial state.
/// Otherwise return itself.
fn origin_state(&mut self) -> Self::Origin;

/// Return a modifies `Rx` stream of the state, user can subscribe it to
/// response the state changes.
fn modifies(&mut self) -> BoxOp<'static, (), Infallible>;

/// Store an anonymous data to the state, so we can keep the `data` live as
/// long as the state.
fn own_data(&mut self, data: impl Any);
}

/// A trait that a reference of a share state should implement.
pub trait RefShare: DerefMut {
/// Convert this state reference to silent reference which notifies will be
/// ignored by the framework.
fn silent(&mut self) -> &mut Self;
/// Convert this state reference to a shallow reference. Modify across this
/// reference will notify framework only. That means the modifies on shallow
/// reference should only effect framework but not effect on data. eg.
/// temporary to modify the state and then modifies it back to trigger the
/// view update. Use it only if you know how a shallow reference works.
fn shallow(&mut self) -> &mut Self;
/// Forget all modifies record in this reference. So all the modifies before
/// this call will not be notified.
fn forget_modifies(&self) -> ModifyScope;
}

/// A trait that to split part from a state.
pub trait StateSplitter {
type Origin;
type Target;
fn split(origin: &Self::Origin) -> &Self::Target;
fn split_mut(origin: &mut Self::Origin) -> &mut Self::Target;
}

/// Enum to store both stateless and stateful object.
pub enum State<W> {
Stateless(W),
Expand All @@ -24,6 +111,33 @@ pub enum StateRef<'a, W> {
Stateless(&'a mut W),
}

impl<T> Share for State<T> {
type S = T;
type Origin = Self;
type Ref<'a> = StateRef<'a, T> where Self: 'a;

fn state_ref(&'_ mut self) -> StateRef<'_, T> {
match self {
State::Stateless(w) => StateRef::Stateless(w),
State::Stateful(w) => StateRef::Stateful(w.state_ref()),
}
}

fn clone(&mut self) -> State<T> {
let stateful = self.to_stateful().clone();
State::Stateful(stateful)
}

#[inline]
fn modifies(&mut self) -> BoxOp<'static, (), Infallible> { self.to_stateful().modifies() }

#[inline]
fn origin_state(&mut self) -> Self::Origin { self.clone() }

#[inline]
fn own_data(&mut self, data: impl Any) { self.to_stateful().own_data(data); }
}

impl<W: SingleChild> SingleChild for State<W> {}
impl<W: MultiChild> MultiChild for State<W> {}

Expand Down Expand Up @@ -82,13 +196,6 @@ impl<W> State<W> {

pub fn clone_stateful(&mut self) -> Stateful<W> { self.to_stateful().clone() }

pub fn modifies(&mut self) -> BoxOp<'static, (), Infallible> { self.to_stateful().modifies() }

pub fn clone(&mut self) -> State<W> {
let stateful = self.to_stateful().clone();
State::Stateful(stateful)
}

pub fn stateful_ref(&mut self) -> StatefulRef<W> { self.to_stateful().state_ref() }

pub fn to_stateful(&mut self) -> &mut Stateful<W> {
Expand All @@ -110,13 +217,6 @@ impl<W> State<W> {
State::Stateful(w) => w,
}
}

pub fn state_ref(&mut self) -> StateRef<W> {
match self {
State::Stateless(w) => StateRef::Stateless(w),
State::Stateful(w) => StateRef::Stateful(w.state_ref()),
}
}
}

pub(crate) trait StateFrom<V> {
Expand Down Expand Up @@ -159,22 +259,39 @@ impl<W: 'static> StateFrom<Stateful<DynWidget<W>>> for State<W> {
}
}

impl<'a, T> StateRef<'a, T> {
pub fn forget_modifies(&self) {
match self {
StateRef::Stateless(_) => {}
StateRef::Stateful(w) => w.forget_modifies(),
}
}
}

impl<W, T> From<T> for State<W>
where
Self: StateFrom<T>,
{
fn from(value: T) -> Self { StateFrom::state_from(value) }
}

impl<'a, W> RefShare for StateRef<'a, W> {
fn silent(&mut self) -> &mut Self {
if let StateRef::Stateful(w) = self {
w.silent();
}

self
}

fn shallow(&mut self) -> &mut Self {
if let StateRef::Stateful(w) = self {
w.shallow();
}

self
}

fn forget_modifies(&self) -> ModifyScope {
if let StateRef::Stateful(w) = self {
w.forget_modifies()
} else {
ModifyScope::empty()
}
}
}

impl<'a, W> std::ops::Deref for StateRef<'a, W> {
type Target = W;

Expand All @@ -197,15 +314,77 @@ impl<'a, W> std::ops::DerefMut for StateRef<'a, W> {

#[cfg(test)]
mod tests {
use std::cell::Cell;

use super::*;
use crate::{context::AppCtx, prelude::*, rest_test_env, timer::Timer};

#[test]
fn fix_dyn_widget_to_state_circular_mut_borrow_panic() {
let _guard = unsafe { AppCtx::new_lock_scope() };

let dyn_widget = Stateful::new(DynWidget { dyns: Some(1) });
let c_dyns = dyn_widget.clone();
let _: State<i32> = dyn_widget.into();
{
c_dyns.state_ref().dyns = Some(2);
}
}

struct Origin {
a: i32,
b: i32,
}

#[test]
fn path_state_splitter_test() {
rest_test_env!();

let mut origin = State::Stateless(Origin { a: 0, b: 0 });
let mut a = partial_state!($origin.a, Origin -> i32);
let mut b = orphan_partial_state!($origin.b, Origin -> i32);

let track_origin = Rc::new(Cell::new(0));
let track_a = Rc::new(Cell::new(0));
let track_b = Rc::new(Cell::new(0));

let c_origin = track_origin.clone();
origin.modifies().subscribe(move |_| {
c_origin.set(c_origin.get() + 1);
});

let c_a = track_a.clone();
a.modifies().subscribe(move |_| {
c_a.set(c_a.get() + 1);
});

let c_b = track_b.clone();
b.modifies().subscribe(move |_| {
c_b.set(c_b.get() + 1);
});

origin.state_ref().a = 1;
Timer::wake_timeout_futures();
AppCtx::run_until_stalled();

assert_eq!(track_origin.get(), 1);
assert_eq!(track_a.get(), 1);
assert_eq!(track_b.get(), 0);

*a.state_ref() = 1;
Timer::wake_timeout_futures();
AppCtx::run_until_stalled();

assert_eq!(track_origin.get(), 2);
assert_eq!(track_a.get(), 2);
assert_eq!(track_b.get(), 0);

*b.state_ref() = 1;
Timer::wake_timeout_futures();
AppCtx::run_until_stalled();

assert_eq!(track_origin.get(), 2);
assert_eq!(track_a.get(), 2);
assert_eq!(track_b.get(), 1);
}
}
Loading

0 comments on commit f1405a7

Please sign in to comment.