wip: some project refactor

This commit is contained in:
Jonathan Kelley committed 2021-07-09 11:54:07 -04:00
1 parent feab50f24a
commit 8cfc437bfe
27 files changed
+430 -677

No files matched your search

+2 -1
View File
@@ -13,7 +13,7 @@ dioxus-core-macro = { path="./packages/core-macro", version="0.1.0" }
dioxus-html = { path="./packages/html", optional=true }
dioxus-web = { path="./packages/web", optional=true }
dioxus-webview = { path="./packages/webview", optional=true }
# dioxus-hooks = { path="./packages/hooks", version="0.0.0" }
dioxus-hooks = { path="./packages/hooks", version="0.0.0" }
[features]
@@ -49,6 +49,7 @@ members = [
"packages/html",
"packages/web",
"packages/webview",
"packages/hooks",
# "packages/atoms",
# "packages/ssr",
# "packages/docsite",
+4 -3
View File
@@ -6,16 +6,17 @@ use futures::Future;
fn main() {}
const App: FC<()> = |cx| {
// create a new future
let mut fut = cx.use_hook(
|| {
//
Box::pin(async { loop {} }) as Pin<Box<dyn Future<Output = ()>>>
async { loop {} }
// Box::pin(async { loop {} }) as Pin<Box<dyn Future<Output = ()>>>
},
|f| f,
|_| {},
);
cx.submit_task(fut);
// let g = unsafe { Pin::new_unchecked(fut) };
cx.submit_task(fut);
+1 -1
View File
@@ -20,7 +20,7 @@ struct ListItem {
}
fn app(cx: Context<AppProps>) -> VNode {
let (val, set_val) = use_state_classic(cx, || 0);
// let (val, set_val) = use_state_classic(cx, || 0);
cx.render(LazyNodes::new(move |_nodecx| {
todo!()
+11 -15
View File
@@ -2,37 +2,33 @@ use std::{
cell::{RefCell, UnsafeCell},
collections::HashMap,
rc::Rc,
sync::Arc,
};
use crate::innerlude::*;
use slotmap::{DefaultKey, SlotMap};
#[derive(Clone)]
pub struct ScopeArena(pub Rc<RefCell<ScopeArenaInner>>);
pub type ScopeMap = SlotMap<DefaultKey, Scope>;
pub struct ScopeArenaInner {
pub(crate) arena: UnsafeCell<ScopeMap>,
locks: HashMap<ScopeIdx, MutStatus>,
pub struct SharedArena {
pub components: Arc<UnsafeCell<ScopeMap>>,
}
pub type ScopeMap = SlotMap<DefaultKey, Scope>;
enum MutStatus {
Immut,
Mut,
}
impl ScopeArena {
impl SharedArena {
pub fn new(arena: ScopeMap) -> Self {
ScopeArena(Rc::new(RefCell::new(ScopeArenaInner {
arena: UnsafeCell::new(arena),
locks: Default::default(),
})))
let components = Arc::new(UnsafeCell::new(arena));
SharedArena { components }
}
/// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn try_get(&self, idx: ScopeIdx) -> Result<&Scope> {
let inner = unsafe { &*self.0.borrow().arena.get() };
let inner = unsafe { &*self.components.get() };
let scope = inner.get(idx);
scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
}
@@ -40,7 +36,7 @@ impl ScopeArena {
/// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn try_get_mut(&self, idx: ScopeIdx) -> Result<&mut Scope> {
let inner = unsafe { &mut *self.0.borrow().arena.get() };
let inner = unsafe { &mut *self.components.get() };
let scope = inner.get_mut(idx);
scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
}
@@ -56,7 +52,7 @@ impl ScopeArena {
/// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn with<T>(&self, f: impl FnOnce(&mut ScopeMap) -> T) -> Result<T> {
let inner = unsafe { &mut *self.0.borrow().arena.get() };
let inner = unsafe { &mut *self.components.get() };
Ok(f(inner))
// todo!()
}
@@ -80,7 +76,7 @@ impl ScopeArena {
}
pub fn try_remove(&self, id: ScopeIdx) -> Result<Scope> {
let inner = unsafe { &mut *self.0.borrow().arena.get() };
let inner = unsafe { &mut *self.components.get() };
inner
.remove(id)
.ok_or_else(|| Error::FatalInternal("Scope not found"))
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::hooklist::HookList;
use crate::{arena::ScopeArena, innerlude::*};
use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList;
use bumpalo::Bump;
use futures::FutureExt;
-1
View File
@@ -43,7 +43,6 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
}
/// Create inline fragments
/// ------------------------
///
/// Fragments capture a series of children without rendering extra nodes.
///
+10 -6
View File
@@ -1,5 +1,5 @@
use crate::hooklist::HookList;
use crate::{arena::ScopeArena, innerlude::*};
use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList;
use bumpalo::Bump;
use futures::FutureExt;
@@ -40,10 +40,11 @@ use std::{
pub struct Context<'src, T> {
pub props: &'src T,
pub scope: &'src Scope,
pub tasks: &'src AppendList<&'src mut DTask>,
pub tasks: &'src AppendList<&'src mut DTask<'src>>,
}
pub type DTask = Pin<Box<dyn Future<Output = ()>>>;
pub type DTask<'s> = dyn Future<Output = ()> + 's;
// pub type DTask = Pin<Box<dyn Future<Output = ()>>>;
impl<'src, T> Copy for Context<'src, T> {}
impl<'src, T> Clone for Context<'src, T> {
@@ -293,12 +294,15 @@ Any function prefixed with "use" should not be called conditionally.
///
/// Tasks can't return anything, but they can be controlled with the returned handle
///
/// Tasks will only run until the component renders again. If you want your task to last longer than one frame, you'll need
/// to store it somehow.
/// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it
/// is considered "stable"
///
///
///
pub fn submit_task(
&self,
mut task: &'src mut Pin<Box<dyn Future<Output = ()> + 'static>>,
mut task: &'src mut DTask<'src>,
// mut task: &'src mut Pin<Box<dyn Future<Output = ()> + 'static>>,
) -> TaskHandle {
self.tasks.push(task);
// let mut g = self.task.borrow_mut();
+7 -10
View File
@@ -22,10 +22,7 @@
//! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{
arena::{ScopeArena, ScopeArenaInner},
innerlude::*,
};
use crate::{arena::SharedArena, innerlude::*};
use fxhash::{FxHashMap, FxHashSet};
use std::{
@@ -96,7 +93,7 @@ pub trait RealDom<'a> {
/// subscriptions and props changes.
pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
pub dom: &'real mut Dom,
pub components: &'bump ScopeArena,
pub components: &'bump SharedArena,
pub cur_idx: ScopeIdx,
pub diffed: FxHashSet<ScopeIdx>,
pub event_queue: EventQueue,
@@ -106,7 +103,7 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
pub fn new(
dom: &'real mut Dom,
components: &'bump ScopeArena,
components: &'bump SharedArena,
cur_idx: ScopeIdx,
event_queue: EventQueue,
) -> Self {
@@ -376,7 +373,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// self.dom.save_known_root(root_id);
log::debug!("Mounting a new component");
let caller: Weak<OpaqueComponent> = Rc::downgrade(&component.caller);
let caller: Weak<WrappedCaller> = Rc::downgrade(&component.caller);
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
@@ -413,7 +410,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// yaaaaay lifetimes out of thin air
// really tho, we're merging the frame lifetimes together
let inner: &'bump mut _ = unsafe { &mut *self.components.0.borrow().arena.get() };
let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
let new_component = inner.get_mut(idx).unwrap();
// Actually initialize the caller's slot with the right address
@@ -1233,7 +1230,7 @@ enum KeyedPrefixResult {
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
/// "InsertBefore".
struct RealChildIterator<'a> {
scopes: &'a ScopeArena,
scopes: &'a SharedArena,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
@@ -1241,7 +1238,7 @@ struct RealChildIterator<'a> {
}
impl<'a> RealChildIterator<'a> {
fn new(starter: &'a VNode<'a>, scopes: &'a ScopeArena) -> Self {
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
Self {
scopes,
stack: smallvec::smallvec![(0, starter)],
+1 -1
View File
@@ -6,7 +6,7 @@
use std::{ops::Deref, rc::Rc};
use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode};
use crate::innerlude::{RealDomNode, ScopeIdx};
#[derive(Debug)]
pub struct EventTrigger {
-413
View File
@@ -1,413 +0,0 @@
//! Useful, foundational hooks that 3rd parties can implement.
//! Currently implemented:
//! - [x] use_ref
//! - [x] use_state
//! - [ ] use_reducer
//! - [ ] use_effect
use crate::innerlude::Context;
use std::{
cell::{Cell, RefCell},
ops::{Deref, DerefMut},
rc::Rc,
};
/// Store state between component renders!
/// When called, this hook retrives a stored value and provides a setter to update that value.
/// When the setter is called, the component is re-ran with the new value.
///
/// This is behaves almost exactly the same way as React's "use_state".
///
/// Usage:
/// ```ignore
/// static Example: FC<()> = |cx| {
/// let (counter, set_counter) = use_state(cx, || 0);
/// let increment = |_| set_couter(counter + 1);
/// let decrement = |_| set_couter(counter + 1);
///
/// html! {
/// <div>
/// <h1>"Counter: {counter}" </h1>
/// <button onclick={increment}> "Increment" </button>
/// <button onclick={decrement}> "Decrement" </button>
/// </div>
/// }
/// }
/// ```
pub fn use_state_classic<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
cx: Context<'a, P>,
initial_state_fn: F,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
struct UseState<T: 'static> {
new_val: Rc<RefCell<Option<T>>>,
current_val: T,
caller: Rc<dyn Fn(T) + 'static>,
}
cx.use_hook(
move || UseState {
new_val: Rc::new(RefCell::new(None)),
current_val: initial_state_fn(),
caller: Rc::new(|_| println!("setter called!")),
},
move |hook| {
log::debug!("Use_state set called");
let inner = hook.new_val.clone();
let scheduled_update = cx.schedule_update();
// get ownership of the new val and replace the current with the new
// -> as_ref -> borrow_mut -> deref_mut -> take
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
hook.current_val = new_val;
}
// todo: swap out the caller with a subscription call and an internal update
hook.caller = Rc::new(move |new_val| {
// update the setter with the new value
let mut new_inner = inner.as_ref().borrow_mut();
*new_inner = Some(new_val);
// Ensure the component gets updated
scheduled_update();
});
// box gets derefed into a ref which is then taken as ref with the hook
(&hook.current_val, &hook.caller)
},
|_| {},
)
}
use crate::innerlude::*;
use std::{
fmt::{Debug, Display},
pin::Pin,
};
pub struct UseState<'a, T: 'static> {
inner: &'a UseStateInner<T>,
}
impl<T> Copy for UseState<'_, T> {}
impl<'a, T> Clone for UseState<'a, T>
where
T: 'static,
{
fn clone(&self) -> Self {
UseState { inner: self.inner }
}
}
impl<'a, T: 'static> UseState<'a, T> {
/// Tell the Dioxus Scheduler that we need to be processed
pub fn needs_update(&self) {
if !self.inner.update_scheuled.get() {
self.inner.update_scheuled.set(true);
(self.inner.callback)();
}
}
pub fn set(&self, new_val: T) {
self.needs_update();
*self.inner.wip.borrow_mut() = Some(new_val);
}
pub fn get(&self) -> &T {
&self.inner.current_val
}
/// Get the current status of the work-in-progress data
pub fn get_wip(&self) -> Ref<Option<T>> {
self.inner.wip.borrow()
}
}
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
pub fn get_mut(self) -> RefMut<'a, T> {
// make sure we get processed
self.needs_update();
// Bring out the new value, cloning if it we need to
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.inner.wip.borrow_mut(), |slot| {
if slot.is_none() {
*slot = Some(self.inner.current_val.to_owned());
}
slot.as_mut().unwrap()
})
}
}
impl<'a, T: 'static> std::ops::Deref for UseState<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner.current_val
}
}
use std::ops::{Add, AddAssign, Sub, SubAssign};
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
type Output = T;
fn add(self, rhs: T) -> Self::Output {
self.inner.current_val.add(rhs)
}
}
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.add(rhs));
}
}
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
type Output = T;
fn sub(self, rhs: T) -> Self::Output {
self.inner.current_val.sub(rhs)
}
}
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.sub(rhs));
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.current_val)
}
}
struct UseStateInner<T: 'static> {
current_val: T,
update_scheuled: Cell<bool>,
callback: Rc<dyn Fn()>,
wip: RefCell<Option<T>>,
}
/// Store state between component renders!
///
/// ## The "King" of state hooks
///
/// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render.
///
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
///
/// [`use_state`] exposes a few helper methods to modify the underlying state:
/// - `.set(new)` allows you to override the "work in progress" value with a new value
/// - `.get_mut()` allows you to modify the WIP value
/// - `.get_wip()` allows you to access the WIP value
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
///
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
/// will automatically be called on the WIP value.
///
///
/// Usage:
/// ```ignore
/// const Example: FC<()> = |cx| {
/// let counter = use_state(cx, || 0);
/// let increment = |_| counter += 1;
/// let decrement = |_| counter += 1;
///
/// html! {
/// <div>
/// <h1>"Counter: {counter}" </h1>
/// <button onclick={increment}> "Increment" </button>
/// <button onclick={decrement}> "Decrement" </button>
/// </div>
/// }
/// }
/// ```
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
cx: Context<'a, P>,
initial_state_fn: F,
) -> UseState<T> {
cx.use_hook(
move || UseStateInner {
current_val: initial_state_fn(),
callback: cx.schedule_update(),
wip: RefCell::new(None),
update_scheuled: Cell::new(false),
},
move |hook| {
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
hook.current_val = new_val.take().unwrap();
}
UseState { inner: &*hook }
},
|_| {},
)
}
/// Store a mutable value between renders!
/// To read the value, borrow the ref.
/// To change it, use modify.
/// Modifications to this value do not cause updates to the component
/// Attach to inner context reference, so context can be consumed
pub fn use_ref<'a, T: 'static, P>(
cx: Context<'a, P>,
initial_state_fn: impl FnOnce() -> T + 'static,
) -> &'a RefCell<T> {
cx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
}
struct UseReducer<T: 'static, R: 'static> {
new_val: Rc<RefCell<Option<T>>>,
current_val: T,
caller: Box<dyn Fn(R) + 'static>,
}
/// Store state between component renders!
/// When called, this hook retrives a stored value and provides a setter to update that value.
/// When the setter is called, the component is re-ran with the new value.
///
/// This is behaves almost exactly the same way as React's "use_state".
///
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static, P>(
cx: Context<'a, P>,
initial_state_fn: impl FnOnce() -> State,
_reducer: impl Fn(&mut State, Action),
) -> (&'a State, &'a Box<dyn Fn(Action)>) {
cx.use_hook(
move || UseReducer {
new_val: Rc::new(RefCell::new(None)),
current_val: initial_state_fn(),
caller: Box::new(|_| println!("setter called!")),
},
move |hook| {
let _inner = hook.new_val.clone();
let scheduled_update = cx.schedule_update();
// get ownership of the new val and replace the current with the new
// -> as_ref -> borrow_mut -> deref_mut -> take
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
hook.current_val = new_val;
}
// todo: swap out the caller with a subscription call and an internal update
hook.caller = Box::new(move |_new_val| {
// update the setter with the new value
// let mut new_inner = inner.as_ref().borrow_mut();
// *new_inner = Some(new_val);
// Ensure the component gets updated
scheduled_update();
});
// box gets derefed into a ref which is then taken as ref with the hook
(&hook.current_val, &hook.caller)
},
|_| {},
)
}
/// Use model makes it easy to use "models" as state for components. To modify the model, call "modify" and a clone of the
/// current model will be made, with a RefMut lock on it. Dioxus will never run your components multithreaded, so you can
/// be relatively sure that this won't fail in practice
pub fn use_model<'a, T: ToOwned<Owned = T> + 'static, P>(
cx: Context<'a, P>,
f: impl FnOnce() -> T,
) -> &'a UseModel<T> {
cx.use_hook(
move || {
let real = f();
let wip = RefCell::new(real.to_owned());
let update = cx.schedule_update();
UseModel { real, wip, update }
},
|hook| {
hook.real = hook.wip.borrow().to_owned();
&*hook
},
|_| {},
)
}
pub struct UseModel<T: ToOwned> {
real: T,
wip: RefCell<T>,
update: Rc<dyn Fn()>,
}
use std::cell::{Ref, RefMut};
impl<T: ToOwned> Deref for UseModel<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.real
}
}
impl<T: Display + ToOwned> Display for UseModel<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.real)
}
}
impl<T: ToOwned> UseModel<T> {
pub fn get_mut(&self) -> RefMut<'_, T> {
(self.update)();
self.wip.borrow_mut()
}
pub fn modify(&self, f: impl FnOnce(&mut T)) {
(self.update)();
let mut g = self.get_mut();
let r = g.deref_mut();
f(r)
}
}
// #[cfg(test)]
mod tests {
use crate::prelude::*;
enum Actions {
Incr,
Decr,
}
// #[allow(unused)]
// static Example: FC<()> = |cx| {
// let (count, reduce) = use_reducer(
// &cx,
// || 0,
// |count, action| match action {
// Actions::Incr => *count += 1,
// Actions::Decr => *count -= 1,
// },
// );
// cx.render(rsx! {
// div {
// h1 {"Count: {count}"}
// button {
// "Increment"
// onclick: move |_| reduce(Actions::Incr)
// }
// button {
// "Decrement"
// onclick: move |_| reduce(Actions::Decr)
// }
// }
// })
// };
}
pub fn use_is_initialized<'a, P>(cx: Context<'a, P>) -> bool {
let val = use_ref(cx, || false);
match *val.borrow() {
true => true,
false => {
//
*val.borrow_mut() = true;
false
}
}
}
-5
View File
@@ -19,7 +19,6 @@ pub mod diff;
pub mod error;
pub mod events;
pub mod hooklist;
pub mod hooks;
pub mod nodebuilder;
pub mod nodes;
pub mod scope;
@@ -41,7 +40,6 @@ pub(crate) mod innerlude {
pub use crate::diff::*;
pub use crate::error::*;
pub use crate::events::*;
pub use crate::hooks::*;
pub use crate::nodebuilder::*;
pub use crate::nodes::*;
pub use crate::scope::*;
@@ -84,7 +82,4 @@ pub mod prelude {
pub use crate::diff::DiffMachine;
pub use crate::virtual_dom::ScopeIdx;
// pub use crate::debug_renderer::DebugRenderer;
pub use crate::hooks::*;
}
+5 -5
View File
@@ -14,10 +14,9 @@ use bumpalo::Bump;
use crate::{
events::VirtualEvent,
innerlude::{Properties, Scope, VComponent, VText, FC},
innerlude::{Properties, RealDomNode, Scope, VComponent, VText, FC},
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::{VElement, VFragment},
virtual_dom::RealDomNode,
};
/// A virtual DOM element builder.
@@ -509,6 +508,7 @@ where
/// .finish();
/// ```
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
todo!();
let len_before = self.children.len();
for item in nodes {
todo!()
@@ -542,13 +542,13 @@ impl<'a> IntoIterator for VNode<'a> {
}
}
impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self
}
}
impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self.clone()
}
}
@@ -801,7 +801,7 @@ impl<'a> NodeFactory<'a> {
use std::fmt::Debug;
impl Debug for NodeFactory<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
+3 -4
View File
@@ -4,11 +4,10 @@
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
use crate::{
arena::ScopeArena,
arena::SharedArena,
events::VirtualEvent,
innerlude::{Context, Properties, Scope, ScopeIdx, FC},
innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC},
nodebuilder::{text3, NodeFactory},
virtual_dom::RealDomNode,
};
use bumpalo::Bump;
use std::{
@@ -165,7 +164,7 @@ impl<'a> VNode<'a> {
}
}
pub fn get_mounted_id(&self, components: &ScopeArena) -> Option<RealDomNode> {
pub fn get_mounted_id(&self, components: &SharedArena) -> Option<RealDomNode> {
match self {
VNode::Element(el) => Some(el.dom_id.get()),
VNode::Text(te) => Some(te.dom_id.get()),
+17 -51
View File
@@ -1,5 +1,5 @@
use crate::hooklist::HookList;
use crate::{arena::ScopeArena, innerlude::*};
use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList;
use bumpalo::Bump;
use futures::FutureExt;
@@ -17,6 +17,11 @@ use std::{
rc::{Rc, Weak},
};
// We need to pin the hook so it doesn't move as we initialize the list of hooks
type Hook = Box<dyn std::any::Any>;
type EventChannel = Rc<dyn Fn()>;
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
/// Every component in Dioxus is represented by a `Scope`.
///
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -35,7 +40,7 @@ pub struct Scope {
// A reference to the list of components.
// This lets us traverse the component list whenever we need to access our parent or children.
pub arena_link: ScopeArena,
pub arena_link: SharedArena,
pub shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
@@ -46,7 +51,7 @@ pub struct Scope {
pub event_channel: Rc<dyn Fn() + 'static>,
pub caller: Weak<OpaqueComponent>,
pub caller: Weak<WrappedCaller>,
// ==========================
// slightly unsafe stuff
@@ -71,10 +76,6 @@ pub struct Scope {
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
}
// We need to pin the hook so it doesn't move as we initialize the list of hooks
type Hook = Box<dyn std::any::Any>;
type EventChannel = Rc<dyn Fn()>;
impl Scope {
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
// we are going to break this lifetime by force in order to save it on ourselves.
@@ -84,12 +85,12 @@ impl Scope {
// Scopes cannot be made anywhere else except for this file
// Therefore, their lifetimes are connected exclusively to the virtual dom
pub fn new<'creator_node>(
caller: Weak<OpaqueComponent>,
caller: Weak<WrappedCaller>,
arena_idx: ScopeIdx,
parent: Option<ScopeIdx>,
height: u32,
event_channel: EventChannel,
arena_link: ScopeArena,
arena_link: SharedArena,
child_nodes: &'creator_node [VNode<'creator_node>],
) -> Self {
log::debug!(
@@ -98,25 +99,6 @@ impl Scope {
arena_idx
);
// The function to run this scope is actually located in the parent's bump arena.
// Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame.
// If children try to run this invalid caller, it *will* result in UB.
//
// During the lifecycle progression process, this caller will need to be updated. Right now,
// until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller
// lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully,
// this is a bit of a hack, but will remain this way until we've figured out a cleaner solution.
//
// Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
let caller = unsafe {
std::mem::transmute::<
Weak<OpaqueComponent>,
Weak<OpaqueComponent>,
// Weak<OpaqueComponent<'creator_node>>,
// Weak<OpaqueComponent<'static>>,
>(caller)
};
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
Self {
@@ -137,18 +119,8 @@ impl Scope {
}
}
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent>) {
// pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
let broken_caller = unsafe {
std::mem::transmute::<
Weak<OpaqueComponent>,
Weak<OpaqueComponent>,
// Weak<OpaqueComponent<'creator_node>>,
// Weak<OpaqueComponent<'static>>,
>(caller)
};
self.caller = broken_caller;
pub fn update_caller<'creator_node>(&mut self, caller: Weak<WrappedCaller>) {
self.caller = caller;
}
pub fn update_children<'creator_node>(
@@ -159,10 +131,6 @@ impl Scope {
self.child_nodes = child_nodes;
}
/// Create a new context and run the component with references from the Virtual Dom
/// This function downcasts the function pointer based on the stored props_type
///
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
pub fn run_scope<'sel>(&'sel mut self) -> Result<()> {
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
@@ -180,19 +148,17 @@ impl Scope {
.ok_or(Error::FatalInternal("Failed to get caller"))?;
// Cast the caller ptr from static to one with our own reference
let c2: &OpaqueComponent = caller.as_ref();
let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) };
let c3: &WrappedCaller = caller.as_ref();
self.frames.cur_frame_mut().head_node = unsafe { self.own_vnodes(c3) };
self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) };
Ok(())
}
// this is its own function so we can preciesly control how lifetimes flow
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> {
let new_head: VNode<'a> = f(self);
let out: VNode<'static> = std::mem::transmute(new_head);
out
unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> {
let new_head: VNode<'a> = caller(self);
std::mem::transmute(new_head)
}
// A safe wrapper around calling listeners
+9
View File
@@ -1,3 +1,12 @@
//! Serialization
//! -------------
//!
//!
//!
//!
//!
//!
use crate::prelude::ScopeIdx;
use serde::{Deserialize, Serialize};
+8 -1
View File
@@ -1 +1,8 @@
//! Support for jumping out of the diff engine
//! Signas: Avoid the Diff Engine
//! -----------------------------
//!
//!
//!
//!
//!
//!
+33 -7
View File
@@ -1,13 +1,15 @@
use std::{cell::RefCell, rc::Rc};
use std::{
cell::{RefCell, RefMut},
rc::Rc,
vec::Drain,
};
use crate::innerlude::*;
// We actually allocate the properties for components in their parent's properties
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
pub type OpaqueComponent = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
#[derive(PartialEq, Debug, Clone, Default)]
pub struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
pub struct EventQueue {
pub queue: Rc<RefCell<Vec<HeightMarker>>>,
}
impl EventQueue {
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
@@ -15,9 +17,17 @@ impl EventQueue {
let marker = HeightMarker { height, idx };
Rc::new(move || {
log::debug!("channel updated {:#?}", marker);
inner.0.as_ref().borrow_mut().push(marker)
inner.queue.as_ref().borrow_mut().push(marker)
})
}
pub fn sort_unstable(&self) {
self.queue.borrow_mut().sort_unstable()
}
pub fn borrow_mut(&self) -> RefMut<Vec<HeightMarker>> {
self.queue.borrow_mut()
}
}
/// A helper type that lets scopes be ordered by their height
@@ -39,6 +49,22 @@ impl PartialOrd for HeightMarker {
}
}
/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node.
///
/// "u64" was chosen for two reasons
/// - 0 cost hashing
/// - use with slotmap and other versioned slot arenas
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RealDomNode(pub u64);
impl RealDomNode {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn empty() -> Self {
Self(u64::MIN)
}
}
pub struct DebugDom {
counter: u64,
}
+7 -33
View File
@@ -20,7 +20,7 @@
//! Additional functionality is defined in the respective files.
use crate::tasks::TaskQueue;
use crate::{arena::ScopeArena, innerlude::*};
use crate::{arena::SharedArena, innerlude::*};
use slotmap::DefaultKey;
use slotmap::SlotMap;
use std::{any::TypeId, fmt::Debug, rc::Rc};
@@ -35,7 +35,7 @@ pub struct VirtualDom {
///
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
pub components: ScopeArena,
pub components: SharedArena,
/// The index of the root component
/// Should always be the first (gen=0, id=0)
@@ -48,7 +48,7 @@ pub struct VirtualDom {
/// a strong allocation to the "caller" for the original component and its props
#[doc(hidden)]
_root_caller: Rc<OpaqueComponent>,
_root_caller: Rc<WrappedCaller>,
/// Type of the original cx. This is stored as TypeId so VirtualDom does not need to be generic.
///
@@ -58,22 +58,6 @@ pub struct VirtualDom {
_root_prop_type: std::any::TypeId,
}
/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node.
///
/// "u64" was chosen for two reasons
/// - 0 cost hashing
/// - use with slotmap and other versioned slot arenas
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RealDomNode(pub u64);
impl RealDomNode {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn empty() -> Self {
Self(u64::MIN)
}
}
// ======================================
// Public Methods for the VirtualDom
// ======================================
@@ -142,11 +126,11 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let components = ScopeArena::new(SlotMap::new());
let components = SharedArena::new(SlotMap::new());
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |scope| {
let _root_caller: Rc<WrappedCaller> = Rc::new(move |scope| {
// let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
// the lifetime of this closure is just as long as the lifetime on the scope reference
// this closure moves root props (which is static) into this closure
@@ -283,12 +267,8 @@ impl VirtualDom {
&'bump self,
diff_machine: &'_ mut DiffMachine<'a, 'bump, Dom>,
) -> Result<()> {
// Add this component to the list of components that need to be difed
// #[allow(unused_assignments)]
// let mut cur_height: u32 = 0;
// Now, there are events in the queue
let mut updates = self.event_queue.0.as_ref().borrow_mut();
let mut updates = self.event_queue.queue.as_ref().borrow_mut();
// Order the nodes by their height, we want the nodes with the smallest depth on top
// This prevents us from running the same component multiple times
@@ -299,6 +279,7 @@ impl VirtualDom {
// Iterate through the triggered nodes (sorted by height) and begin to diff them
for update in updates.drain(..) {
log::debug!("Running updates for: {:#?}", update);
// Make sure this isn't a node we've already seen, we don't want to double-render anything
// If we double-renderer something, this would cause memory safety issues
if diff_machine.seen_nodes.contains(&update.idx) {
@@ -310,20 +291,13 @@ impl VirtualDom {
// Start a new mutable borrow to components
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
let cur_component = self.components.try_get_mut(update.idx).unwrap();
// let inner: &'s mut _ = unsafe { &mut *self.components.0.borrow().arena.get() };
// let cur_component = inner.get_mut(update.idx).unwrap();
cur_component.run_scope()?;
// diff_machine.change_list.load_known_root(1);
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
// let (old, new) = cur_component.get_frames_mut();
diff_machine.diff_node(old, new);
// cur_height = cur_component.height;
// log::debug!(
// "Processing update: {:#?} with height {}",
// &update.idx,
+61
View File
@@ -0,0 +1,61 @@
# Common hooks for Dioxus
This crate includes some basic useful hooks for dioxus:
- use_state
- use_ref
- use_collection
- use_task
- use_signal
## use_state
The king daddy of state hooks.
You can always use it "normally" with the `split` method:
```rust
// Normal usage:
let value = use_state(cx, || 10);
// "Classic" usage:
let (value, set_value) = use_state(cx, || 0).classic();
```
## use_ref
## use_rwlock
A multithreaded form of RwLock for use in tasks
```rust
let val = use_rwlock(cx, || 10);
use_task((), || async loop {
*val.write().unwrap() += 1;
async_std::task::delay(Duration::from_ms(1000)).await;
});
use_task((), || async loop {
*val.write().unwrap() -= 1;
async_std::task::delay(Duration::from_ms(500)).await;
});
```
## use_hashmap
Store a memoized collection with similar semantics to use_state. Comes with a bunch of utility methods to make working with collections easier. Is essentially a wrapper over the immutable hashmap in im-rc.
```rust
let todos = use_hashmap(cx, |map| map.insert("bob", "bill"));
cx.render(rsx!(
button { onclick: move |_| todos.insert("bob", "bill")
"add random todo"
}
)
```
## use_task
use_task submits a task to the dioxus task queue to be progressed during Dioxus's async event loop. The task must not return anything
## use_signal
+1 -4
View File
@@ -1,7 +1,4 @@
fn main() {
// let mut s = Context { props: &() };
// let g = Component(&mut s);
}
fn main() {}
// struct CompState {
// tasks: Vec<()>,
+1
View File
@@ -0,0 +1 @@
fn main() {}
+2 -115
View File
@@ -2,118 +2,5 @@ use std::collections::HashMap;
use dioxus_core::prelude::*;
/*
a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
Why?
---
Traditionally, it's possible to use the "use_state" hook for collections in the React world.
Adding a new entry would look something similar to:
```js
let (map, set_map) = useState({});
set_map({ ...map, [key]: value });
```
The new value then causes the appropriate update when passed into children.
This is moderately efficient because the fields of the map are moved, but the data itself is not cloned.
However, if you used similar approach with Dioxus:
```rust
let (map, set_map) = use_state(cx, || HashMap::new());
set_map({
let mut newmap = map.clone();
newmap.set(key, value);
newmap
})
```
Unfortunately, you'd be cloning the entire state every time a value is changed. The obvious solution is to
wrap every element in the HashMap with an Rc. That way, cloning the HashMap is on par with its JS equivalent.
Fortunately, we can make this operation even more efficient in Dioxus, leveraging the borrow rules of Rust.
This hook provides a memoized collection, memoized setters, and memoized getters. This particular hook is
extremely powerful for implementing lists and supporting core state management needs for small apps.
If you need something even more powerful, check out the dedicated atomic state management Dioxus Dataflow, which
uses the same memoization on top of the use_context API.
Here's a fully-functional todo app using the use_map API:
```rust
static TodoList: FC<()> = |cx| {
let todos = use_map(cx, || HashMap::new());
let input = use_ref(|| None);
cx.render(rsx!{
div {
button {
"Add todo"
onclick: move |_| {
let new_todo = TodoItem::new(input.contents());
todos.insert(new_todo.id.clone(), new_todo);
input.clear();
}
}
button {
"Clear todos"
onclick: move |_| todos.clear()
}
input {
placeholder: "What needs to be done?"
ref: input
}
ul {
{todos.iter().map(|todo| rsx!(
li {
key: todo.id
span { "{todo.content}" }
button {"x", onclick: move |_| todos.remove(todo.key.clone())}
}
))}
}
}
})
}
```
*/
fn use_map() {}
// a form of "use_state" that allows collection memoization
// Elements are received as Rc<T> in case the underlying collection is shuffled around
// Setters/getters can be generated
fn use_collection<'a, T: Collection>(
cx: &impl Scoped<'a>,
f: impl Fn() -> T,
) -> CollectionHandle<'a, T> {
cx.use_hook(
|| {},
|h| {
//
CollectionHandle {
_p: Default::default(),
}
},
|h| {},
)
}
struct CollectionMemo {}
struct CollectionHandle<'a, T: Collection> {
_p: std::marker::PhantomData<&'a T>,
}
trait Collection {}
impl<K, V> Collection for std::collections::HashMap<K, V> {}
struct MapCollection<K, V> {
inner: HashMap<K, V>,
}
impl<K, V> MapCollection<K, V> {
fn set(&self, key: K, val: V) {
//
}
}
mod usestate;
pub use usestate::{use_state, UseState};
+76
View File
@@ -0,0 +1,76 @@
/*
a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
Why?
---
Traditionally, it's possible to use the "use_state" hook for collections in the React world.
Adding a new entry would look something similar to:
```js
let (map, set_map) = useState({});
set_map({ ...map, [key]: value });
```
The new value then causes the appropriate update when passed into children.
This is moderately efficient because the fields of the map are moved, but the data itself is not cloned.
However, if you used similar approach with Dioxus:
```rust
let (map, set_map) = use_state(cx, || HashMap::new());
set_map({
let mut newmap = map.clone();
newmap.set(key, value);
newmap
})
```
Unfortunately, you'd be cloning the entire state every time a value is changed. The obvious solution is to
wrap every element in the HashMap with an Rc. That way, cloning the HashMap is on par with its JS equivalent.
Fortunately, we can make this operation even more efficient in Dioxus, leveraging the borrow rules of Rust.
This hook provides a memoized collection, memoized setters, and memoized getters. This particular hook is
extremely powerful for implementing lists and supporting core state management needs for small apps.
If you need something even more powerful, check out the dedicated atomic state management Dioxus Dataflow, which
uses the same memoization on top of the use_context API.
Here's a fully-functional todo app using the use_map API:
```rust
static TodoList: FC<()> = |cx| {
let todos = use_map(cx, || HashMap::new());
let input = use_ref(|| None);
cx.render(rsx!{
div {
button {
"Add todo"
onclick: move |_| {
let new_todo = TodoItem::new(input.contents());
todos.insert(new_todo.id.clone(), new_todo);
input.clear();
}
}
button {
"Clear todos"
onclick: move |_| todos.clear()
}
input {
placeholder: "What needs to be done?"
ref: input
}
ul {
{todos.iter().map(|todo| rsx!(
li {
key: todo.id
span { "{todo.content}" }
button {"x", onclick: move |_| todos.remove(todo.key.clone())}
}
))}
}
}
})
}
```
*/
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+170
View File
@@ -0,0 +1,170 @@
use dioxus_core::prelude::Context;
use std::{
cell::{Cell, Ref, RefCell, RefMut},
fmt::Display,
ops::{Deref, DerefMut},
rc::Rc,
};
/// Store state between component renders!
///
/// ## The "King" of state hooks
///
/// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render.
///
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
///
/// [`use_state`] exposes a few helper methods to modify the underlying state:
/// - `.set(new)` allows you to override the "work in progress" value with a new value
/// - `.get_mut()` allows you to modify the WIP value
/// - `.get_wip()` allows you to access the WIP value
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
///
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
/// will automatically be called on the WIP value.
///
///
/// Usage:
/// ```ignore
/// const Example: FC<()> = |cx| {
/// let counter = use_state(cx, || 0);
/// let increment = |_| counter += 1;
/// let decrement = |_| counter += 1;
///
/// html! {
/// <div>
/// <h1>"Counter: {counter}" </h1>
/// <button onclick={increment}> "Increment" </button>
/// <button onclick={decrement}> "Decrement" </button>
/// </div>
/// }
/// }
/// ```
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
cx: Context<'a, P>,
initial_state_fn: F,
) -> UseState<T> {
cx.use_hook(
move || UseStateInner {
current_val: initial_state_fn(),
callback: cx.schedule_update(),
wip: RefCell::new(None),
update_scheuled: Cell::new(false),
},
move |hook| {
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
hook.current_val = new_val.take().unwrap();
}
UseState { inner: &*hook }
},
|_| {},
)
}
struct UseStateInner<T: 'static> {
current_val: T,
update_scheuled: Cell<bool>,
callback: Rc<dyn Fn()>,
wip: RefCell<Option<T>>,
updater:
}
pub struct UseState<'a, T: 'static> {
inner: &'a UseStateInner<T>,
}
impl<T> Copy for UseState<'_, T> {}
impl<'a, T> Clone for UseState<'a, T>
where
T: 'static,
{
fn clone(&self) -> Self {
UseState { inner: self.inner }
}
}
impl<'a, T: 'static> UseState<'a, T> {
/// Tell the Dioxus Scheduler that we need to be processed
pub fn needs_update(&self) {
if !self.inner.update_scheuled.get() {
self.inner.update_scheuled.set(true);
(self.inner.callback)();
}
}
pub fn set(&self, new_val: T) {
self.needs_update();
*self.inner.wip.borrow_mut() = Some(new_val);
}
pub fn get(&self) -> &T {
&self.inner.current_val
}
/// Get the current status of the work-in-progress data
pub fn get_wip(&self) -> Ref<Option<T>> {
self.inner.wip.borrow()
}
pub fn classic(self) -> (&'a T, &'a Rc<dyn Fn(T)>) {
(&self.inner.current_val)
}
}
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
pub fn get_mut(self) -> RefMut<'a, T> {
// make sure we get processed
self.needs_update();
// Bring out the new value, cloning if it we need to
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.inner.wip.borrow_mut(), |slot| {
if slot.is_none() {
*slot = Some(self.inner.current_val.to_owned());
}
slot.as_mut().unwrap()
})
}
}
impl<'a, T: 'static> std::ops::Deref for UseState<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner.current_val
}
}
use std::ops::{Add, AddAssign, Sub, SubAssign};
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
type Output = T;
fn add(self, rhs: T) -> Self::Output {
self.inner.current_val.add(rhs)
}
}
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.add(rhs));
}
}
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
type Output = T;
fn sub(self, rhs: T) -> Self::Output {
self.inner.current_val.sub(rhs)
}
}
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.sub(rhs));
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.current_val)
}
}
View File
Whitespace-only changes.