feat: diffing works on desktop!
This commit is contained in:
1 parent
30ef225812
commit
20f9957fbe
10 files changed
+176
-166
No files matched your search
@@ -42,8 +42,10 @@ impl<'b> VirtualDom {
|
|||||||
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
|
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
|
||||||
let scope_state = &mut self.scopes[scope.0];
|
let scope_state = &mut self.scopes[scope.0];
|
||||||
|
|
||||||
let cur_arena = scope_state.current_frame();
|
let cur_arena = scope_state.previous_frame();
|
||||||
let prev_arena = scope_state.previous_frame();
|
let prev_arena = scope_state.current_frame();
|
||||||
|
// let cur_arena = scope_state.current_frame();
|
||||||
|
// let prev_arena = scope_state.previous_frame();
|
||||||
|
|
||||||
// relax the borrow checker
|
// relax the borrow checker
|
||||||
let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
|
let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
|
||||||
@@ -62,8 +64,10 @@ impl<'b> VirtualDom {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.scope_stack.push(scope);
|
self.scope_stack.push(scope);
|
||||||
|
|
||||||
let left = unsafe { prev_arena.load_node() };
|
let left = unsafe { prev_arena.load_node() };
|
||||||
let right = unsafe { cur_arena.load_node() };
|
let right = unsafe { cur_arena.load_node() };
|
||||||
|
|
||||||
self.diff_maybe_node(mutations, left, right);
|
self.diff_maybe_node(mutations, left, right);
|
||||||
self.scope_stack.pop();
|
self.scope_stack.pop();
|
||||||
}
|
}
|
||||||
@@ -105,6 +109,8 @@ impl<'b> VirtualDom {
|
|||||||
left_template: &'b VNode<'b>,
|
left_template: &'b VNode<'b>,
|
||||||
right_template: &'b VNode<'b>,
|
right_template: &'b VNode<'b>,
|
||||||
) {
|
) {
|
||||||
|
println!("diffing {:?} and {:?}", left_template, right_template);
|
||||||
|
|
||||||
if left_template.template.id != right_template.template.id {
|
if left_template.template.id != right_template.template.id {
|
||||||
// do a light diff of the roots nodes.
|
// do a light diff of the roots nodes.
|
||||||
return;
|
return;
|
||||||
@@ -125,6 +131,7 @@ impl<'b> VirtualDom {
|
|||||||
.set(left_attr.mounted_element.get());
|
.set(left_attr.mounted_element.get());
|
||||||
|
|
||||||
if left_attr.value != right_attr.value {
|
if left_attr.value != right_attr.value {
|
||||||
|
println!("DIFF ATTR: {:?} -> {:?}", left_attr, right_attr);
|
||||||
let value = "todo!()";
|
let value = "todo!()";
|
||||||
muts.push(Mutation::SetAttribute {
|
muts.push(Mutation::SetAttribute {
|
||||||
id: left_attr.mounted_element.get(),
|
id: left_attr.mounted_element.get(),
|
||||||
|
|||||||
@@ -19,6 +19,19 @@ pub struct SuspenseBoundary {
|
|||||||
pub waiting_on: RefCell<HashSet<SuspenseId>>,
|
pub waiting_on: RefCell<HashSet<SuspenseId>>,
|
||||||
pub mutations: RefCell<Mutations<'static>>,
|
pub mutations: RefCell<Mutations<'static>>,
|
||||||
pub placeholder: Cell<Option<ElementId>>,
|
pub placeholder: Cell<Option<ElementId>>,
|
||||||
|
|
||||||
|
// whenever the suspense resolves, we call this onresolve function
|
||||||
|
// this lets us do things like putting up a loading spinner
|
||||||
|
//
|
||||||
|
// todo: we need a way of controlling whether or not a component hides itself but still processes changes
|
||||||
|
// If we run into suspense, we perform a diff, so its important that the old elements are still around.
|
||||||
|
//
|
||||||
|
// When the timer expires, I imagine a container could hide the elements and show the spinner. This, however,
|
||||||
|
// can not be
|
||||||
|
pub onresolve: Option<Box<dyn FnOnce()>>,
|
||||||
|
|
||||||
|
/// Called when
|
||||||
|
pub onstart: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SuspenseBoundary {
|
impl SuspenseBoundary {
|
||||||
@@ -28,6 +41,8 @@ impl SuspenseBoundary {
|
|||||||
waiting_on: Default::default(),
|
waiting_on: Default::default(),
|
||||||
mutations: RefCell::new(Mutations::new(0)),
|
mutations: RefCell::new(Mutations::new(0)),
|
||||||
placeholder: Cell::new(None),
|
placeholder: Cell::new(None),
|
||||||
|
onresolve: None,
|
||||||
|
onstart: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use super::{waker::RcWake, Scheduler, SchedulerMsg};
|
use super::{waker::RcWake, Scheduler, SchedulerMsg};
|
||||||
use crate::ScopeId;
|
use crate::ScopeId;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::task::Context;
|
use std::task::Context;
|
||||||
use std::{cell::UnsafeCell, pin::Pin, rc::Rc, task::Poll};
|
use std::{pin::Pin, rc::Rc, task::Poll};
|
||||||
|
|
||||||
#[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)]
|
||||||
@@ -10,23 +11,19 @@ pub struct TaskId(pub usize);
|
|||||||
|
|
||||||
/// the task itself is the waker
|
/// the task itself is the waker
|
||||||
pub(crate) struct LocalTask {
|
pub(crate) struct LocalTask {
|
||||||
pub id: TaskId,
|
|
||||||
pub scope: ScopeId,
|
pub scope: ScopeId,
|
||||||
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
id: TaskId,
|
||||||
|
tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
// todo: use rc and weak, or the bump slab instead of unsafecell
|
task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
||||||
pub task: UnsafeCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalTask {
|
impl LocalTask {
|
||||||
pub fn progress(self: &Rc<Self>) -> bool {
|
/// Poll this task and return whether or not it is complete
|
||||||
|
pub(super) fn progress(self: &Rc<Self>) -> bool {
|
||||||
let waker = self.waker();
|
let waker = self.waker();
|
||||||
let mut cx = Context::from_waker(&waker);
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
// safety: the waker owns its task and everythig is single threaded
|
match self.task.borrow_mut().as_mut().poll(&mut cx) {
|
||||||
let fut = unsafe { &mut *self.task.get() };
|
|
||||||
|
|
||||||
match Pin::new(fut).poll(&mut cx) {
|
|
||||||
Poll::Ready(_) => true,
|
Poll::Ready(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@@ -34,6 +31,15 @@ impl LocalTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Scheduler {
|
impl Scheduler {
|
||||||
|
/// Start a new future on the same thread as the rest of the VirtualDom.
|
||||||
|
///
|
||||||
|
/// This future will not contribute to suspense resolving, so you should primarily use this for reacting to changes
|
||||||
|
/// and long running tasks.
|
||||||
|
///
|
||||||
|
/// Whenever the component that owns this future is dropped, the future will be dropped as well.
|
||||||
|
///
|
||||||
|
/// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which
|
||||||
|
/// will only occur when the VirtuaalDom itself has been dropped.
|
||||||
pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
|
pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
let mut tasks = self.tasks.borrow_mut();
|
let mut tasks = self.tasks.borrow_mut();
|
||||||
let entry = tasks.vacant_entry();
|
let entry = tasks.vacant_entry();
|
||||||
@@ -42,7 +48,7 @@ impl Scheduler {
|
|||||||
entry.insert(Rc::new(LocalTask {
|
entry.insert(Rc::new(LocalTask {
|
||||||
id: task_id,
|
id: task_id,
|
||||||
tx: self.sender.clone(),
|
tx: self.sender.clone(),
|
||||||
task: UnsafeCell::new(Box::pin(task)),
|
task: RefCell::new(Box::pin(task)),
|
||||||
scope,
|
scope,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -53,9 +59,11 @@ impl Scheduler {
|
|||||||
task_id
|
task_id
|
||||||
}
|
}
|
||||||
|
|
||||||
// drops the future
|
/// Drop the future with the given TaskId
|
||||||
|
///
|
||||||
|
/// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you
|
||||||
pub fn remove(&self, id: TaskId) {
|
pub fn remove(&self, id: TaskId) {
|
||||||
//
|
self.tasks.borrow_mut().remove(id.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use std::task::{Context, Poll};
|
|||||||
use crate::{
|
use crate::{
|
||||||
factory::RenderReturn,
|
factory::RenderReturn,
|
||||||
innerlude::{Mutation, Mutations, SuspenseContext},
|
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||||
ScopeId, TaskId, VNode, VirtualDom,
|
TaskId, VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{waker::RcWake, SuspenseId, SuspenseLeaf};
|
use super::{waker::RcWake, SuspenseId};
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Handle notifications by tasks inside the scheduler
|
/// Handle notifications by tasks inside the scheduler
|
||||||
|
|||||||
@@ -65,8 +65,11 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||||
|
println!("run_scope: {:?}", scope_id);
|
||||||
|
|
||||||
let mut new_nodes = unsafe {
|
let mut new_nodes = unsafe {
|
||||||
let scope = &mut self.scopes[scope_id.0];
|
let scope = &mut self.scopes[scope_id.0];
|
||||||
|
println!("run_scope: scope: {:?}", scope.render_cnt.get());
|
||||||
scope.hook_idx.set(0);
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||||
@@ -123,17 +126,22 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
todo: use proper mutability here
|
||||||
|
|
||||||
|
right now we're aliasing the scope, which is not allowed
|
||||||
|
*/
|
||||||
|
|
||||||
let scope = &mut self.scopes[scope_id.0];
|
let scope = &mut self.scopes[scope_id.0];
|
||||||
let frame = match scope.render_cnt % 2 {
|
let frame = scope.current_frame();
|
||||||
0 => &mut scope.node_arena_1,
|
|
||||||
1 => &mut scope.node_arena_2,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// set the head of the bump frame
|
// set the head of the bump frame
|
||||||
let alloced = frame.bump.alloc(new_nodes);
|
let alloced = frame.bump.alloc(new_nodes);
|
||||||
frame.node.set(alloced);
|
frame.node.set(alloced);
|
||||||
|
|
||||||
|
// And move the render generation forward by one
|
||||||
|
scope.render_cnt.set(scope.render_cnt.get() + 1);
|
||||||
|
|
||||||
// rebind the lifetime now that its stored internally
|
// rebind the lifetime now that its stored internally
|
||||||
unsafe { mem::transmute(alloced) }
|
unsafe { mem::transmute(alloced) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
|||||||
pub struct ScopeId(pub usize);
|
pub struct ScopeId(pub usize);
|
||||||
|
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
pub(crate) render_cnt: usize,
|
pub(crate) render_cnt: Cell<usize>,
|
||||||
|
|
||||||
pub(crate) node_arena_1: BumpFrame,
|
pub(crate) node_arena_1: BumpFrame,
|
||||||
pub(crate) node_arena_2: BumpFrame,
|
pub(crate) node_arena_2: BumpFrame,
|
||||||
@@ -69,14 +69,14 @@ pub struct ScopeState {
|
|||||||
|
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
pub fn current_frame(&self) -> &BumpFrame {
|
pub fn current_frame(&self) -> &BumpFrame {
|
||||||
match self.render_cnt % 2 {
|
match self.render_cnt.get() % 2 {
|
||||||
0 => &self.node_arena_1,
|
0 => &self.node_arena_1,
|
||||||
1 => &self.node_arena_2,
|
1 => &self.node_arena_2,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn previous_frame(&self) -> &BumpFrame {
|
pub fn previous_frame(&self) -> &BumpFrame {
|
||||||
match self.render_cnt % 2 {
|
match self.render_cnt.get() % 2 {
|
||||||
1 => &self.node_arena_1,
|
1 => &self.node_arena_1,
|
||||||
0 => &self.node_arena_2,
|
0 => &self.node_arena_2,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use crate::{
|
|||||||
nodes::{Template, TemplateId},
|
nodes::{Template, TemplateId},
|
||||||
scheduler::{SuspenseBoundary, SuspenseId},
|
scheduler::{SuspenseBoundary, SuspenseId},
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent,
|
AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent,
|
||||||
};
|
};
|
||||||
use futures_util::{pin_mut, FutureExt, StreamExt};
|
use futures_util::{pin_mut, StreamExt};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -244,7 +244,7 @@ impl VirtualDom {
|
|||||||
))));
|
))));
|
||||||
|
|
||||||
// The root component is always a suspense boundary for any async children
|
// The root component is always a suspense boundary for any async children
|
||||||
// This could be unexpected, so we might rethink this behavior
|
// This could be unexpected, so we might rethink this behavior later
|
||||||
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
||||||
|
|
||||||
// the root element is always given element 0
|
// the root element is always given element 0
|
||||||
@@ -253,26 +253,36 @@ impl VirtualDom {
|
|||||||
dom
|
dom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the state for any scope given its ID
|
||||||
|
///
|
||||||
|
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
|
||||||
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||||
self.scopes.get(id.0)
|
self.scopes.get(id.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the single scope at the top of the VirtualDom tree that will always be around
|
||||||
|
///
|
||||||
|
/// This scope has a ScopeId of 0 and is the root of the tree
|
||||||
pub fn base_scope(&self) -> &ScopeState {
|
pub fn base_scope(&self) -> &ScopeState {
|
||||||
self.scopes.get(0).unwrap()
|
self.scopes.get(0).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the virtualdom with a global context inserted into the base scope
|
/// Build the virtualdom with a global context inserted into the base scope
|
||||||
|
///
|
||||||
|
/// This is useful for what is essentially dependency injection, when building the app
|
||||||
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
|
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
|
||||||
self.base_scope().provide_context(context);
|
self.base_scope().provide_context(context);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_dirty_scope(&mut self, id: ScopeId) {
|
/// Manually mark a scope as requiring a re-render
|
||||||
|
pub fn mark_dirty_scope(&mut self, id: ScopeId) {
|
||||||
let height = self.scopes[id.0].height;
|
let height = self.scopes[id.0].height;
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
/// Determine whether or not a scope is currently in a suspended state
|
||||||
|
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
||||||
!self.scopes[id.0]
|
!self.scopes[id.0]
|
||||||
.consume_context::<SuspenseContext>()
|
.consume_context::<SuspenseContext>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -281,35 +291,29 @@ impl VirtualDom {
|
|||||||
.is_empty()
|
.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if there is any suspended work left to be done.
|
/// Determine is the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is
|
||||||
|
/// ready to be rendered.
|
||||||
pub fn has_suspended_work(&self) -> bool {
|
pub fn has_suspended_work(&self) -> bool {
|
||||||
!self.scheduler.leaves.borrow().is_empty()
|
!self.scheduler.leaves.borrow().is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
|
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
|
||||||
///
|
///
|
||||||
/// This method will identify the appropriate element
|
/// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
|
||||||
///
|
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
|
||||||
///
|
/// nothing will happen.
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
///
|
||||||
|
/// 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"
|
||||||
pub fn handle_event(
|
pub fn handle_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
data: Rc<dyn Any>,
|
data: Rc<dyn Any>,
|
||||||
element: ElementId,
|
element: ElementId,
|
||||||
bubbles: bool,
|
bubbles: bool,
|
||||||
// todo: priority is helpful when scheduling work around suspense, but we don't currently use it
|
|
||||||
_priority: EventPriority,
|
_priority: EventPriority,
|
||||||
) {
|
) {
|
||||||
let uievent = UiEvent {
|
|
||||||
bubbles: Rc::new(Cell::new(bubbles)),
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
------------------------
|
------------------------
|
||||||
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
|
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
|
||||||
@@ -321,37 +325,35 @@ impl VirtualDom {
|
|||||||
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
|
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
|
||||||
----------------------
|
----------------------
|
||||||
| <-- yes (is ascendant)
|
| <-- yes (is ascendant)
|
||||||
| | | <-- no (is not ascendant)
|
| | | <-- no (is not direct ascendant)
|
||||||
| | <-- yes (is ascendant)
|
| | <-- yes (is ascendant)
|
||||||
| | | | | <--- target element, break early
|
| | | | | <--- target element, break early, don't check other listeners
|
||||||
| | | <-- no, broke early
|
| | | <-- no, broke early
|
||||||
| <-- no, broke early
|
| <-- no, broke early
|
||||||
*/
|
*/
|
||||||
let mut parent_path = self.elements.get(element.0);
|
let mut parent_path = self.elements.get(element.0);
|
||||||
let mut listeners = vec![];
|
let mut listeners = vec![];
|
||||||
|
|
||||||
|
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
||||||
|
let uievent = UiEvent {
|
||||||
|
bubbles: Rc::new(Cell::new(bubbles)),
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop through each dynamic attribute in this template before moving up to the template's parent.
|
||||||
while let Some(el_ref) = parent_path {
|
while let Some(el_ref) = parent_path {
|
||||||
|
// safety: we maintain references of all vnodes in the element slab
|
||||||
let template = unsafe { &*el_ref.template };
|
let template = unsafe { &*el_ref.template };
|
||||||
let target_path = el_ref.path;
|
let target_path = el_ref.path;
|
||||||
|
|
||||||
let mut attrs = template.dynamic_attrs.iter().enumerate();
|
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||||
|
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
||||||
while let Some((idx, attr)) = attrs.next() {
|
|
||||||
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
|
||||||
small.len() >= big.len() && small == &big[..small.len()]
|
small.len() >= big.len() && small == &big[..small.len()]
|
||||||
}
|
}
|
||||||
|
|
||||||
let this_path = template.template.attr_paths[idx];
|
let this_path = template.template.attr_paths[idx];
|
||||||
|
|
||||||
println!(
|
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
||||||
"is {:?} ascendant of {:?} ? {}",
|
|
||||||
this_path,
|
|
||||||
target_path,
|
|
||||||
is_path_ascendant(this_path, target_path)
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("{ } - {name}, - {}", attr.name, &attr.name[2..]);
|
|
||||||
|
|
||||||
if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
|
if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
|
||||||
listeners.push(&attr.value);
|
listeners.push(&attr.value);
|
||||||
|
|
||||||
@@ -367,11 +369,11 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
for listener in listeners.drain(..).rev() {
|
for listener in listeners.drain(..).rev() {
|
||||||
if let AttributeValue::Listener(listener) = listener {
|
if let AttributeValue::Listener(listener) = listener {
|
||||||
listener.borrow_mut()(uievent.clone());
|
listener.borrow_mut()(uievent.clone());
|
||||||
|
|
||||||
// Break if the event doesn't bubble
|
|
||||||
if !uievent.bubbles.get() {
|
if !uievent.bubbles.get() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -452,13 +454,17 @@ impl VirtualDom {
|
|||||||
pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
|
pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
|
||||||
let mut mutations = Mutations::new(0);
|
let mut mutations = Mutations::new(0);
|
||||||
|
|
||||||
let root_node = unsafe { self.run_scope_extend(ScopeId(0)) };
|
match unsafe { self.run_scope_extend(ScopeId(0)) } {
|
||||||
match root_node {
|
// Rebuilding implies we append the created elements to the root
|
||||||
RenderReturn::Sync(Some(node)) => {
|
RenderReturn::Sync(Some(node)) => {
|
||||||
let m = self.create_scope(ScopeId(0), &mut mutations, node);
|
let m = self.create_scope(ScopeId(0), &mut mutations, node);
|
||||||
mutations.push(Mutation::AppendChildren { m });
|
mutations.push(Mutation::AppendChildren { m });
|
||||||
}
|
}
|
||||||
RenderReturn::Sync(None) => {}
|
// If nothing was rendered, then insert a placeholder element instead
|
||||||
|
RenderReturn::Sync(None) => {
|
||||||
|
mutations.push(Mutation::CreatePlaceholder { id: ElementId(1) });
|
||||||
|
mutations.push(Mutation::AppendChildren { m: 1 });
|
||||||
|
}
|
||||||
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
|
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +481,9 @@ impl VirtualDom {
|
|||||||
// Now run render with deadline but dont even try to poll any async tasks
|
// Now run render with deadline but dont even try to poll any async tasks
|
||||||
let fut = self.render_with_deadline(std::future::ready(()));
|
let fut = self.render_with_deadline(std::future::ready(()));
|
||||||
pin_mut!(fut);
|
pin_mut!(fut);
|
||||||
match fut.poll_unpin(&mut cx) {
|
|
||||||
|
// The root component is not allowed to be async
|
||||||
|
match fut.poll(&mut cx) {
|
||||||
std::task::Poll::Ready(mutations) => mutations,
|
std::task::Poll::Ready(mutations) => mutations,
|
||||||
std::task::Poll::Pending => panic!("render_immediate should never return pending"),
|
std::task::Poll::Pending => panic!("render_immediate should never return pending"),
|
||||||
}
|
}
|
||||||
@@ -491,20 +499,20 @@ impl VirtualDom {
|
|||||||
deadline: impl Future<Output = ()>,
|
deadline: impl Future<Output = ()>,
|
||||||
) -> Mutations<'a> {
|
) -> Mutations<'a> {
|
||||||
use futures_util::future::{select, Either};
|
use futures_util::future::{select, Either};
|
||||||
|
pin_mut!(deadline);
|
||||||
|
|
||||||
let mut mutations = Mutations::new(0);
|
let mut mutations = Mutations::new(0);
|
||||||
pin_mut!(deadline);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// first, unload any complete suspense trees
|
// first, unload any complete suspense trees
|
||||||
for finished_fiber in self.finished_fibers.drain(..) {
|
for finished_fiber in self.finished_fibers.drain(..) {
|
||||||
let scope = &mut self.scopes[finished_fiber.0];
|
let scope = &mut self.scopes[finished_fiber.0];
|
||||||
let context = scope.has_context::<SuspenseContext>().unwrap();
|
let context = scope.has_context::<SuspenseContext>().unwrap();
|
||||||
println!("unloading suspense tree {:?}", context.mutations);
|
|
||||||
|
|
||||||
mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..));
|
mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..));
|
||||||
mutations.extend(context.mutations.borrow_mut().drain(..));
|
mutations.extend(context.mutations.borrow_mut().drain(..));
|
||||||
|
|
||||||
|
// TODO: count how many nodes are on the stack?
|
||||||
mutations.push(Mutation::ReplaceWith {
|
mutations.push(Mutation::ReplaceWith {
|
||||||
id: context.placeholder.get().unwrap(),
|
id: context.placeholder.get().unwrap(),
|
||||||
m: 1,
|
m: 1,
|
||||||
@@ -525,36 +533,22 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// Wait for suspense, or a deadline
|
// Wait for suspense, or a deadline
|
||||||
if self.dirty_scopes.is_empty() {
|
if self.dirty_scopes.is_empty() {
|
||||||
|
// If there's no suspense, then we have no reason to wait
|
||||||
if self.scheduler.leaves.borrow().is_empty() {
|
if self.scheduler.leaves.borrow().is_empty() {
|
||||||
return mutations;
|
return mutations;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (work, deadline) = (self.wait_for_work(), &mut deadline);
|
// Poll the suspense leaves in the meantime
|
||||||
|
let work = self.wait_for_work();
|
||||||
pin_mut!(work);
|
pin_mut!(work);
|
||||||
|
|
||||||
if let Either::Left((_, _)) = select(deadline, work).await {
|
// If the deadline is exceded (left) then we should return the mutations we have
|
||||||
|
if let Either::Left((_, _)) = select(&mut deadline, work).await {
|
||||||
return mutations;
|
return mutations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
|
|
||||||
// let scopes = &self.scopes;
|
|
||||||
// if let Some(scope) = scopes.get_scope(scope_id) {
|
|
||||||
// let height = scope.height;
|
|
||||||
// let id = scope_id.0;
|
|
||||||
// if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
|
|
||||||
// let scope = scopes.get_scope(*new).unwrap();
|
|
||||||
// let new_height = scope.height;
|
|
||||||
// let new_id = &scope.scope_id();
|
|
||||||
// height.cmp(&new_height).then(new_id.0.cmp(&id))
|
|
||||||
// }) {
|
|
||||||
// self.dirty_scopes.insert(index, scope_id);
|
|
||||||
// log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for VirtualDom {
|
impl Drop for VirtualDom {
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
use crate::desktop_context::{DesktopContext, UserWindowEvent};
|
use crate::desktop_context::{DesktopContext, UserWindowEvent};
|
||||||
|
use crate::events::{decode_event, EventMessage};
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use dioxus_html::events::*;
|
|
||||||
use futures_channel::mpsc::UnboundedReceiver;
|
use futures_channel::mpsc::UnboundedReceiver;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_json::from_value;
|
|
||||||
use std::any::Any;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -19,25 +15,6 @@ use wry::{
|
|||||||
webview::WebView,
|
webview::WebView,
|
||||||
};
|
};
|
||||||
|
|
||||||
macro_rules! match_data {
|
|
||||||
(
|
|
||||||
$m:ident;
|
|
||||||
$name:ident;
|
|
||||||
$(
|
|
||||||
$tip:ty => $($mname:literal)|* ;
|
|
||||||
)*
|
|
||||||
) => {
|
|
||||||
match $name {
|
|
||||||
$( $($mname)|* => {
|
|
||||||
println!("casting to type {:?}", std::any::TypeId::of::<$tip>());
|
|
||||||
let val: $tip = from_value::<$tip>($m).ok()?;
|
|
||||||
Rc::new(val) as Rc<dyn Any>
|
|
||||||
})*
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct DesktopController {
|
pub(super) struct DesktopController {
|
||||||
pub(super) webviews: HashMap<WindowId, WebView>,
|
pub(super) webviews: HashMap<WindowId, WebView>,
|
||||||
pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
|
pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
|
||||||
@@ -86,9 +63,7 @@ impl DesktopController {
|
|||||||
if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
|
if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
|
||||||
let name = value.event.clone();
|
let name = value.event.clone();
|
||||||
let el_id = ElementId(value.mounted_dom_id);
|
let el_id = ElementId(value.mounted_dom_id);
|
||||||
let evt = decode_event(value);
|
if let Some(evt) = decode_event(value) {
|
||||||
|
|
||||||
if let Some(evt) = evt {
|
|
||||||
dom.handle_event(&name, evt, el_id, true, EventPriority::Medium);
|
dom.handle_event(&name, evt, el_id, true, EventPriority::Medium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,44 +118,3 @@ impl DesktopController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct EventMessage {
|
|
||||||
contents: serde_json::Value,
|
|
||||||
event: String,
|
|
||||||
mounted_dom_id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
|
|
||||||
let val = value.contents;
|
|
||||||
let name = value.event.as_str();
|
|
||||||
let el_id = ElementId(value.mounted_dom_id);
|
|
||||||
type DragData = MouseData;
|
|
||||||
|
|
||||||
let evt = match_data! { val; name;
|
|
||||||
MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup";
|
|
||||||
ClipboardData => "copy" | "cut" | "paste";
|
|
||||||
CompositionData => "compositionend" | "compositionstart" | "compositionupdate";
|
|
||||||
KeyboardData => "keydown" | "keypress" | "keyup";
|
|
||||||
FocusData => "blur" | "focus" | "focusin" | "focusout";
|
|
||||||
FormData => "change" | "input" | "invalid" | "reset" | "submit";
|
|
||||||
DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop";
|
|
||||||
PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture";
|
|
||||||
SelectionData => "selectstart" | "selectionchange" | "select";
|
|
||||||
TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart";
|
|
||||||
ScrollData => "scroll";
|
|
||||||
WheelData => "wheel";
|
|
||||||
MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
|
|
||||||
| "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata"
|
|
||||||
| "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress"
|
|
||||||
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate"
|
|
||||||
| "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout";
|
|
||||||
AnimationData => "animationstart" | "animationend" | "animationiteration";
|
|
||||||
TransitionData => "transitionend";
|
|
||||||
ToggleData => "toggle";
|
|
||||||
// ImageData => "load" | "error";
|
|
||||||
// OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange";
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(evt)
|
|
||||||
}
|
|
||||||
@@ -33,17 +33,61 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
macro_rules! match_data {
|
||||||
struct ImEvent {
|
(
|
||||||
event: String,
|
$m:ident;
|
||||||
mounted_dom_id: ElementId,
|
$name:ident;
|
||||||
contents: serde_json::Value,
|
$(
|
||||||
|
$tip:ty => $($mname:literal)|* ;
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
match $name {
|
||||||
|
$( $($mname)|* => {
|
||||||
|
println!("casting to type {:?}", std::any::TypeId::of::<$tip>());
|
||||||
|
let val: $tip = from_value::<$tip>($m).ok()?;
|
||||||
|
Rc::new(val) as Rc<dyn Any>
|
||||||
|
})*
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn make_synthetic_event(name: &str, val: serde_json::Value) -> Option<Rc<dyn Any>> {
|
#[derive(Deserialize)]
|
||||||
// // right now we don't support the datatransfer in Drag
|
pub struct EventMessage {
|
||||||
// type DragData = MouseData;
|
pub contents: serde_json::Value,
|
||||||
// type ProgressData = MediaData;
|
pub event: String,
|
||||||
|
pub mounted_dom_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
// Some(res)
|
pub fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
|
||||||
// }
|
let val = value.contents;
|
||||||
|
let name = value.event.as_str();
|
||||||
|
type DragData = MouseData;
|
||||||
|
|
||||||
|
let evt = match_data! { val; name;
|
||||||
|
MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup";
|
||||||
|
ClipboardData => "copy" | "cut" | "paste";
|
||||||
|
CompositionData => "compositionend" | "compositionstart" | "compositionupdate";
|
||||||
|
KeyboardData => "keydown" | "keypress" | "keyup";
|
||||||
|
FocusData => "blur" | "focus" | "focusin" | "focusout";
|
||||||
|
FormData => "change" | "input" | "invalid" | "reset" | "submit";
|
||||||
|
DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop";
|
||||||
|
PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture";
|
||||||
|
SelectionData => "selectstart" | "selectionchange" | "select";
|
||||||
|
TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart";
|
||||||
|
ScrollData => "scroll";
|
||||||
|
WheelData => "wheel";
|
||||||
|
MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
|
||||||
|
| "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata"
|
||||||
|
| "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress"
|
||||||
|
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate"
|
||||||
|
| "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout";
|
||||||
|
AnimationData => "animationstart" | "animationend" | "animationiteration";
|
||||||
|
TransitionData => "transitionend";
|
||||||
|
ToggleData => "toggle";
|
||||||
|
// ImageData => "load" | "error";
|
||||||
|
// OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange";
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(evt)
|
||||||
|
}
|
||||||
@@ -311,7 +311,7 @@ export class Interpreter {
|
|||||||
this.CreateElementNs(edit.name, edit.id, edit.ns);
|
this.CreateElementNs(edit.name, edit.id, edit.ns);
|
||||||
break;
|
break;
|
||||||
case "SetText":
|
case "SetText":
|
||||||
this.SetText(edit.id, edit.text);
|
this.SetText(edit.id, edit.value);
|
||||||
break;
|
break;
|
||||||
case "SetAttribute":
|
case "SetAttribute":
|
||||||
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
|
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
|
||||||
|
|||||||
Reference in new issue
Block a user