wip: remove runner on hook and then update docs

This commit is contained in:
Jonathan Kelley committed 2022-01-02 02:15:04 -05:00
1 parent 7d41a16208
commit d1560450ba
95 files changed
+1339 -675

No files matched your search

+12 -12
View File
@@ -25,16 +25,16 @@ criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) {
static App: Component = |cx| {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
});
rsx!(cx, table {
tbody {
{rows}
(0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
})
}
})
};
@@ -58,12 +58,12 @@ fn Row(cx: Scope<RowProps>) -> Element {
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.props.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
a { class: "lbl", "{adj}" "{col}" "{noun}" }
}
td { class: "col-md-1"
a { class: "remove", onclick: move |_| {/* remove */}
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
td { class: "col-md-1",
a { class: "remove", onclick: move |_| {/* remove */},
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
}
}
td { class: "col-md-6" }
+3 -3
View File
@@ -12,7 +12,7 @@ fn main() {
}
fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
@@ -33,8 +33,8 @@ struct ChildProps<'a> {
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
div {
"it's nested {cx.props.name}"
{&cx.props.children}
"it's nested {cx.props.name}",
&cx.props.children
}
})
}
+1 -1
View File
@@ -10,7 +10,7 @@ fn main() {
}
fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
+12 -19
View File
@@ -473,9 +473,16 @@ impl<'bump> DiffState<'bump> {
// Check the most common cases first
// these are *actual* elements, not wrappers around lists
(Text(old), Text(new)) => {
self.diff_text_nodes(old, new, old_node, new_node);
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
(Placeholder(old), Placeholder(new)) => {
if let Some(root) = old.id.get() {
self.scopes.update_node(new_node, root);
@@ -483,10 +490,13 @@ impl<'bump> DiffState<'bump> {
}
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
// These two sets are pointers to nodes but are not actually nodes themselves
(Component(old), Component(new)) => {
self.diff_component_nodes(old_node, new_node, *old, *new)
}
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
// The normal pathway still works, but generates slightly weird instructions
@@ -506,23 +516,6 @@ impl<'bump> DiffState<'bump> {
}
}
fn diff_text_nodes(
&mut self,
old: &'bump VText<'bump>,
new: &'bump VText<'bump>,
_old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
}
fn diff_element_nodes(
&mut self,
old: &'bump VElement<'bump>,
+4 -4
View File
@@ -65,10 +65,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder,
VText, VirtualDom,
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator, Event,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement,
VFragment, VNode, VPlaceholder, VText, VirtualDom,
};
pub mod prelude {
+18 -12
View File
@@ -320,6 +320,18 @@ impl ScopeArena {
pub fn root_node(&self, id: ScopeId) -> &VNode {
self.fin_head(id)
}
// this is totally okay since all our nodes are always in a valid state
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
let ptr = self.nodes.borrow().get(id.0).cloned();
match ptr {
Some(ptr) => {
let node = unsafe { &*ptr };
Some(unsafe { extend_vnode(node) })
}
None => None,
}
}
}
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
@@ -620,7 +632,7 @@ impl ScopeState {
/// struct SharedState(&'static str);
///
/// static App: Component = |cx| {
/// cx.use_hook(|_| cx.provide_context(SharedState("world")), |_| {}, |_| {});
/// cx.use_hook(|_| cx.provide_context(SharedState("world")));
/// rsx!(cx, Child {})
/// }
///
@@ -711,17 +723,13 @@ impl ScopeState {
/// ```ignore
/// // use_ref is the simplest way of storing a value between renders
/// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
/// use_hook(
/// || Rc::new(RefCell::new(initial_value())),
/// |state| state,
/// )
/// use_hook(|| Rc::new(RefCell::new(initial_value())))
/// }
/// ```
pub fn use_hook<'src, State: 'static, Output: 'src>(
pub fn use_hook<'src, State: 'static>(
&'src self,
initializer: impl FnOnce(usize) -> State,
runner: impl FnOnce(&'src mut State) -> Output,
) -> Output {
) -> &'src mut State {
let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len();
@@ -731,7 +739,7 @@ impl ScopeState {
vals.push(self.hook_arena.alloc(initializer(hook_len)));
}
let state = vals
vals
.get(cur_idx)
.and_then(|inn| {
self.hook_idx.set(cur_idx + 1);
@@ -746,9 +754,7 @@ impl ScopeState {
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Functions prefixed with "use" should never be called conditionally.
"###,
);
runner(state)
)
}
/// The "work in progress frame" represents the frame that is currently being worked on.
+104
View File
@@ -260,6 +260,10 @@ impl VirtualDom {
self.channel.0.clone()
}
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
self.scopes.get_element(id)
}
/// Add a new message to the scheduler queue directly.
///
///
@@ -1003,3 +1007,103 @@ impl EmptyBuilder {
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
T::builder()
}
pub struct Event<T> {
data: Arc<T>,
_cancel: std::rc::Rc<std::cell::Cell<bool>>,
}
impl<T> std::ops::Deref for Event<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data.as_ref()
}
}
impl<T> Event<T> {
pub fn cancel(&self) {}
pub fn timestamp(&self) {}
pub fn triggered_element(&self) -> Option<Element> {
None
}
}
pub struct ElementIdIterator<'a> {
vdom: &'a VirtualDom,
// 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
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> ElementIdIterator<'a> {
pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
Self {
vdom,
stack: smallvec::smallvec![(0, node)],
}
}
}
impl<'a> Iterator for ElementIdIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
// println!("Found element! Returning it!");
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let _count = *count as usize;
if _count >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[_count]);
}
}
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
// todo!();
let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root_node();
}
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ fn test_borrowed_state() {
}
fn Parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
+1 -1
View File
@@ -25,7 +25,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_early_abort() {
const app: Component = |cx| {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
+3 -3
View File
@@ -46,7 +46,7 @@ fn manual_diffing() {
#[test]
fn events_generate() {
static App: Component = |cx| {
let count = cx.use_hook(|_| 0, |f| f);
let count = cx.use_hook(|_| 0);
let inner = match *count {
0 => {
@@ -105,7 +105,7 @@ fn events_generate() {
#[test]
fn components_generate() {
static App: Component = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
@@ -221,7 +221,7 @@ fn components_generate() {
#[test]
fn component_swap() {
static App: Component = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
+6 -6
View File
@@ -29,7 +29,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_memory_leak() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
@@ -37,7 +37,7 @@ fn test_memory_leak() {
return None;
}
let name = cx.use_hook(|_| String::from("asd"), |f| f);
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
@@ -86,7 +86,7 @@ fn test_memory_leak() {
#[test]
fn memo_works_properly() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
@@ -94,7 +94,7 @@ fn memo_works_properly() {
return None;
}
let name = cx.use_hook(|_| String::from("asd"), |f| f);
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
@@ -204,7 +204,7 @@ fn free_works_on_root_hooks() {
}
fn app(cx: Scope) -> Element {
let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f);
let name = cx.use_hook(|_| Droppable(String::from("asd")));
rsx!(cx, div { "{name.0}" })
}
@@ -216,7 +216,7 @@ fn free_works_on_root_hooks() {
fn old_props_arent_stale() {
fn app(cx: Scope) -> Element {
dbg!("rendering parent");
let cnt = cx.use_hook(|_| 0, |f| f);
let cnt = cx.use_hook(|_| 0);
*cnt += 1;
if *cnt == 1 {