wip: changes to scheduler

This commit is contained in:
Jonathan Kelley committed 2021-09-22 01:25:28 -04:00
1 parent b32e2611e3
commit 098d3821ed
14 files changed
+151 -282

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"rust-analyzer.inlayHints.enable": false, "rust-analyzer.inlayHints.enable": true,
"rust-analyzer.cargo.allFeatures": true "rust-analyzer.cargo.allFeatures": true
} }
-1
View File
@@ -1 +0,0 @@
fn main() {}
+1 -1
View File
@@ -20,5 +20,5 @@ struct SomeProps {
/// This implementation does not require a "PartialEq" because it does not memoize /// This implementation does not require a "PartialEq" because it does not memoize
#[derive(dioxus_core_macro::Props)] #[derive(dioxus_core_macro::Props)]
struct SomePropsTwo<'a> { struct SomePropsTwo<'a> {
a: &'a str, _a: &'a str,
} }
-125
View File
@@ -1,125 +0,0 @@
pub mod dioxus {
pub mod builder {
pub struct Builder;
struct AttrVal;
impl Into<AttrVal> for &'static str {
fn into(self) -> AttrVal {
todo!()
}
}
impl Into<AttrVal> for String {
fn into(self) -> AttrVal {
todo!()
}
}
// impl<T> From<T> for AttrVal {
// fn from(_: T) -> Self {
// todo!()
// }
// }
impl Builder {
// fn attr<T>(mut self, key: &str, value: impl Into<AttrVal>) -> Self {
pub fn attr<T>(self, _key: &str, _value: T) -> Self {
Self
}
pub fn on<T>(self, _key: &str, _value: T) -> Self {
Self
}
pub fn finish(self) {
// Self
}
}
pub struct Bump;
pub fn div(_bump: &Bump) -> Builder {
todo!()
}
pub fn h1(_bump: &Bump) -> Builder {
todo!()
}
pub fn h2(_bump: &Bump) -> Builder {
todo!()
}
}
}
pub fn main() {
// render(rsx! {
// div { // we can actually support just a list of nodes too
// h1 {"Hello Dioxus"}
// p {"This is a beautful app you're building"}
// section {
// "custom section to the rescue",
// class: "abc123"
// }
// span {
// class: "abc123"
// "Try backwards too."
// "Anything goes!"
// "As long as it's within the rules"
// {0..10.map(|f| rsx!{
// div {
// h3 {"totally okay to drop in iterators and expressions"}
// p {"however, debug information is lost"}
// }
// })}
// }
// span {
// "Feel free"
// class: "abc123"
// "To mix to your heart's content"
// }
// span { class: "some-very-long-and-tedious-class-name-is-now-separated"
// "Very ergonomic"
// }
// span { "Innovative design 🛠"
// class: "some-very-long-and-tedious-class-name-is-now-separated"
// }
// }
// });
let _g = String::from("asd");
// let lazy = rsx! {
// div {
// a: "asd",
// a: "asd",
// a: "asd",
// a: "asd",
// a: "asd",
// // a: {rsx!{ h1 {"hello world"} }}, // include
// a: {&g},
// b: {1 + 2},
// onclick: {move |e: ()| {
// println!("hello world!")
// }},
// div {
// a: "asd"
// div {
// div {
// div {
// }
// }
// }
// }
// h1 {
// }
// h2 {
// "child"
// }
// "Childnode"
// }
// };
// render(lazy);
}
fn render(_f: impl Fn(&dioxus::builder::Bump)) {}
+1 -1
View File
@@ -22,7 +22,7 @@ criterion_group!(mbenches, create_rows);
criterion_main!(mbenches); criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) { fn create_rows(c: &mut Criterion) {
static App: FC<()> = |cx, props| { static App: FC<()> = |cx, _| {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..10_000).map(|f| { let rows = (0..10_000).map(|f| {
let label = Label::new(&mut rng); let label = Label::new(&mut rng);
+6 -2
View File
@@ -1,8 +1,12 @@
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
fn main() {} fn main() {
let mut dom = VirtualDom::new(EXAMPLE);
dom.rebuild();
println!("{}", dom);
}
pub static Example: FC<()> = |cx, props| { pub static EXAMPLE: FC<()> = |cx, _| {
let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!()));
cx.render(LazyNodes::new(move |cx| { cx.render(LazyNodes::new(move |cx| {
+7 -31
View File
@@ -1,39 +1,15 @@
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
fn main() {} fn main() {
let mut dom = VirtualDom::new(App);
dom.rebuild();
}
const App: FC<()> = |cx, props| { const App: FC<()> = |cx, props| {
// create a new future let id = cx.scope_id();
let _fut = cx.use_hook( cx.submit_task(Box::pin(async move { id }));
|_| {
//
async { loop {} }
// Box::pin(async { loop {} }) as Pin<Box<dyn Future<Output = ()>>>
},
|f| f,
|_| {},
);
// let g = unsafe { Pin::new_unchecked(fut) };
// cx.submit_task(fut); let (handle, contents) = use_task(cx, || async { "hello world".to_string() });
todo!() todo!()
}; };
const Task: FC<()> = |cx, props| {
let (_task, _res) = use_task(cx, || async { true });
// task.pause();
// task.restart();
// task.stop();
// task.drop();
//
let _s = use_task(cx, || async { "hello world".to_string() });
todo!()
};
fn use_mut<P, T>(_cx: Context, _f: impl FnOnce() -> T) -> &mut T {
todo!()
}
+1 -1
View File
@@ -137,7 +137,7 @@ impl<'a> SavedDiffWork<'a> {
std::mem::transmute(self) std::mem::transmute(self)
} }
pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> { pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> {
let extended: SavedDiffWork<'b> = std::mem::transmute(self); let extended: SavedDiffWork<'b> = std::mem::transmute(self);
DiffMachine { DiffMachine {
vdom, vdom,
+13 -1
View File
@@ -15,9 +15,21 @@ use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
/// Awaits the given task, forcing the component to re-render when the value is ready. /// Awaits the given task, forcing the component to re-render when the value is ready.
/// ///
/// Returns the handle to the task and the value (if it is ready, else None).
/// ///
/// ```
/// static Example: FC<()> = |cx, props| {
/// let (task, value) = use_task(|| async {
/// timer::sleep(Duration::from_secs(1)).await;
/// "Hello World"
/// });
/// ///
/// /// match contents {
/// Some(contents) => rsx!(cx, div { "{title}" }),
/// None => rsx!(cx, div { "Loading..." }),
/// }
/// };
/// ```
pub fn use_task<'src, Out, Fut, Init>( pub fn use_task<'src, Out, Fut, Init>(
cx: Context<'src>, cx: Context<'src>,
task_initializer: Init, task_initializer: Init,
+78 -101
View File
@@ -101,6 +101,10 @@ pub enum SchedulerMsg {
Immediate(ScopeId), Immediate(ScopeId),
// tasks // tasks
Task(TaskMsg),
}
pub enum TaskMsg {
SubmitTask(FiberTask, u64), SubmitTask(FiberTask, u64),
ToggleTask(u64), ToggleTask(u64),
PauseTask(u64), PauseTask(u64),
@@ -152,7 +156,9 @@ pub(crate) struct Scheduler {
pub garbage_scopes: HashSet<ScopeId>, pub garbage_scopes: HashSet<ScopeId>,
pub lane: PriorityLane, pub dirty_scopes: IndexSet<ScopeId>,
pub saved_state: Option<SavedDiffWork<'static>>,
pub in_progress: bool,
} }
impl Scheduler { impl Scheduler {
@@ -181,7 +187,9 @@ impl Scheduler {
let task_id = task_counter.get(); let task_id = task_counter.get();
task_counter.set(task_id + 1); task_counter.set(task_id + 1);
sender sender
.unbounded_send(SchedulerMsg::SubmitTask(fiber_task, task_id)) .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
fiber_task, task_id,
)))
.unwrap(); .unwrap();
TaskHandle { TaskHandle {
our_id: task_id, our_id: task_id,
@@ -214,6 +222,12 @@ impl Scheduler {
let async_tasks = FuturesUnordered::new(); let async_tasks = FuturesUnordered::new();
// push a task that would never resolve - prevents us from immediately aborting the scheduler
async_tasks.push(Box::pin(async {
std::future::pending::<()>().await;
ScopeId(0)
}) as FiberTask);
Self { Self {
pool, pool,
@@ -235,24 +249,9 @@ impl Scheduler {
garbage_scopes: HashSet::new(), garbage_scopes: HashSet::new(),
// current_priority: EventPriority::Low, dirty_scopes: Default::default(),
lane: PriorityLane::new(), saved_state: None,
} in_progress: false,
}
pub fn manually_poll_events(&mut self) {
while let Ok(Some(msg)) = self.receiver.try_next() {
match msg {
SchedulerMsg::UiEvent(event) => self.ui_events.push_front(event),
SchedulerMsg::Immediate(im) => self.pending_immediates.push_front(im),
// task stuff
SchedulerMsg::SubmitTask(_, _) => todo!(),
SchedulerMsg::ToggleTask(_) => todo!(),
SchedulerMsg::PauseTask(_) => todo!(),
SchedulerMsg::ResumeTask(_) => todo!(),
SchedulerMsg::DropTask(_) => todo!(),
}
} }
} }
@@ -272,31 +271,27 @@ impl Scheduler {
} }
} }
use EventPriority::*; // use EventPriority::*;
match priority { // match priority {
Immediate => todo!(), // Immediate => todo!(),
High => todo!(), // High => todo!(),
Medium => todo!(), // Medium => todo!(),
Low => todo!(), // Low => todo!(),
} // }
discrete discrete
} }
fn prepare_work(&mut self) { fn prepare_work(&mut self) {
while let Some(trigger) = self.ui_events.pop_back() { // while let Some(trigger) = self.ui_events.pop_back() {
if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {} // if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
} // }
} }
// nothing to do, no events on channels, no work // nothing to do, no events on channels, no work
pub fn has_any_work(&self) -> bool { pub fn has_any_work(&self) -> bool {
self.lane.has_work() || self.has_pending_events() !(self.dirty_scopes.is_empty() && self.ui_events.is_empty())
}
pub fn has_pending_events(&self) -> bool {
!self.ui_events.is_empty()
} }
/// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
@@ -304,35 +299,39 @@ impl Scheduler {
fn save_work(&mut self, lane: SavedDiffWork) { fn save_work(&mut self, lane: SavedDiffWork) {
let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) }; let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
self.lane.saved_state = Some(saved); self.saved_state = Some(saved);
} }
fn load_work(&mut self) -> SavedDiffWork<'static> { unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
// match self.current_priority { self.saved_state.take().unwrap().extend()
// 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) { pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
match msg { match msg {
//
SchedulerMsg::Task(msg) => todo!(),
SchedulerMsg::Immediate(_) => todo!(), SchedulerMsg::Immediate(_) => todo!(),
SchedulerMsg::UiEvent(event) => { SchedulerMsg::UiEvent(event) => {
// //
self.handle_ui_event(event); let (discrete, priority) = event_meta(&event);
}
// if let Some(scope) = self.pool.get_scope_mut(event.scope) {
SchedulerMsg::SubmitTask(_, _) => todo!(), if let Some(element) = event.mounted_dom_id {
SchedulerMsg::ToggleTask(_) => todo!(), // TODO: bubble properly here
SchedulerMsg::PauseTask(_) => todo!(), scope.call_listener(event.event, element);
SchedulerMsg::ResumeTask(_) => todo!(),
SchedulerMsg::DropTask(_) => todo!(), while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
//
// self.add_dirty_scope(dirty_scope, trigger.priority)
}
}
}
discrete;
}
} }
} }
@@ -346,22 +345,22 @@ impl Scheduler {
) -> bool { ) -> bool {
// Work through the current subtree, and commit the results when it finishes // Work through the current subtree, and commit the results when it finishes
// When the deadline expires, give back the work // When the deadline expires, give back the work
let saved_state = self.load_work(); let saved_state = unsafe { self.load_work() };
// We have to split away some parts of ourself - current lane is borrowed mutably // We have to split away some parts of ourself - current lane is borrowed mutably
let mut shared = self.pool.clone(); let shared = self.pool.clone();
let mut machine = unsafe { saved_state.promote(&mut shared) }; let mut machine = unsafe { saved_state.promote(&shared) };
if machine.stack.is_empty() { if machine.stack.is_empty() {
let shared = self.pool.clone(); let shared = self.pool.clone();
self.lane.dirty_scopes.sort_by(|a, b| { self.dirty_scopes.sort_by(|a, b| {
let h1 = shared.get_scope(*a).unwrap().height; let h1 = shared.get_scope(*a).unwrap().height;
let h2 = shared.get_scope(*b).unwrap().height; let h2 = shared.get_scope(*b).unwrap().height;
h1.cmp(&h2) h1.cmp(&h2)
}); });
if let Some(scope) = self.lane.dirty_scopes.pop() { if let Some(scope) = self.dirty_scopes.pop() {
let component = self.pool.get_scope(scope).unwrap(); let component = self.pool.get_scope(scope).unwrap();
let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
machine.stack.push(DiffInstruction::Diff { new, old }); machine.stack.push(DiffInstruction::Diff { new, old });
@@ -378,7 +377,7 @@ impl Scheduler {
false false
} else { } else {
for node in saved.seen_scopes.drain() { for node in saved.seen_scopes.drain() {
self.lane.dirty_scopes.remove(&node); self.dirty_scopes.remove(&node);
} }
let mut new_mutations = Mutations::new(); let mut new_mutations = Mutations::new();
@@ -395,9 +394,9 @@ impl Scheduler {
/// Uses some fairly complex logic to schedule what work should be produced. /// Uses some fairly complex logic to schedule what work should be produced.
/// ///
/// Returns a list of successful mutations. /// Returns a list of successful mutations.
pub async fn work_with_deadline<'a>( pub fn work_with_deadline<'a>(
&'a mut self, &'a mut self,
deadline: impl Future<Output = ()>, mut deadline: impl FnMut() -> bool,
) -> Vec<Mutations<'a>> { ) -> Vec<Mutations<'a>> {
/* /*
Strategy: Strategy:
@@ -429,33 +428,29 @@ impl Scheduler {
- but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff? - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
*/ */
let mut committed_mutations = Vec::<Mutations<'static>>::new(); let mut committed_mutations = Vec::<Mutations<'static>>::new();
pin_mut!(deadline);
loop { loop {
// Move work out of the scheduler message receiver and into dedicated message lanes // switch our priority, pop off any work
self.manually_poll_events(); for event in self.ui_events.drain(..) {
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
if let Some(element) = event.mounted_dom_id {
// TODO: bubble properly here
scope.call_listener(event.event, element);
// Wait for any new events if we have nothing to do while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
// todo: poll the events once even if there is work to do to prevent starvation match dirty_scope {
if !self.has_any_work() { SchedulerMsg::Immediate(im) => {
futures_util::select! { self.dirty_scopes.insert(im);
// todo: find a solution for the exhausted queue problem }
// msg = self.async_tasks.next() => {} _ => todo!(),
msg = self.receiver.next() => { }
log::debug!("received work in scheduler"); }
self.handle_channel_msg(msg.unwrap()) }
},
_ = (&mut deadline).fuse() => return committed_mutations,
} }
} }
// switch our priority, pop off any work
self.prepare_work();
let mut deadline_reached = || (&mut deadline).now_or_never().is_some();
let finished_before_deadline = let finished_before_deadline =
self.work_on_current_lane(&mut deadline_reached, &mut committed_mutations); self.work_on_current_lane(&mut deadline, &mut committed_mutations);
if !finished_before_deadline { if !finished_before_deadline {
break; break;
@@ -471,7 +466,9 @@ impl Scheduler {
pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> { pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
let mut committed_mutations = Vec::new(); let mut committed_mutations = Vec::new();
self.manually_poll_events(); while let Ok(Some(msg)) = self.receiver.try_next() {
self.handle_channel_msg(msg);
}
if !self.has_any_work() { if !self.has_any_work() {
return committed_mutations; return committed_mutations;
@@ -534,23 +531,3 @@ impl Scheduler {
} }
} }
} }
pub(crate) struct PriorityLane {
pub dirty_scopes: IndexSet<ScopeId>,
pub saved_state: Option<SavedDiffWork<'static>>,
pub in_progress: bool,
}
impl PriorityLane {
pub fn new() -> Self {
Self {
saved_state: None,
dirty_scopes: Default::default(),
in_progress: false,
}
}
fn has_work(&self) -> bool {
!self.dirty_scopes.is_empty() || self.in_progress
}
}
+4 -4
View File
@@ -12,28 +12,28 @@ impl TaskHandle {
/// This method is not synchronous - your task will not stop immediately. /// This method is not synchronous - your task will not stop immediately.
pub fn toggle(&self) { pub fn toggle(&self) {
self.sender self.sender
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
.unwrap() .unwrap()
} }
/// This method is not synchronous - your task will not stop immediately. /// This method is not synchronous - your task will not stop immediately.
pub fn resume(&self) { pub fn resume(&self) {
self.sender self.sender
.unbounded_send(SchedulerMsg::ResumeTask(self.our_id)) .unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id)))
.unwrap() .unwrap()
} }
/// This method is not synchronous - your task will not stop immediately. /// This method is not synchronous - your task will not stop immediately.
pub fn stop(&self) { pub fn stop(&self) {
self.sender self.sender
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
.unwrap() .unwrap()
} }
/// This method is not synchronous - your task will not stop immediately. /// This method is not synchronous - your task will not stop immediately.
pub fn restart(&self) { pub fn restart(&self) {
self.sender self.sender
.unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
.unwrap() .unwrap()
} }
} }
+33 -10
View File
@@ -334,11 +334,8 @@ impl VirtualDom {
/// applied the edits. /// applied the edits.
/// ///
/// Mutations are the only link between the RealDOM and the VirtualDOM. /// Mutations are the only link between the RealDOM and the VirtualDOM.
pub async fn run_with_deadline( pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
&mut self, self.scheduler.work_with_deadline(deadline)
deadline: impl Future<Output = ()>,
) -> Vec<Mutations<'_>> {
self.scheduler.work_with_deadline(deadline).await
} }
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> { pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
@@ -355,12 +352,38 @@ impl VirtualDom {
log::debug!("No active work.... waiting for some..."); log::debug!("No active work.... waiting for some...");
use futures_util::StreamExt; use futures_util::StreamExt;
// right now this won't poll events if there is ongoing work
// in the future we want to prioritize some events over ongoing work
// this is coming in the "priorities" PR
// Wait for any new events if we have nothing to do
// todo: poll the events once even if there is work to do to prevent starvation
futures_util::select! { futures_util::select! {
// // hmm - will this resolve to none if there are no async tasks? _ = self.scheduler.async_tasks.next() => {}
// _ = self.scheduler.async_tasks.next() => { msg = self.scheduler.receiver.next() => {
// log::debug!("async task completed!"); match msg.unwrap() {
// } SchedulerMsg::Task(t) => todo!(),
msg = self.scheduler.receiver.next() => self.scheduler.handle_channel_msg(msg.unwrap()), SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
},
}
while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
} }
} }
} }
+3 -2
View File
@@ -61,6 +61,7 @@ use dioxus::prelude::Properties;
use dioxus::virtual_dom::VirtualDom; use dioxus::virtual_dom::VirtualDom;
pub use dioxus_core as dioxus; pub use dioxus_core as dioxus;
use dioxus_core::prelude::FC; use dioxus_core::prelude::FC;
use futures_util::FutureExt;
mod cache; mod cache;
mod cfg; mod cfg;
@@ -145,10 +146,10 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
dom.wait_for_work().await; dom.wait_for_work().await;
// wait for the mainthread to schedule us in // wait for the mainthread to schedule us in
let deadline = work_loop.wait_for_idle_time().await; let mut deadline = work_loop.wait_for_idle_time().await;
// run the virtualdom work phase until the frame deadline is reached // run the virtualdom work phase until the frame deadline is reached
let mutations = dom.run_with_deadline(deadline).await; let mutations = dom.run_with_deadline(|| (&mut deadline).now_or_never().is_some());
// wait for the animation frame to fire so we can apply our changes // wait for the animation frame to fire so we can apply our changes
work_loop.wait_for_raf().await; work_loop.wait_for_raf().await;
+3 -1
View File
@@ -33,8 +33,10 @@ impl RafLoop {
.call0(&JsValue::NULL) .call0(&JsValue::NULL)
.unwrap(); .unwrap();
let window = web_sys::window().unwrap();
Self { Self {
window: web_sys::window().unwrap(), window,
raf_receiver, raf_receiver,
raf_closure, raf_closure,
ric_receiver, ric_receiver,