wip: bubbling
This commit is contained in:
1 parent
c16b92ee7e
commit
7978a17419
12 files changed
+385
-363
No files matched your search
+27
-20
@@ -216,11 +216,6 @@ impl<'bump> DiffMachine<'bump> {
|
||||
MountType::Absorb => {
|
||||
self.stack.add_child_count(nodes_created);
|
||||
}
|
||||
MountType::Append => {
|
||||
self.mutations.edits.push(AppendChildren {
|
||||
many: nodes_created as u32,
|
||||
});
|
||||
}
|
||||
|
||||
MountType::Replace { old } => {
|
||||
if let Some(old_id) = old.try_mounted_id() {
|
||||
@@ -240,6 +235,12 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
MountType::Append => {
|
||||
self.mutations.edits.push(AppendChildren {
|
||||
many: nodes_created as u32,
|
||||
});
|
||||
}
|
||||
|
||||
MountType::InsertAfter { other_node } => {
|
||||
let root = self.find_last_element(other_node).unwrap();
|
||||
self.mutations.insert_after(root, nodes_created as u32);
|
||||
@@ -258,24 +259,24 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
fn create_node(&mut self, node: &'bump VNode<'bump>) {
|
||||
match node {
|
||||
VNode::Text(vtext) => self.create_text_node(vtext),
|
||||
VNode::Suspended(suspended) => self.create_suspended_node(suspended),
|
||||
VNode::Anchor(anchor) => self.create_anchor_node(anchor),
|
||||
VNode::Element(element) => self.create_element_node(element),
|
||||
VNode::Text(vtext) => self.create_text_node(vtext, node),
|
||||
VNode::Suspended(suspended) => self.create_suspended_node(suspended, node),
|
||||
VNode::Anchor(anchor) => self.create_anchor_node(anchor, node),
|
||||
VNode::Element(element) => self.create_element_node(element, node),
|
||||
VNode::Fragment(frag) => self.create_fragment_node(frag),
|
||||
VNode::Component(component) => self.create_component_node(component),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_text_node(&mut self, vtext: &'bump VText<'bump>) {
|
||||
let real_id = self.vdom.reserve_node();
|
||||
fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) {
|
||||
let real_id = self.vdom.reserve_node(node);
|
||||
self.mutations.create_text_node(vtext.text, real_id);
|
||||
vtext.dom_id.set(Some(real_id));
|
||||
self.stack.add_child_count(1);
|
||||
}
|
||||
|
||||
fn create_suspended_node(&mut self, suspended: &'bump VSuspended) {
|
||||
let real_id = self.vdom.reserve_node();
|
||||
fn create_suspended_node(&mut self, suspended: &'bump VSuspended, node: &'bump VNode<'bump>) {
|
||||
let real_id = self.vdom.reserve_node(node);
|
||||
self.mutations.create_placeholder(real_id);
|
||||
|
||||
suspended.dom_id.set(Some(real_id));
|
||||
@@ -284,14 +285,14 @@ impl<'bump> DiffMachine<'bump> {
|
||||
self.attach_suspended_node_to_scope(suspended);
|
||||
}
|
||||
|
||||
fn create_anchor_node(&mut self, anchor: &'bump VAnchor) {
|
||||
let real_id = self.vdom.reserve_node();
|
||||
fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) {
|
||||
let real_id = self.vdom.reserve_node(node);
|
||||
self.mutations.create_placeholder(real_id);
|
||||
anchor.dom_id.set(Some(real_id));
|
||||
self.stack.add_child_count(1);
|
||||
}
|
||||
|
||||
fn create_element_node(&mut self, element: &'bump VElement<'bump>) {
|
||||
fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) {
|
||||
let VElement {
|
||||
tag_name,
|
||||
listeners,
|
||||
@@ -302,7 +303,8 @@ impl<'bump> DiffMachine<'bump> {
|
||||
..
|
||||
} = element;
|
||||
|
||||
let real_id = self.vdom.reserve_node();
|
||||
let real_id = self.vdom.reserve_node(node);
|
||||
|
||||
dom_id.set(Some(real_id));
|
||||
|
||||
self.mutations.create_element(tag_name, *namespace, real_id);
|
||||
@@ -398,7 +400,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
||||
(Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
|
||||
(Suspended(old), Suspended(new)) => self.diff_suspended_nodes(old, new),
|
||||
(Element(old), Element(new)) => self.diff_element_nodes(old, new),
|
||||
(Element(old), Element(new)) => self.diff_element_nodes(old, new, new_node),
|
||||
|
||||
// Anything else is just a basic replace and create
|
||||
(
|
||||
@@ -422,7 +424,12 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
|
||||
fn diff_element_nodes(
|
||||
&mut self,
|
||||
old: &'bump VElement<'bump>,
|
||||
new: &'bump VElement<'bump>,
|
||||
new_node: &'bump VNode<'bump>,
|
||||
) {
|
||||
let root = old.dom_id.get();
|
||||
|
||||
// If the element type is completely different, the element needs to be re-rendered completely
|
||||
@@ -438,7 +445,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
el: old.dom_id.get(),
|
||||
},
|
||||
});
|
||||
self.create_element_node(new);
|
||||
self.create_element_node(new, new_node);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ pub(crate) struct DiffStack<'bump> {
|
||||
instructions: Vec<DiffInstruction<'bump>>,
|
||||
nodes_created_stack: SmallVec<[usize; 10]>,
|
||||
pub scope_stack: SmallVec<[ScopeId; 5]>,
|
||||
pub element_id_stack: SmallVec<[ElementId; 5]>,
|
||||
}
|
||||
|
||||
impl<'bump> DiffStack<'bump> {
|
||||
@@ -47,6 +48,7 @@ impl<'bump> DiffStack<'bump> {
|
||||
instructions: Vec::with_capacity(1000),
|
||||
nodes_created_stack: smallvec![],
|
||||
scope_stack: smallvec![],
|
||||
element_id_stack: smallvec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +82,10 @@ impl<'bump> DiffStack<'bump> {
|
||||
self.nodes_created_stack.push(count);
|
||||
}
|
||||
|
||||
pub fn push_element_id(&mut self, id: ElementId) {
|
||||
self.element_id_stack.push(id);
|
||||
}
|
||||
|
||||
pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
|
||||
self.nodes_created_stack.push(0);
|
||||
self.instructions.push(DiffInstruction::Mount { and });
|
||||
|
||||
@@ -76,6 +76,118 @@ impl std::fmt::Debug for SyntheticEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Priority of Event Triggers.
|
||||
///
|
||||
/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
|
||||
/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
|
||||
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
|
||||
///
|
||||
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
|
||||
///
|
||||
/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
|
||||
/// we keep it simple, and just use a 3-tier priority system.
|
||||
///
|
||||
/// - NoPriority = 0
|
||||
/// - LowPriority = 1
|
||||
/// - NormalPriority = 2
|
||||
/// - UserBlocking = 3
|
||||
/// - HighPriority = 4
|
||||
/// - ImmediatePriority = 5
|
||||
///
|
||||
/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
|
||||
/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
|
||||
/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
||||
pub enum EventPriority {
|
||||
/// Work that must be completed during the EventHandler phase.
|
||||
///
|
||||
/// Currently this is reserved for controlled inputs.
|
||||
Immediate = 3,
|
||||
|
||||
/// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
|
||||
///
|
||||
/// This is typically reserved for things like user interaction.
|
||||
///
|
||||
/// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
|
||||
High = 2,
|
||||
|
||||
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
|
||||
/// than "High Priority" events and will take presedence over low priority events.
|
||||
///
|
||||
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
|
||||
///
|
||||
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
|
||||
Medium = 1,
|
||||
|
||||
/// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
|
||||
/// advanced to the front of the work queue until completed.
|
||||
///
|
||||
/// The primary user of Low Priority work is the asynchronous work system (suspense).
|
||||
///
|
||||
/// This is considered "idle" work or "background" work.
|
||||
Low = 0,
|
||||
}
|
||||
|
||||
pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
|
||||
use EventPriority::*;
|
||||
|
||||
match event.name {
|
||||
// clipboard
|
||||
"copy" | "cut" | "paste" => (true, Medium),
|
||||
|
||||
// Composition
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => (true, Low),
|
||||
|
||||
// Keyboard
|
||||
"keydown" | "keypress" | "keyup" => (true, High),
|
||||
|
||||
// Focus
|
||||
"focus" | "blur" => (true, Low),
|
||||
|
||||
// Form
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => (true, Medium),
|
||||
|
||||
// Mouse
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High),
|
||||
|
||||
"mousemove" => (false, Medium),
|
||||
|
||||
// Pointer
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
(true, Medium)
|
||||
}
|
||||
|
||||
// Selection
|
||||
"select" | "touchcancel" | "touchend" => (true, Medium),
|
||||
|
||||
// Touch
|
||||
"touchmove" | "touchstart" => (true, Medium),
|
||||
|
||||
// Wheel
|
||||
"scroll" | "wheel" => (false, Medium),
|
||||
|
||||
// Media
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
|
||||
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
|
||||
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
| "timeupdate" | "volumechange" | "waiting" => (true, Medium),
|
||||
|
||||
// Animation
|
||||
"animationstart" | "animationend" | "animationiteration" => (true, Medium),
|
||||
|
||||
// Transition
|
||||
"transitionend" => (true, Medium),
|
||||
|
||||
// Toggle
|
||||
"toggle" => (true, Medium),
|
||||
|
||||
_ => (true, Low),
|
||||
}
|
||||
}
|
||||
|
||||
pub mod on {
|
||||
//! This module defines the synthetic events that all Dioxus apps enable. No matter the platform, every dioxus renderer
|
||||
//! will implement the same events and same behavior (bubbling, cancelation, etc).
|
||||
|
||||
@@ -24,8 +24,10 @@ pub mod hooklist;
|
||||
pub mod hooks;
|
||||
pub mod mutations;
|
||||
pub mod nodes;
|
||||
pub mod resources;
|
||||
pub mod scheduler;
|
||||
pub mod scope;
|
||||
pub mod tasks;
|
||||
pub mod test_dom;
|
||||
pub mod util;
|
||||
pub mod virtual_dom;
|
||||
@@ -43,8 +45,10 @@ pub(crate) mod innerlude {
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
pub(crate) use crate::resources::*;
|
||||
pub use crate::scheduler::*;
|
||||
pub use crate::scope::*;
|
||||
pub use crate::tasks::*;
|
||||
pub use crate::test_dom::*;
|
||||
pub use crate::util::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
@@ -58,7 +62,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::innerlude::{
|
||||
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
|
||||
LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId, SuspendedContext,
|
||||
SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
|
||||
SyntheticEvent, TaskHandle, TestDom, UserEvent, VNode, VirtualDom, FC,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
|
||||
@@ -188,6 +188,8 @@ pub struct VElement<'a> {
|
||||
|
||||
pub dom_id: Cell<Option<ElementId>>,
|
||||
|
||||
pub parent_id: Cell<Option<ElementId>>,
|
||||
|
||||
pub listeners: &'a [Listener<'a>],
|
||||
|
||||
pub attributes: &'a [Attribute<'a>],
|
||||
@@ -415,6 +417,7 @@ impl<'a> NodeFactory<'a> {
|
||||
attributes,
|
||||
children,
|
||||
dom_id: empty_cell(),
|
||||
parent_id: empty_cell(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use crate::innerlude::*;
|
||||
use slab::Slab;
|
||||
|
||||
use std::{cell::UnsafeCell, rc::Rc};
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ResourcePool {
|
||||
/*
|
||||
This *has* to be an UnsafeCell.
|
||||
|
||||
Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
|
||||
its bumpframe conents immutably.
|
||||
|
||||
However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
|
||||
simple to audit.
|
||||
|
||||
Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
|
||||
*/
|
||||
pub components: Rc<UnsafeCell<Slab<Scope>>>,
|
||||
|
||||
/*
|
||||
Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
|
||||
that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client.
|
||||
|
||||
This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse.
|
||||
|
||||
In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes...
|
||||
*/
|
||||
pub raw_elements: Rc<UnsafeCell<Slab<*const VNode<'static>>>>,
|
||||
|
||||
pub channel: EventChannel,
|
||||
}
|
||||
|
||||
impl ResourcePool {
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
|
||||
let inner = unsafe { &*self.components.get() };
|
||||
inner.get(idx.0)
|
||||
}
|
||||
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
inner.get_mut(idx.0)
|
||||
}
|
||||
|
||||
// return a bumpframe with a lifetime attached to the arena borrow
|
||||
// this is useful for merging lifetimes
|
||||
pub fn with_scope_vnode<'b>(
|
||||
&self,
|
||||
_id: ScopeId,
|
||||
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
||||
) -> Option<&VNode<'b>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
Some(inner.remove(id.0))
|
||||
// .try_remove(id.0)
|
||||
// .ok_or_else(|| Error::FatalInternal("Scope not found"))
|
||||
}
|
||||
|
||||
pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
|
||||
let els = unsafe { &mut *self.raw_elements.get() };
|
||||
let entry = els.vacant_entry();
|
||||
let key = entry.key();
|
||||
let id = ElementId(key);
|
||||
let node = node as *const _;
|
||||
let node = unsafe { std::mem::transmute(node) };
|
||||
entry.insert(node);
|
||||
id
|
||||
}
|
||||
|
||||
/// return the id, freeing the space of the original node
|
||||
pub fn collect_garbage(&self, id: ElementId) {
|
||||
todo!("garabge collection currently WIP")
|
||||
// self.raw_elements.remove(id.0);
|
||||
}
|
||||
|
||||
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
|
||||
let g = unsafe { &mut *self.components.get() };
|
||||
let entry = g.vacant_entry();
|
||||
let id = ScopeId(entry.key());
|
||||
entry.insert(f(id));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn borrow_bumpframe(&self) {}
|
||||
}
|
||||
+21
-341
@@ -71,18 +71,14 @@ For the rest, we defer to the rIC period and work down each queue from high to l
|
||||
use crate::heuristics::*;
|
||||
use crate::innerlude::*;
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::stream::FuturesUnordered;
|
||||
use futures_util::{future::FusedFuture, pin_mut, Future, FutureExt, StreamExt};
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use fxhash::FxHashSet;
|
||||
use indexmap::IndexSet;
|
||||
use slab::Slab;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell, RefMut, UnsafeCell},
|
||||
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque},
|
||||
fmt::Display,
|
||||
pin::Pin,
|
||||
cell::{Cell, UnsafeCell},
|
||||
collections::{HashSet, VecDeque},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -142,9 +138,8 @@ pub(crate) struct Scheduler {
|
||||
// In-flight futures
|
||||
pub async_tasks: FuturesUnordered<FiberTask>,
|
||||
|
||||
// scheduler stuff
|
||||
pub current_priority: EventPriority,
|
||||
|
||||
// // scheduler stuff
|
||||
// pub current_priority: EventPriority,
|
||||
pub ui_events: VecDeque<UserEvent>,
|
||||
|
||||
pub pending_immediates: VecDeque<ScopeId>,
|
||||
@@ -155,7 +150,7 @@ pub(crate) struct Scheduler {
|
||||
|
||||
pub garbage_scopes: HashSet<ScopeId>,
|
||||
|
||||
pub lanes: [PriorityLane; 4],
|
||||
pub lane: PriorityLane,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
@@ -239,15 +234,8 @@ impl Scheduler {
|
||||
|
||||
garbage_scopes: HashSet::new(),
|
||||
|
||||
current_priority: EventPriority::Low,
|
||||
|
||||
// sorted high to low by priority (0 = immediate, 3 = low)
|
||||
lanes: [
|
||||
PriorityLane::new(),
|
||||
PriorityLane::new(),
|
||||
PriorityLane::new(),
|
||||
PriorityLane::new(),
|
||||
],
|
||||
// current_priority: EventPriority::Low,
|
||||
lane: PriorityLane::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,25 +284,6 @@ impl Scheduler {
|
||||
}
|
||||
|
||||
fn prepare_work(&mut self) {
|
||||
// consume all events that are "continuous" to be batched
|
||||
// if we run into a discrete event, then bail early
|
||||
|
||||
self.current_priority = match (
|
||||
self.lanes[0].has_work(),
|
||||
self.lanes[1].has_work(),
|
||||
self.lanes[2].has_work(),
|
||||
self.lanes[3].has_work(),
|
||||
) {
|
||||
(true, _, _, _) => EventPriority::Immediate,
|
||||
(false, true, _, _) => EventPriority::High,
|
||||
(false, false, true, _) => EventPriority::Medium,
|
||||
(false, false, false, true) => EventPriority::Low,
|
||||
(false, false, false, false) => {
|
||||
// no work to do, process events
|
||||
EventPriority::Low
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(trigger) = self.ui_events.pop_back() {
|
||||
if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
|
||||
}
|
||||
@@ -322,8 +291,7 @@ impl Scheduler {
|
||||
|
||||
// nothing to do, no events on channels, no work
|
||||
pub fn has_any_work(&self) -> bool {
|
||||
let pending_lanes = self.lanes.iter().find(|f| f.has_work()).is_some();
|
||||
pending_lanes || self.has_pending_events()
|
||||
self.lane.has_work() || self.has_pending_events()
|
||||
}
|
||||
|
||||
pub fn has_pending_events(&self) -> bool {
|
||||
@@ -333,36 +301,19 @@ impl Scheduler {
|
||||
/// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
|
||||
fn balance_lanes(&mut self) {}
|
||||
|
||||
fn load_current_lane(&mut self) -> &mut PriorityLane {
|
||||
match self.current_priority {
|
||||
EventPriority::Immediate => &mut self.lanes[0],
|
||||
EventPriority::High => &mut self.lanes[1],
|
||||
EventPriority::Medium => &mut self.lanes[2],
|
||||
EventPriority::Low => &mut self.lanes[3],
|
||||
}
|
||||
}
|
||||
|
||||
fn save_work(&mut self, lane: SavedDiffWork) {
|
||||
let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
|
||||
self.load_current_lane().saved_state = Some(saved);
|
||||
self.lane.saved_state = Some(saved);
|
||||
}
|
||||
|
||||
fn load_work(&mut self) -> SavedDiffWork<'static> {
|
||||
match self.current_priority {
|
||||
EventPriority::Immediate => todo!(),
|
||||
EventPriority::High => todo!(),
|
||||
EventPriority::Medium => todo!(),
|
||||
EventPriority::Low => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_lane(&mut self) -> &mut PriorityLane {
|
||||
match self.current_priority {
|
||||
EventPriority::Immediate => &mut self.lanes[0],
|
||||
EventPriority::High => &mut self.lanes[1],
|
||||
EventPriority::Medium => &mut self.lanes[2],
|
||||
EventPriority::Low => &mut self.lanes[3],
|
||||
}
|
||||
// match self.current_priority {
|
||||
// EventPriority::Immediate => todo!(),
|
||||
// EventPriority::High => todo!(),
|
||||
// EventPriority::Medium => todo!(),
|
||||
// EventPriority::Low => todo!(),
|
||||
// }
|
||||
unsafe { self.lane.saved_state.take().unwrap().extend() }
|
||||
}
|
||||
|
||||
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
|
||||
@@ -384,17 +335,6 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
|
||||
todo!()
|
||||
// match priority {
|
||||
// EventPriority::High => self.high_priorty.dirty_scopes.insert(scope),
|
||||
// EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope),
|
||||
// EventPriority::Low => self.low_priority.dirty_scopes.insert(scope),
|
||||
// };
|
||||
}
|
||||
|
||||
async fn wait_for_any_work(&mut self) {}
|
||||
|
||||
/// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
|
||||
///
|
||||
/// Returns true if the lane is finished before the deadline could be met.
|
||||
@@ -414,13 +354,13 @@ impl Scheduler {
|
||||
if machine.stack.is_empty() {
|
||||
let shared = self.pool.clone();
|
||||
|
||||
self.current_lane().dirty_scopes.sort_by(|a, b| {
|
||||
self.lane.dirty_scopes.sort_by(|a, b| {
|
||||
let h1 = shared.get_scope(*a).unwrap().height;
|
||||
let h2 = shared.get_scope(*b).unwrap().height;
|
||||
h1.cmp(&h2)
|
||||
});
|
||||
|
||||
if let Some(scope) = self.current_lane().dirty_scopes.pop() {
|
||||
if let Some(scope) = self.lane.dirty_scopes.pop() {
|
||||
let component = self.pool.get_scope(scope).unwrap();
|
||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
@@ -437,7 +377,7 @@ impl Scheduler {
|
||||
false
|
||||
} else {
|
||||
for node in saved.seen_scopes.drain() {
|
||||
self.current_lane().dirty_scopes.remove(&node);
|
||||
self.lane.dirty_scopes.remove(&node);
|
||||
}
|
||||
|
||||
let mut new_mutations = Mutations::new();
|
||||
@@ -594,8 +534,6 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
impl Scheduler {}
|
||||
|
||||
pub(crate) struct PriorityLane {
|
||||
pub dirty_scopes: IndexSet<ScopeId>,
|
||||
pub saved_state: Option<SavedDiffWork<'static>>,
|
||||
@@ -614,262 +552,4 @@ impl PriorityLane {
|
||||
fn has_work(&self) -> bool {
|
||||
self.dirty_scopes.len() > 0 || self.in_progress == true
|
||||
}
|
||||
|
||||
fn work(&mut self) {
|
||||
let scope = self.dirty_scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskHandle {
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) our_id: u64,
|
||||
}
|
||||
|
||||
impl TaskHandle {
|
||||
/// Toggles this coroutine off/on.
|
||||
///
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn toggle(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn resume(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ResumeTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn stop(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn restart(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
/// unmounted, then the `ScopeId` will be reused for a new component.
|
||||
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ScopeId(pub usize);
|
||||
|
||||
/// An Element's unique identifier.
|
||||
///
|
||||
/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
/// unmounted, then the `ElementId` will be reused for a new component.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ElementId(pub usize);
|
||||
impl Display for ElementId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementId {
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
/// Priority of Event Triggers.
|
||||
///
|
||||
/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
|
||||
/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
|
||||
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
|
||||
///
|
||||
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
|
||||
///
|
||||
/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
|
||||
/// we keep it simple, and just use a 3-tier priority system.
|
||||
///
|
||||
/// - NoPriority = 0
|
||||
/// - LowPriority = 1
|
||||
/// - NormalPriority = 2
|
||||
/// - UserBlocking = 3
|
||||
/// - HighPriority = 4
|
||||
/// - ImmediatePriority = 5
|
||||
///
|
||||
/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
|
||||
/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
|
||||
/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
||||
pub enum EventPriority {
|
||||
/// Work that must be completed during the EventHandler phase.
|
||||
///
|
||||
/// Currently this is reserved for controlled inputs.
|
||||
Immediate = 3,
|
||||
|
||||
/// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
|
||||
///
|
||||
/// This is typically reserved for things like user interaction.
|
||||
///
|
||||
/// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
|
||||
High = 2,
|
||||
|
||||
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
|
||||
/// than "High Priority" events and will take presedence over low priority events.
|
||||
///
|
||||
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
|
||||
///
|
||||
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
|
||||
Medium = 1,
|
||||
|
||||
/// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
|
||||
/// advanced to the front of the work queue until completed.
|
||||
///
|
||||
/// The primary user of Low Priority work is the asynchronous work system (suspense).
|
||||
///
|
||||
/// This is considered "idle" work or "background" work.
|
||||
Low = 0,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ResourcePool {
|
||||
/*
|
||||
This *has* to be an UnsafeCell.
|
||||
|
||||
Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
|
||||
its bumpframe conents immutably.
|
||||
|
||||
However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
|
||||
simple to audit.
|
||||
|
||||
Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
|
||||
*/
|
||||
pub components: Rc<UnsafeCell<Slab<Scope>>>,
|
||||
|
||||
/*
|
||||
Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
|
||||
that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client.
|
||||
|
||||
This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse.
|
||||
|
||||
In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes...
|
||||
*/
|
||||
pub raw_elements: Rc<UnsafeCell<Slab<()>>>,
|
||||
|
||||
pub channel: EventChannel,
|
||||
}
|
||||
|
||||
impl ResourcePool {
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
|
||||
let inner = unsafe { &*self.components.get() };
|
||||
inner.get(idx.0)
|
||||
}
|
||||
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
inner.get_mut(idx.0)
|
||||
}
|
||||
|
||||
// return a bumpframe with a lifetime attached to the arena borrow
|
||||
// this is useful for merging lifetimes
|
||||
pub fn with_scope_vnode<'b>(
|
||||
&self,
|
||||
_id: ScopeId,
|
||||
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
||||
) -> Option<&VNode<'b>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
Some(inner.remove(id.0))
|
||||
// .try_remove(id.0)
|
||||
// .ok_or_else(|| Error::FatalInternal("Scope not found"))
|
||||
}
|
||||
|
||||
pub fn reserve_node(&self) -> ElementId {
|
||||
let els = unsafe { &mut *self.raw_elements.get() };
|
||||
ElementId(els.insert(()))
|
||||
}
|
||||
|
||||
/// return the id, freeing the space of the original node
|
||||
pub fn collect_garbage(&self, id: ElementId) {
|
||||
todo!("garabge collection currently WIP")
|
||||
// self.raw_elements.remove(id.0);
|
||||
}
|
||||
|
||||
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
|
||||
let g = unsafe { &mut *self.components.get() };
|
||||
let entry = g.vacant_entry();
|
||||
let id = ScopeId(entry.key());
|
||||
entry.insert(f(id));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn borrow_bumpframe(&self) {}
|
||||
}
|
||||
|
||||
fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
|
||||
use EventPriority::*;
|
||||
|
||||
match event.name {
|
||||
// clipboard
|
||||
"copy" | "cut" | "paste" => (true, Medium),
|
||||
|
||||
// Composition
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => (true, Low),
|
||||
|
||||
// Keyboard
|
||||
"keydown" | "keypress" | "keyup" => (true, High),
|
||||
|
||||
// Focus
|
||||
"focus" | "blur" => (true, Low),
|
||||
|
||||
// Form
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => (true, Medium),
|
||||
|
||||
// Mouse
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High),
|
||||
|
||||
"mousemove" => (false, Medium),
|
||||
|
||||
// Pointer
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
(true, Medium)
|
||||
}
|
||||
|
||||
// Selection
|
||||
"select" | "touchcancel" | "touchend" => (true, Medium),
|
||||
|
||||
// Touch
|
||||
"touchmove" | "touchstart" => (true, Medium),
|
||||
|
||||
// Wheel
|
||||
"scroll" | "wheel" => (false, Medium),
|
||||
|
||||
// Media
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
|
||||
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
|
||||
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
| "timeupdate" | "volumechange" | "waiting" => (true, Medium),
|
||||
|
||||
// Animation
|
||||
"animationstart" | "animationend" | "animationiteration" => (true, Medium),
|
||||
|
||||
// Transition
|
||||
"transitionend" => (true, Medium),
|
||||
|
||||
// Toggle
|
||||
"toggle" => (true, Medium),
|
||||
|
||||
_ => (true, Low),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::innerlude::*;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
|
||||
pub struct TaskHandle {
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) our_id: u64,
|
||||
}
|
||||
|
||||
impl TaskHandle {
|
||||
/// Toggles this coroutine off/on.
|
||||
///
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn toggle(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn resume(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ResumeTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn stop(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn restart(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::innerlude::*;
|
||||
|
||||
@@ -64,3 +65,28 @@ impl Future for YieldNow {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
/// unmounted, then the `ScopeId` will be reused for a new component.
|
||||
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ScopeId(pub usize);
|
||||
|
||||
/// An Element's unique identifier.
|
||||
///
|
||||
/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
/// unmounted, then the `ElementId` will be reused for a new component.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ElementId(pub usize);
|
||||
impl Display for ElementId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementId {
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0 as u64
|
||||
}
|
||||
}
|
||||
@@ -380,3 +380,8 @@ impl std::fmt::Display for VirtualDom {
|
||||
renderer.render(self, root, f, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VirtualDomConfig {
|
||||
component_slab_size: usize,
|
||||
element_slab_size: usize,
|
||||
}
|
||||
Reference in new issue
Block a user