wip: more work on priority
This commit is contained in:
1 parent
05e960b6b0
commit
cef116aa3a
14 files changed
+279
-379
No files matched your search
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
"rust-analyzer.inlayHints.enable": false
|
||||
}
|
||||
@@ -96,13 +96,11 @@ impl<'src, P> Context<'src, P> {
|
||||
/// ## Notice: you should prefer using prepare_update and get_scope_id
|
||||
///
|
||||
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
||||
let cb = self.scope.vdom.schedule_update();
|
||||
let id = self.get_scope_id();
|
||||
Rc::new(move || cb(id))
|
||||
self.scope.memoized_updater.clone()
|
||||
}
|
||||
|
||||
pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
|
||||
self.scope.vdom.schedule_update()
|
||||
self.scope.shared.schedule_any_immediate.clone()
|
||||
}
|
||||
|
||||
pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
|
||||
@@ -160,7 +158,7 @@ impl<'src, P> Context<'src, P> {
|
||||
///
|
||||
///
|
||||
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
|
||||
self.scope.vdom.submit_task(task)
|
||||
(self.scope.shared.submit_task)(task)
|
||||
}
|
||||
|
||||
/// Add a state globally accessible to child components via tree walking
|
||||
@@ -174,46 +172,11 @@ impl<'src, P> Context<'src, P> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Walk the tree to find a shared state with the TypeId of the generic type
|
||||
///
|
||||
pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
|
||||
let mut scope = Some(self.scope);
|
||||
let mut parent = None;
|
||||
|
||||
let getter = &self.scope.shared.get_shared_context;
|
||||
let ty = TypeId::of::<T>();
|
||||
while let Some(inner) = scope {
|
||||
log::debug!(
|
||||
"Searching {:#?} for valid shared_context",
|
||||
inner.our_arena_idx
|
||||
);
|
||||
let shared_ctx = {
|
||||
let shared_contexts = inner.shared_contexts.borrow();
|
||||
|
||||
log::debug!(
|
||||
"This component has {} shared contexts",
|
||||
shared_contexts.len()
|
||||
);
|
||||
shared_contexts.get(&ty).map(|f| f.clone())
|
||||
};
|
||||
|
||||
if let Some(shared_cx) = shared_ctx {
|
||||
log::debug!("found matching cx");
|
||||
let rc = shared_cx
|
||||
.clone()
|
||||
.downcast::<T>()
|
||||
.expect("Should not fail, already validated the type from the hashmap");
|
||||
parent = Some(rc);
|
||||
break;
|
||||
} else {
|
||||
match inner.parent_idx {
|
||||
Some(parent_id) => {
|
||||
scope = unsafe { inner.vdom.get_scope(parent_id) };
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
parent
|
||||
let idx = self.scope.our_arena_idx;
|
||||
getter(idx, ty).map(|f| f.downcast().unwrap())
|
||||
}
|
||||
|
||||
/// Store a value between renders
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
//! More info on how to improve this diffing algorithm:
|
||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||
|
||||
use crate::{arena::Scheduler, innerlude::*};
|
||||
use crate::{innerlude::*, scheduler::Scheduler};
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use DomEdit::*;
|
||||
|
||||
@@ -110,9 +110,7 @@ pub struct DiffMachine<'bump> {
|
||||
pub mutations: &'bump mut Mutations<'bump>,
|
||||
|
||||
pub stack: DiffStack<'bump>,
|
||||
|
||||
pub diffed: FxHashSet<ScopeId>,
|
||||
|
||||
pub seen_scopes: FxHashSet<ScopeId>,
|
||||
}
|
||||
|
||||
@@ -310,6 +308,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
let parent_idx = self.stack.current_scope().unwrap();
|
||||
|
||||
let shared = self.vdom.channel.clone();
|
||||
// Insert a new scope into our component list
|
||||
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
||||
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
|
||||
@@ -320,8 +319,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
Some(parent_idx),
|
||||
height,
|
||||
ScopeChildren(vcomponent.children),
|
||||
// self.vdom.clone(),
|
||||
vcomponent.name,
|
||||
shared,
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
+8
-154
@@ -17,169 +17,34 @@ use crate::{
|
||||
#[derive(Debug)]
|
||||
pub struct EventTrigger {
|
||||
/// The originator of the event trigger
|
||||
pub originator: ScopeId,
|
||||
pub scope: ScopeId,
|
||||
|
||||
/// The optional real node associated with the trigger
|
||||
pub real_node_id: Option<ElementId>,
|
||||
pub mounted_dom_id: Option<ElementId>,
|
||||
|
||||
/// The type of event
|
||||
pub event: VirtualEvent,
|
||||
|
||||
/// The priority of the event
|
||||
pub priority: EventPriority,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct EventKey {
|
||||
/// The originator of the event trigger
|
||||
pub originator: ScopeId,
|
||||
/// The priority of the event
|
||||
pub priority: EventPriority,
|
||||
/// The height of the scope (used for ordering)
|
||||
pub height: u32,
|
||||
// TODO: add the time that the event was queued
|
||||
}
|
||||
|
||||
impl PartialOrd for EventKey {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl Ord for EventKey {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// "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".
|
||||
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,
|
||||
}
|
||||
|
||||
impl EventTrigger {
|
||||
pub fn new(
|
||||
event: VirtualEvent,
|
||||
scope: ScopeId,
|
||||
mounted_dom_id: Option<ElementId>,
|
||||
priority: EventPriority,
|
||||
) -> Self {
|
||||
Self {
|
||||
priority,
|
||||
originator: scope,
|
||||
real_node_id: mounted_dom_id,
|
||||
event,
|
||||
}
|
||||
}
|
||||
pub discrete: bool,
|
||||
}
|
||||
|
||||
pub enum VirtualEvent {
|
||||
// Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
|
||||
//
|
||||
// Async events don't necessarily propagate into a scope being ran. It's up to the event itself
|
||||
// to force an update for itself.
|
||||
//
|
||||
// Most async events should have a low priority.
|
||||
//
|
||||
// This type exists for the task/concurrency system to signal that a task is ready.
|
||||
// However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
|
||||
// own re-run.
|
||||
AsyncEvent {
|
||||
should_rerender: bool,
|
||||
},
|
||||
|
||||
// Suspense events are a type of async event generated when suspended nodes are ready to be processed.
|
||||
//
|
||||
// they have the lowest priority
|
||||
SuspenseEvent {
|
||||
hook_idx: usize,
|
||||
domnode: Rc<Cell<Option<ElementId>>>,
|
||||
},
|
||||
|
||||
// image event has conflicting method types
|
||||
// ImageEvent(event_data::ImageEvent),
|
||||
|
||||
// Real events
|
||||
KeyboardEvent(on::KeyboardEvent),
|
||||
TouchEvent(on::TouchEvent),
|
||||
MouseEvent(on::MouseEvent),
|
||||
ClipboardEvent(on::ClipboardEvent),
|
||||
CompositionEvent(on::CompositionEvent),
|
||||
KeyboardEvent(on::KeyboardEvent),
|
||||
FocusEvent(on::FocusEvent),
|
||||
FormEvent(on::FormEvent),
|
||||
SelectionEvent(on::SelectionEvent),
|
||||
TouchEvent(on::TouchEvent),
|
||||
UIEvent(on::UIEvent),
|
||||
WheelEvent(on::WheelEvent),
|
||||
MediaEvent(on::MediaEvent),
|
||||
AnimationEvent(on::AnimationEvent),
|
||||
TransitionEvent(on::TransitionEvent),
|
||||
ToggleEvent(on::ToggleEvent),
|
||||
MouseEvent(on::MouseEvent),
|
||||
PointerEvent(on::PointerEvent),
|
||||
}
|
||||
|
||||
impl VirtualEvent {
|
||||
pub fn is_input_event(&self) -> bool {
|
||||
match self {
|
||||
VirtualEvent::ClipboardEvent(_)
|
||||
| VirtualEvent::CompositionEvent(_)
|
||||
| VirtualEvent::KeyboardEvent(_)
|
||||
| VirtualEvent::FocusEvent(_)
|
||||
| VirtualEvent::FormEvent(_)
|
||||
| VirtualEvent::SelectionEvent(_)
|
||||
| VirtualEvent::TouchEvent(_)
|
||||
| VirtualEvent::UIEvent(_)
|
||||
| VirtualEvent::WheelEvent(_)
|
||||
| VirtualEvent::MediaEvent(_)
|
||||
| VirtualEvent::AnimationEvent(_)
|
||||
| VirtualEvent::TransitionEvent(_)
|
||||
| VirtualEvent::ToggleEvent(_)
|
||||
| VirtualEvent::MouseEvent(_)
|
||||
| VirtualEvent::PointerEvent(_) => true,
|
||||
|
||||
VirtualEvent::AsyncEvent { .. } | VirtualEvent::SuspenseEvent { .. } => false,
|
||||
}
|
||||
}
|
||||
// ImageEvent(event_data::ImageEvent),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VirtualEvent {
|
||||
@@ -200,8 +65,6 @@ impl std::fmt::Debug for VirtualEvent {
|
||||
VirtualEvent::ToggleEvent(_) => "ToggleEvent",
|
||||
VirtualEvent::MouseEvent(_) => "MouseEvent",
|
||||
VirtualEvent::PointerEvent(_) => "PointerEvent",
|
||||
VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
|
||||
VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
|
||||
};
|
||||
|
||||
f.debug_struct("VirtualEvent").field("type", &name).finish()
|
||||
@@ -215,8 +78,7 @@ pub mod on {
|
||||
//! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
|
||||
//! Arc allocation through "get_mut"
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! React recently dropped support for re-using event allocation and just passes the real event along.
|
||||
|
||||
#![allow(unused)]
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
@@ -285,14 +147,6 @@ pub mod on {
|
||||
}
|
||||
|
||||
// The Dioxus Synthetic event system
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
event_directory! {
|
||||
ClipboardEventInner(ClipboardEvent): [
|
||||
/// Called when "copy"
|
||||
|
||||
@@ -130,9 +130,9 @@ where
|
||||
event: VirtualEvent::AsyncEvent {
|
||||
should_rerender: false,
|
||||
},
|
||||
originator,
|
||||
scope: originator,
|
||||
priority: EventPriority::Low,
|
||||
real_node_id: None,
|
||||
mounted_dom_id: None,
|
||||
}
|
||||
})));
|
||||
|
||||
@@ -209,9 +209,9 @@ where
|
||||
*slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
|
||||
EventTrigger {
|
||||
event: VirtualEvent::SuspenseEvent { hook_idx, domnode },
|
||||
originator,
|
||||
scope: originator,
|
||||
priority: EventPriority::Low,
|
||||
real_node_id: None,
|
||||
mounted_dom_id: None,
|
||||
}
|
||||
})));
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ pub mod prelude {
|
||||
|
||||
// types used internally that are important
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::arena::*;
|
||||
pub use crate::bumpframe::*;
|
||||
pub use crate::childiter::*;
|
||||
pub use crate::component::*;
|
||||
@@ -43,6 +42,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::scheduler::*;
|
||||
// pub use crate::scheduler::*;
|
||||
pub use crate::scope::*;
|
||||
pub use crate::util::*;
|
||||
@@ -56,11 +56,12 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub mod exports {
|
||||
// export important things here
|
||||
//! Important dependencies that are used by the rest of the library
|
||||
|
||||
// the foundation of this library
|
||||
pub use bumpalo;
|
||||
}
|
||||
|
||||
pub mod arena;
|
||||
pub mod bumpframe;
|
||||
pub mod childiter;
|
||||
pub mod component;
|
||||
@@ -75,7 +76,7 @@ pub mod hooklist;
|
||||
pub mod hooks;
|
||||
pub mod mutations;
|
||||
pub mod nodes;
|
||||
// pub mod scheduler;
|
||||
pub mod scheduler;
|
||||
pub mod scope;
|
||||
pub mod signals;
|
||||
pub mod util;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::cell::{Cell, RefCell, RefMut};
|
||||
use std::fmt::Display;
|
||||
use std::{cell::UnsafeCell, rc::Rc};
|
||||
|
||||
@@ -23,25 +23,68 @@ use futures_util::Future;
|
||||
use futures_util::FutureExt;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
type UiReceiver = UnboundedReceiver<EventTrigger>;
|
||||
type UiSender = UnboundedSender<EventTrigger>;
|
||||
#[derive(Clone)]
|
||||
pub struct EventChannel {
|
||||
pub task_counter: Rc<Cell<u64>>,
|
||||
pub sender: UnboundedSender<SchedulerMsg>,
|
||||
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
|
||||
pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
|
||||
pub get_shared_context: Rc<dyn Fn(ScopeId, TypeId) -> Option<Rc<dyn Any>>>,
|
||||
}
|
||||
|
||||
type TaskReceiver = UnboundedReceiver<ScopeId>;
|
||||
type TaskSender = UnboundedSender<ScopeId>;
|
||||
pub enum SchedulerMsg {
|
||||
Immediate(ScopeId),
|
||||
UiEvent(),
|
||||
SubmitTask(u64),
|
||||
ToggleTask(u64),
|
||||
PauseTask(u64),
|
||||
ResumeTask(u64),
|
||||
DropTask(u64),
|
||||
}
|
||||
|
||||
/// These are resources shared among all the components and the virtualdom itself
|
||||
/// The scheduler holds basically everything around "working"
|
||||
///
|
||||
/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
|
||||
///
|
||||
/// In Dioxus, the scheduler provides 3 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
|
||||
/// out.
|
||||
///
|
||||
/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
|
||||
/// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
|
||||
/// task shift to a higher priority task that needs mutable access to the same scopes.
|
||||
///
|
||||
/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
|
||||
///
|
||||
///
|
||||
pub struct Scheduler {
|
||||
pub components: UnsafeCell<Slab<Scope>>,
|
||||
/*
|
||||
This *has* to be an UnsafeCell.
|
||||
|
||||
pub(crate) heuristics: HeuristicsEngine,
|
||||
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.
|
||||
|
||||
// Used by "set_state" and co - is its own queue
|
||||
pub immediate_sender: TaskSender,
|
||||
pub immediate_receiver: TaskReceiver,
|
||||
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.
|
||||
|
||||
/// Triggered by event listeners
|
||||
pub ui_event_sender: UiSender,
|
||||
pub ui_event_receiver: UiReceiver,
|
||||
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: Slab<()>,
|
||||
|
||||
pub heuristics: HeuristicsEngine,
|
||||
|
||||
pub channel: EventChannel,
|
||||
|
||||
pub receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
|
||||
// Garbage stored
|
||||
pub pending_garbage: FxHashSet<ScopeId>,
|
||||
@@ -49,12 +92,6 @@ pub struct Scheduler {
|
||||
// In-flight futures
|
||||
pub async_tasks: FuturesUnordered<FiberTask>,
|
||||
|
||||
/// We use a SlotSet to keep track of the keys that are currently being used.
|
||||
/// However, we don't store any specific data since the "mirror"
|
||||
pub raw_elements: Slab<()>,
|
||||
|
||||
pub task_setter: Rc<dyn Fn(ScopeId)>,
|
||||
|
||||
// scheduler stuff
|
||||
pub current_priority: EventPriority,
|
||||
|
||||
@@ -66,75 +103,78 @@ pub struct Scheduler {
|
||||
|
||||
pub garbage_scopes: HashSet<ScopeId>,
|
||||
|
||||
pub high_priorty: PriortySystem,
|
||||
pub medium_priority: PriortySystem,
|
||||
pub low_priority: PriortySystem,
|
||||
pub fibers: [PriortySystem; 3],
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
pub fn new() -> Self {
|
||||
// preallocate 2000 elements and 20 scopes to avoid dynamic allocation
|
||||
let components: UnsafeCell<Slab<Scope>> = UnsafeCell::new(Slab::with_capacity(100));
|
||||
let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
|
||||
|
||||
// elements are super cheap - the value takes no space
|
||||
let raw_elements = Slab::with_capacity(2000);
|
||||
|
||||
let (ui_sender, ui_receiver) = futures_channel::mpsc::unbounded();
|
||||
let (immediate_sender, immediate_receiver) = futures_channel::mpsc::unbounded();
|
||||
|
||||
let heuristics = HeuristicsEngine::new();
|
||||
|
||||
// we allocate this task setter once to save us from having to allocate later
|
||||
let task_setter = {
|
||||
let queue = immediate_sender.clone();
|
||||
let components = components.clone();
|
||||
Rc::new(move |idx: ScopeId| {
|
||||
let comps = unsafe { &*components.get() };
|
||||
|
||||
if let Some(scope) = comps.get(idx.0) {
|
||||
// todo!("implement immediates again")
|
||||
//
|
||||
|
||||
// queue
|
||||
// .unbounded_send(EventTrigger::new(
|
||||
// V
|
||||
// idx,
|
||||
// None,
|
||||
// EventPriority::High,
|
||||
// ))
|
||||
// .expect("The event queu receiver should *never* be dropped");
|
||||
}
|
||||
}) as Rc<dyn Fn(ScopeId)>
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
|
||||
let task_counter = Rc::new(Cell::new(0));
|
||||
let channel = EventChannel {
|
||||
task_counter,
|
||||
sender: sender.clone(),
|
||||
schedule_any_immediate: {
|
||||
Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
|
||||
},
|
||||
submit_task: Rc::new(|_| {
|
||||
//
|
||||
todo!()
|
||||
// TaskHandle {}
|
||||
}),
|
||||
get_shared_context: {
|
||||
let components = components.clone();
|
||||
Rc::new(|id, ty| {
|
||||
let components = unsafe { &*components.get() };
|
||||
let mut search: Option<&Scope> = components.get(id.0);
|
||||
while let Some(inner) = search.take() {
|
||||
if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
|
||||
return Some(shared.clone());
|
||||
} else {
|
||||
search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
Self {
|
||||
channel,
|
||||
receiver,
|
||||
|
||||
components,
|
||||
async_tasks: FuturesUnordered::new(),
|
||||
|
||||
ui_event_receiver: ui_receiver,
|
||||
ui_event_sender: ui_sender,
|
||||
|
||||
immediate_receiver: immediate_receiver,
|
||||
immediate_sender: immediate_sender,
|
||||
|
||||
pending_garbage: FxHashSet::default(),
|
||||
|
||||
heuristics: heuristics,
|
||||
raw_elements: raw_elements,
|
||||
task_setter,
|
||||
heuristics,
|
||||
raw_elements,
|
||||
|
||||
// a storage for our receiver to dump into
|
||||
pending_events: VecDeque::new(),
|
||||
|
||||
pending_immediates: VecDeque::new(),
|
||||
|
||||
pending_tasks: VecDeque::new(),
|
||||
|
||||
garbage_scopes: HashSet::new(),
|
||||
|
||||
current_priority: EventPriority::Low,
|
||||
|
||||
high_priorty: PriortySystem::new(),
|
||||
medium_priority: PriortySystem::new(),
|
||||
low_priority: PriortySystem::new(),
|
||||
// a dedicated fiber for each priority
|
||||
fibers: [
|
||||
PriortySystem::new(),
|
||||
PriortySystem::new(),
|
||||
PriortySystem::new(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,12 +216,12 @@ impl Scheduler {
|
||||
}
|
||||
|
||||
pub fn reserve_node(&self) -> ElementId {
|
||||
ElementId(self.raw_elements.borrow_mut().insert(()))
|
||||
ElementId(self.raw_elements.insert(()))
|
||||
}
|
||||
|
||||
/// return the id, freeing the space of the original node
|
||||
pub fn collect_garbage(&self, id: ElementId) {
|
||||
self.raw_elements.borrow_mut().remove(id.0);
|
||||
self.raw_elements.remove(id.0);
|
||||
}
|
||||
|
||||
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
|
||||
@@ -192,24 +232,12 @@ impl Scheduler {
|
||||
id
|
||||
}
|
||||
|
||||
pub fn schedule_update(&self) -> Rc<dyn Fn(ScopeId)> {
|
||||
self.task_setter.clone()
|
||||
}
|
||||
|
||||
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
|
||||
self.async_tasks.borrow_mut().push(task);
|
||||
TaskHandle {}
|
||||
}
|
||||
|
||||
pub fn make_trigger_key(&self, trigger: &EventTrigger) -> EventKey {
|
||||
let height = self
|
||||
.get_scope(trigger.originator)
|
||||
.map(|f| f.height)
|
||||
.unwrap();
|
||||
let height = self.get_scope(trigger.scope).map(|f| f.height).unwrap();
|
||||
|
||||
EventKey {
|
||||
height,
|
||||
originator: trigger.originator,
|
||||
originator: trigger.scope,
|
||||
priority: trigger.priority,
|
||||
}
|
||||
}
|
||||
@@ -356,8 +384,8 @@ impl Scheduler {
|
||||
| VirtualEvent::ToggleEvent(_)
|
||||
| VirtualEvent::MouseEvent(_)
|
||||
| VirtualEvent::PointerEvent(_) => {
|
||||
if let Some(scope) = self.get_scope_mut(trigger.originator) {
|
||||
if let Some(element) = trigger.real_node_id {
|
||||
if let Some(scope) = self.get_scope_mut(trigger.scope) {
|
||||
if let Some(element) = trigger.mounted_dom_id {
|
||||
scope.call_listener(trigger.event, element)?;
|
||||
|
||||
let receiver = self.immediate_receiver.clone();
|
||||
@@ -406,10 +434,10 @@ impl Scheduler {
|
||||
|
||||
/// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope
|
||||
pub async fn work_with_deadline<'a>(
|
||||
&mut self,
|
||||
mutations: &mut Mutations<'_>,
|
||||
&'a mut self,
|
||||
mutations: &mut Mutations<'a>,
|
||||
deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
|
||||
) -> FiberResult {
|
||||
) -> bool {
|
||||
// check if we need to elevate priority
|
||||
self.current_priority = match (
|
||||
self.high_priorty.has_work(),
|
||||
@@ -421,7 +449,7 @@ impl Scheduler {
|
||||
(false, false, _) => EventPriority::Low,
|
||||
};
|
||||
|
||||
let mut machine = DiffMachine::new(mutations, ScopeId(0), &self);
|
||||
// let mut machine = DiffMachine::new(mutations, ScopeId(0), &self);
|
||||
|
||||
let dirty_root = {
|
||||
let dirty_roots = match self.current_priority {
|
||||
@@ -433,7 +461,7 @@ impl Scheduler {
|
||||
let mut dirty_root = {
|
||||
let root = dirty_roots.iter().next();
|
||||
if root.is_none() {
|
||||
return FiberResult::Done;
|
||||
return true;
|
||||
}
|
||||
root.unwrap()
|
||||
};
|
||||
@@ -449,17 +477,12 @@ impl Scheduler {
|
||||
dirty_root
|
||||
};
|
||||
|
||||
match {
|
||||
let fut = machine.diff_scope(*dirty_root).fuse();
|
||||
pin_mut!(fut);
|
||||
let fut = machine.diff_scope(*dirty_root).fuse();
|
||||
pin_mut!(fut);
|
||||
|
||||
match futures_util::future::select(deadline, fut).await {
|
||||
futures_util::future::Either::Left((deadline, work_fut)) => true,
|
||||
futures_util::future::Either::Right((_, deadline_fut)) => false,
|
||||
}
|
||||
} {
|
||||
true => FiberResult::Done,
|
||||
false => FiberResult::Interrupted,
|
||||
match futures_util::future::select(deadline, fut).await {
|
||||
futures_util::future::Either::Left((deadline, work_fut)) => true,
|
||||
futures_util::future::Either::Right((_, deadline_fut)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,45 +491,31 @@ impl Scheduler {
|
||||
// does not return the trigger, but caches it in the scheduler
|
||||
pub async fn wait_for_any_trigger(
|
||||
&mut self,
|
||||
mut deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
|
||||
deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
|
||||
) -> bool {
|
||||
use futures_util::select;
|
||||
use futures_util::future::{select, Either};
|
||||
|
||||
let _immediates = self.immediate_receiver.clone();
|
||||
let mut immediates = _immediates.borrow_mut();
|
||||
|
||||
let mut _ui_events = self.ui_event_receiver.clone();
|
||||
let mut ui_events = _ui_events.borrow_mut();
|
||||
|
||||
let mut _tasks = self.async_tasks.clone();
|
||||
let mut tasks = _tasks.borrow_mut();
|
||||
|
||||
// set_state called
|
||||
select! {
|
||||
dirty_scope = immediates.next() => {
|
||||
if let Some(scope) = dirty_scope {
|
||||
self.add_dirty_scope(scope, EventPriority::Low);
|
||||
let event_fut = async {
|
||||
match select(self.receiver.next(), self.async_tasks.next()).await {
|
||||
Either::Left((msg, _other)) => {
|
||||
self.handle_channel_msg(msg.unwrap());
|
||||
}
|
||||
Either::Right((task, _other)) => {
|
||||
// do nothing, async task will likely generate a set of scheduler messages
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ui_event = ui_events.next() => {
|
||||
if let Some(event) = ui_event {
|
||||
self.pending_events.push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
async_task = tasks.next() => {
|
||||
if let Some(event) = async_task {
|
||||
self.pending_events.push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
_ = deadline => {
|
||||
return true;
|
||||
}
|
||||
pin_mut!(event_fut);
|
||||
|
||||
match select(event_fut, deadline).await {
|
||||
Either::Left((msg, _other)) => false,
|
||||
Either::Right((deadline, _)) => true,
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
|
||||
//
|
||||
}
|
||||
|
||||
pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
|
||||
@@ -518,12 +527,24 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskHandle {}
|
||||
pub struct TaskHandle {
|
||||
channel: EventChannel,
|
||||
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) {}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn start(&self) {}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn stop(&self) {}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn restart(&self) {}
|
||||
}
|
||||
|
||||
@@ -567,3 +588,72 @@ impl ElementId {
|
||||
self.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
// // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
|
||||
// //
|
||||
// // Async events don't necessarily propagate into a scope being ran. It's up to the event itself
|
||||
// // to force an update for itself.
|
||||
// //
|
||||
// // Most async events should have a low priority.
|
||||
// //
|
||||
// // This type exists for the task/concurrency system to signal that a task is ready.
|
||||
// // However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
|
||||
// // own re-run.
|
||||
// AsyncEvent {
|
||||
// should_rerender: bool,
|
||||
// },
|
||||
|
||||
// // Suspense events are a type of async event generated when suspended nodes are ready to be processed.
|
||||
// //
|
||||
// // they have the lowest priority
|
||||
// SuspenseEvent {
|
||||
// hook_idx: usize,
|
||||
// domnode: Rc<Cell<Option<ElementId>>>,
|
||||
// },
|
||||
|
||||
/// 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 {
|
||||
/// "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".
|
||||
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,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use fxhash::FxHashSet;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
@@ -43,10 +44,9 @@ pub struct Scope {
|
||||
pub(crate) hooks: HookList,
|
||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
||||
|
||||
// meta
|
||||
pub(crate) function_name: &'static str,
|
||||
// // A reference to the resources shared by all the comonents
|
||||
// pub(crate) vdom: SharedResources,
|
||||
pub(crate) memoized_updater: Rc<dyn Fn() + 'static>,
|
||||
|
||||
pub(crate) shared: EventChannel,
|
||||
}
|
||||
|
||||
// The type of closure that wraps calling components
|
||||
@@ -74,28 +74,22 @@ impl Scope {
|
||||
|
||||
child_nodes: ScopeChildren,
|
||||
|
||||
// vdom: SharedResources,
|
||||
function_name: &'static str,
|
||||
shared: EventChannel,
|
||||
) -> Self {
|
||||
let child_nodes = unsafe { child_nodes.extend_lifetime() };
|
||||
|
||||
// // insert ourself as a descendent of the parent
|
||||
// // when the parent is removed, this map will be traversed, and we will also be cleaned up.
|
||||
// if let Some(parent) = &parent {
|
||||
// let parent = unsafe { vdom.get_scope(*parent) }.unwrap();
|
||||
// parent.descendents.borrow_mut().insert(arena_idx);
|
||||
// }
|
||||
let up = shared.schedule_any_immediate.clone();
|
||||
let memoized_updater = Rc::new(move || up(arena_idx));
|
||||
|
||||
Self {
|
||||
function_name,
|
||||
memoized_updater,
|
||||
shared,
|
||||
child_nodes,
|
||||
caller,
|
||||
parent_idx: parent,
|
||||
our_arena_idx: arena_idx,
|
||||
height,
|
||||
// vdom,
|
||||
frames: ActiveFrame::new(),
|
||||
|
||||
hooks: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
listeners: Default::default(),
|
||||
|
||||
@@ -105,18 +105,17 @@ impl VirtualDom {
|
||||
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
||||
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
|
||||
|
||||
let link = components.clone();
|
||||
let update_sender = components.immediate_sender.clone();
|
||||
|
||||
let base_scope = components.insert_scope_with_key(move |myidx| {
|
||||
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
||||
let name = type_name_of(root);
|
||||
Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link, name)
|
||||
Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), update_sender)
|
||||
});
|
||||
|
||||
Self {
|
||||
base_scope,
|
||||
_root_props: root_props,
|
||||
scheduler: Scheduler::new(components.clone()),
|
||||
scheduler: components,
|
||||
_root_prop_type: TypeId::of::<P>(),
|
||||
}
|
||||
@@ -152,28 +151,29 @@ impl VirtualDom {
|
||||
/// the diff and creating nodes can be expensive, so we provide this method to avoid blocking the main thread. This
|
||||
/// method can be useful when needing to perform some crucial periodic tasks.
|
||||
pub async fn rebuild_async<'s>(&'s mut self) -> Mutations<'s> {
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.scheduler);
|
||||
todo!()
|
||||
// let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.scheduler);
|
||||
|
||||
let cur_component = self
|
||||
.scheduler
|
||||
.get_scope_mut(self.base_scope)
|
||||
.expect("The base scope should never be moved");
|
||||
// let cur_component = self
|
||||
// .scheduler
|
||||
// .get_scope_mut(self.base_scope)
|
||||
// .expect("The base scope should never be moved");
|
||||
|
||||
// // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
||||
if cur_component.run_scope().is_ok() {
|
||||
diff_machine
|
||||
.stack
|
||||
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||
diff_machine.work().await;
|
||||
} else {
|
||||
// todo: should this be a hard error?
|
||||
log::warn!(
|
||||
"Component failed to run succesfully during rebuild.
|
||||
This does not result in a failed rebuild, but indicates a logic failure within your app."
|
||||
);
|
||||
}
|
||||
// // // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
||||
// if cur_component.run_scope().is_ok() {
|
||||
// diff_machine
|
||||
// .stack
|
||||
// .create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||
// diff_machine.work().await;
|
||||
// } else {
|
||||
// // todo: should this be a hard error?
|
||||
// log::warn!(
|
||||
// "Component failed to run succesfully during rebuild.
|
||||
// This does not result in a failed rebuild, but indicates a logic failure within your app."
|
||||
// );
|
||||
// }
|
||||
|
||||
diff_machine.mutations
|
||||
// diff_machine.mutations
|
||||
}
|
||||
|
||||
pub fn diff_sync<'s>(&'s mut self) -> Mutations<'s> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! tests to prove that the iterative implementation works
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, DomEdit, Mutations};
|
||||
use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, DomEdit, Mutations};
|
||||
mod test_logging;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use bumpalo::Bump;
|
||||
|
||||
use dioxus::{
|
||||
arena::Scheduler, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType,
|
||||
diff::DiffMachine, prelude::*, scheduler::Scheduler, DiffInstruction, DomEdit, MountType,
|
||||
};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use bumpalo::Bump;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, DomEdit};
|
||||
use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, DomEdit};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, DomEdit};
|
||||
use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, DomEdit};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! This means you can actually call it synchronously if you want.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, scope::Scope};
|
||||
use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, scope::Scope};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use futures_util::FutureExt;
|
||||
|
||||
Reference in new issue
Block a user