Skip to content

Commit

Permalink
feat(core): 🎸 ensure object safety of state's traits
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Jan 16, 2025
1 parent 588dcd2 commit df1c0f0
Show file tree
Hide file tree
Showing 15 changed files with 475 additions and 247 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

## [@Unreleased] - @ReleaseDate

### Features

- **core**: ensure object safety of `StateReader`, `StateWatcher` and `StateWriter` (#692 @M-Adoo)

## [0.4.0-alpha.23] - 2025-01-15

### Features
Expand Down
4 changes: 2 additions & 2 deletions core/src/animation/animate_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub trait AnimateState: AnimateStateSetter {
.state(self)
.finish();

let c_animate = animate.clone_writer();
let c_animate = animate.as_stateful().clone_writer();
let init_value = observable::of(state.get());
state
.animate_state_modifies()
Expand Down Expand Up @@ -66,7 +66,7 @@ where
S: StateWriter,
S::Value: Clone,
{
type C = S::Writer;
type C = S;
type Value = S::Value;

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion core/src/builtin_widgets/mix_builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ impl Default for MixBuiltin {

impl Clone for MixBuiltin {
fn clone(&self) -> Self {
let flags = State::stateful(self.flags.clone_writer());
let flags = self.flags.clone_writer();
Self { flags, subject: self.subject.clone() }
}
}
Expand Down
204 changes: 165 additions & 39 deletions core/src/builtin_widgets/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,19 @@
//! };
//! ```
//!
//! State can be provided as either its value or itself. When providing a writer
//! as its value, you can access its write reference using
//! [`Provider::write_of`] to modify the state.
//! ## Providing State
//!
//! State can be provided in unique manner, allowing both its value and the
//! self reference to be accessed through a single provider. You can choose to
//! provide a writer, watcher, or reader using [`Provider::value_of_writer`],
//! [`Provider::value_of_watcher`], and [`Provider::value_of_reader`]
//! respectively.
//!
//! The value can be accessed using [`Provider::of`], and the state itself can
//! be accessed using [`Provider::state_of`].
//!
//! If a writer is provided, the write reference can be accessed using
//! [`Provider::write_of`] to make writer reference to the state.
//!
//! ```
//! use ribir::prelude::*;
Expand All @@ -64,19 +74,17 @@
//!
//! providers! {
//! providers: smallvec![
//! // Providing a `Stateful<i32>`
//! Provider::new(state.clone_writer()),
//! // Providing an `i32`
//! // Providing an writer of `i32`
//! Provider::value_of_writer(state, None),
//! ],
//! @ {
//! let ctx = BuildCtx::get();
//! // Accessing the state value
//! let value = *Provider::of::<i32>(ctx).unwrap();
//! assert_eq!(value, 1);
//! // Accessing the state itself
//! {
//! let _state = Provider::of::<Stateful<i32>>(ctx).unwrap();
//! // Accessing the state itself
//! let _state = Provider::state_of::<Stateful<i32>>(ctx).unwrap();
//! }
//! // Accessing the write reference of the state to modify the value
//! let mut value = Provider::write_of::<i32>(ctx).unwrap();
Expand All @@ -86,6 +94,32 @@
//! };
//! ```
//!
//! If the type of the state is unknown, we can provide it using a boxed state
//! trait.
//!
//! ```
//! use ribir::prelude::*;
//! use smallvec::smallvec;
//!
//! fn provide_box_state(writer: impl StateWriter<Value = i32>) {
//! let writer: Box<dyn StateWriter<Value = i32>> = Box::new(writer);
//! let _ = providers! {
//! providers: [Provider::value_of_writer(writer, None)],
//! @ {
//! let ctx = BuildCtx::get();
//! // Accessing the state value
//! let value = *Provider::of::<i32>(ctx).unwrap();
//! assert_eq!(value, 1);
//! // Accessing the state itself
//! {
//! let _state = Provider::state_of::<Box<dyn StateWriter<Value = i32>>>(ctx).unwrap();
//! }
//! @Text { text: "Both state and its value provided!" }
//! }
//! };
//! }
//! ```
//!
//! ## Scope of Providers in the Build Process
//!
//! Providers are visible to their descendants. However, in the build process,
Expand All @@ -112,7 +146,6 @@
//! ```
//!
//! The correct approach is as follows:
//!
//! ```
//! use ribir::prelude::*;
//!
Expand Down Expand Up @@ -229,22 +262,42 @@ impl Provider {
/// assert_eq!(*Provider::of::<i32>(ctx).unwrap(), 1);
///
/// // Obtain the reader
/// let reader = Provider::state_of::
/// <<Stateful<i32> as StateReader>::Reader>(ctx);
/// let reader = Provider::state_of::<Reader<i32>>(ctx);
/// assert_eq!(*reader.unwrap().read(), 1);
/// Void
/// }
/// };
/// ```
pub fn value_of_reader<R>(value: R) -> Provider
where
R: StateReader<Reader: Query>,
R::Value: Sized + 'static,
{
match value.try_into_value() {
Ok(v) => Provider::Setup(Box::new(Setup::new(v))),
Err(v) => Provider::Setup(Box::new(Setup::from_state(v.clone_reader()))),
}
pub fn value_of_reader(value: impl StateReader<Value: Sized, Reader: Query>) -> Provider {
Provider::Setup(Box::new(Setup::from_state(value.clone_reader())))
}

/// Establish a provider for the value of a watcher. It will clone the watcher
/// to create the provider to prevent any writer leaks.
///
/// Obtain the value using [`Provider::of`], and if you want to access the
/// watcher, using [`Provider::state_of`].
///
/// ## Example
///
/// ```
/// use ribir_core::prelude::*;
///
/// let w = providers! {
/// providers: [Provider::value_of_watcher(Stateful::new(1i32))],
/// @ {
/// let ctx = BuildCtx::get();
/// // Obtain the value
/// assert_eq!(*Provider::of::<i32>(ctx).unwrap(), 1);
/// // Obtain the watcher
/// let watcher = Provider::state_of::<Watcher<Reader<i32>>>(ctx);
/// assert_eq!(*watcher.unwrap().read(), 1);
/// Void
/// }
/// };
/// ```
pub fn value_of_watcher(value: impl StateWatcher<Value: Sized, Watcher: Query>) -> Provider {
Provider::Setup(Box::new(Setup::from_state(value.clone_watcher())))
}

/// Establish a provider for the value of a writer.
Expand Down Expand Up @@ -289,21 +342,16 @@ impl Provider {
pub fn value_of_writer<V: 'static>(
value: impl StateWriter<Value = V> + Query, dirty: Option<DirtyPhase>,
) -> Provider {
match value.try_into_value() {
Ok(v) => Provider::Setup(Box::new(Setup::new(v))),
Err(this) => {
if let Some(dirty) = dirty {
let writer = WriterSetup {
modifies: this.raw_modifies(),
info: Provider::info::<V>(),
value: Box::new(this),
dirty,
};
Provider::Setup(Box::new(writer))
} else {
Provider::Setup(Box::new(Setup::from_state(this)))
}
}
if let Some(dirty) = dirty {
let writer = WriterSetup {
modifies: value.raw_modifies(),
info: Provider::info::<V>(),
value: Box::new(value),
dirty,
};
Provider::Setup(Box::new(writer))
} else {
Provider::Setup(Box::new(Setup::from_state(value)))
}
}

Expand Down Expand Up @@ -514,7 +562,7 @@ impl ProviderCtx {
self
.data
.get(&info)
.and_then(|q| q.query_write(&QueryId::of::<S>()))
.and_then(|q| q.query(&QueryId::of::<S>()))
.and_then(QueryHandle::into_ref)
}

Expand Down Expand Up @@ -839,7 +887,7 @@ mod tests {

let (value, w_value) = split_value(0);
let w = providers! {
providers: smallvec![Provider::new(1i32)],
providers: [Provider::new(1i32)],
@MockBox {
size: Size::new(1.,1.),
@ {
Expand All @@ -864,7 +912,7 @@ mod tests {
let (value2, w_value2) = split_value(0);
let w = mock_multi! {
@Providers {
providers: smallvec![Provider::new(1i32)],
providers: [Provider::new(1i32)],
@ {
let v = Provider::of::<i32>(BuildCtx::get()).unwrap();
*$w_value1.write() = *v;
Expand Down Expand Up @@ -896,7 +944,7 @@ mod tests {
let (trigger, w_trigger) = split_value(true);

let w = providers! {
providers: smallvec![Provider::new(w_value.clone_writer())],
providers: [Provider::new(w_value.clone_writer())],
@ {
// We do not allow the use of the build context in the pipe at the moment.
let value = Provider::of::<Stateful<i32>>(BuildCtx::get())
Expand Down Expand Up @@ -970,4 +1018,82 @@ mod tests {
assert_eq!(cnt.read().layout_cnt.get(), 2);
assert_eq!(cnt.read().paint_cnt.get(), 3);
}

#[test]
fn boxed_reader() {
reset_test_env!();

let w = fn_widget! {
let boxed_reader: Box<dyn StateReader<Value = i32>> = Box::new(Stateful::new(1i32));

@Providers {
providers: [Provider::value_of_reader(boxed_reader)],
@ {
let v = Provider::of::<i32>(BuildCtx::get()).unwrap();
assert_eq!(*v, 1);

let v = Provider::state_of::<Box<dyn StateReader<Value = i32>>>(BuildCtx::get()).unwrap();
assert_eq!(*v.read(), 1);
Void
}
}
};

let mut wnd = TestWindow::new(w);
wnd.draw_frame();
}

#[test]
fn boxed_watcher() {
reset_test_env!();

let w = fn_widget! {
let boxed_watcher: Box<dyn StateWatcher<Value = i32>> = Box::new(Stateful::new(1i32));

@Providers {
providers: [Provider::value_of_watcher(boxed_watcher)],
@ {
let v = Provider::of::<i32>(BuildCtx::get()).unwrap();
assert_eq!(*v, 1);

let v = Provider::state_of::<Box<dyn StateWatcher<Value = i32>>>(
BuildCtx::get()
)
.unwrap();
assert_eq!(*v.read(), 1);
Void
}
}
};

let mut wnd = TestWindow::new(w);
wnd.draw_frame();
}

#[test]
fn boxed_writer() {
reset_test_env!();

let w = fn_widget! {
let boxed_writer: Box<dyn StateWriter<Value = i32>> = Box::new(Stateful::new(1i32));

@Providers {
providers: [Provider::value_of_writer(boxed_writer, None)],
@ {
let v = Provider::of::<i32>(BuildCtx::get()).unwrap();
assert_eq!(*v, 1);

let v = Provider::state_of::<Box<dyn StateWriter<Value = i32>>>(
BuildCtx::get()
)
.unwrap();
assert_eq!(*v.read(), 1);
Void
}
}
};

let mut wnd = TestWindow::new(w);
wnd.draw_frame();
}
}
23 changes: 12 additions & 11 deletions core/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use std::any::{Any, TypeId};

use smallvec::SmallVec;

use crate::state::{
MapReader, MapWriterAsReader, ReadRef, Reader, StateReader, StateWriter, WriteRef,
};
use crate::state::*;

/// A type can composed by many types, this trait help us to query the type and
/// the inner type by type id.
Expand Down Expand Up @@ -282,23 +280,26 @@ macro_rules! impl_query_for_reader {
};
}

impl<S, F, V> Query for MapReader<S, F>
impl<S, F, V: 'static> Query for MapReader<S, F>
where
Self: StateReader<Value = V>,
V: Any,
{
impl_query_for_reader!();
}

impl<S, F, V> Query for MapWriterAsReader<S, F>
where
Self: StateReader<Value = V>,
V: Any,
{
impl<V: 'static> Query for Reader<V> {
impl_query_for_reader!();
}

impl<V: 'static> Query for Box<dyn StateReader<Value = V>> {
impl_query_for_reader!();
}

impl<V: 'static, R: StateReader<Value = V>> Query for Watcher<R> {
impl_query_for_reader!();
}

impl<V: Any> Query for Reader<V> {
impl<V: 'static> Query for Box<dyn StateWatcher<Value = V>> {
impl_query_for_reader!();
}

Expand Down
Loading

0 comments on commit df1c0f0

Please sign in to comment.