Cleanup more of core
This commit is contained in:
10 files changed
+192
-237
No files matched your search
@@ -11,27 +11,26 @@ pub(crate) trait AnyProps {
|
|||||||
fn duplicate(&self) -> BoxedAnyProps;
|
fn duplicate(&self) -> BoxedAnyProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct VProps<P: 'static> {
|
/// Create a new boxed props object.
|
||||||
pub render_fn: Component<P>,
|
pub fn new_any_props<P: 'static + Clone>(
|
||||||
pub memo: fn(&P, &P) -> bool,
|
render_fn: Component<P>,
|
||||||
pub props: P,
|
memo: fn(&P, &P) -> bool,
|
||||||
pub name: &'static str,
|
props: P,
|
||||||
|
name: &'static str,
|
||||||
|
) -> Box<dyn AnyProps> {
|
||||||
|
Box::new(VProps {
|
||||||
|
render_fn,
|
||||||
|
memo,
|
||||||
|
props,
|
||||||
|
name,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: 'static> VProps<P> {
|
struct VProps<P> {
|
||||||
pub(crate) fn new(
|
render_fn: Component<P>,
|
||||||
render_fn: Component<P>,
|
memo: fn(&P, &P) -> bool,
|
||||||
memo: fn(&P, &P) -> bool,
|
props: P,
|
||||||
props: P,
|
name: &'static str,
|
||||||
name: &'static str,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
render_fn,
|
|
||||||
memo,
|
|
||||||
props,
|
|
||||||
name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Clone + 'static> AnyProps for VProps<P> {
|
impl<P: Clone + 'static> AnyProps for VProps<P> {
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ pub struct Event<T: 'static + ?Sized> {
|
|||||||
pub(crate) propagates: Rc<Cell<bool>>,
|
pub(crate) propagates: Rc<Cell<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + 'static> Event<T> {
|
||||||
|
pub(crate) fn new(data: Rc<T>, bubbles: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
propagates: Rc::new(Cell::new(bubbles)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Event<T> {
|
impl<T> Event<T> {
|
||||||
/// Map the event data to a new type
|
/// Map the event data to a new type
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ mod nodes;
|
|||||||
mod platform;
|
mod platform;
|
||||||
mod properties;
|
mod properties;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod scheduler;
|
|
||||||
mod scope_arena;
|
mod scope_arena;
|
||||||
mod scope_context;
|
mod scope_context;
|
||||||
mod scopes;
|
mod scopes;
|
||||||
|
mod tasks;
|
||||||
mod virtual_dom;
|
mod virtual_dom;
|
||||||
|
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
@@ -34,8 +34,8 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::platform::*;
|
pub use crate::platform::*;
|
||||||
pub use crate::properties::*;
|
pub use crate::properties::*;
|
||||||
pub use crate::runtime::{Runtime, RuntimeGuard};
|
pub use crate::runtime::{Runtime, RuntimeGuard};
|
||||||
pub use crate::scheduler::*;
|
|
||||||
pub use crate::scopes::*;
|
pub use crate::scopes::*;
|
||||||
|
pub use crate::tasks::*;
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
|
|
||||||
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
|
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
any_props::{BoxedAnyProps, VProps},
|
any_props::{new_any_props, BoxedAnyProps},
|
||||||
innerlude::ScopeState,
|
innerlude::ScopeState,
|
||||||
};
|
};
|
||||||
use crate::{arena::ElementId, Element, Event};
|
use crate::{arena::ElementId, Element, Event};
|
||||||
@@ -520,36 +520,22 @@ pub struct VComponent {
|
|||||||
|
|
||||||
impl VComponent {
|
impl VComponent {
|
||||||
/// Create a new [`VComponent`] variant
|
/// Create a new [`VComponent`] variant
|
||||||
///
|
|
||||||
///
|
|
||||||
/// The given component can be any of four signatures. Remember that an [`Element`] is really a [`Result<VNode>`].
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// // Without explicit props
|
|
||||||
/// fn() -> Element;
|
|
||||||
/// async fn(Scope<'_>) -> Element;
|
|
||||||
///
|
|
||||||
/// // With explicit props
|
|
||||||
/// fn(Props) -> Element;
|
|
||||||
/// async fn(Scope<Props<'_>>) -> Element;
|
|
||||||
/// ```
|
|
||||||
pub fn new<P, M>(
|
pub fn new<P, M>(
|
||||||
component: impl ComponentFunction<P, M>,
|
component: impl ComponentFunction<P, M>,
|
||||||
props: P,
|
props: P,
|
||||||
fn_name: &'static str,
|
fn_name: &'static str,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
// The properties must be valid until the next bump frame
|
|
||||||
P: Properties + 'static,
|
P: Properties + 'static,
|
||||||
{
|
{
|
||||||
let component = Rc::new(component);
|
let component = Rc::new(component);
|
||||||
let render_fn = component.id();
|
let render_fn = component.id();
|
||||||
let component = component.as_component();
|
let component = component.as_component();
|
||||||
let vcomp = VProps::new(component, <P as Properties>::memoize, props, fn_name);
|
let props = new_any_props(component, <P as Properties>::memoize, props, fn_name);
|
||||||
|
|
||||||
VComponent {
|
VComponent {
|
||||||
name: fn_name,
|
name: fn_name,
|
||||||
props: Box::new(vcomp),
|
props,
|
||||||
render_fn,
|
render_fn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ pub struct Runtime {
|
|||||||
pub(crate) rendering: Cell<bool>,
|
pub(crate) rendering: Cell<bool>,
|
||||||
|
|
||||||
/// Tasks created with cx.spawn
|
/// Tasks created with cx.spawn
|
||||||
pub tasks: RefCell<Slab<LocalTask>>,
|
pub(crate) tasks: RefCell<Slab<LocalTask>>,
|
||||||
|
|
||||||
pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
use crate::ScopeId;
|
|
||||||
|
|
||||||
mod task;
|
|
||||||
mod wait;
|
|
||||||
|
|
||||||
pub use task::*;
|
|
||||||
|
|
||||||
/// The type of message that can be sent to the scheduler.
|
|
||||||
///
|
|
||||||
/// These messages control how the scheduler will process updates to the UI.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum SchedulerMsg {
|
|
||||||
/// Immediate updates from Components that mark them as dirty
|
|
||||||
Immediate(ScopeId),
|
|
||||||
|
|
||||||
/// A task has woken and needs to be progressed
|
|
||||||
TaskNotified(Task),
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
use futures_util::task::ArcWake;
|
|
||||||
|
|
||||||
use super::SchedulerMsg;
|
|
||||||
use crate::ElementId;
|
|
||||||
use crate::{innerlude::Mutations, Element, ScopeId};
|
|
||||||
use std::future::Future;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::task::Waker;
|
|
||||||
use std::{
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
collections::HashSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A boundary in the VirtualDom that captures all suspended components below it
|
|
||||||
pub struct SuspenseContext {
|
|
||||||
pub(crate) id: ScopeId,
|
|
||||||
pub(crate) waiting_on: RefCell<HashSet<ScopeId>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SuspenseContext {
|
|
||||||
/// Create a new boundary for suspense
|
|
||||||
pub fn new(id: ScopeId) -> Self {
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
waiting_on: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mark_suspend(&self, id: ScopeId) {
|
|
||||||
self.waiting_on.borrow_mut().insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
use crate::{runtime::RuntimeGuard, Task, VirtualDom};
|
|
||||||
use std::task::Context;
|
|
||||||
|
|
||||||
impl VirtualDom {
|
|
||||||
/// Handle notifications by tasks inside the scheduler
|
|
||||||
///
|
|
||||||
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
|
||||||
/// queue
|
|
||||||
pub(crate) fn handle_task_wakeup(&mut self, id: Task) {
|
|
||||||
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
|
||||||
let mut tasks = self.runtime.tasks.borrow_mut();
|
|
||||||
|
|
||||||
let task = match tasks.get(id.0) {
|
|
||||||
Some(task) => task,
|
|
||||||
// The task was removed from the scheduler, so we can just ignore it
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut cx = Context::from_waker(&task.waker);
|
|
||||||
|
|
||||||
// update the scope stack
|
|
||||||
self.runtime.scope_stack.borrow_mut().push(task.scope);
|
|
||||||
self.runtime.rendering.set(false);
|
|
||||||
self.runtime.current_task.set(Some(id));
|
|
||||||
|
|
||||||
// If the task completes...
|
|
||||||
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
|
||||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
|
||||||
let scope = &self.get_scope(task.scope).unwrap();
|
|
||||||
scope.context().spawned_tasks.borrow_mut().remove(&id);
|
|
||||||
|
|
||||||
// Remove it from the scheduler
|
|
||||||
tasks.try_remove(id.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the scope from the stack
|
|
||||||
self.runtime.scope_stack.borrow_mut().pop();
|
|
||||||
self.runtime.rendering.set(true);
|
|
||||||
self.runtime.current_task.set(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
use futures_util::task::ArcWake;
|
|
||||||
|
|
||||||
use super::SchedulerMsg;
|
|
||||||
use crate::innerlude::{remove_future, spawn, Runtime};
|
use crate::innerlude::{remove_future, spawn, Runtime};
|
||||||
use crate::ScopeId;
|
use crate::ScopeId;
|
||||||
|
use futures_util::task::ArcWake;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@@ -11,8 +9,7 @@ use std::task::Waker;
|
|||||||
|
|
||||||
/// A task's unique identifier.
|
/// A task's unique identifier.
|
||||||
///
|
///
|
||||||
/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused
|
/// `Task` is a unique identifier for a task that has been spawned onto the runtime. It can be used to cancel the task
|
||||||
/// once a Task has been completed.
|
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct Task(pub(crate) usize);
|
pub struct Task(pub(crate) usize);
|
||||||
@@ -39,14 +36,6 @@ impl Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the task itself is the waker
|
|
||||||
pub(crate) struct LocalTask {
|
|
||||||
pub scope: ScopeId,
|
|
||||||
pub parent: Option<Task>,
|
|
||||||
pub task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
|
||||||
pub waker: Waker,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
/// Start a new future on the same thread as the rest of the VirtualDom.
|
/// Start a new future on the same thread as the rest of the VirtualDom.
|
||||||
///
|
///
|
||||||
@@ -86,6 +75,43 @@ impl Runtime {
|
|||||||
task_id
|
task_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_task_wakeup(&self, id: Task) {
|
||||||
|
let mut tasks = self.tasks.borrow_mut();
|
||||||
|
|
||||||
|
let task = match tasks.get(id.0) {
|
||||||
|
Some(task) => task,
|
||||||
|
// The task was removed from the scheduler, so we can just ignore it
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::task::Context;
|
||||||
|
|
||||||
|
let mut cx = Context::from_waker(&task.waker);
|
||||||
|
|
||||||
|
// update the scope stack
|
||||||
|
self.scope_stack.borrow_mut().push(task.scope);
|
||||||
|
self.rendering.set(false);
|
||||||
|
self.current_task.set(Some(id));
|
||||||
|
|
||||||
|
// If the task completes...
|
||||||
|
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
||||||
|
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||||
|
self.get_context(task.scope)
|
||||||
|
.unwrap()
|
||||||
|
.spawned_tasks
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&id);
|
||||||
|
|
||||||
|
// Remove it from the scheduler
|
||||||
|
tasks.try_remove(id.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the scope from the stack
|
||||||
|
self.scope_stack.borrow_mut().pop();
|
||||||
|
self.rendering.set(true);
|
||||||
|
self.current_task.set(None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Drop the future with the given TaskId
|
/// Drop the future with the given TaskId
|
||||||
///
|
///
|
||||||
/// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
|
/// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
|
||||||
@@ -97,9 +123,34 @@ impl Runtime {
|
|||||||
pub fn current_task(&self) -> Option<Task> {
|
pub fn current_task(&self) -> Option<Task> {
|
||||||
self.current_task.get()
|
self.current_task.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the parent task of the given task, if it exists
|
||||||
|
pub fn parent_task(&self, task: Task) -> Option<Task> {
|
||||||
|
self.tasks.borrow().get(task.0)?.parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocalTaskHandle {
|
/// the task itself is the waker
|
||||||
|
pub(crate) struct LocalTask {
|
||||||
|
scope: ScopeId,
|
||||||
|
parent: Option<Task>,
|
||||||
|
task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
||||||
|
waker: Waker,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of message that can be sent to the scheduler.
|
||||||
|
///
|
||||||
|
/// These messages control how the scheduler will process updates to the UI.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum SchedulerMsg {
|
||||||
|
/// Immediate updates from Components that mark them as dirty
|
||||||
|
Immediate(ScopeId),
|
||||||
|
|
||||||
|
/// A task has woken and needs to be progressed
|
||||||
|
TaskNotified(Task),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LocalTaskHandle {
|
||||||
id: Task,
|
id: Task,
|
||||||
tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::VProps,
|
any_props::new_any_props,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
innerlude::{
|
innerlude::{
|
||||||
DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount,
|
DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount,
|
||||||
@@ -14,12 +14,12 @@ use crate::{
|
|||||||
properties::ComponentFunction,
|
properties::ComponentFunction,
|
||||||
runtime::{Runtime, RuntimeGuard},
|
runtime::{Runtime, RuntimeGuard},
|
||||||
scopes::ScopeId,
|
scopes::ScopeId,
|
||||||
AttributeValue, BoxedContext, Element, Event, Mutations,
|
AttributeValue, BoxedContext, Element, Event, Mutations, Task,
|
||||||
};
|
};
|
||||||
use futures_util::{pin_mut, StreamExt};
|
use futures_util::{pin_mut, StreamExt};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
||||||
|
|
||||||
/// A virtual node system that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -287,12 +287,7 @@ impl VirtualDom {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let root = dom.new_scope(
|
let root = dom.new_scope(
|
||||||
Box::new(VProps::new(
|
new_any_props(Rc::new(root).as_component(), |_, _| true, root_props, "app"),
|
||||||
Rc::new(root).as_component(),
|
|
||||||
|_, _| true,
|
|
||||||
root_props,
|
|
||||||
"root",
|
|
||||||
)),
|
|
||||||
"app",
|
"app",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -363,7 +358,6 @@ impl VirtualDom {
|
|||||||
/// It is up to the listeners themselves to mark nodes as dirty.
|
/// It is up to the listeners themselves to mark nodes as dirty.
|
||||||
///
|
///
|
||||||
/// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
|
/// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
|
||||||
|
|
||||||
pub fn handle_event(
|
pub fn handle_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
@@ -394,87 +388,86 @@ impl VirtualDom {
|
|||||||
| | | <-- no, broke early
|
| | | <-- no, broke early
|
||||||
| <-- no, broke early
|
| <-- no, broke early
|
||||||
*/
|
*/
|
||||||
let parent_path = match self.elements.get(element.0) {
|
let Some(Some(parent_path)) = self.elements.get(element.0).copied() else {
|
||||||
Some(Some(el)) => *el,
|
return;
|
||||||
_ => return,
|
|
||||||
};
|
};
|
||||||
let mut parent_node = Some(parent_path);
|
|
||||||
|
|
||||||
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
||||||
let uievent = Event {
|
let uievent = Event::new(data, bubbles);
|
||||||
propagates: Rc::new(Cell::new(bubbles)),
|
|
||||||
data,
|
// Use the simple non-bubbling algorithm if the event doesn't bubble
|
||||||
};
|
if !bubbles {
|
||||||
|
return self.handle_non_bubbling_event(parent_path, name, uievent);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parent_node = Some(parent_path);
|
||||||
|
|
||||||
// If the event bubbles, we traverse through the tree until we find the target element.
|
// If the event bubbles, we traverse through the tree until we find the target element.
|
||||||
if bubbles {
|
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
||||||
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
while let Some(path) = parent_node {
|
||||||
while let Some(path) = parent_node {
|
let mut listeners = vec![];
|
||||||
let mut listeners = vec![];
|
|
||||||
|
|
||||||
let el_ref = &self.mounts[path.mount.0].node;
|
let el_ref = &self.mounts[path.mount.0].node;
|
||||||
let node_template = el_ref.template.get();
|
let node_template = el_ref.template.get();
|
||||||
let target_path = path.path;
|
let target_path = path.path;
|
||||||
|
|
||||||
for (idx, attrs) in el_ref.dynamic_attrs.iter().enumerate() {
|
for (idx, attrs) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||||
let this_path = node_template.attr_paths[idx];
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
for attr in attrs.iter() {
|
for attr in attrs.iter() {
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||||
if attr.name.trim_start_matches("on") == name
|
if attr.name.trim_start_matches("on") == name
|
||||||
&& target_path.is_decendant(&this_path)
|
&& target_path.is_decendant(&this_path)
|
||||||
{
|
{
|
||||||
listeners.push(&attr.value);
|
listeners.push(&attr.value);
|
||||||
|
|
||||||
// Break if this is the exact target element.
|
// Break if this is the exact target element.
|
||||||
// This means we won't call two listeners with the same name on the same element. This should be
|
// This means we won't call two listeners with the same name on the same element. This should be
|
||||||
// documented, or be rejected from the rsx! macro outright
|
// documented, or be rejected from the rsx! macro outright
|
||||||
if target_path == this_path {
|
if target_path == this_path {
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
||||||
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
||||||
for listener in listeners.into_iter().rev() {
|
for listener in listeners.into_iter().rev() {
|
||||||
if let AttributeValue::Listener(listener) = listener {
|
if let AttributeValue::Listener(listener) = listener {
|
||||||
|
self.runtime.rendering.set(false);
|
||||||
|
listener.call(uievent.clone());
|
||||||
|
self.runtime.rendering.set(true);
|
||||||
|
|
||||||
|
if !uievent.propagates.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mount = el_ref.mount.get().as_usize();
|
||||||
|
parent_node = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call an event listener in the simplest way possible without bubbling upwards
|
||||||
|
fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
|
||||||
|
let el_ref = &self.mounts[node.mount.0].node;
|
||||||
|
let node_template = el_ref.template.get();
|
||||||
|
let target_path = node.path;
|
||||||
|
|
||||||
|
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||||
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
|
for attr in attr.iter() {
|
||||||
|
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||||
|
// Only call the listener if this is the exact target element.
|
||||||
|
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
||||||
|
if let AttributeValue::Listener(listener) = &attr.value {
|
||||||
self.runtime.rendering.set(false);
|
self.runtime.rendering.set(false);
|
||||||
listener.call(uievent.clone());
|
listener.call(uievent.clone());
|
||||||
self.runtime.rendering.set(true);
|
self.runtime.rendering.set(true);
|
||||||
|
break;
|
||||||
if !uievent.propagates.get() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mount = el_ref.mount.get().as_usize();
|
|
||||||
parent_node = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, we just call the listener on the target element
|
|
||||||
if let Some(path) = parent_node {
|
|
||||||
let el_ref = &self.mounts[path.mount.0].node;
|
|
||||||
let node_template = el_ref.template.get();
|
|
||||||
let target_path = path.path;
|
|
||||||
|
|
||||||
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
|
||||||
let this_path = node_template.attr_paths[idx];
|
|
||||||
|
|
||||||
for attr in attr.iter() {
|
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
|
||||||
// Only call the listener if this is the exact target element.
|
|
||||||
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
|
||||||
if let AttributeValue::Listener(listener) = &attr.value {
|
|
||||||
self.runtime.rendering.set(false);
|
|
||||||
listener.call(uievent.clone());
|
|
||||||
self.runtime.rendering.set(true);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,27 +494,26 @@ impl VirtualDom {
|
|||||||
let mut some_msg = None;
|
let mut some_msg = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match some_msg.take() {
|
// If a bunch of messages are ready in a sequence, try to pop them off synchronously
|
||||||
// If a bunch of messages are ready in a sequence, try to pop them off synchronously
|
if let Some(msg) = some_msg.take() {
|
||||||
Some(msg) => match msg {
|
match msg {
|
||||||
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
|
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
|
||||||
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
|
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
|
||||||
},
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If they're not ready, then we should wait for them to be ready
|
// If they're not ready, then we should wait for them to be ready
|
||||||
None => {
|
match self.rx.try_next() {
|
||||||
match self.rx.try_next() {
|
Ok(Some(val)) => some_msg = Some(val),
|
||||||
Ok(Some(val)) => some_msg = Some(val),
|
Ok(None) => return,
|
||||||
Ok(None) => return,
|
Err(_) => {
|
||||||
Err(_) => {
|
// If we have any dirty scopes, or finished fiber trees then we should exit
|
||||||
// If we have any dirty scopes, or finished fiber trees then we should exit
|
if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
|
||||||
if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
some_msg = self.rx.next().await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
some_msg = self.rx.next().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,6 +529,15 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle notifications by tasks inside the scheduler
|
||||||
|
///
|
||||||
|
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
||||||
|
/// queue
|
||||||
|
fn handle_task_wakeup(&mut self, id: Task) {
|
||||||
|
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||||
|
self.runtime.handle_task_wakeup(id);
|
||||||
|
}
|
||||||
|
|
||||||
/// Replace a template at runtime. This will re-render all components that use this template.
|
/// Replace a template at runtime. This will re-render all components that use this template.
|
||||||
/// This is the primitive that enables hot-reloading.
|
/// This is the primitive that enables hot-reloading.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in new issue
Block a user