Skip to main content

TaskScope

Struct TaskScope 

Source
pub struct TaskScope { /* private fields */ }
Expand description

A node in the scope tree that owns spawned tasks and carries a typed context for dependency injection.

§Drop guarantee

When a TaskScope is dropped, all descendant scopes and their tasks are cancelled iteratively using a work queue — recursion is never used, so deeply nested UI trees (200+ levels) never overflow the stack.

§Cancellation is cooperative

Cancellation drops the task’s Future and removes it from the executor. Like all async Rust, this only takes effect at the next .await point — a task stuck in a synchronous compute loop cannot be interrupted mid-execution. This is the same trade-off made by tokio::task::JoinHandle::abort. For long synchronous work, insert yield_now at checkpoints.

§Context

Use provide / consume for lightweight dependency injection that walks up the scope tree.

§Callback lifecycle

CallbackHandles registered via register_callback_handle are dropped before spawned tasks are cancelled, ensuring that signal subscriptions are removed before any task cleanup.

Implementations§

Source§

impl TaskScope

Source

pub fn new() -> Self

Create a new root scope on the global thread-local executor.

For explicit executor ownership use TaskScope::with_executor.

Examples found in repository?
examples/counter.rs (line 65)
37fn main() {
38    // One-time executor setup.
39    init_flush_scheduler(Rc::new(CliScheduler));
40    init_time_source(Rc::new(CliClock::new()));
41
42    // ---- Signal basics ----
43    println!("=== Signal basics ===");
44    let count = Signal::new(0);
45    println!("count = {}", count.read());
46    count.set(42);
47    println!("count = {}", count.read());
48
49    // ---- Memo (auto-tracking computed value) ----
50    println!("\n=== Memo ===");
51    let a = Signal::new(2);
52    let b = Signal::new(3);
53    let a2 = a.clone();
54    let b2 = b.clone();
55    let sum = Memo::new(move || a2.read() + b2.read());
56    println!("2 + 3 = {}", sum.read());
57    a.set(10);
58    println!("10 + 3 = {}", sum.read());
59
60    // ---- TaskScope: spawn a task that watches a signal ----
61    println!("\n=== TaskScope (scoped signal watcher) ===");
62    let messages = Signal::new(vec!["hello".to_string()]);
63
64    {
65        let scope = TaskScope::new();
66        let msgs = messages.clone();
67        scope.spawn(async move {
68            loop {
69                let val = msgs.changed().await;
70                println!("  task observed: {:?}", val);
71            }
72        });
73
74        messages.set(vec!["hello".to_string(), "world".to_string()]);
75        messages.set(vec![
76            "hello".to_string(),
77            "world".to_string(),
78            "!".to_string(),
79        ]);
80
81        // Explicit drop — cancels the task and cleans up resources.
82        drop(scope);
83    }
84    println!("(scope dropped — task and subscriptions cleaned up)");
85
86    // ---- Batch: multiple sets, one notification ----
87    println!("\n=== Batch ===");
88    let x = Signal::new(0);
89    let notifications = Rc::new(Cell::new(0u32));
90    let n = Rc::clone(&notifications);
91    let x2 = x.clone();
92
93    let scope = TaskScope::new();
94    scope.spawn(async move {
95        loop {
96            x2.changed().await;
97            n.set(n.get() + 1);
98        }
99    });
100
101    batch(|| {
102        x.set(1);
103        x.set(2);
104        x.set(3);
105    });
106    println!(
107        "After 3 sets in a batch: {} notification(s)",
108        notifications.get()
109    );
110    // Should be 1 — not 3.
111
112    println!("\nDone.");
113}
More examples
Hide additional examples
examples/scope_bench.rs (line 75)
68fn main() {
69    println!("=== Auralis Task Scope Benchmarks ===\n");
70
71    // 1. Scope create + destroy (100 tasks)
72    let iterations = 50_000;
73    init();
74    time("scope_100_tasks_create_destroy", iterations, || {
75        let scope = TaskScope::new();
76        for _ in 0..100 {
77            scope.spawn(async {});
78        }
79        drop(scope);
80    });
81
82    // 2. Deep nesting drop (200 levels)
83    init();
84    time("scope_200_levels_deep_drop", 5_000, || {
85        let root = TaskScope::new();
86        {
87            let mut current = TaskScope::new_child(&root);
88            for _ in 0..199 {
89                current.spawn(async {});
90                current = TaskScope::new_child(&current);
91            }
92        }
93        drop(root);
94    });
95
96    // 3. Priority ordering (batched flush)
97    {
98        let order: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
99        let min_iterations = 5_000;
100        // Warm-up
101        for _ in 0..500 {
102            let sched = BatchedFlush::new();
103            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
104            order.borrow_mut().clear();
105            for i in 0..1000 {
106                let o = Rc::clone(&order);
107                auralis_task::spawn_global_with_priority(Priority::Low, async move {
108                    o.borrow_mut().push(format!("low_{i}"));
109                });
110            }
111            for i in 0..10 {
112                let o = Rc::clone(&order);
113                auralis_task::spawn_global_with_priority(Priority::High, async move {
114                    o.borrow_mut().push(format!("high_{i}"));
115                });
116            }
117            sched.drain();
118        }
119
120        let start = Instant::now();
121        for _ in 0..min_iterations {
122            let sched = BatchedFlush::new();
123            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
124            order.borrow_mut().clear();
125            for i in 0..1000 {
126                let o = Rc::clone(&order);
127                auralis_task::spawn_global_with_priority(Priority::Low, async move {
128                    o.borrow_mut().push(format!("low_{i}"));
129                });
130            }
131            for i in 0..10 {
132                let o = Rc::clone(&order);
133                auralis_task::spawn_global_with_priority(Priority::High, async move {
134                    o.borrow_mut().push(format!("high_{i}"));
135                });
136            }
137            sched.drain();
138            // Verify correctness.
139            let result = order.borrow().clone();
140            debug_assert_eq!(result.len(), 1010);
141        }
142        let elapsed = start.elapsed().as_secs_f64() * 1_000_000.0 / min_iterations as f64;
143        println!(
144            "  {:<42} {:>8.2} µs/iter",
145            "priority_1000_low_10_high", elapsed
146        );
147        init(); // restore sync scheduler
148    }
149
150    // 4. Scope churn (100 scopes x 10 tasks, batch drop)
151    init();
152    time("scope_churn_100x10_batch_drop", 5_000, || {
153        let scopes: Vec<TaskScope> = (0..100)
154            .map(|_| {
155                let s = TaskScope::new();
156                for _ in 0..10 {
157                    s.spawn(async {});
158                }
159                s
160            })
161            .collect();
162        drop(scopes);
163    });
164
165    // 5. Scope suspend + resume (1000 tasks)
166    init();
167    let scope = TaskScope::new();
168    for _ in 0..1000 {
169        scope.spawn(async {});
170    }
171    time("scope_suspend_resume_1000_tasks", 500_000, || {
172        scope.suspend();
173        scope.resume();
174    });
175
176    // 6. Wide shallow tree drop (50x50, ~2550 tasks)
177    init();
178    time("scope_wide_tree_50x3_drop", 500, || {
179        let root = TaskScope::new();
180        for _ in 0..50 {
181            let child = TaskScope::new_child(&root);
182            child.spawn(async {});
183            for _ in 0..50 {
184                let grandchild = TaskScope::new_child(&child);
185                grandchild.spawn(async {});
186            }
187        }
188        drop(root);
189    });
190
191    println!("\n=== Done ===");
192}
Source

pub fn with_executor(ex: &Rc<RefCell<Executor>>) -> Self

Create a new root scope on the given executor.

All tasks spawned in this scope (and its descendants) run on ex. The scope holds a strong reference, keeping the executor alive at least as long as the scope.

Examples found in repository?
examples/multi_instance_isolated.rs (line 32)
28fn handle_request(request_id: u32) -> String {
29    let ex = Executor::new_instance();
30    Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
31
32    let scope = TaskScope::with_executor(&ex);
33    let data = Signal::new(String::new());
34
35    // "route handler" spawns a reactive effect
36    let d = data.clone();
37    scope.spawn(async move {
38        loop {
39            d.changed().await;
40            let val = d.read();
41            if val == "done" {
42                break;
43            }
44        }
45    });
46
47    // "middleware" sets up cleanup
48    let cleanup_called = Rc::new(Cell::new(false));
49    let cc = Rc::clone(&cleanup_called);
50    scope.on_cleanup(move || cc.set(true));
51
52    // "business logic" — would be I/O in real app
53    let result = auralis_task::with_executor(&ex, || {
54        data.set(format!("request {request_id}: processing"));
55        data.set(format!("request {request_id}: done"));
56        data.read()
57    });
58
59    // Drop scope → cancels effect, runs cleanup.
60    drop(scope);
61
62    assert!(cleanup_called.get());
63    result
64}
65
66fn main() {
67    // ---- Pattern 1: sequential requests (same thread, isolated) ----------
68    println!("=== 1. Sequential request isolation ===");
69    let r1 = handle_request(1);
70    let r2 = handle_request(2);
71    println!("  {r1}");
72    println!("  {r2}");
73    assert_eq!(r1, "request 1: done");
74    assert_eq!(r2, "request 2: done");
75
76    // ---- Pattern 2: same-signal isolation test (different executors) -----
77    println!("\n=== 2. Cross-executor signal isolation ===");
78    {
79        let ex_a = Executor::new_instance();
80        Executor::install_flush_scheduler(&ex_a, Rc::new(SyncScheduler));
81        let ex_b = Executor::new_instance();
82        Executor::install_flush_scheduler(&ex_b, Rc::new(SyncScheduler));
83
84        let sig_a1 = Signal::new(0i32);
85        let sig_b1 = Signal::new(0i32);
86
87        // Scope on executor A sets sig_a and reads sig_b.
88        let scope_a = TaskScope::with_executor(&ex_a);
89        let a_val = Rc::new(RefCell::new(Vec::new()));
90        let av = Rc::clone(&a_val);
91        {
92            let s = sig_a1.clone();
93            scope_a.spawn(async move {
94                s.set(42);
95                av.borrow_mut().push(s.read());
96            });
97        }
98        drop(scope_a);
99
100        // Scope on executor B sets sig_b and reads sig_a.
101        let scope_b = TaskScope::with_executor(&ex_b);
102        let b_val = Rc::new(RefCell::new(Vec::new()));
103        let bv = Rc::clone(&b_val);
104        {
105            let s = sig_b1.clone();
106            scope_b.spawn(async move {
107                s.set(99);
108                bv.borrow_mut().push(s.read());
109            });
110        }
111        drop(scope_b);
112
113        // Each executor only sees its own signal changes.
114        assert_eq!(*a_val.borrow(), vec![42]);
115        assert_eq!(*b_val.borrow(), vec![99]);
116        assert_eq!(sig_a1.read(), 42);
117        assert_eq!(sig_b1.read(), 99);
118    }
119
120    // ---- Pattern 3: the SSR mental model (one request = one executor) ----
121    println!("\n=== 3. SSR mental model ===");
122    for i in 0..3 {
123        let ex = Executor::new_instance();
124        Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
125        let counter = Signal::new(0i32);
126
127        let scope = TaskScope::with_executor(&ex);
128        let c = counter.clone();
129        let results = Rc::new(RefCell::new(Vec::new()));
130        let res = Rc::clone(&results);
131
132        scope.spawn(async move {
133            loop {
134                c.changed().await;
135                let v = c.read();
136                res.borrow_mut().push(v);
137                if v >= 3 {
138                    break;
139                }
140            }
141        });
142
143        auralis_task::with_executor(&ex, || {
144            counter.set(1);
145            counter.set(2);
146            counter.set(3);
147        });
148
149        drop(scope);
150        assert_eq!(*results.borrow(), vec![1, 2, 3]);
151        println!("  request {i}: signals {:?}", results.borrow());
152    }
153
154    println!("\nAll patterns completed — zero cross-request leakage.");
155}
Source

pub fn new_child(parent: &Self) -> Self

Create a child scope that inherits the parent’s executor.

The child is stored in the parent’s children list. This means dropping all external clones of the child does not immediately cancel it — the parent’s strong reference keeps it alive. The child is fully cancelled only when the parent itself is dropped (or when TaskScope::drop runs on the last reference).

To explicitly cancel a child while the parent is still alive, call suspend on the child, or use a JoinHandle to cancel individual tasks.

Examples found in repository?
examples/scope_bench.rs (line 87)
68fn main() {
69    println!("=== Auralis Task Scope Benchmarks ===\n");
70
71    // 1. Scope create + destroy (100 tasks)
72    let iterations = 50_000;
73    init();
74    time("scope_100_tasks_create_destroy", iterations, || {
75        let scope = TaskScope::new();
76        for _ in 0..100 {
77            scope.spawn(async {});
78        }
79        drop(scope);
80    });
81
82    // 2. Deep nesting drop (200 levels)
83    init();
84    time("scope_200_levels_deep_drop", 5_000, || {
85        let root = TaskScope::new();
86        {
87            let mut current = TaskScope::new_child(&root);
88            for _ in 0..199 {
89                current.spawn(async {});
90                current = TaskScope::new_child(&current);
91            }
92        }
93        drop(root);
94    });
95
96    // 3. Priority ordering (batched flush)
97    {
98        let order: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
99        let min_iterations = 5_000;
100        // Warm-up
101        for _ in 0..500 {
102            let sched = BatchedFlush::new();
103            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
104            order.borrow_mut().clear();
105            for i in 0..1000 {
106                let o = Rc::clone(&order);
107                auralis_task::spawn_global_with_priority(Priority::Low, async move {
108                    o.borrow_mut().push(format!("low_{i}"));
109                });
110            }
111            for i in 0..10 {
112                let o = Rc::clone(&order);
113                auralis_task::spawn_global_with_priority(Priority::High, async move {
114                    o.borrow_mut().push(format!("high_{i}"));
115                });
116            }
117            sched.drain();
118        }
119
120        let start = Instant::now();
121        for _ in 0..min_iterations {
122            let sched = BatchedFlush::new();
123            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
124            order.borrow_mut().clear();
125            for i in 0..1000 {
126                let o = Rc::clone(&order);
127                auralis_task::spawn_global_with_priority(Priority::Low, async move {
128                    o.borrow_mut().push(format!("low_{i}"));
129                });
130            }
131            for i in 0..10 {
132                let o = Rc::clone(&order);
133                auralis_task::spawn_global_with_priority(Priority::High, async move {
134                    o.borrow_mut().push(format!("high_{i}"));
135                });
136            }
137            sched.drain();
138            // Verify correctness.
139            let result = order.borrow().clone();
140            debug_assert_eq!(result.len(), 1010);
141        }
142        let elapsed = start.elapsed().as_secs_f64() * 1_000_000.0 / min_iterations as f64;
143        println!(
144            "  {:<42} {:>8.2} µs/iter",
145            "priority_1000_low_10_high", elapsed
146        );
147        init(); // restore sync scheduler
148    }
149
150    // 4. Scope churn (100 scopes x 10 tasks, batch drop)
151    init();
152    time("scope_churn_100x10_batch_drop", 5_000, || {
153        let scopes: Vec<TaskScope> = (0..100)
154            .map(|_| {
155                let s = TaskScope::new();
156                for _ in 0..10 {
157                    s.spawn(async {});
158                }
159                s
160            })
161            .collect();
162        drop(scopes);
163    });
164
165    // 5. Scope suspend + resume (1000 tasks)
166    init();
167    let scope = TaskScope::new();
168    for _ in 0..1000 {
169        scope.spawn(async {});
170    }
171    time("scope_suspend_resume_1000_tasks", 500_000, || {
172        scope.suspend();
173        scope.resume();
174    });
175
176    // 6. Wide shallow tree drop (50x50, ~2550 tasks)
177    init();
178    time("scope_wide_tree_50x3_drop", 500, || {
179        let root = TaskScope::new();
180        for _ in 0..50 {
181            let child = TaskScope::new_child(&root);
182            child.spawn(async {});
183            for _ in 0..50 {
184                let grandchild = TaskScope::new_child(&child);
185                grandchild.spawn(async {});
186            }
187        }
188        drop(root);
189    });
190
191    println!("\n=== Done ===");
192}
Source

pub fn spawn(&self, future: impl Future<Output = ()> + 'static) -> JoinHandle

Spawn a future in this scope at low priority.

Returns a JoinHandle that can cancel this individual task. Drop the handle to detach (the task keeps running until the scope is dropped).

Examples found in repository?
examples/multi_instance_isolated.rs (lines 37-45)
28fn handle_request(request_id: u32) -> String {
29    let ex = Executor::new_instance();
30    Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
31
32    let scope = TaskScope::with_executor(&ex);
33    let data = Signal::new(String::new());
34
35    // "route handler" spawns a reactive effect
36    let d = data.clone();
37    scope.spawn(async move {
38        loop {
39            d.changed().await;
40            let val = d.read();
41            if val == "done" {
42                break;
43            }
44        }
45    });
46
47    // "middleware" sets up cleanup
48    let cleanup_called = Rc::new(Cell::new(false));
49    let cc = Rc::clone(&cleanup_called);
50    scope.on_cleanup(move || cc.set(true));
51
52    // "business logic" — would be I/O in real app
53    let result = auralis_task::with_executor(&ex, || {
54        data.set(format!("request {request_id}: processing"));
55        data.set(format!("request {request_id}: done"));
56        data.read()
57    });
58
59    // Drop scope → cancels effect, runs cleanup.
60    drop(scope);
61
62    assert!(cleanup_called.get());
63    result
64}
65
66fn main() {
67    // ---- Pattern 1: sequential requests (same thread, isolated) ----------
68    println!("=== 1. Sequential request isolation ===");
69    let r1 = handle_request(1);
70    let r2 = handle_request(2);
71    println!("  {r1}");
72    println!("  {r2}");
73    assert_eq!(r1, "request 1: done");
74    assert_eq!(r2, "request 2: done");
75
76    // ---- Pattern 2: same-signal isolation test (different executors) -----
77    println!("\n=== 2. Cross-executor signal isolation ===");
78    {
79        let ex_a = Executor::new_instance();
80        Executor::install_flush_scheduler(&ex_a, Rc::new(SyncScheduler));
81        let ex_b = Executor::new_instance();
82        Executor::install_flush_scheduler(&ex_b, Rc::new(SyncScheduler));
83
84        let sig_a1 = Signal::new(0i32);
85        let sig_b1 = Signal::new(0i32);
86
87        // Scope on executor A sets sig_a and reads sig_b.
88        let scope_a = TaskScope::with_executor(&ex_a);
89        let a_val = Rc::new(RefCell::new(Vec::new()));
90        let av = Rc::clone(&a_val);
91        {
92            let s = sig_a1.clone();
93            scope_a.spawn(async move {
94                s.set(42);
95                av.borrow_mut().push(s.read());
96            });
97        }
98        drop(scope_a);
99
100        // Scope on executor B sets sig_b and reads sig_a.
101        let scope_b = TaskScope::with_executor(&ex_b);
102        let b_val = Rc::new(RefCell::new(Vec::new()));
103        let bv = Rc::clone(&b_val);
104        {
105            let s = sig_b1.clone();
106            scope_b.spawn(async move {
107                s.set(99);
108                bv.borrow_mut().push(s.read());
109            });
110        }
111        drop(scope_b);
112
113        // Each executor only sees its own signal changes.
114        assert_eq!(*a_val.borrow(), vec![42]);
115        assert_eq!(*b_val.borrow(), vec![99]);
116        assert_eq!(sig_a1.read(), 42);
117        assert_eq!(sig_b1.read(), 99);
118    }
119
120    // ---- Pattern 3: the SSR mental model (one request = one executor) ----
121    println!("\n=== 3. SSR mental model ===");
122    for i in 0..3 {
123        let ex = Executor::new_instance();
124        Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
125        let counter = Signal::new(0i32);
126
127        let scope = TaskScope::with_executor(&ex);
128        let c = counter.clone();
129        let results = Rc::new(RefCell::new(Vec::new()));
130        let res = Rc::clone(&results);
131
132        scope.spawn(async move {
133            loop {
134                c.changed().await;
135                let v = c.read();
136                res.borrow_mut().push(v);
137                if v >= 3 {
138                    break;
139                }
140            }
141        });
142
143        auralis_task::with_executor(&ex, || {
144            counter.set(1);
145            counter.set(2);
146            counter.set(3);
147        });
148
149        drop(scope);
150        assert_eq!(*results.borrow(), vec![1, 2, 3]);
151        println!("  request {i}: signals {:?}", results.borrow());
152    }
153
154    println!("\nAll patterns completed — zero cross-request leakage.");
155}
More examples
Hide additional examples
examples/counter.rs (lines 67-72)
37fn main() {
38    // One-time executor setup.
39    init_flush_scheduler(Rc::new(CliScheduler));
40    init_time_source(Rc::new(CliClock::new()));
41
42    // ---- Signal basics ----
43    println!("=== Signal basics ===");
44    let count = Signal::new(0);
45    println!("count = {}", count.read());
46    count.set(42);
47    println!("count = {}", count.read());
48
49    // ---- Memo (auto-tracking computed value) ----
50    println!("\n=== Memo ===");
51    let a = Signal::new(2);
52    let b = Signal::new(3);
53    let a2 = a.clone();
54    let b2 = b.clone();
55    let sum = Memo::new(move || a2.read() + b2.read());
56    println!("2 + 3 = {}", sum.read());
57    a.set(10);
58    println!("10 + 3 = {}", sum.read());
59
60    // ---- TaskScope: spawn a task that watches a signal ----
61    println!("\n=== TaskScope (scoped signal watcher) ===");
62    let messages = Signal::new(vec!["hello".to_string()]);
63
64    {
65        let scope = TaskScope::new();
66        let msgs = messages.clone();
67        scope.spawn(async move {
68            loop {
69                let val = msgs.changed().await;
70                println!("  task observed: {:?}", val);
71            }
72        });
73
74        messages.set(vec!["hello".to_string(), "world".to_string()]);
75        messages.set(vec![
76            "hello".to_string(),
77            "world".to_string(),
78            "!".to_string(),
79        ]);
80
81        // Explicit drop — cancels the task and cleans up resources.
82        drop(scope);
83    }
84    println!("(scope dropped — task and subscriptions cleaned up)");
85
86    // ---- Batch: multiple sets, one notification ----
87    println!("\n=== Batch ===");
88    let x = Signal::new(0);
89    let notifications = Rc::new(Cell::new(0u32));
90    let n = Rc::clone(&notifications);
91    let x2 = x.clone();
92
93    let scope = TaskScope::new();
94    scope.spawn(async move {
95        loop {
96            x2.changed().await;
97            n.set(n.get() + 1);
98        }
99    });
100
101    batch(|| {
102        x.set(1);
103        x.set(2);
104        x.set(3);
105    });
106    println!(
107        "After 3 sets in a batch: {} notification(s)",
108        notifications.get()
109    );
110    // Should be 1 — not 3.
111
112    println!("\nDone.");
113}
examples/scope_bench.rs (line 77)
68fn main() {
69    println!("=== Auralis Task Scope Benchmarks ===\n");
70
71    // 1. Scope create + destroy (100 tasks)
72    let iterations = 50_000;
73    init();
74    time("scope_100_tasks_create_destroy", iterations, || {
75        let scope = TaskScope::new();
76        for _ in 0..100 {
77            scope.spawn(async {});
78        }
79        drop(scope);
80    });
81
82    // 2. Deep nesting drop (200 levels)
83    init();
84    time("scope_200_levels_deep_drop", 5_000, || {
85        let root = TaskScope::new();
86        {
87            let mut current = TaskScope::new_child(&root);
88            for _ in 0..199 {
89                current.spawn(async {});
90                current = TaskScope::new_child(&current);
91            }
92        }
93        drop(root);
94    });
95
96    // 3. Priority ordering (batched flush)
97    {
98        let order: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
99        let min_iterations = 5_000;
100        // Warm-up
101        for _ in 0..500 {
102            let sched = BatchedFlush::new();
103            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
104            order.borrow_mut().clear();
105            for i in 0..1000 {
106                let o = Rc::clone(&order);
107                auralis_task::spawn_global_with_priority(Priority::Low, async move {
108                    o.borrow_mut().push(format!("low_{i}"));
109                });
110            }
111            for i in 0..10 {
112                let o = Rc::clone(&order);
113                auralis_task::spawn_global_with_priority(Priority::High, async move {
114                    o.borrow_mut().push(format!("high_{i}"));
115                });
116            }
117            sched.drain();
118        }
119
120        let start = Instant::now();
121        for _ in 0..min_iterations {
122            let sched = BatchedFlush::new();
123            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
124            order.borrow_mut().clear();
125            for i in 0..1000 {
126                let o = Rc::clone(&order);
127                auralis_task::spawn_global_with_priority(Priority::Low, async move {
128                    o.borrow_mut().push(format!("low_{i}"));
129                });
130            }
131            for i in 0..10 {
132                let o = Rc::clone(&order);
133                auralis_task::spawn_global_with_priority(Priority::High, async move {
134                    o.borrow_mut().push(format!("high_{i}"));
135                });
136            }
137            sched.drain();
138            // Verify correctness.
139            let result = order.borrow().clone();
140            debug_assert_eq!(result.len(), 1010);
141        }
142        let elapsed = start.elapsed().as_secs_f64() * 1_000_000.0 / min_iterations as f64;
143        println!(
144            "  {:<42} {:>8.2} µs/iter",
145            "priority_1000_low_10_high", elapsed
146        );
147        init(); // restore sync scheduler
148    }
149
150    // 4. Scope churn (100 scopes x 10 tasks, batch drop)
151    init();
152    time("scope_churn_100x10_batch_drop", 5_000, || {
153        let scopes: Vec<TaskScope> = (0..100)
154            .map(|_| {
155                let s = TaskScope::new();
156                for _ in 0..10 {
157                    s.spawn(async {});
158                }
159                s
160            })
161            .collect();
162        drop(scopes);
163    });
164
165    // 5. Scope suspend + resume (1000 tasks)
166    init();
167    let scope = TaskScope::new();
168    for _ in 0..1000 {
169        scope.spawn(async {});
170    }
171    time("scope_suspend_resume_1000_tasks", 500_000, || {
172        scope.suspend();
173        scope.resume();
174    });
175
176    // 6. Wide shallow tree drop (50x50, ~2550 tasks)
177    init();
178    time("scope_wide_tree_50x3_drop", 500, || {
179        let root = TaskScope::new();
180        for _ in 0..50 {
181            let child = TaskScope::new_child(&root);
182            child.spawn(async {});
183            for _ in 0..50 {
184                let grandchild = TaskScope::new_child(&child);
185                grandchild.spawn(async {});
186            }
187        }
188        drop(root);
189    });
190
191    println!("\n=== Done ===");
192}
Source

pub fn spawn_with_priority( &self, priority: Priority, future: impl Future<Output = ()> + 'static, ) -> JoinHandle

Spawn a future in this scope at the given priority.

The current scope is set to self during the spawn so that any synchronous work inside the future constructor (e.g. bind_text) can discover the owning scope via current_scope.

Returns a JoinHandle that can cancel this individual task.

Source

pub fn watch<T: Clone + 'static>( &self, sig: &Signal<T>, f: impl FnMut(&T) + 'static, ) -> JoinHandle

Spawn a task that calls f with the new value whenever sig changes.

This is a convenience wrapper around the common pattern:

scope.spawn({
    let s = sig.clone();
    async move { loop { s.changed().await; f(&s.read()); } }
});

Returns a JoinHandle for individual cancellation.

Source

pub fn watch_effect(&self, effect: impl Fn() + 'static) -> JoinHandle

Spawn a task that re-runs effect whenever any Signal read inside it changes — using a Memo internally to auto-track dependencies.

The effect is run once immediately to discover its dependencies. Subsequent runs happen on the executor when a dependency changes.

Returns a JoinHandle for individual cancellation.

Source

pub fn register_callback_handle(&self, handle: CallbackHandle)

Register a CallbackHandle that will be dropped when this scope is dropped (or when clear_callbacks is called).

Used by bind_* functions to ensure signal subscriptions are cleaned up when the owning component is destroyed.

Source

pub fn on_cleanup(&self, f: impl FnOnce() + 'static)

Register a cleanup function that runs when this scope is dropped.

Equivalent to register_callback_handle(CallbackHandle::new(f)).

Cleanup functions run before spawned tasks are cancelled, so they can safely interact with signals and other resources.

If the scope is already cancelled, f is dropped immediately.

Examples found in repository?
examples/multi_instance_isolated.rs (line 50)
28fn handle_request(request_id: u32) -> String {
29    let ex = Executor::new_instance();
30    Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
31
32    let scope = TaskScope::with_executor(&ex);
33    let data = Signal::new(String::new());
34
35    // "route handler" spawns a reactive effect
36    let d = data.clone();
37    scope.spawn(async move {
38        loop {
39            d.changed().await;
40            let val = d.read();
41            if val == "done" {
42                break;
43            }
44        }
45    });
46
47    // "middleware" sets up cleanup
48    let cleanup_called = Rc::new(Cell::new(false));
49    let cc = Rc::clone(&cleanup_called);
50    scope.on_cleanup(move || cc.set(true));
51
52    // "business logic" — would be I/O in real app
53    let result = auralis_task::with_executor(&ex, || {
54        data.set(format!("request {request_id}: processing"));
55        data.set(format!("request {request_id}: done"));
56        data.read()
57    });
58
59    // Drop scope → cancels effect, runs cleanup.
60    drop(scope);
61
62    assert!(cleanup_called.get());
63    result
64}
Source

pub fn provide<T: 'static>(&self, value: T)

Store a value of type T in this scope.

The value is wrapped in Rc so it can be shared. A subsequent call to consume on this scope (or any descendant) will discover it by walking up the parent chain.

Source

pub fn consume<T: 'static>(&self) -> Option<Rc<T>>

Look up a value of type T by walking up the scope tree.

Returns None if no ancestor (including self) has provided a value of this type.

Source

pub fn expect_context<T: 'static>(&self) -> Rc<T>

Like consume but panics if the value is not found.

§Panics

Panics if no ancestor scope has provided a value of type T.

Source

pub fn is_cancelled(&self) -> bool

Return true if this scope has been cancelled (dropped).

A cancelled scope silently ignores spawn calls.

Source

pub fn set_label(&self, label: impl Into<String>)

Set a human-readable label for this scope.

Labels appear in dump_reactive_graph() output and are useful for debugging.

Source

pub fn label(&self) -> Option<String>

Return the label set by set_label, if any.

Source

pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R

Run f with self set as the current scope for the thread.

Used by framework glue code so bind functions can discover the owning scope via current_scope.

Source

pub fn suspend(&self)

Suspend all tasks owned by this scope and its descendants.

Suspended tasks are skipped during executor polling. Signal subscriptions remain registered but their callbacks are not invoked while the scope is suspended. Use resume to restart execution.

Used by if_async_cached and match_async_cached to pause hidden branches.

Examples found in repository?
examples/scope_bench.rs (line 172)
68fn main() {
69    println!("=== Auralis Task Scope Benchmarks ===\n");
70
71    // 1. Scope create + destroy (100 tasks)
72    let iterations = 50_000;
73    init();
74    time("scope_100_tasks_create_destroy", iterations, || {
75        let scope = TaskScope::new();
76        for _ in 0..100 {
77            scope.spawn(async {});
78        }
79        drop(scope);
80    });
81
82    // 2. Deep nesting drop (200 levels)
83    init();
84    time("scope_200_levels_deep_drop", 5_000, || {
85        let root = TaskScope::new();
86        {
87            let mut current = TaskScope::new_child(&root);
88            for _ in 0..199 {
89                current.spawn(async {});
90                current = TaskScope::new_child(&current);
91            }
92        }
93        drop(root);
94    });
95
96    // 3. Priority ordering (batched flush)
97    {
98        let order: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
99        let min_iterations = 5_000;
100        // Warm-up
101        for _ in 0..500 {
102            let sched = BatchedFlush::new();
103            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
104            order.borrow_mut().clear();
105            for i in 0..1000 {
106                let o = Rc::clone(&order);
107                auralis_task::spawn_global_with_priority(Priority::Low, async move {
108                    o.borrow_mut().push(format!("low_{i}"));
109                });
110            }
111            for i in 0..10 {
112                let o = Rc::clone(&order);
113                auralis_task::spawn_global_with_priority(Priority::High, async move {
114                    o.borrow_mut().push(format!("high_{i}"));
115                });
116            }
117            sched.drain();
118        }
119
120        let start = Instant::now();
121        for _ in 0..min_iterations {
122            let sched = BatchedFlush::new();
123            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
124            order.borrow_mut().clear();
125            for i in 0..1000 {
126                let o = Rc::clone(&order);
127                auralis_task::spawn_global_with_priority(Priority::Low, async move {
128                    o.borrow_mut().push(format!("low_{i}"));
129                });
130            }
131            for i in 0..10 {
132                let o = Rc::clone(&order);
133                auralis_task::spawn_global_with_priority(Priority::High, async move {
134                    o.borrow_mut().push(format!("high_{i}"));
135                });
136            }
137            sched.drain();
138            // Verify correctness.
139            let result = order.borrow().clone();
140            debug_assert_eq!(result.len(), 1010);
141        }
142        let elapsed = start.elapsed().as_secs_f64() * 1_000_000.0 / min_iterations as f64;
143        println!(
144            "  {:<42} {:>8.2} µs/iter",
145            "priority_1000_low_10_high", elapsed
146        );
147        init(); // restore sync scheduler
148    }
149
150    // 4. Scope churn (100 scopes x 10 tasks, batch drop)
151    init();
152    time("scope_churn_100x10_batch_drop", 5_000, || {
153        let scopes: Vec<TaskScope> = (0..100)
154            .map(|_| {
155                let s = TaskScope::new();
156                for _ in 0..10 {
157                    s.spawn(async {});
158                }
159                s
160            })
161            .collect();
162        drop(scopes);
163    });
164
165    // 5. Scope suspend + resume (1000 tasks)
166    init();
167    let scope = TaskScope::new();
168    for _ in 0..1000 {
169        scope.spawn(async {});
170    }
171    time("scope_suspend_resume_1000_tasks", 500_000, || {
172        scope.suspend();
173        scope.resume();
174    });
175
176    // 6. Wide shallow tree drop (50x50, ~2550 tasks)
177    init();
178    time("scope_wide_tree_50x3_drop", 500, || {
179        let root = TaskScope::new();
180        for _ in 0..50 {
181            let child = TaskScope::new_child(&root);
182            child.spawn(async {});
183            for _ in 0..50 {
184                let grandchild = TaskScope::new_child(&child);
185                grandchild.spawn(async {});
186            }
187        }
188        drop(root);
189    });
190
191    println!("\n=== Done ===");
192}
Source

pub fn resume(&self)

Resume all tasks owned by this scope and its descendants.

This reverses the effect of suspend. Tasks become eligible for polling again on the next executor flush.

Examples found in repository?
examples/scope_bench.rs (line 173)
68fn main() {
69    println!("=== Auralis Task Scope Benchmarks ===\n");
70
71    // 1. Scope create + destroy (100 tasks)
72    let iterations = 50_000;
73    init();
74    time("scope_100_tasks_create_destroy", iterations, || {
75        let scope = TaskScope::new();
76        for _ in 0..100 {
77            scope.spawn(async {});
78        }
79        drop(scope);
80    });
81
82    // 2. Deep nesting drop (200 levels)
83    init();
84    time("scope_200_levels_deep_drop", 5_000, || {
85        let root = TaskScope::new();
86        {
87            let mut current = TaskScope::new_child(&root);
88            for _ in 0..199 {
89                current.spawn(async {});
90                current = TaskScope::new_child(&current);
91            }
92        }
93        drop(root);
94    });
95
96    // 3. Priority ordering (batched flush)
97    {
98        let order: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
99        let min_iterations = 5_000;
100        // Warm-up
101        for _ in 0..500 {
102            let sched = BatchedFlush::new();
103            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
104            order.borrow_mut().clear();
105            for i in 0..1000 {
106                let o = Rc::clone(&order);
107                auralis_task::spawn_global_with_priority(Priority::Low, async move {
108                    o.borrow_mut().push(format!("low_{i}"));
109                });
110            }
111            for i in 0..10 {
112                let o = Rc::clone(&order);
113                auralis_task::spawn_global_with_priority(Priority::High, async move {
114                    o.borrow_mut().push(format!("high_{i}"));
115                });
116            }
117            sched.drain();
118        }
119
120        let start = Instant::now();
121        for _ in 0..min_iterations {
122            let sched = BatchedFlush::new();
123            init_flush_scheduler(Rc::clone(&sched) as Rc<dyn ScheduleFlush>);
124            order.borrow_mut().clear();
125            for i in 0..1000 {
126                let o = Rc::clone(&order);
127                auralis_task::spawn_global_with_priority(Priority::Low, async move {
128                    o.borrow_mut().push(format!("low_{i}"));
129                });
130            }
131            for i in 0..10 {
132                let o = Rc::clone(&order);
133                auralis_task::spawn_global_with_priority(Priority::High, async move {
134                    o.borrow_mut().push(format!("high_{i}"));
135                });
136            }
137            sched.drain();
138            // Verify correctness.
139            let result = order.borrow().clone();
140            debug_assert_eq!(result.len(), 1010);
141        }
142        let elapsed = start.elapsed().as_secs_f64() * 1_000_000.0 / min_iterations as f64;
143        println!(
144            "  {:<42} {:>8.2} µs/iter",
145            "priority_1000_low_10_high", elapsed
146        );
147        init(); // restore sync scheduler
148    }
149
150    // 4. Scope churn (100 scopes x 10 tasks, batch drop)
151    init();
152    time("scope_churn_100x10_batch_drop", 5_000, || {
153        let scopes: Vec<TaskScope> = (0..100)
154            .map(|_| {
155                let s = TaskScope::new();
156                for _ in 0..10 {
157                    s.spawn(async {});
158                }
159                s
160            })
161            .collect();
162        drop(scopes);
163    });
164
165    // 5. Scope suspend + resume (1000 tasks)
166    init();
167    let scope = TaskScope::new();
168    for _ in 0..1000 {
169        scope.spawn(async {});
170    }
171    time("scope_suspend_resume_1000_tasks", 500_000, || {
172        scope.suspend();
173        scope.resume();
174    });
175
176    // 6. Wide shallow tree drop (50x50, ~2550 tasks)
177    init();
178    time("scope_wide_tree_50x3_drop", 500, || {
179        let root = TaskScope::new();
180        for _ in 0..50 {
181            let child = TaskScope::new_child(&root);
182            child.spawn(async {});
183            for _ in 0..50 {
184                let grandchild = TaskScope::new_child(&child);
185                grandchild.spawn(async {});
186            }
187        }
188        drop(root);
189    });
190
191    println!("\n=== Done ===");
192}
Source

pub fn is_suspended(&self) -> bool

Return true if this scope is currently suspended.

Trait Implementations§

Source§

impl Clone for TaskScope

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Default for TaskScope

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Drop for TaskScope

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.