WIP: move to static props
This commit is contained in:
1 parent
c5089ba3c5
commit
c1fd848f89
37 files changed
+1014
-339
No files matched your search
@@ -9,7 +9,7 @@ use crate::innerlude::FC;
|
||||
|
||||
pub type ScopeIdx = generational_arena::Index;
|
||||
|
||||
pub trait Properties: PartialEq {
|
||||
pub trait Properties: PartialEq + 'static {
|
||||
type Builder;
|
||||
fn builder() -> Self::Builder;
|
||||
}
|
||||
|
||||
@@ -387,8 +387,17 @@ where
|
||||
/// // Create the `<div id="my-div"/>` element.
|
||||
/// let my_div = div(&b).attr("id", "my-div").finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn attr(mut self, name: &'static str, value: &'a str) -> Self {
|
||||
pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self {
|
||||
let value = match args.as_str() {
|
||||
Some(static_str) => static_str,
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(self.ctx.bump());
|
||||
s.write_fmt(args).unwrap();
|
||||
s.into_bump_str()
|
||||
}
|
||||
};
|
||||
|
||||
self.attributes.push(Attribute { name, value });
|
||||
self
|
||||
}
|
||||
@@ -578,6 +587,12 @@ impl<'a> IntoDomTree<'a> for () {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDomTree<'a> for Option<()> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
VNode::Suspended
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a text VNode.
|
||||
///
|
||||
/// This is `dioxus`'s virtual DOM equivalent of `document.createTextVNode`.
|
||||
@@ -599,6 +614,26 @@ pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
|
||||
VNode::text(f)
|
||||
}
|
||||
|
||||
pub fn text3<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> VNode<'a> {
|
||||
// This is a cute little optimization
|
||||
//
|
||||
// We use format_args! on everything. However, not all textnodes have dynamic content, and thus are completely static.
|
||||
// we can just short-circuit to the &'static str version instead of having to allocate in the bump arena.
|
||||
//
|
||||
// In the most general case, this prevents the need for any string allocations for simple code IE:
|
||||
// div {"abc"}
|
||||
//
|
||||
match args.as_str() {
|
||||
Some(static_str) => VNode::text(static_str),
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(bump);
|
||||
s.write_fmt(args).unwrap();
|
||||
VNode::text(s.into_bump_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct an attribute for an element.
|
||||
///
|
||||
/// # Example
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::{cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
/// It's a placeholder over vnodes, to make working with lifetimes easier
|
||||
pub struct DomTree {
|
||||
// this should *never* be publicly accessible to external
|
||||
pub(crate) root: VNode<'static>,
|
||||
pub root: VNode<'static>,
|
||||
}
|
||||
|
||||
/// Tools for the base unit of the virtual dom - the VNode
|
||||
@@ -80,6 +80,9 @@ impl<'a> VNode<'a> {
|
||||
VNode::Suspended => {
|
||||
todo!()
|
||||
}
|
||||
// Self::PhantomChild { id } => {
|
||||
// todo!()
|
||||
// }
|
||||
VNode::Component(c) => c.key,
|
||||
}
|
||||
}
|
||||
@@ -218,6 +221,8 @@ pub struct VComponent<'src> {
|
||||
pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
||||
pub caller: Rc<dyn Fn(Context) -> DomTree + 'src>,
|
||||
|
||||
pub children: &'src [VNode<'src>],
|
||||
|
||||
// a pointer into the bump arena (given by the 'src lifetime)
|
||||
raw_props: *const (),
|
||||
|
||||
@@ -266,6 +271,7 @@ impl<'a> VComponent<'a> {
|
||||
user_fc: caller_ref,
|
||||
raw_props: props as *const P as *const _,
|
||||
_p: PhantomData,
|
||||
children: &[],
|
||||
caller,
|
||||
comparator: Rc::new(props_comparator),
|
||||
stable_addr: Rc::new(RefCell::new(None)),
|
||||
|
||||
@@ -482,7 +482,7 @@ pub struct Scope {
|
||||
|
||||
// IDs of children that this scope has created
|
||||
// This enables us to drop the children and their children when this scope is destroyed
|
||||
pub children: RefCell<HashSet<ScopeIdx>>,
|
||||
children: RefCell<HashSet<ScopeIdx>>,
|
||||
|
||||
// A reference to the list of components.
|
||||
// This lets us traverse the component list whenever we need to access our parent or children.
|
||||
@@ -595,6 +595,14 @@ impl Scope {
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
self.frames.next().bump.reset();
|
||||
|
||||
// Remove all the outdated listeners
|
||||
//
|
||||
self.listeners
|
||||
.try_borrow_mut()
|
||||
.ok()
|
||||
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
|
||||
.drain(..);
|
||||
|
||||
*self.hookidx.borrow_mut() = 0;
|
||||
|
||||
let caller = self
|
||||
@@ -621,7 +629,7 @@ impl Scope {
|
||||
let EventTrigger {
|
||||
listener_id, event, ..
|
||||
} = trigger;
|
||||
|
||||
//
|
||||
unsafe {
|
||||
// Convert the raw ptr into an actual object
|
||||
// This operation is assumed to be safe
|
||||
@@ -637,15 +645,6 @@ impl Scope {
|
||||
|
||||
// Run the callback with the user event
|
||||
listener_fn(event);
|
||||
|
||||
// drain all the event listeners
|
||||
// if we don't, then they'll stick around and become invalid
|
||||
// big big big big safety issue
|
||||
self.listeners
|
||||
.try_borrow_mut()
|
||||
.ok()
|
||||
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
|
||||
.drain(..);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -687,7 +686,7 @@ pub type Context<'src> = &'src Scope;
|
||||
|
||||
impl Scope {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
pub fn children(&self) -> &[VNode] {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
@@ -743,6 +742,17 @@ impl Scope {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render2<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
|
||||
&'scope self,
|
||||
lazy_nodes: LazyNodes<'scope, F>,
|
||||
) -> VNode<'scope> {
|
||||
let ctx = NodeCtx {
|
||||
scope_ref: self,
|
||||
listener_id: 0.into(),
|
||||
};
|
||||
lazy_nodes.into_vnode(&ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================
|
||||
@@ -863,60 +873,66 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// There are hooks going on here!
|
||||
pub fn use_context<T: 'static>(&self) -> Rc<T> {
|
||||
pub fn use_context<'a, T: 'static>(&'a self) -> &'a Rc<T> {
|
||||
self.try_use_context().unwrap()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn try_use_context<T: 'static>(&self) -> Result<Rc<T>> {
|
||||
let ty = TypeId::of::<T>();
|
||||
|
||||
let mut scope = Some(self);
|
||||
let cached_root = use_ref(self, || None as Option<Weak<T>>);
|
||||
|
||||
// Try to provide the cached version without having to re-climb the tree
|
||||
if let Some(ptr) = cached_root.borrow().as_ref() {
|
||||
if let Some(pt) = ptr.clone().upgrade() {
|
||||
return Ok(pt);
|
||||
} else {
|
||||
/*
|
||||
failed to upgrade the weak is strange
|
||||
this means the root dropped the context (the scope was killed)
|
||||
|
||||
The main idea here is to prevent memory leaks where parents should be cleaning up their own memory.
|
||||
|
||||
However, this behavior allows receivers/providers to move around in the hierarchy.
|
||||
This works because we climb the tree if upgrading the Rc failed.
|
||||
*/
|
||||
}
|
||||
/// Uses a context, storing the cached value around
|
||||
pub fn try_use_context<T: 'static>(&self) -> Result<&Rc<T>> {
|
||||
struct UseContextHook<C> {
|
||||
par: Option<Rc<C>>,
|
||||
we: Option<Weak<C>>,
|
||||
}
|
||||
|
||||
while let Some(inner) = scope {
|
||||
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
|
||||
let shared_contexts = inner.shared_contexts.borrow();
|
||||
if let Some(shared_ctx) = shared_contexts.get(&ty) {
|
||||
let rc = shared_ctx
|
||||
.downcast_ref()
|
||||
.expect("Should not fail, already validated the type from the hashmap");
|
||||
self.use_hook(
|
||||
move || UseContextHook {
|
||||
par: None as Option<Rc<T>>,
|
||||
we: None as Option<Weak<T>>,
|
||||
},
|
||||
move |hook| {
|
||||
let mut scope = Some(self);
|
||||
|
||||
*cached_root.borrow_mut() = Some(Rc::downgrade(&rc));
|
||||
return Ok(rc.clone());
|
||||
} else {
|
||||
match inner.parent {
|
||||
Some(parent_id) => {
|
||||
let parent = inner
|
||||
.arena_link
|
||||
.try_get(parent_id)
|
||||
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
|
||||
|
||||
scope = Some(parent);
|
||||
if let Some(we) = &hook.we {
|
||||
if let Some(re) = we.upgrade() {
|
||||
hook.par = Some(re);
|
||||
return Ok(hook.par.as_ref().unwrap());
|
||||
}
|
||||
None => return Err(Error::MissingSharedContext),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::MissingSharedContext)
|
||||
let ty = TypeId::of::<T>();
|
||||
while let Some(inner) = scope {
|
||||
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
|
||||
let shared_contexts = inner.shared_contexts.borrow();
|
||||
|
||||
if let Some(shared_ctx) = shared_contexts.get(&ty) {
|
||||
log::debug!("found matching ctx");
|
||||
let rc = shared_ctx
|
||||
.clone()
|
||||
.downcast::<T>()
|
||||
.expect("Should not fail, already validated the type from the hashmap");
|
||||
|
||||
hook.we = Some(Rc::downgrade(&rc));
|
||||
hook.par = Some(rc);
|
||||
return Ok(hook.par.as_ref().unwrap());
|
||||
} else {
|
||||
match inner.parent {
|
||||
Some(parent_id) => {
|
||||
let parent = inner
|
||||
.arena_link
|
||||
.try_get(parent_id)
|
||||
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
|
||||
|
||||
scope = Some(parent);
|
||||
}
|
||||
None => return Err(Error::MissingSharedContext),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::MissingSharedContext)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user