wip: portals

This commit is contained in:
Jonathan Kelley 2021-10-08 16:01:13 -04:00
parent 1f22a06a36
commit 4a0bb8cf84
9 changed files with 154 additions and 1027 deletions

View file

@ -0,0 +1,54 @@
# Subtrees
One way of extending the Dioxus VirtualDom is through the use of "Subtrees." Subtrees are chunks of the VirtualDom tree distinct from the rest of the tree. They still participate in event bubbling, diffing, etc, but will have a separate set of edits generated during the diff phase.
For a VirtualDom that has a root tree with two subtrees, the edits follow a pattern of:
Root
-> Tree 1
-> Tree 2
-> Original root tree
- Root edits
- Tree 1 Edits
- Tree 2 Edits
- Root Edits
The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom.
With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept.
The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows.
Subtrees also solve the "bridging" issues in React where two different renderers need two different VirtualDoms to work properly. In Dioxus, you only ever need one VirtualDom and the right renderer plugins.
## API
Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots.
```rust
fn Subtree<P>(cx: Context, props: P) -> DomTree {
}
fn Window() -> DomTree {
Subtree {
onassign: move |e| {
// create window
}
children()
}
}
fn 3dRenderer -> DomTree {
Subtree {
onassign: move |e| {
// initialize bevy
}
}
}
```

View file

@ -239,6 +239,46 @@ impl<'src> Context<'src> {
)
}
/// Create a new subtree with this scope as the root of the subtree.
///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
/// the mutations to the correct window/portal/subtree.
///
/// This method
///
/// # Example
///
/// ```rust
/// static App: FC<()> = |cx, props| {
/// let id = cx.get_current_subtree();
/// let id = cx.use_create_subtree();
/// subtree {
///
/// }
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
pub fn use_create_subtree(self) -> Option<u32> {
self.scope.new_subtree()
}
/// Get the subtree ID that this scope belongs to.
///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
/// the mutations to the correct window/portal/subtree.
///
/// # Example
///
/// ```rust
/// static App: FC<()> = |cx, props| {
/// let id = cx.get_current_subtree();
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
pub fn get_current_subtree(self) -> u32 {
self.scope.subtree()
}
/// Store a value between renders
///
/// This is *the* foundational hook for all other hooks.

View file

@ -352,12 +352,12 @@ impl<'bump> DiffMachine<'bump> {
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
let height = parent_scope.height + 1;
Scope::new(
caller,
new_idx,
Some(parent_idx),
height,
parent_scope.height + 1,
parent_scope.subtree(),
ScopeChildren(vcomponent.children),
shared,
)
@ -384,6 +384,17 @@ impl<'bump> DiffMachine<'bump> {
// Take the node that was just generated from running the component
let nextnode = new_component.frames.fin_head();
self.stack.create_component(new_idx, nextnode);
//
/*
tree_item {
}
*/
if new_component.is_subtree_root.get() {
self.stack.push_subtree();
}
}
// Finally, insert this scope as a seen node.

View file

@ -76,6 +76,13 @@ impl<'bump> DiffStack<'bump> {
}
}
pub fn push_subtree(&mut self) {
self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount {
and: MountType::Append,
});
}
pub fn push_nodes_created(&mut self, count: usize) {
self.nodes_created_stack.push(count);
}

File diff suppressed because it is too large Load diff

View file

@ -184,7 +184,6 @@ pub enum DomEdit<'bump> {
Remove {
root: u64,
},
CreateTextNode {
text: &'bump str,
root: u64,

View file

@ -86,6 +86,7 @@ use std::{
#[derive(Clone)]
pub(crate) struct EventChannel {
pub task_counter: Rc<Cell<u64>>,
pub cur_subtree: Rc<Cell<u32>>,
pub sender: UnboundedSender<SchedulerMsg>,
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
@ -178,8 +179,10 @@ impl Scheduler {
let heuristics = HeuristicsEngine::new();
let task_counter = Rc::new(Cell::new(0));
let cur_subtree = Rc::new(Cell::new(0));
let channel = EventChannel {
cur_subtree,
task_counter: task_counter.clone(),
sender: sender.clone(),
schedule_any_immediate: {

View file

@ -2,7 +2,7 @@ use crate::innerlude::*;
use fxhash::FxHashMap;
use std::{
any::{Any, TypeId},
cell::RefCell,
cell::{Cell, RefCell},
collections::HashMap,
future::Future,
pin::Pin,
@ -23,6 +23,8 @@ pub struct Scope {
pub(crate) parent_idx: Option<ScopeId>,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) subtree: Cell<u32>,
pub(crate) is_subtree_root: Cell<bool>,
// Nodes
pub(crate) frames: ActiveFrame,
@ -71,6 +73,36 @@ impl Scope {
self.frames.fin_head()
}
/// Get the subtree ID that this scope belongs to.
///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
/// the mutations to the correct window/portal/subtree.
///
///
/// # Example
///
/// ```rust
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
/// dom.rebuild();
///
/// let base = dom.base_scope();
///
/// assert_eq!(base.subtree(), 0);
/// ```
pub fn subtree(&self) -> u32 {
self.subtree.get()
}
pub(crate) fn new_subtree(&self) -> Option<u32> {
if self.is_subtree_root.get() {
None
} else {
let cur = self.shared.cur_subtree.get();
self.shared.cur_subtree.set(cur + 1);
Some(cur)
}
}
/// Get the height of this Scope - IE the number of scopes above it.
///
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@ -146,6 +178,7 @@ impl Scope {
our_arena_idx: ScopeId,
parent_idx: Option<ScopeId>,
height: u32,
subtree: u32,
child_nodes: ScopeChildren,
shared: EventChannel,
) -> Self {
@ -168,6 +201,8 @@ impl Scope {
parent_idx,
our_arena_idx,
height,
subtree: Cell::new(subtree),
is_subtree_root: Cell::new(false),
frames: ActiveFrame::new(),
hooks: Default::default(),

View file

@ -156,6 +156,7 @@ impl VirtualDom {
myidx,
None,
0,
0,
ScopeChildren(&[]),
scheduler.pool.channel.clone(),
)