瀏覽代碼

Merge branch 'master' of github.com:DioxusLabs/dioxus

Jonathan Kelley 3 年之前
父節點
當前提交
ddd4875e13
共有 4 個文件被更改,包括 76 次插入23 次删除
  1. 2 1
      .github/workflows/docs.yml
  2. 9 0
      packages/core/src/diff.rs
  3. 60 19
      packages/core/src/scopes.rs
  4. 5 3
      packages/core/tests/diffing.rs

+ 2 - 1
.github/workflows/docs.yml

@@ -24,7 +24,8 @@ jobs:
         run: cd docs &&
           cd guide && mdbook build -d ../nightly/guide && cd .. &&
           cd reference && mdbook build -d ../nightly/reference && cd .. &&
-          cd router && mdbook build -d ../nightly/router  && cd ..
+          cd router && mdbook build -d ../nightly/router  && cd .. &&
+          cd cli && mdbook build -d ../nightly/cli  && cd ..
 
       - name: Deploy 🚀
         uses: JamesIves/github-pages-deploy-action@v4.2.3

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

@@ -813,6 +813,15 @@ impl<'b> DiffState<'b> {
             return;
         }
 
+        // remove any old children that are not shared
+        // todo: make this an iterator
+        for child in old {
+            let key = child.key().unwrap();
+            if !shared_keys.contains(&key) {
+                self.remove_nodes([child], true);
+            }
+        }
+
         // 4. Compute the LIS of this list
         let mut lis_sequence = Vec::default();
         lis_sequence.reserve(new_index_to_old_index.len());

+ 60 - 19
packages/core/src/scopes.rs

@@ -7,7 +7,7 @@ use std::{
     any::{Any, TypeId},
     borrow::Borrow,
     cell::{Cell, RefCell},
-    collections::HashMap,
+    collections::{HashMap, HashSet},
     future::Future,
     pin::Pin,
     rc::Rc,
@@ -68,7 +68,12 @@ impl ScopeArena {
             heuristics: RefCell::new(FxHashMap::default()),
             free_scopes: RefCell::new(Vec::new()),
             nodes: RefCell::new(nodes),
-            tasks: TaskQueue::new(sender),
+            tasks: Rc::new(TaskQueue {
+                tasks: RefCell::new(FxHashMap::default()),
+                task_map: RefCell::new(FxHashMap::default()),
+                gen: Cell::new(0),
+                sender,
+            }),
         }
     }
 
@@ -179,6 +184,15 @@ impl ScopeArena {
         log::trace!("removing scope {:?}", id);
         self.ensure_drop_safety(id);
 
+        // Dispose of any ongoing tasks
+        let mut tasks = self.tasks.tasks.borrow_mut();
+        let mut task_map = self.tasks.task_map.borrow_mut();
+        if let Some(cur_tasks) = task_map.remove(&id) {
+            for task in cur_tasks {
+                tasks.remove(&task);
+            }
+        }
+
         // Safety:
         // - ensure_drop_safety ensures that no references to this scope are in use
         // - this raw pointer is removed from the map
@@ -435,7 +449,13 @@ pub struct ScopeId(pub usize);
 /// once a Task has been completed.
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct TaskId(pub usize);
+pub struct TaskId {
+    /// The global ID of the task
+    pub id: usize,
+
+    /// The original scope that this task was scheduled in
+    pub scope: ScopeId,
+}
 
 /// Every component in Dioxus is represented by a `ScopeState`.
 ///
@@ -745,7 +765,7 @@ impl ScopeState {
             .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
             .unwrap();
 
-        self.tasks.push_fut(fut)
+        self.tasks.spawn(self.our_arena_idx, fut)
     }
 
     /// Spawns the future but does not return the TaskId
@@ -753,10 +773,24 @@ impl ScopeState {
         self.push_future(fut);
     }
 
+    /// Spawn a future that Dioxus will never clean up
+    ///
+    /// This is good for tasks that need to be run after the component has been dropped.
+    pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
+        // wake up the scheduler if it is sleeping
+        self.tasks
+            .sender
+            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
+            .unwrap();
+
+        // The root scope will never be unmounted so we can just add the task at the top of the app
+        self.tasks.spawn(ScopeId(0), fut)
+    }
+
     /// Informs the scheduler that this task is no longer needed and should be removed
     /// on next poll.
     pub fn remove_future(&self, id: TaskId) {
-        self.tasks.remove_fut(id);
+        self.tasks.remove(id);
     }
 
     /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
@@ -940,36 +974,43 @@ impl BumpFrame {
 
 pub(crate) struct TaskQueue {
     pub(crate) tasks: RefCell<FxHashMap<TaskId, InnerTask>>,
+    pub(crate) task_map: RefCell<FxHashMap<ScopeId, HashSet<TaskId>>>,
     gen: Cell<usize>,
     sender: UnboundedSender<SchedulerMsg>,
 }
+
 pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
 impl TaskQueue {
-    fn new(sender: UnboundedSender<SchedulerMsg>) -> Rc<Self> {
-        Rc::new(Self {
-            tasks: RefCell::new(FxHashMap::default()),
-            gen: Cell::new(0),
-            sender,
-        })
-    }
-    fn push_fut(&self, task: impl Future<Output = ()> + 'static) -> TaskId {
+    fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
         let pinned = Box::pin(task);
         let id = self.gen.get();
         self.gen.set(id + 1);
-        let tid = TaskId(id);
+        let tid = TaskId { id, scope };
 
         self.tasks.borrow_mut().insert(tid, pinned);
+
+        // also add to the task map
+        // when the component is unmounted we know to remove it from the map
+        self.task_map
+            .borrow_mut()
+            .entry(scope)
+            .or_default()
+            .insert(tid);
+
         tid
     }
-    fn remove_fut(&self, id: TaskId) {
+
+    fn remove(&self, id: TaskId) {
         if let Ok(mut tasks) = self.tasks.try_borrow_mut() {
             let _ = tasks.remove(&id);
-        } else {
-            // todo: it should be okay to remote a fut while the queue is being polled
-            // However, it's not currently possible to do that.
-            log::trace!("Unable to remove task from task queue. This is probably a bug.");
+        }
+
+        // the task map is still around, but it'll be removed when the scope is unmounted
+        if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
+            task_map.remove(&id);
         }
     }
+
     pub(crate) fn has_tasks(&self) -> bool {
         !self.tasks.borrow().is_empty()
     }

+ 5 - 3
packages/core/tests/diffing.rs

@@ -623,16 +623,17 @@ fn controlled_keyed_diffing_out_of_order() {
     assert_eq!(
         changes.edits,
         [
+            Remove { root: 4 },
             // move 4 to after 6
             PushRoot { root: 1 },
             InsertAfter { n: 1, root: 3 },
             // remove 7
 
             // create 9 and insert before 6
-            CreateElement { root: 5, tag: "div" },
+            CreateElement { root: 4, tag: "div" },
             InsertBefore { n: 1, root: 3 },
             // create 0 and insert before 5
-            CreateElement { root: 6, tag: "div" },
+            CreateElement { root: 5, tag: "div" },
             InsertBefore { n: 1, root: 2 },
         ]
     );
@@ -659,7 +660,8 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
     assert_eq!(
         changes.edits,
         [
-            CreateElement { root: 6, tag: "div" },
+            Remove { root: 5 },
+            CreateElement { root: 5, tag: "div" },
             InsertBefore { n: 1, root: 3 },
             PushRoot { root: 4 },
             InsertBefore { n: 1, root: 1 },