1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// use crate::{changelist::EditList, nodes::VNode};
use crate::{
    changelist::{self, EditList},
    dodriodiff::DiffMachine,
};
use crate::{events::EventTrigger, innerlude::*};

use bumpalo::Bump;

use generational_arena::{Arena, Index};
use std::{
    any::{self, TypeId},
    borrow::BorrowMut,
    cell::{RefCell, UnsafeCell},
    collections::{vec_deque, VecDeque},
    future::Future,
    marker::PhantomData,
    rc::Rc,
    sync::atomic::AtomicUsize,
};

/// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom {
    /// All mounted components are arena allocated to make additions, removals, and references easy to work with
    /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
    pub(crate) components: Arena<Scope>,

    /// The index of the root component.
    /// Will not be ready if the dom is fresh
    base_scope: Index,

    event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,

    // todo: encapsulate more state into this so we can better reuse it
    diff_bump: Bump,

    #[doc(hidden)]
    _root_prop_type: std::any::TypeId,
}

impl VirtualDom {
    /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
    ///
    /// This means that the root component must either consumes its own context, or statics are used to generate the page.
    /// The root component can access things like routing in its context.
    pub fn new(root: FC<()>) -> Self {
        Self::new_with_props(root, ())
    }

    /// Start a new VirtualDom instance with a dependent props.
    /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
    ///
    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
    /// to toss out the entire tree.
    pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
        let mut components = Arena::new();

        let event_queue = Rc::new(RefCell::new(VecDeque::new()));

        // Create a reference to the component in the arena
        // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
        // This puts the dom in a usable state on creation, rather than being potentially invalid
        let base_scope = components.insert(Scope::new::<_, P>(root, root_props, None));

        // evaluate the component, pushing any updates its generates into the lifecycle queue
        // todo!

        let _root_prop_type = TypeId::of::<P>();
        let diff_bump = Bump::new();

        Self {
            components,
            base_scope,
            event_queue,
            diff_bump,
            _root_prop_type,
        }
    }

    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
    ///
    ///
    pub fn rebuild(&mut self) -> Result<EditList<'_>> {
        // Reset and then build a new diff machine
        // The previous edit list cannot be around while &mut is held
        // Make sure variance doesnt break this
        self.diff_bump.reset();
        let mut diff_machine = DiffMachine::new(&self.diff_bump);

        // this is still a WIP
        // we'll need to re-fecth all the scopes that were changed and build the diff machine
        // fetch the component again
        let component = self
            .components
            .get_mut(self.base_scope)
            .expect("Root should always exist");

        component.run::<()>();

        diff_machine.diff_node(
            component.old_frame(),
            component.new_frame(),
            Some(self.base_scope),
        );

        Ok(diff_machine.consume())
    }

    /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
    ///  
    /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
    /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
    /// change list.
    ///
    /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
    /// listeners.
    ///
    /// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
    /// executor and handlers for suspense as show in the example.
    ///
    /// ```ignore
    /// let (sender, receiver) = channel::new();
    /// sender.send(EventTrigger::start());
    ///
    /// let mut dom = VirtualDom::new();
    /// dom.suspense_handler(|event| sender.send(event));
    ///
    /// while let Ok(diffs) = dom.progress_with_event(receiver.recv().await) {
    ///     render(diffs);
    /// }
    ///
    /// ```
    pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
        let EventTrigger {
            component_id,
            listener_id,
            event: source,
        } = event;

        let component = self
            .components
            .get_mut(component_id)
            .expect("Component should exist if an event was triggered");

        log::debug!("list: {}", component.listeners.len());

        let listener = unsafe {
            component
                .listeners
                .get(listener_id as usize)
                .expect("Listener should exist if it was triggered")
                .as_ref()
        }
        .unwrap();

        // Run the callback with the user event
        listener(source);

        // Reset and then build a new diff machine
        // The previous edit list cannot be around while &mut is held
        // Make sure variance doesnt break this
        self.diff_bump.reset();
        let mut diff_machine = DiffMachine::new(&self.diff_bump);

        // this is still a WIP
        // we'll need to re-fecth all the scopes that were changed and build the diff machine
        // fetch the component again
        // let component = self
        //     .components
        //     .get_mut(self.base_scope)
        //     .expect("Root should always exist");

        component.run::<()>();

        diff_machine.diff_node(
            component.old_frame(),
            component.new_frame(),
            Some(self.base_scope),
        );
        // diff_machine.diff_node(
        //     component.old_frame(),
        //     component.new_frame(),
        //     Some(self.base_scope),
        // );

        Ok(diff_machine.consume())
        // Err(crate::error::Error::NoEvent)
        // Mark dirty components. Descend from the highest node until all dirty nodes are updated.
        // let mut affected_components = Vec::new();

        // while let Some(event) = self.pop_event() {
        //     if let Some(component_idx) = event.index() {
        //         affected_components.push(component_idx);
        //     }
        //     self.process_lifecycle(event)?;
        // }

        // todo!()
    }

    /// Using mutable access to the Virtual Dom, progress a given lifecycle event
    fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> {
        match event_type {
            // Component needs to be mounted to the virtual dom
            LifecycleType::Mount {
                to: _,
                under: _,
                props: _,
            } => {}

            // The parent for this component generated new props and the component needs update
            LifecycleType::PropsChanged {
                props: _,
                component: _,
            } => {}

            // Component was messaged via the internal subscription service
            LifecycleType::Callback { component: _ } => {}
        }

        Ok(())
    }

    /// Pop the top event of the internal lifecycle event queu
    pub fn pop_event(&self) -> Option<LifecycleEvent> {
        self.event_queue.as_ref().borrow_mut().pop_front()
    }

    /// With access to the virtual dom, schedule an update to the Root component's props.
    /// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event
    /// back into the event system to get an edit list.
    pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
        // Ensure the props match
        if TypeId::of::<P>() != self._root_prop_type {
            return Err(Error::WrongProps);
        }

        Ok(LifecycleEvent {
            event_type: LifecycleType::PropsChanged {
                props: Box::new(new_props),
                component: self.base_scope,
            },
        })
    }
}

pub struct LifecycleEvent {
    pub event_type: LifecycleType,
}

pub enum LifecycleType {
    // Component needs to be mounted, but its scope doesn't exist yet
    Mount {
        to: Index,
        under: usize,
        props: Box<dyn std::any::Any>,
    },

    // Parent was evalauted causing new props to generate
    PropsChanged {
        props: Box<dyn std::any::Any>,
        component: Index,
    },

    // Hook for the subscription API
    Callback {
        component: Index,
    },
}

impl LifecycleEvent {
    fn index(&self) -> Option<Index> {
        match &self.event_type {
            LifecycleType::Mount {
                to: _,
                under: _,
                props: _,
            } => None,

            LifecycleType::PropsChanged { component, .. }
            | LifecycleType::Callback { component } => Some(component.clone()),
        }
    }
}

mod tests {
    use super::*;

    #[test]
    fn start_dom() {
        let mut dom = VirtualDom::new(|ctx, props| {
            ctx.view(|bump| {
                use crate::builder::*;
                div(bump).child(text("hello,    world")).finish()
            })
        });
        let edits = dom.rebuild().unwrap();
        println!("{:#?}", edits);
    }
}