Преглед изворни кода

feat: diffing works on desktop!

Jonathan Kelley пре 2 година
родитељ
комит
20f9957fbe

+ 9 - 2
packages/core/src/diff.rs

@@ -42,8 +42,10 @@ impl<'b> VirtualDom {
     pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
         let scope_state = &mut self.scopes[scope.0];
 
-        let cur_arena = scope_state.current_frame();
-        let prev_arena = scope_state.previous_frame();
+        let cur_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
         let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
@@ -62,8 +64,10 @@ impl<'b> VirtualDom {
         );
 
         self.scope_stack.push(scope);
+
         let left = unsafe { prev_arena.load_node() };
         let right = unsafe { cur_arena.load_node() };
+
         self.diff_maybe_node(mutations, left, right);
         self.scope_stack.pop();
     }
@@ -105,6 +109,8 @@ impl<'b> VirtualDom {
         left_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 {
             // do a light diff of the roots nodes.
             return;
@@ -125,6 +131,7 @@ impl<'b> VirtualDom {
                 .set(left_attr.mounted_element.get());
 
             if left_attr.value != right_attr.value {
+                println!("DIFF ATTR: {:?} -> {:?}", left_attr, right_attr);
                 let value = "todo!()";
                 muts.push(Mutation::SetAttribute {
                     id: left_attr.mounted_element.get(),

+ 15 - 0
packages/core/src/scheduler/suspense.rs

@@ -19,6 +19,19 @@ pub struct SuspenseBoundary {
     pub waiting_on: RefCell<HashSet<SuspenseId>>,
     pub mutations: RefCell<Mutations<'static>>,
     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 {
@@ -28,6 +41,8 @@ impl SuspenseBoundary {
             waiting_on: Default::default(),
             mutations: RefCell::new(Mutations::new(0)),
             placeholder: Cell::new(None),
+            onresolve: None,
+            onstart: None,
         })
     }
 }

+ 22 - 14
packages/core/src/scheduler/task.rs

@@ -1,8 +1,9 @@
 use super::{waker::RcWake, Scheduler, SchedulerMsg};
 use crate::ScopeId;
+use std::cell::RefCell;
 use std::future::Future;
 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))]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -10,23 +11,19 @@ pub struct TaskId(pub usize);
 
 /// the task itself is the waker
 pub(crate) struct LocalTask {
-    pub id: TaskId,
     pub scope: ScopeId,
-    pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
-
-    // todo: use rc and weak, or the bump slab instead of unsafecell
-    pub task: UnsafeCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
+    id: TaskId,
+    tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+    task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
 }
 
 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 mut cx = Context::from_waker(&waker);
 
-        // safety: the waker owns its task and everythig is single threaded
-        let fut = unsafe { &mut *self.task.get() };
-
-        match Pin::new(fut).poll(&mut cx) {
+        match self.task.borrow_mut().as_mut().poll(&mut cx) {
             Poll::Ready(_) => true,
             _ => false,
         }
@@ -34,6 +31,15 @@ impl LocalTask {
 }
 
 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 {
         let mut tasks = self.tasks.borrow_mut();
         let entry = tasks.vacant_entry();
@@ -42,7 +48,7 @@ impl Scheduler {
         entry.insert(Rc::new(LocalTask {
             id: task_id,
             tx: self.sender.clone(),
-            task: UnsafeCell::new(Box::pin(task)),
+            task: RefCell::new(Box::pin(task)),
             scope,
         }));
 
@@ -53,9 +59,11 @@ impl Scheduler {
         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) {
-        //
+        self.tasks.borrow_mut().remove(id.0);
     }
 }
 

+ 2 - 2
packages/core/src/scheduler/wait.rs

@@ -4,10 +4,10 @@ use std::task::{Context, Poll};
 use crate::{
     factory::RenderReturn,
     innerlude::{Mutation, Mutations, SuspenseContext},
-    ScopeId, TaskId, VNode, VirtualDom,
+    TaskId, VNode, VirtualDom,
 };
 
-use super::{waker::RcWake, SuspenseId, SuspenseLeaf};
+use super::{waker::RcWake, SuspenseId};
 
 impl VirtualDom {
     /// Handle notifications by tasks inside the scheduler

+ 13 - 5
packages/core/src/scope_arena.rs

@@ -65,8 +65,11 @@ impl VirtualDom {
     }
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
+        println!("run_scope: {:?}", scope_id);
+
         let mut new_nodes = unsafe {
             let scope = &mut self.scopes[scope_id.0];
+            println!("run_scope: scope: {:?}", scope.render_cnt.get());
             scope.hook_idx.set(0);
 
             // 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 frame = match scope.render_cnt % 2 {
-            0 => &mut scope.node_arena_1,
-            1 => &mut scope.node_arena_2,
-            _ => unreachable!(),
-        };
+        let frame = scope.current_frame();
 
         // set the head of the bump frame
         let alloced = frame.bump.alloc(new_nodes);
         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
         unsafe { mem::transmute(alloced) }
     }

+ 3 - 3
packages/core/src/scopes.rs

@@ -43,7 +43,7 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
 pub struct ScopeId(pub usize);
 
 pub struct ScopeState {
-    pub(crate) render_cnt: usize,
+    pub(crate) render_cnt: Cell<usize>,
 
     pub(crate) node_arena_1: BumpFrame,
     pub(crate) node_arena_2: BumpFrame,
@@ -69,14 +69,14 @@ pub struct ScopeState {
 
 impl ScopeState {
     pub fn current_frame(&self) -> &BumpFrame {
-        match self.render_cnt % 2 {
+        match self.render_cnt.get() % 2 {
             0 => &self.node_arena_1,
             1 => &self.node_arena_2,
             _ => unreachable!(),
         }
     }
     pub fn previous_frame(&self) -> &BumpFrame {
-        match self.render_cnt % 2 {
+        match self.render_cnt.get() % 2 {
             1 => &self.node_arena_1,
             0 => &self.node_arena_2,
             _ => unreachable!(),

+ 54 - 60
packages/core/src/virtual_dom.rs

@@ -9,9 +9,9 @@ use crate::{
     nodes::{Template, TemplateId},
     scheduler::{SuspenseBoundary, SuspenseId},
     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 std::rc::Rc;
 use std::{
@@ -244,7 +244,7 @@ impl VirtualDom {
         ))));
 
         // 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)));
 
         // the root element is always given element 0
@@ -253,26 +253,36 @@ impl VirtualDom {
         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> {
         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 {
         self.scopes.get(0).unwrap()
     }
 
     /// 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 {
         self.base_scope().provide_context(context);
         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;
         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]
             .consume_context::<SuspenseContext>()
             .unwrap()
@@ -281,35 +291,29 @@ impl VirtualDom {
             .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 {
         !self.scheduler.leaves.borrow().is_empty()
     }
 
     /// 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(
         &mut self,
         name: &str,
         data: Rc<dyn Any>,
         element: ElementId,
         bubbles: bool,
-        // todo: priority is helpful when scheduling work around suspense, but we don't currently use it
         _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
@@ -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.
         ----------------------
         |           <-- yes (is ascendant)
-        | | |       <-- no  (is not ascendant)
+        | | |       <-- no  (is not direct ascendant)
         | |         <-- yes (is ascendant)
-        | | | | |   <--- target element, break early
+        | | | | |   <--- target element, break early, don't check other listeners
         | | |       <-- no, broke early
         |           <-- no, broke early
         */
         let mut parent_path = self.elements.get(element.0);
         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 {
+            // safety: we maintain references of all vnodes in the element slab
             let template = unsafe { &*el_ref.template };
             let target_path = el_ref.path;
 
-            let mut attrs = template.dynamic_attrs.iter().enumerate();
-
-            while let Some((idx, attr)) = attrs.next() {
-                pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
+            for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
                     small.len() >= big.len() && small == &big[..small.len()]
                 }
 
                 let this_path = template.template.attr_paths[idx];
 
-                println!(
-                    "is {:?} ascendant of {:?} ? {}",
-                    this_path,
-                    target_path,
-                    is_path_ascendant(this_path, target_path)
-                );
-
-                println!("{ } - {name}, - {}", attr.name, &attr.name[2..]);
-
+                // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
                 if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
                     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() {
                 if let AttributeValue::Listener(listener) = listener {
                     listener.borrow_mut()(uievent.clone());
-
-                    // Break if the event doesn't bubble
                     if !uievent.bubbles.get() {
                         return;
                     }
@@ -452,13 +454,17 @@ impl VirtualDom {
     pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
         let mut mutations = Mutations::new(0);
 
-        let root_node = unsafe { self.run_scope_extend(ScopeId(0)) };
-        match root_node {
+        match unsafe { self.run_scope_extend(ScopeId(0)) } {
+            // Rebuilding implies we append the created elements to the root
             RenderReturn::Sync(Some(node)) => {
                 let m = self.create_scope(ScopeId(0), &mut mutations, node);
                 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"),
         }
 
@@ -475,7 +481,9 @@ impl VirtualDom {
         // Now run render with deadline but dont even try to poll any async tasks
         let fut = self.render_with_deadline(std::future::ready(()));
         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::Pending => panic!("render_immediate should never return pending"),
         }
@@ -491,20 +499,20 @@ impl VirtualDom {
         deadline: impl Future<Output = ()>,
     ) -> Mutations<'a> {
         use futures_util::future::{select, Either};
+        pin_mut!(deadline);
 
         let mut mutations = Mutations::new(0);
-        pin_mut!(deadline);
 
         loop {
             // first, unload any complete suspense trees
             for finished_fiber in self.finished_fibers.drain(..) {
                 let scope = &mut self.scopes[finished_fiber.0];
                 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().drain(..));
 
+                // TODO: count how many nodes are on the stack?
                 mutations.push(Mutation::ReplaceWith {
                     id: context.placeholder.get().unwrap(),
                     m: 1,
@@ -525,36 +533,22 @@ impl VirtualDom {
 
             // Wait for suspense, or a deadline
             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() {
                     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);
 
-                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;
                 }
             }
         }
     }
-
-    // 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 {

+ 2 - 68
packages/desktop/src/controller.rs

@@ -1,12 +1,8 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
+use crate::events::{decode_event, EventMessage};
 use dioxus_core::*;
-use dioxus_html::events::*;
 use futures_channel::mpsc::UnboundedReceiver;
 use futures_util::StreamExt;
-use serde::Deserialize;
-use serde_json::from_value;
-use std::any::Any;
-use std::rc::Rc;
 use std::{
     collections::HashMap,
     sync::Arc,
@@ -19,25 +15,6 @@ use wry::{
     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) webviews: HashMap<WindowId, WebView>,
     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) {
                                 let name = value.event.clone();
                                 let el_id = ElementId(value.mounted_dom_id);
-                                let evt = decode_event(value);
-
-                                if let Some(evt) = evt {
+                                if let Some(evt) = decode_event(value) {
                                     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)
-}

+ 55 - 11
packages/desktop/src/events.rs

@@ -33,17 +33,61 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
     }
 }
 
-#[derive(Deserialize, Serialize)]
-struct ImEvent {
-    event: String,
-    mounted_dom_id: ElementId,
-    contents: serde_json::Value,
+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,
+        }
+    };
+}
+
+#[derive(Deserialize)]
+pub struct EventMessage {
+    pub contents: serde_json::Value,
+    pub event: String,
+    pub mounted_dom_id: usize,
 }
 
-// pub fn make_synthetic_event(name: &str, val: serde_json::Value) -> Option<Rc<dyn Any>> {
-//     // right now we don't support the datatransfer in Drag
-//     type DragData = MouseData;
-//     type ProgressData = MediaData;
+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(res)
-// }
+    Some(evt)
+}

+ 1 - 1
packages/interpreter/src/interpreter.js

@@ -311,7 +311,7 @@ export class Interpreter {
         this.CreateElementNs(edit.name, edit.id, edit.ns);
         break;
       case "SetText":
-        this.SetText(edit.id, edit.text);
+        this.SetText(edit.id, edit.value);
         break;
       case "SetAttribute":
         this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);