From 0d44f009b0176cb9cf8203374cf534f5af7de63c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 18 May 2021 10:36:17 -0400 Subject: [PATCH] Feat: introduce children for walking down the tree --- packages/core/src/arena.rs | 18 +++- packages/core/src/events.rs | 2 +- packages/core/src/nodebuilder.rs | 2 +- packages/core/src/virtual_dom.rs | 40 +++++---- packages/web/examples/context.rs | 66 +++++++++++++++ packages/web/examples/derive.rs | 4 + packages/web/examples/todomvcsingle.rs | 110 ++++++++++++------------- 7 files changed, 165 insertions(+), 77 deletions(-) create mode 100644 packages/web/examples/context.rs create mode 100644 packages/web/examples/derive.rs diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 10c21eb87..bca37a866 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -29,12 +29,20 @@ impl ScopeArena { }))) } + /// 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> { - todo!() + let inner = unsafe { &*self.0.borrow().arena.get() }; + let scope = inner.get(idx); + scope.ok_or_else(|| Error::FatalInternal("Scope not found")) } + /// 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> { - todo!() + let inner = unsafe { &mut *self.0.borrow().arena.get() }; + let scope = inner.get_mut(idx); + scope.ok_or_else(|| Error::FatalInternal("Scope not found")) } fn inner(&self) -> &Arena { @@ -45,8 +53,12 @@ impl ScopeArena { todo!() } + /// THIS METHOD IS CURRENTLY UNSAFE + /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS pub fn with(&self, f: impl FnOnce(&mut Arena) -> T) -> Result { - todo!() + let inner = unsafe { &mut *self.0.borrow().arena.get() }; + Ok(f(inner)) + // todo!() } unsafe fn inner_unchecked<'s>() -> &'s mut Arena { diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index dbe48c144..03ea198c3 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -81,7 +81,7 @@ pub mod on { Listener { event: stringify!($name), id: *c.listener_id.borrow(), - scope: c.scope_ref.myidx, + scope: c.scope_ref.arena_idx, callback: bump.alloc(move |evt: VirtualEvent| match evt { VirtualEvent::$eventdata(event) => callback(event), _ => { diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 25646271f..491e655f5 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -342,7 +342,7 @@ where event, callback: bump.alloc(callback), id: *self.ctx.listener_id.borrow(), - scope: self.ctx.scope_ref.myidx, + scope: self.ctx.scope_ref.arena_idx, }; self.add_listener(listener) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 155c7810d..a9219904a 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -277,13 +277,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 component = self.components.try_get_mut(update.idx).unwrap(); + let mut cur_component = self.components.try_get_mut(update.idx).unwrap(); - component.run_scope()?; + cur_component.run_scope()?; - diff_machine.diff_node(component.old_frame(), component.next_frame()); + diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame()); - cur_height = component.height; + cur_height = cur_component.height; log::debug!( "Processing update: {:#?} with height {}", @@ -315,7 +315,7 @@ impl VirtualDom { Scope::new( caller, f, - None, + Some(cur_component.arena_idx), cur_height + 1, self.event_queue.clone(), self.components.clone(), @@ -323,20 +323,23 @@ impl VirtualDom { }) })?; + cur_component.children.borrow_mut().insert(idx); + // Grab out that component - let component = self.components.try_get_mut(idx).unwrap(); + let new_component = self.components.try_get_mut(idx).unwrap(); // Actually initialize the caller's slot with the right address *stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx); // Run the scope for one iteration to initialize it - component.run_scope()?; + new_component.run_scope()?; // Navigate the diff machine to the right point in the output dom diff_machine.change_list.load_known_root(id); // And then run the diff algorithm - diff_machine.diff_node(component.old_frame(), component.next_frame()); + diff_machine + .diff_node(new_component.old_frame(), new_component.next_frame()); // Finally, insert this node as a seen node. seen_nodes.insert(idx); @@ -439,6 +442,8 @@ pub struct Scope { // The parent's scope ID pub parent: Option, + pub children: RefCell>, + // A reference to the list of components. // This lets us traverse the component list whenever we need to access our parent or children. arena_link: ScopeArena, @@ -446,7 +451,7 @@ pub struct Scope { pub shared_contexts: RefCell>>, // Our own ID accessible from the component map - pub myidx: ScopeIdx, + pub arena_idx: ScopeIdx, pub height: u32, @@ -521,8 +526,9 @@ impl Scope { frames: ActiveFrame::new(), listeners: Default::default(), hookidx: Default::default(), + children: Default::default(), parent, - myidx, + arena_idx: myidx, height, event_queue, arena_link, @@ -549,6 +555,8 @@ impl Scope { // This breaks any latent references, invalidating every pointer referencing into it. self.frames.next().bump.reset(); + *self.hookidx.borrow_mut() = 0; + let caller = self .caller .upgrade() @@ -777,23 +785,24 @@ impl Scope { let mut ctxs = self.shared_contexts.borrow_mut(); let ty = TypeId::of::(); - let initialized = self.use_hook( + let is_initialized = self.use_hook( || false, |s| { - let i = *s; + let i = s.clone(); *s = true; i }, |_| {}, ); - match (initialized, ctxs.contains_key(&ty)) { + match (is_initialized, ctxs.contains_key(&ty)) { // Do nothing, already initialized and already exists (true, true) => {} // Needs to be initialized (false, false) => { - ctxs.insert(ty, Rc::new(init())).unwrap(); + log::debug!("Initializing context..."); + ctxs.insert(ty, Rc::new(init())); } (false, true) => panic!("Cannot initialize two contexts of the same type"), @@ -807,6 +816,7 @@ impl Scope { let mut scope = Some(self); 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) { return Ok(shared_ctx.clone().downcast().unwrap()); @@ -849,7 +859,7 @@ impl EventQueue { let inner = self.clone(); let marker = HeightMarker { height: source.height, - idx: source.myidx, + idx: source.arena_idx, }; move || inner.0.as_ref().borrow_mut().push(marker) } diff --git a/packages/web/examples/context.rs b/packages/web/examples/context.rs new file mode 100644 index 000000000..aec76a9f6 --- /dev/null +++ b/packages/web/examples/context.rs @@ -0,0 +1,66 @@ + +use std::fmt::Display; + +use dioxus::{events::on::MouseEvent, prelude::*}; +use dioxus_core as dioxus; +use dioxus_web::WebsysRenderer; + +fn main() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Trace)); + console_error_panic_hook::set_once(); + + wasm_bindgen_futures::spawn_local(async { + WebsysRenderer::new_with_props(Example, ()) + .run() + .await + .unwrap() + }); +} + + +#[derive(Debug)] +struct CustomContext([&'static str; 3]); + + +static Example: FC<()> = |ctx, props| { + ctx.create_context(|| CustomContext(["Jack", "Jill", "Bob"])); + + let names = ctx.use_context::(); + // let name = names.0[props.id as usize]; + + ctx.render(rsx! { + div { + class: "py-12 px-4 text-center w-full max-w-2xl mx-auto" + span { + class: "text-sm font-semibold" + "Dioxus Example: Jack and Jill" + } + h2 { + class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading" + "Hello" + } + + CustomButton { id: 0 } + CustomButton { id: 1 } + CustomButton { id: 2 } + } + }) +}; + + +#[derive(Props, PartialEq)] +struct ButtonProps { + id: u8, +} + +fn CustomButton<'b, 'a,>(ctx: Context<'a>, props: &'b ButtonProps) -> DomTree { + let names = ctx.use_context::(); + let name = names.0[props.id as usize]; + + ctx.render(rsx!{ + button { + class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" + "{name}" + } + }) +} diff --git a/packages/web/examples/derive.rs b/packages/web/examples/derive.rs new file mode 100644 index 000000000..53ad28129 --- /dev/null +++ b/packages/web/examples/derive.rs @@ -0,0 +1,4 @@ +use dioxus::{events::on::MouseEvent, prelude::*}; +use dioxus_core as dioxus; +use dioxus_web::WebsysRenderer; +fn main() {} diff --git a/packages/web/examples/todomvcsingle.rs b/packages/web/examples/todomvcsingle.rs index 1e39d889d..1897f6e0f 100644 --- a/packages/web/examples/todomvcsingle.rs +++ b/packages/web/examples/todomvcsingle.rs @@ -1,68 +1,24 @@ +#![allow(non_snake_case)] use dioxus_core::prelude::*; use dioxus_web::WebsysRenderer; static APP_STYLE: &'static str = include_str!("./todomvc/style.css"); -fn main() { - wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); -} -// ======================= -// state-related items -// ======================= pub static TODOS: AtomFamily = atom_family(|_| {}); pub static FILTER: Atom = atom(|_| FilterState::All); pub static SHOW_ALL_TODOS: selector = selector(|g| g.getter(|f| false)); -#[derive(PartialEq)] -pub enum FilterState { - All, - Active, - Completed, -} - -#[derive(Debug, PartialEq, Clone)] -pub struct TodoItem { - pub id: uuid::Uuid, - pub checked: bool, - pub contents: String, -} - -impl RecoilContext<()> { - pub fn add_todo(&self, contents: String) {} - pub fn remove_todo(&self, id: &uuid::Uuid) {} - pub fn select_all_todos(&self) {} - pub fn toggle_todo(&self, id: &uuid::Uuid) {} - pub fn clear_completed(&self) {} - pub fn set_filter(&self, filter: &FilterState) {} -} - -// ======================= -// Components -// ======================= -pub fn App(ctx: Context, props: &()) -> DomTree { - ctx.render(rsx! { - div { - id: "app" - style { "{APP_STYLE}" } - - // list - TodoList {} - - // footer - footer { - class: "info" - p {"Double-click to edit a todo"} - p { - "Created by " - a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" } - } - p { - "Part of " - a { "TodoMVC", href: "http://todomvc.com" } - } +fn main() { + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, props| { + ctx.render(rsx! { + div { + id: "app", + style { "{APP_STYLE}" } + TodoList {} + Footer {} } - } - }) + }) + })); } pub fn TodoList(ctx: Context, props: &()) -> DomTree { @@ -125,11 +81,11 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree { type: "checkbox" "{todo.checked}" } - {is_editing.then(|| rsx!( + {is_editing.then(|| rsx!( input { value: "{contents}" } - ))} + ))} } )) } @@ -175,6 +131,46 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree { }) } +pub fn Footer(ctx: Context, props: &()) -> DomTree { + ctx.render(rsx! { + footer { + class: "info" + p {"Double-click to edit a todo"} + p { + "Created by " + a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" } + } + p { + "Part of " + a { "TodoMVC", href: "http://todomvc.com" } + } + } + }) +} + +#[derive(PartialEq)] +pub enum FilterState { + All, + Active, + Completed, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct TodoItem { + pub id: uuid::Uuid, + pub checked: bool, + pub contents: String, +} + +impl RecoilContext<()> { + pub fn add_todo(&self, contents: String) {} + pub fn remove_todo(&self, id: &uuid::Uuid) {} + pub fn select_all_todos(&self) {} + pub fn toggle_todo(&self, id: &uuid::Uuid) {} + pub fn clear_completed(&self) {} + pub fn set_filter(&self, filter: &FilterState) {} +} + pub use recoil::*; mod recoil { use dioxus_core::context::Context;