Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): 🎸 ensure object safety of state's traits #692

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading