|
@@ -1,14 +1,3 @@
|
|
|
-use std::{
|
|
|
- any::{Any, TypeId},
|
|
|
- cell::{Cell, RefCell},
|
|
|
- collections::{HashMap, HashSet},
|
|
|
- rc::Rc,
|
|
|
- sync::Arc,
|
|
|
-};
|
|
|
-
|
|
|
-use bumpalo::Bump;
|
|
|
-use std::future::Future;
|
|
|
-
|
|
|
use crate::{
|
|
|
any_props::AnyProps,
|
|
|
arena::ElementId,
|
|
@@ -18,17 +7,40 @@ use crate::{
|
|
|
nodes::VNode,
|
|
|
TaskId,
|
|
|
};
|
|
|
+use bumpalo::Bump;
|
|
|
+use std::future::Future;
|
|
|
+use std::{
|
|
|
+ any::{Any, TypeId},
|
|
|
+ cell::{Cell, RefCell},
|
|
|
+ collections::{HashMap, HashSet},
|
|
|
+ rc::Rc,
|
|
|
+ sync::Arc,
|
|
|
+};
|
|
|
|
|
|
+/// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given
|
|
|
+/// component.
|
|
|
+///
|
|
|
+/// The [`Scope`] is your handle to the [`VirtualDom`] and the component state. Every component is given its own
|
|
|
+/// [`ScopeState`] and merged with its properties to create a [`Scoped`].
|
|
|
+///
|
|
|
+/// The [`Scope`] handle specifically exists to provide a stable reference to these items for the lifetime of the
|
|
|
+/// component render.
|
|
|
pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
|
|
|
|
|
|
+/// A wrapper around a component's [`ScopeState`] and properties. The [`ScopeState`] provides the majority of methods
|
|
|
+/// for the VirtualDom and component state.
|
|
|
pub struct Scoped<'a, T = ()> {
|
|
|
+ /// The component's state and handle to the scheduler.
|
|
|
+ ///
|
|
|
+ /// Stores things like the custom bump arena, spawn functions, hooks, and the scheduler.
|
|
|
pub scope: &'a ScopeState,
|
|
|
+
|
|
|
+ /// The component's properties.
|
|
|
pub props: &'a T,
|
|
|
}
|
|
|
|
|
|
impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
|
|
type Target = &'a ScopeState;
|
|
|
-
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
&self.scope
|
|
|
}
|
|
@@ -42,6 +54,9 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
|
|
pub struct ScopeId(pub usize);
|
|
|
|
|
|
+/// A component's state.
|
|
|
+///
|
|
|
+/// This struct stores all the important information about a component's state without the props.
|
|
|
pub struct ScopeState {
|
|
|
pub(crate) render_cnt: Cell<usize>,
|
|
|
|
|
@@ -55,7 +70,7 @@ pub struct ScopeState {
|
|
|
pub(crate) height: u32,
|
|
|
|
|
|
pub(crate) hook_arena: Bump,
|
|
|
- pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
|
|
|
+ pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
|
|
|
pub(crate) hook_idx: Cell<usize>,
|
|
|
|
|
|
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
|
@@ -75,6 +90,7 @@ impl ScopeState {
|
|
|
_ => unreachable!(),
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
pub fn previous_frame(&self) -> &BumpFrame {
|
|
|
match self.render_cnt.get() % 2 {
|
|
|
1 => &self.node_arena_1,
|
|
@@ -83,10 +99,19 @@ impl ScopeState {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Get a handle to the currently active bump arena for this Scope
|
|
|
+ ///
|
|
|
+ /// This is a bump memory allocator. Be careful using this directly since the contents will be wiped on the next render.
|
|
|
+ /// It's easy to leak memory here since the drop implementation will not be called for any objects allocated in this arena.
|
|
|
+ ///
|
|
|
+ /// If you need to allocate items that need to be dropped, use bumpalo's box.
|
|
|
pub fn bump(&self) -> &Bump {
|
|
|
&self.current_frame().bump
|
|
|
}
|
|
|
|
|
|
+ /// Get a handle to the currently active head node arena for this Scope
|
|
|
+ ///
|
|
|
+ /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
|
|
|
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
|
|
let r = unsafe { &*self.current_frame().node.get() };
|
|
|
unsafe { std::mem::transmute(r) }
|
|
@@ -166,6 +191,7 @@ impl ScopeState {
|
|
|
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
|
|
}
|
|
|
|
|
|
+ /// Mark this scope as dirty, and schedule a render for it.
|
|
|
pub fn needs_update(&self) {
|
|
|
self.needs_update_any(self.scope_id());
|
|
|
}
|
|
@@ -180,6 +206,42 @@ impl ScopeState {
|
|
|
.expect("Scheduler to exist if scope exists");
|
|
|
}
|
|
|
|
|
|
+ /// Return any context of type T if it exists on this scope
|
|
|
+ pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
|
+ self.shared_contexts
|
|
|
+ .borrow()
|
|
|
+ .get(&TypeId::of::<T>())
|
|
|
+ .and_then(|shared| shared.downcast_ref::<T>())
|
|
|
+ .cloned()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Try to retrieve a shared state with type `T` from any parent scope.
|
|
|
+ ///
|
|
|
+ /// The state will be cloned and returned, if it exists.
|
|
|
+ ///
|
|
|
+ /// We recommend wrapping the state in an `Rc` or `Arc` to avoid deep cloning.
|
|
|
+ pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
|
+ if let Some(this_ctx) = self.has_context() {
|
|
|
+ return Some(this_ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut search_parent = self.parent;
|
|
|
+ while let Some(parent_ptr) = search_parent {
|
|
|
+ // safety: all parent pointers are valid thanks to the bump arena
|
|
|
+ let parent = unsafe { &*parent_ptr };
|
|
|
+ if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
|
+ return Some(
|
|
|
+ shared
|
|
|
+ .downcast_ref::<T>()
|
|
|
+ .expect("Context of type T should exist")
|
|
|
+ .clone(),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ search_parent = parent.parent;
|
|
|
+ }
|
|
|
+ None
|
|
|
+ }
|
|
|
+
|
|
|
/// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
|
|
|
///
|
|
|
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
|
@@ -212,96 +274,6 @@ impl ScopeState {
|
|
|
value
|
|
|
}
|
|
|
|
|
|
- /// Provide a context for the root component from anywhere in your app.
|
|
|
- ///
|
|
|
- ///
|
|
|
- /// # Example
|
|
|
- ///
|
|
|
- /// ```rust, ignore
|
|
|
- /// struct SharedState(&'static str);
|
|
|
- ///
|
|
|
- /// static App: Component = |cx| {
|
|
|
- /// cx.use_hook(|| cx.provide_root_context(SharedState("world")));
|
|
|
- /// render!(Child {})
|
|
|
- /// }
|
|
|
- ///
|
|
|
- /// static Child: Component = |cx| {
|
|
|
- /// let state = cx.consume_state::<SharedState>();
|
|
|
- /// render!(div { "hello {state.0}" })
|
|
|
- /// }
|
|
|
- /// ```
|
|
|
- pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
|
|
|
- // if we *are* the root component, then we can just provide the context directly
|
|
|
- if self.scope_id() == ScopeId(0) {
|
|
|
- self.shared_contexts
|
|
|
- .borrow_mut()
|
|
|
- .insert(TypeId::of::<T>(), Box::new(value.clone()))
|
|
|
- .and_then(|f| f.downcast::<T>().ok());
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- let mut search_parent = self.parent;
|
|
|
-
|
|
|
- while let Some(parent) = search_parent.take() {
|
|
|
- let parent = unsafe { &*parent };
|
|
|
-
|
|
|
- if parent.scope_id() == ScopeId(0) {
|
|
|
- let _ = parent
|
|
|
- .shared_contexts
|
|
|
- .borrow_mut()
|
|
|
- .insert(TypeId::of::<T>(), Box::new(value.clone()));
|
|
|
-
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- search_parent = parent.parent;
|
|
|
- }
|
|
|
-
|
|
|
- unreachable!("all apps have a root scope")
|
|
|
- }
|
|
|
-
|
|
|
- /// Try to retrieve a shared state with type T from the any parent Scope.
|
|
|
- pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
|
- if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
|
- Some(
|
|
|
- (*shared
|
|
|
- .downcast_ref::<T>()
|
|
|
- .expect("Context of type T should exist"))
|
|
|
- .clone(),
|
|
|
- )
|
|
|
- } else {
|
|
|
- let mut search_parent = self.parent;
|
|
|
-
|
|
|
- while let Some(parent_ptr) = search_parent {
|
|
|
- // safety: all parent pointers are valid thanks to the bump arena
|
|
|
- let parent = unsafe { &*parent_ptr };
|
|
|
- if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
|
- return Some(
|
|
|
- shared
|
|
|
- .downcast_ref::<T>()
|
|
|
- .expect("Context of type T should exist")
|
|
|
- .clone(),
|
|
|
- );
|
|
|
- }
|
|
|
- search_parent = parent.parent;
|
|
|
- }
|
|
|
- None
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return any context of type T if it exists on this scope
|
|
|
- pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
|
- match self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
|
- Some(shared) => Some(
|
|
|
- (*shared
|
|
|
- .downcast_ref::<T>()
|
|
|
- .expect("Context of type T should exist"))
|
|
|
- .clone(),
|
|
|
- ),
|
|
|
- None => None,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/// Pushes the future onto the poll queue to be polled after the component renders.
|
|
|
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
|
|
self.tasks.spawn(self.id, fut)
|
|
@@ -312,7 +284,7 @@ impl ScopeState {
|
|
|
self.push_future(fut);
|
|
|
}
|
|
|
|
|
|
- /// Spawn a future that Dioxus will never clean up
|
|
|
+ /// Spawn a future that Dioxus won't clean up when this component is unmounted
|
|
|
///
|
|
|
/// This is good for tasks that need to be run after the component has been dropped.
|
|
|
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
|
@@ -328,13 +300,14 @@ impl ScopeState {
|
|
|
id
|
|
|
}
|
|
|
|
|
|
- /// Informs the scheduler that this task is no longer needed and should be removed
|
|
|
- /// on next poll.
|
|
|
+ /// Informs the scheduler that this task is no longer needed and should be removed.
|
|
|
+ ///
|
|
|
+ /// This drops the task immediately.
|
|
|
pub fn remove_future(&self, id: TaskId) {
|
|
|
self.tasks.remove(id);
|
|
|
}
|
|
|
|
|
|
- /// Take a lazy [`VNode`] structure and actually build it with the context of the Vdoms efficient [`VNode`] allocator.
|
|
|
+ /// Take a lazy [`VNode`] structure and actually build it with the context of the efficient [`Bump`] allocator.
|
|
|
///
|
|
|
/// ## Example
|
|
|
///
|
|
@@ -369,19 +342,17 @@ impl ScopeState {
|
|
|
/// ```
|
|
|
#[allow(clippy::mut_from_ref)]
|
|
|
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
|
|
- let mut vals = self.hook_vals.borrow_mut();
|
|
|
-
|
|
|
- let hook_len = vals.len();
|
|
|
- let cur_idx = self.hook_idx.get();
|
|
|
+ let cur_hook = self.hook_idx.get();
|
|
|
+ let mut hook_list = self.hook_list.borrow_mut();
|
|
|
|
|
|
- if cur_idx >= hook_len {
|
|
|
- vals.push(self.hook_arena.alloc(initializer()));
|
|
|
+ if cur_hook >= hook_list.len() {
|
|
|
+ hook_list.push(self.hook_arena.alloc(initializer()));
|
|
|
}
|
|
|
|
|
|
- vals
|
|
|
- .get(cur_idx)
|
|
|
+ hook_list
|
|
|
+ .get(cur_hook)
|
|
|
.and_then(|inn| {
|
|
|
- self.hook_idx.set(cur_idx + 1);
|
|
|
+ self.hook_idx.set(cur_hook + 1);
|
|
|
let raw_box = unsafe { &mut **inn };
|
|
|
raw_box.downcast_mut::<State>()
|
|
|
})
|