polish: change in cx to cx
This commit is contained in:
1 parent
0ded32e184
commit
9971ff215d
21 files changed
+426
-320
No files matched your search
@@ -113,9 +113,9 @@ pub static Example: FC<()> = |cx| {
|
||||
// rsx! is lazy, and the underlying closures cannot have the same type
|
||||
// Rendering produces the VNode type
|
||||
{match rand::gen_range::<i32>(1..3) {
|
||||
1 => rsx!(in cx, h1 { "big" })
|
||||
2 => rsx!(in cx, h2 { "medium" })
|
||||
_ => rsx!(in cx, h3 { "small" })
|
||||
1 => rsx!(cx, h1 { "big" })
|
||||
2 => rsx!(cx, h2 { "medium" })
|
||||
_ => rsx!(cx, h3 { "small" })
|
||||
}}
|
||||
|
||||
// Optionals
|
||||
@@ -128,7 +128,7 @@ pub static Example: FC<()> = |cx| {
|
||||
// Duplicating nodes
|
||||
// Clones the nodes by reference, so they are literally identical
|
||||
{{
|
||||
let node = rsx!(in cx, h1{ "TopNode" });
|
||||
let node = rsx!(cx, h1{ "TopNode" });
|
||||
(0..10).map(|_| node.clone())
|
||||
}}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa
|
||||
const TITLE: Atom<String> = || "".to_string();
|
||||
const Provider: FC<()> = |cx| {
|
||||
let title = use_signal(&cx, &TITLE);
|
||||
rsx!(in cx, input { value: title })
|
||||
rsx!(cx, input { value: title })
|
||||
};
|
||||
```
|
||||
|
||||
@@ -108,7 +108,7 @@ If we use the `TITLE` atom in another component, we can cause updates to flow be
|
||||
const Receiver: FC<()> = |cx| {
|
||||
let title = use_signal(&cx, &TITLE);
|
||||
log::info!("This will only be called once!");
|
||||
rsx!(in cx,
|
||||
rsx!(cx,
|
||||
div {
|
||||
h1 { "{title}" }
|
||||
div {}
|
||||
|
||||
@@ -34,11 +34,11 @@ struct NoKeysProps {
|
||||
}
|
||||
static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
|
||||
// WRONG: Make sure to add keys!
|
||||
rsx!(in cx, ul {
|
||||
rsx!(cx, ul {
|
||||
{cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
|
||||
});
|
||||
// RIGHT: Like this:
|
||||
rsx!(in cx, ul {
|
||||
rsx!(cx, ul {
|
||||
{cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
|
||||
})
|
||||
};
|
||||
@@ -56,7 +56,7 @@ static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
|
||||
/// an API for registering shared state without the ContextProvider pattern.
|
||||
static AntipatternNestedFragments: FC<()> = |cx| {
|
||||
// Try to avoid heavily nesting fragments
|
||||
rsx!(in cx,
|
||||
rsx!(cx,
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
@@ -125,9 +125,9 @@ static AntipatternMisusedHooks: FC<MisuedHooksProps> = |cx| {
|
||||
// do not place a hook in the conditional!
|
||||
// prefer to move it out of the conditional
|
||||
let (state, set_state) = use_state(cx, || "hello world").classic();
|
||||
rsx!(in cx, div { "{state}" })
|
||||
rsx!(cx, div { "{state}" })
|
||||
} else {
|
||||
rsx!(in cx, div { "Not rendering state" })
|
||||
rsx!(cx, div { "Not rendering state" })
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ pub static Example0: FC<MyProps> = |cx| {
|
||||
// which will do essentially the same thing as `cx.render`.
|
||||
//
|
||||
// In short:
|
||||
// `rsx!(in cx, ...)` is shorthand for `cx.render(rsx!(...))`
|
||||
// `rsx!(cx, ...)` is shorthand for `cx.render(rsx!(...))`
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct MyProps1 {
|
||||
should_show: bool,
|
||||
@@ -45,21 +45,21 @@ pub static Example1: FC<MyProps1> = |cx| {
|
||||
// With matching
|
||||
{match cx.should_show {
|
||||
true => cx.render(rsx!(div {"it is true!"})),
|
||||
false => rsx!(in cx, div {"it is false!"}),
|
||||
false => rsx!(cx, div {"it is false!"}),
|
||||
}}
|
||||
|
||||
// or with just regular conditions
|
||||
{if cx.should_show {
|
||||
rsx!(in cx, div {"it is true!"})
|
||||
rsx!(cx, div {"it is true!"})
|
||||
} else {
|
||||
rsx!(in cx, div {"it is false!"})
|
||||
rsx!(cx, div {"it is false!"})
|
||||
}}
|
||||
|
||||
// or with optional chaining
|
||||
{
|
||||
cx.should_show
|
||||
.then(|| rsx!(in cx, div {"it is false!"}))
|
||||
.unwrap_or_else(|| rsx!(in cx, div {"it is false!"}))
|
||||
.then(|| rsx!(cx, div {"it is false!"}))
|
||||
.unwrap_or_else(|| rsx!(cx, div {"it is false!"}))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -81,9 +81,9 @@ pub static Example2: FC<MyProps2> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
{match cx.color {
|
||||
Color::Green => rsx!(in cx, div {"it is Green!"}),
|
||||
Color::Yellow => rsx!(in cx, div {"it is Yellow!"}),
|
||||
Color::Red => rsx!(in cx, div {"it is Red!"}),
|
||||
Color::Green => rsx!(cx, div {"it is Green!"}),
|
||||
Color::Yellow => rsx!(cx, div {"it is Yellow!"}),
|
||||
Color::Red => rsx!(cx, div {"it is Red!"}),
|
||||
}}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -34,7 +34,7 @@ static ButtonList: FC<()> = |cx| {
|
||||
/// This shows how listeners may be without a visible change in the display.
|
||||
/// Check the console.
|
||||
static NonUpdatingEvents: FC<()> = |cx| {
|
||||
rsx!(in cx, div {
|
||||
rsx!(cx, div {
|
||||
button {
|
||||
onclick: move |_| log::info!("Did not cause any updates!")
|
||||
"Click me to log!"
|
||||
@@ -43,7 +43,7 @@ static NonUpdatingEvents: FC<()> = |cx| {
|
||||
};
|
||||
|
||||
static DisablePropogation: FC<()> = |cx| {
|
||||
rsx!(in cx,
|
||||
rsx!(cx,
|
||||
div {
|
||||
onclick: move |_| log::info!("event propogated to the div!")
|
||||
button {
|
||||
|
||||
@@ -19,8 +19,8 @@ pub static Example: FC<()> = |cx| {
|
||||
cx,
|
||||
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
||||
|cx, res| match res {
|
||||
Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
|
||||
Err(_) => rsx!(in cx, div { "No doggos for you :(" }),
|
||||
Ok(res) => rsx!(cx, img { src: "{res.message}" }),
|
||||
Err(_) => rsx!(cx, div { "No doggos for you :(" }),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ pub static Example: FC<()> = |cx| {
|
||||
// To fix this, call "render" method or use the "in" syntax to produce VNodes.
|
||||
// There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros)
|
||||
{match true {
|
||||
true => rsx!(in cx, h1 {"Top text"}),
|
||||
true => rsx!(cx, h1 {"Top text"}),
|
||||
false => cx.render(rsx!( h1 {"Bottom text"}))
|
||||
}}
|
||||
|
||||
@@ -117,9 +117,9 @@ pub static Example: FC<()> = |cx| {
|
||||
|
||||
// True conditions need to be rendered (same reasons as matching)
|
||||
{if true {
|
||||
rsx!(in cx, h1 {"Top text"})
|
||||
rsx!(cx, h1 {"Top text"})
|
||||
} else {
|
||||
rsx!(in cx, h1 {"Bottom text"})
|
||||
rsx!(cx, h1 {"Bottom text"})
|
||||
}}
|
||||
|
||||
// returning "None" is a bit noisy... but rare in practice
|
||||
|
||||
@@ -15,8 +15,8 @@ static App: FC<()> = |cx| {
|
||||
let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
|
||||
|
||||
let body = match selection {
|
||||
Some(id) => rsx!(in cx, ReferenceItem { selected: *id }),
|
||||
None => rsx!(in cx, div { "Select an concept to explore" }),
|
||||
Some(id) => rsx!(cx, ReferenceItem { selected: *id }),
|
||||
None => rsx!(cx, div { "Select an concept to explore" }),
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||
/// // To fix this, call "render" method or use the "in" syntax to produce VNodes.
|
||||
/// // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros)
|
||||
/// {match true {
|
||||
/// true => rsx!(in cx, h1 {"Top text"}),
|
||||
/// true => rsx!(cx, h1 {"Top text"}),
|
||||
/// false => cx.render(rsx!( h1 {"Bottom text"}))
|
||||
/// }}
|
||||
///
|
||||
@@ -98,9 +98,9 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||
///
|
||||
/// // True conditions need to be rendered (same reasons as matching)
|
||||
/// {if true {
|
||||
/// rsx!(in cx, h1 {"Top text"})
|
||||
/// rsx!(cx, h1 {"Top text"})
|
||||
/// } else {
|
||||
/// rsx!(in cx, h1 {"Bottom text"})
|
||||
/// rsx!(cx, h1 {"Bottom text"})
|
||||
/// }}
|
||||
///
|
||||
/// // returning "None" is a bit noisy... but rare in practice
|
||||
|
||||
@@ -39,8 +39,7 @@ impl Parse for RsxBody<AS_HTML> {
|
||||
}
|
||||
|
||||
fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
|
||||
let res = if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
|
||||
let _ = input.parse::<Token![in]>()?;
|
||||
let res = if input.peek(Ident) && input.peek2(Token![,]) {
|
||||
let name = input.parse::<Ident>()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
Some(name)
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.inlayHints.enable": false
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
}
|
||||
@@ -162,13 +162,84 @@ impl<'src, P> Context<'src, P> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
|
||||
pub fn try_consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
|
||||
let getter = &self.scope.shared.get_shared_context;
|
||||
let ty = TypeId::of::<T>();
|
||||
let idx = self.scope.our_arena_idx;
|
||||
getter(idx, ty).map(|f| f.downcast().expect("TypeID already validated"))
|
||||
}
|
||||
|
||||
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
///
|
||||
/// This is a hook, so it should not be called conditionally!
|
||||
///
|
||||
/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
|
||||
/// so don't put it in a conditional.
|
||||
///
|
||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
||||
/// the context via Rc/Weak.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// struct SharedState(&'static str);
|
||||
///
|
||||
/// static App: FC<()> = |cx| {
|
||||
/// cx.provide_state(|| SharedState("world"));
|
||||
/// rsx!(cx, Child {})
|
||||
/// }
|
||||
///
|
||||
/// static Child: FC<()> = |cx| {
|
||||
/// let state = cx.consume_state::<SharedState>();
|
||||
/// rsx!(cx, div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_state<T, F>(self, init: F) -> &'src Rc<T>
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
//
|
||||
let ty = TypeId::of::<T>();
|
||||
let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty);
|
||||
|
||||
let is_initialized = self.use_hook(
|
||||
|_| false,
|
||||
|s| {
|
||||
let i = s.clone();
|
||||
*s = true;
|
||||
i
|
||||
},
|
||||
|_| {},
|
||||
);
|
||||
|
||||
match (is_initialized, contains_key) {
|
||||
// Do nothing, already initialized and already exists
|
||||
(true, true) => {}
|
||||
|
||||
// Needs to be initialized
|
||||
(false, false) => self.add_shared_state(init()),
|
||||
|
||||
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
|
||||
};
|
||||
|
||||
self.consume_state().unwrap()
|
||||
}
|
||||
|
||||
/// Uses a context, storing the cached value around
|
||||
///
|
||||
/// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a
|
||||
/// context was added later. This allows using another hook as a fallback
|
||||
///
|
||||
pub fn consume_state<T: 'static>(self) -> Option<&'src Rc<T>> {
|
||||
struct UseContextHook<C>(Option<Rc<C>>);
|
||||
self.use_hook(
|
||||
move |_| UseContextHook(self.try_consume_shared_state::<T>()),
|
||||
move |hook| hook.0.as_ref(),
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
/// Store a value between renders
|
||||
///
|
||||
/// This is *the* foundational hook for all other hooks.
|
||||
|
||||
@@ -18,69 +18,6 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
///
|
||||
/// This is a hook, so it may not be called conditionally!
|
||||
///
|
||||
/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
|
||||
/// so don't put it in a conditional.
|
||||
///
|
||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
||||
/// the context via Rc/Weak.
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn use_provide_state<'src, Pr, T, F>(cx: Context<'src, Pr>, init: F) -> &'src Rc<T>
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
let ty = TypeId::of::<T>();
|
||||
let contains_key = cx.scope.shared_contexts.borrow().contains_key(&ty);
|
||||
|
||||
let is_initialized = cx.use_hook(
|
||||
|_| false,
|
||||
|s| {
|
||||
let i = s.clone();
|
||||
*s = true;
|
||||
i
|
||||
},
|
||||
|_| {},
|
||||
);
|
||||
|
||||
match (is_initialized, contains_key) {
|
||||
// Do nothing, already initialized and already exists
|
||||
(true, true) => {}
|
||||
|
||||
// Needs to be initialized
|
||||
(false, false) => cx.add_shared_state(init()),
|
||||
|
||||
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
|
||||
};
|
||||
|
||||
use_consume_state::<T, _>(cx)
|
||||
}
|
||||
|
||||
/// There are hooks going on here!
|
||||
pub fn use_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> &'src Rc<T> {
|
||||
use_try_consume_state::<T, _>(cx).unwrap()
|
||||
}
|
||||
|
||||
/// Uses a context, storing the cached value around
|
||||
///
|
||||
/// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a
|
||||
/// context was added later. This allows using another hook as a fallback
|
||||
///
|
||||
pub fn use_try_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> Option<&'src Rc<T>> {
|
||||
struct UseContextHook<C>(Option<Rc<C>>);
|
||||
|
||||
cx.use_hook(
|
||||
move |_| UseContextHook(cx.consume_shared_state::<T>()),
|
||||
move |hook| hook.0.as_ref(),
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
/// Awaits the given task, forcing the component to re-render when the value is ready.
|
||||
///
|
||||
///
|
||||
|
||||
+245
-204
@@ -151,6 +151,8 @@ pub(crate) struct Scheduler {
|
||||
|
||||
pub pending_tasks: VecDeque<UserEvent>,
|
||||
|
||||
pub batched_events: VecDeque<UserEvent>,
|
||||
|
||||
pub garbage_scopes: HashSet<ScopeId>,
|
||||
|
||||
pub lanes: [PriorityLane; 4],
|
||||
@@ -214,7 +216,7 @@ impl Scheduler {
|
||||
channel,
|
||||
};
|
||||
|
||||
let mut async_tasks = FuturesUnordered::new();
|
||||
let async_tasks = FuturesUnordered::new();
|
||||
|
||||
Self {
|
||||
pool,
|
||||
@@ -233,6 +235,8 @@ impl Scheduler {
|
||||
|
||||
pending_tasks: VecDeque::new(),
|
||||
|
||||
batched_events: VecDeque::new(),
|
||||
|
||||
garbage_scopes: HashSet::new(),
|
||||
|
||||
current_priority: EventPriority::Low,
|
||||
@@ -249,89 +253,70 @@ impl Scheduler {
|
||||
|
||||
pub fn manually_poll_events(&mut self) {
|
||||
while let Ok(Some(msg)) = self.receiver.try_next() {
|
||||
self.handle_channel_msg(msg);
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Converts UI events into dirty scopes with various priorities
|
||||
pub fn consume_pending_events(&mut self) {
|
||||
// returns true if the event is discrete
|
||||
pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
|
||||
let (discrete, priority) = event_meta(&event);
|
||||
|
||||
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);
|
||||
|
||||
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
||||
//
|
||||
// self.add_dirty_scope(dirty_scope, trigger.priority)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use EventPriority::*;
|
||||
|
||||
match priority {
|
||||
Immediate => todo!(),
|
||||
High => todo!(),
|
||||
Medium => todo!(),
|
||||
Low => todo!(),
|
||||
}
|
||||
|
||||
discrete
|
||||
}
|
||||
|
||||
fn prepare_work(&mut self) {
|
||||
// consume all events that are "continuous" to be batched
|
||||
// if we run into a discrete event, then bail early
|
||||
|
||||
while let Some(trigger) = self.ui_events.pop_back() {
|
||||
if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {
|
||||
if let Some(element) = trigger.mounted_dom_id {
|
||||
let priority = match trigger.name {
|
||||
// clipboard
|
||||
"copy" | "cut" | "paste" => EventPriority::Medium,
|
||||
|
||||
// Composition
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||
EventPriority::Low
|
||||
}
|
||||
|
||||
// Keyboard
|
||||
"keydown" | "keypress" | "keyup" => EventPriority::Low,
|
||||
|
||||
// Focus
|
||||
"focus" | "blur" => EventPriority::Low,
|
||||
|
||||
// Form
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => EventPriority::Low,
|
||||
|
||||
// Mouse
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend"
|
||||
| "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
|
||||
| "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove"
|
||||
| "mouseout" | "mouseover" | "mouseup" => EventPriority::Low,
|
||||
|
||||
"mousemove" => EventPriority::Medium,
|
||||
|
||||
// Pointer
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel"
|
||||
| "gotpointercapture" | "lostpointercapture" | "pointerenter"
|
||||
| "pointerleave" | "pointerover" | "pointerout" => EventPriority::Low,
|
||||
|
||||
// Selection
|
||||
"select" | "touchcancel" | "touchend" => EventPriority::Low,
|
||||
|
||||
// Touch
|
||||
"touchmove" | "touchstart" => EventPriority::Low,
|
||||
|
||||
// Wheel
|
||||
"scroll" | "wheel" => EventPriority::Low,
|
||||
|
||||
// Media
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
|
||||
| "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata"
|
||||
| "loadstart" | "pause" | "play" | "playing" | "progress"
|
||||
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
| "timeupdate" | "volumechange" | "waiting" => EventPriority::Low,
|
||||
|
||||
// Animation
|
||||
"animationstart" | "animationend" | "animationiteration" => {
|
||||
EventPriority::Low
|
||||
}
|
||||
|
||||
// Transition
|
||||
"transitionend" => EventPriority::Low,
|
||||
|
||||
// Toggle
|
||||
"toggle" => EventPriority::Low,
|
||||
|
||||
_ => EventPriority::Low,
|
||||
};
|
||||
|
||||
scope.call_listener(trigger.event, element);
|
||||
// let receiver = self.immediate_receiver.clone();
|
||||
// let mut receiver = receiver.borrow_mut();
|
||||
|
||||
// // Drain the immediates into the dirty scopes, setting the appropiate priorities
|
||||
// while let Ok(Some(dirty_scope)) = receiver.try_next() {
|
||||
// self.add_dirty_scope(dirty_scope, trigger.priority)
|
||||
// }
|
||||
}
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,20 +330,6 @@ impl Scheduler {
|
||||
self.ui_events.len() > 0
|
||||
}
|
||||
|
||||
fn shift_priorities(&mut self) {
|
||||
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, _) => EventPriority::Low,
|
||||
};
|
||||
}
|
||||
|
||||
/// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
|
||||
fn balance_lanes(&mut self) {}
|
||||
|
||||
@@ -385,102 +356,45 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Work the scheduler down, not polling any ongoing tasks.
|
||||
///
|
||||
/// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
|
||||
pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
|
||||
let mut committed_mutations = Vec::new();
|
||||
|
||||
self.manually_poll_events();
|
||||
|
||||
if !self.has_any_work() {
|
||||
return committed_mutations;
|
||||
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],
|
||||
}
|
||||
|
||||
self.consume_pending_events();
|
||||
|
||||
while self.has_any_work() {
|
||||
self.shift_priorities();
|
||||
self.work_on_current_lane(|| false, &mut committed_mutations);
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
/// The primary workhorse of the VirtualDOM.
|
||||
///
|
||||
/// Uses some fairly complex logic to schedule what work should be produced.
|
||||
///
|
||||
/// Returns a list of successful mutations.
|
||||
pub async fn work_with_deadline<'a>(
|
||||
&'a mut self,
|
||||
deadline: impl Future<Output = ()>,
|
||||
) -> Vec<Mutations<'a>> {
|
||||
/*
|
||||
Strategy:
|
||||
- When called, check for any UI events that might've been received since the last frame.
|
||||
- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
|
||||
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
|
||||
match msg {
|
||||
SchedulerMsg::Immediate(_) => todo!(),
|
||||
|
||||
- If there are any pending discrete events, then elevate our priorty level. If our priority level is already "high,"
|
||||
then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
|
||||
will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
|
||||
to bump the other work out of the way, or choose to process it so we don't have any conflicts.
|
||||
'static components have a leg up here since their work can be re-used among multiple scopes.
|
||||
"High priority" is only for blocking! Should only be used on "clicks"
|
||||
SchedulerMsg::UiEvent(event) => {
|
||||
//
|
||||
|
||||
- If there are no pending discrete events, then check for continuous events. These can be completely batched
|
||||
|
||||
- we batch completely until we run into a discrete event
|
||||
- all continuous events are batched together
|
||||
- so D C C C C C would be two separate events - D and C. IE onclick and onscroll
|
||||
- D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
|
||||
|
||||
- !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
|
||||
- we need to keep track of element parents to be able to traverse properly
|
||||
|
||||
|
||||
Open questions:
|
||||
- what if we get two clicks from the component during the same slice?
|
||||
- should we batch?
|
||||
- react says no - they are continuous
|
||||
- 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();
|
||||
pin_mut!(deadline);
|
||||
|
||||
loop {
|
||||
// Internalize any pending work since the last time we ran
|
||||
self.manually_poll_events();
|
||||
|
||||
// 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
|
||||
if !self.has_any_work() {
|
||||
futures_util::select! {
|
||||
msg = self.async_tasks.next() => {}
|
||||
msg = self.receiver.next() => self.handle_channel_msg(msg.unwrap()),
|
||||
_ = (&mut deadline).fuse() => return committed_mutations,
|
||||
}
|
||||
self.handle_ui_event(event);
|
||||
}
|
||||
|
||||
// Create work from the pending event queue
|
||||
self.consume_pending_events();
|
||||
|
||||
// shift to the correct lane
|
||||
self.shift_priorities();
|
||||
|
||||
let mut deadline_reached = || (&mut deadline).now_or_never().is_some();
|
||||
|
||||
let finished_before_deadline =
|
||||
self.work_on_current_lane(&mut deadline_reached, &mut committed_mutations);
|
||||
|
||||
if !finished_before_deadline {
|
||||
break;
|
||||
}
|
||||
//
|
||||
SchedulerMsg::SubmitTask(_, _) => todo!(),
|
||||
SchedulerMsg::ToggleTask(_) => todo!(),
|
||||
SchedulerMsg::PauseTask(_) => todo!(),
|
||||
SchedulerMsg::ResumeTask(_) => todo!(),
|
||||
SchedulerMsg::DropTask(_) => todo!(),
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -535,40 +449,105 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
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],
|
||||
/// The primary workhorse of the VirtualDOM.
|
||||
///
|
||||
/// Uses some fairly complex logic to schedule what work should be produced.
|
||||
///
|
||||
/// Returns a list of successful mutations.
|
||||
pub async fn work_with_deadline<'a>(
|
||||
&'a mut self,
|
||||
deadline: impl Future<Output = ()>,
|
||||
) -> Vec<Mutations<'a>> {
|
||||
/*
|
||||
Strategy:
|
||||
- When called, check for any UI events that might've been received since the last frame.
|
||||
- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
|
||||
|
||||
- If there are any pending discrete events, then elevate our priorty level. If our priority level is already "high,"
|
||||
then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
|
||||
will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
|
||||
to bump the other work out of the way, or choose to process it so we don't have any conflicts.
|
||||
'static components have a leg up here since their work can be re-used among multiple scopes.
|
||||
"High priority" is only for blocking! Should only be used on "clicks"
|
||||
|
||||
- If there are no pending discrete events, then check for continuous events. These can be completely batched
|
||||
|
||||
- we batch completely until we run into a discrete event
|
||||
- all continuous events are batched together
|
||||
- so D C C C C C would be two separate events - D and C. IE onclick and onscroll
|
||||
- D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
|
||||
|
||||
- !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
|
||||
- we need to keep track of element parents to be able to traverse properly
|
||||
|
||||
|
||||
Open questions:
|
||||
- what if we get two clicks from the component during the same slice?
|
||||
- should we batch?
|
||||
- react says no - they are continuous
|
||||
- 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();
|
||||
pin_mut!(deadline);
|
||||
|
||||
loop {
|
||||
// Move work out of the scheduler message receiver and into dedicated message lanes
|
||||
self.manually_poll_events();
|
||||
|
||||
// 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
|
||||
if !self.has_any_work() {
|
||||
futures_util::select! {
|
||||
// todo: find a solution for the exhausted queue problem
|
||||
// msg = self.async_tasks.next() => {}
|
||||
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 =
|
||||
self.work_on_current_lane(&mut deadline_reached, &mut committed_mutations);
|
||||
|
||||
if !finished_before_deadline {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
|
||||
match msg {
|
||||
SchedulerMsg::Immediate(_) => todo!(),
|
||||
SchedulerMsg::UiEvent(_) => todo!(),
|
||||
/// Work the scheduler down, not polling any ongoing tasks.
|
||||
///
|
||||
/// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
|
||||
pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
|
||||
let mut committed_mutations = Vec::new();
|
||||
|
||||
//
|
||||
SchedulerMsg::SubmitTask(_, _) => todo!(),
|
||||
SchedulerMsg::ToggleTask(_) => todo!(),
|
||||
SchedulerMsg::PauseTask(_) => todo!(),
|
||||
SchedulerMsg::ResumeTask(_) => todo!(),
|
||||
SchedulerMsg::DropTask(_) => todo!(),
|
||||
self.manually_poll_events();
|
||||
|
||||
if !self.has_any_work() {
|
||||
return committed_mutations;
|
||||
}
|
||||
|
||||
while self.has_any_work() {
|
||||
self.prepare_work();
|
||||
self.work_on_current_lane(|| false, &mut committed_mutations);
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
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),
|
||||
// };
|
||||
}
|
||||
|
||||
/// Restart the entire VirtualDOM from scratch, wiping away any old state and components.
|
||||
///
|
||||
/// Typically used to kickstart the VirtualDOM after initialization.
|
||||
pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
|
||||
//
|
||||
let mut shared = self.pool.clone();
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
|
||||
|
||||
@@ -615,6 +594,8 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
impl Scheduler {}
|
||||
|
||||
pub(crate) struct PriorityLane {
|
||||
pub dirty_scopes: IndexSet<ScopeId>,
|
||||
pub saved_state: Option<SavedDiffWork<'static>>,
|
||||
@@ -631,7 +612,7 @@ impl PriorityLane {
|
||||
}
|
||||
|
||||
fn has_work(&self) -> bool {
|
||||
todo!()
|
||||
self.dirty_scopes.len() > 0 || self.in_progress == true
|
||||
}
|
||||
|
||||
fn work(&mut self) {
|
||||
@@ -824,3 +805,63 @@ impl ResourcePool {
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
@@ -361,7 +361,7 @@ impl<'a> ScopeRenderer<'a> {
|
||||
write_indent(f, il);
|
||||
write!(f, "\"{}\"\n", text.text)?
|
||||
}
|
||||
VNode::Anchor(anchor) => {
|
||||
VNode::Anchor(_anchor) => {
|
||||
write_indent(f, il);
|
||||
write!(f, "Anchor {{}}\n")?;
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ impl VirtualDom {
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// static App: FC<()> = |cx| rsx!(in cx, div {"hello"} );
|
||||
/// static App: FC<()> = |cx| rsx!(cx, div {"hello"} );
|
||||
/// let mut dom = VirtualDom::new(App);
|
||||
/// loop {
|
||||
/// let deadline = TimeoutFuture::from_ms(16);
|
||||
@@ -345,13 +345,17 @@ impl VirtualDom {
|
||||
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
||||
pub async fn wait_for_work(&mut self) {
|
||||
if self.scheduler.has_any_work() {
|
||||
log::debug!("No need to wait for work, we already have some");
|
||||
return;
|
||||
}
|
||||
|
||||
log::debug!("No active work.... waiting for some...");
|
||||
use futures_util::StreamExt;
|
||||
futures_util::select! {
|
||||
// hmm - will this resolve to none if there are no async tasks?
|
||||
_ = self.scheduler.async_tasks.next() => {}
|
||||
// // hmm - will this resolve to none if there are no async tasks?
|
||||
// _ = self.scheduler.async_tasks.next() => {
|
||||
// log::debug!("async task completed!");
|
||||
// }
|
||||
msg = self.scheduler.receiver.next() => self.scheduler.handle_channel_msg(msg.unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use DomEdit::*;
|
||||
|
||||
mod test_logging;
|
||||
|
||||
fn new_dom() -> TestDom {
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||
TestDom::new()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_state_test() {
|
||||
struct MySharedState(&'static str);
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
cx.provide_state(|| MySharedState("world!"));
|
||||
rsx!(cx, Child {})
|
||||
};
|
||||
|
||||
static Child: FC<()> = |cx| {
|
||||
let shared = cx.consume_state::<MySharedState>()?;
|
||||
rsx!(cx, "Hello, {shared.0}")
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let Mutations { edits, .. } = dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
edits,
|
||||
[
|
||||
CreateTextNode {
|
||||
id: 0,
|
||||
text: "Hello, world!"
|
||||
},
|
||||
AppendChildren { many: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -35,8 +35,13 @@ static App: FC<()> = |cx| {
|
||||
cx,
|
||||
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
||||
|cx, res| match res {
|
||||
Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
|
||||
Err(_err) => rsx!(in cx, div { "No doggos for you :(" }),
|
||||
Ok(res) => rsx!(
|
||||
cx,
|
||||
img {
|
||||
src: "{res.message}"
|
||||
}
|
||||
),
|
||||
Err(_err) => rsx!(cx, div { "No doggos for you :(" }),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -27,7 +27,11 @@ static APP: FC<()> = |cx| {
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
button { onclick: move |_| count += 1, "add" }
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Click to add."
|
||||
"Current count: {count}"
|
||||
}
|
||||
ul {
|
||||
{(0..*count).map(|f| rsx!{
|
||||
li { "a - {f}" }
|
||||
|
||||
@@ -93,7 +93,7 @@ impl WebsysDom {
|
||||
|
||||
pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
|
||||
for edit in edits.drain(..) {
|
||||
log::info!("Handling edit: {:#?}", edit);
|
||||
// log::info!("Handling edit: {:#?}", edit);
|
||||
match edit {
|
||||
DomEdit::PushRoot { id: root } => self.push(root),
|
||||
DomEdit::PopRoot => self.pop(),
|
||||
@@ -530,6 +530,8 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
|
||||
|
||||
let typ = event.type_();
|
||||
|
||||
log::debug!("Event type is {:?}", typ);
|
||||
|
||||
// let attrs = target.attributes();
|
||||
// for x in 0..attrs.length() {
|
||||
// let attr = attrs.item(x).unwrap();
|
||||
@@ -569,10 +571,6 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn prepare_websys_dom() -> Element {
|
||||
load_document().get_element_by_id("dioxusroot").unwrap()
|
||||
}
|
||||
|
||||
pub fn load_document() -> Document {
|
||||
web_sys::window()
|
||||
.expect("should have access to the Window")
|
||||
|
||||
+11
-5
@@ -117,10 +117,12 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
|
||||
|
||||
intern_cached_strings();
|
||||
|
||||
let hydrating = cfg.hydrate;
|
||||
let should_hydrate = cfg.hydrate;
|
||||
|
||||
let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
|
||||
|
||||
let tasks = dom.get_event_sender();
|
||||
|
||||
let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
|
||||
|
||||
let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
|
||||
@@ -129,8 +131,10 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
|
||||
|
||||
// hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
|
||||
// ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
|
||||
if !hydrating {
|
||||
if !should_hydrate {
|
||||
websys_dom.process_edits(&mut mutations.edits);
|
||||
} else {
|
||||
// websys dom processed the config and hydrated the dom already
|
||||
}
|
||||
|
||||
let work_loop = ric_raf::RafLoop::new();
|
||||
@@ -144,12 +148,14 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
|
||||
let deadline = work_loop.wait_for_idle_time().await;
|
||||
|
||||
// run the virtualdom work phase until the frame deadline is reached
|
||||
let mut mutations = dom.run_with_deadline(deadline).await;
|
||||
let mutations = dom.run_with_deadline(deadline).await;
|
||||
|
||||
// wait for the animation frame to fire so we can apply our changes
|
||||
work_loop.wait_for_raf().await;
|
||||
|
||||
// actually apply our changes during the animation frame
|
||||
websys_dom.process_edits(&mut mutations[0].edits);
|
||||
for mut edit in mutations {
|
||||
// actually apply our changes during the animation frame
|
||||
websys_dom.process_edits(&mut edit.edits);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user