Skip to main content

telex/
scope.rs

1use std::any::{Any, TypeId};
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::rc::Rc;
5use std::sync::atomic::AtomicBool;
6use std::sync::Arc;
7
8use crate::async_state::{Async, AsyncHandle};
9use crate::channel::{ChannelDrain, ChannelHandle, IntervalHandle, PortHandle};
10use crate::command::{CommandRegistry, KeyBinding};
11use crate::context::ContextStorage;
12use crate::state::State;
13use crate::stream_state::{StreamHandle, TextStreamHandle};
14
15/// Type alias for effect cleanup functions.
16type CleanupFn = Box<dyn FnOnce()>;
17
18/// Type alias for effect functions that return an optional cleanup.
19type EffectFn = Box<dyn FnOnce() -> Option<CleanupFn>>;
20
21/// State for a single effect hook.
22struct EffectState {
23    /// Cleanup function from the last effect run, if any.
24    cleanup: Option<CleanupFn>,
25    /// Dependencies from last run (boxed for type erasure).
26    last_deps: Option<Box<dyn Any>>,
27    /// Whether effect has ever run.
28    initialized: bool,
29}
30
31/// A pending keyed effect to run after render.
32struct PendingKeyedEffect {
33    /// TypeId key for the effect.
34    key: TypeId,
35    /// The effect function that returns an optional cleanup.
36    effect_fn: EffectFn,
37    /// New dependencies to store after running.
38    new_deps: Option<Box<dyn Any>>,
39}
40
41/// Maximum effect executions allowed within a window before we assume infinite loop.
42/// This is generous enough for legitimate use cases but catches runaway effects.
43const MAX_EFFECT_RUNS_PER_WINDOW: usize = 100;
44
45/// Number of frames in the sliding window for effect cycle detection.
46const EFFECT_WINDOW_FRAMES: usize = 10;
47
48/// Storage for component state across re-renders.
49pub struct StateStorage {
50    /// TypeId-keyed state storage (order-independent)
51    keyed_states: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
52    /// TypeId-keyed effect storage (order-independent)
53    keyed_effects: RefCell<HashMap<TypeId, EffectState>>,
54    /// Keyed effects scheduled to run after render
55    pending_keyed_effects: RefCell<Vec<PendingKeyedEffect>>,
56    /// Rolling count of effect executions for cycle detection
57    effect_run_count: RefCell<usize>,
58    /// Frames since last counter decay
59    frames_since_decay: RefCell<usize>,
60    /// Registered channels for the run loop to drain each frame
61    channels: RefCell<Vec<Rc<dyn ChannelDrain>>>,
62    /// Wake flag shared with channel senders for low-latency polling
63    wake_flag: Arc<AtomicBool>,
64}
65
66impl Default for StateStorage {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl StateStorage {
73    pub fn new() -> Self {
74        Self {
75            keyed_states: RefCell::new(HashMap::new()),
76            keyed_effects: RefCell::new(HashMap::new()),
77            pending_keyed_effects: RefCell::new(Vec::new()),
78            effect_run_count: RefCell::new(0),
79            frames_since_decay: RefCell::new(0),
80            channels: RefCell::new(Vec::new()),
81            wake_flag: Arc::new(AtomicBool::new(false)),
82        }
83    }
84
85    /// Get the wake flag (shared with channel senders for low-latency polling).
86    pub fn wake_flag(&self) -> &Arc<AtomicBool> {
87        &self.wake_flag
88    }
89
90    /// Get or create state by TypeId key (order-independent).
91    ///
92    /// The type K acts as the key - same K always returns the same state.
93    pub fn use_state_keyed<K: 'static, T: 'static>(&self, init: impl FnOnce() -> T) -> State<T> {
94        let key = TypeId::of::<K>();
95        let mut keyed_states = self.keyed_states.borrow_mut();
96
97        if let Some(any) = keyed_states.get(&key) {
98            // State exists, retrieve it
99            any.downcast_ref::<State<T>>()
100                .expect("State type mismatch for keyed state")
101                .clone()
102        } else {
103            // First access, create new state
104            let state = State::new(init());
105            keyed_states.insert(key, Rc::new(state.clone()));
106            state
107        }
108    }
109
110    // ========== Keyed Async/Stream/Terminal (order-independent) ==========
111
112    /// Get or create async state by TypeId key (order-independent).
113    pub fn use_async_keyed<K: 'static, T, F>(&self, f: F) -> Async<T>
114    where
115        T: Clone + Send + 'static,
116        F: FnOnce() -> Result<T, String> + Send + 'static,
117    {
118        let key = TypeId::of::<K>();
119        let mut keyed_states = self.keyed_states.borrow_mut();
120
121        let handle = if let Some(any) = keyed_states.get(&key) {
122            any.downcast_ref::<AsyncHandle<T>>()
123                .expect("Async type mismatch for keyed async state")
124                .clone()
125        } else {
126            let handle = AsyncHandle::new();
127            keyed_states.insert(key, Rc::new(handle.clone()));
128            handle
129        };
130
131        drop(keyed_states);
132
133        // Start the async operation if not already started
134        handle.start(f);
135
136        // Poll and return current state
137        handle.poll()
138    }
139
140    /// Get or create stream state by TypeId key (order-independent).
141    pub fn use_stream_keyed<K: 'static, T, F, I>(&self, stream_fn: F) -> StreamHandle<T>
142    where
143        T: Clone + Default + Send + 'static,
144        F: FnOnce() -> I + Send + 'static,
145        I: Iterator<Item = T> + Send + 'static,
146    {
147        let key = TypeId::of::<K>();
148        let mut keyed_states = self.keyed_states.borrow_mut();
149
150        let handle = if let Some(any) = keyed_states.get(&key) {
151            any.downcast_ref::<StreamHandle<T>>()
152                .expect("Stream type mismatch for keyed stream state")
153                .clone()
154        } else {
155            let handle = StreamHandle::new();
156            keyed_states.insert(key, Rc::new(handle.clone()));
157            handle
158        };
159
160        drop(keyed_states);
161
162        // Start the stream if not already started
163        handle.start(stream_fn);
164
165        // Poll for updates
166        handle.poll(|acc, item| *acc = item);
167
168        handle
169    }
170
171    /// Get or create text stream state by TypeId key (order-independent).
172    pub fn use_text_stream_keyed<K: 'static, F, I>(&self, stream_fn: F) -> TextStreamHandle
173    where
174        F: FnOnce() -> I + Send + 'static,
175        I: Iterator<Item = String> + Send + 'static,
176    {
177        self.use_text_stream_with_restart_keyed::<K, F, I>(false, stream_fn)
178    }
179
180    /// Get or create text stream state with restart support, by TypeId key (order-independent).
181    pub fn use_text_stream_with_restart_keyed<K: 'static, F, I>(
182        &self,
183        restart: bool,
184        stream_fn: F,
185    ) -> TextStreamHandle
186    where
187        F: FnOnce() -> I + Send + 'static,
188        I: Iterator<Item = String> + Send + 'static,
189    {
190        let key = TypeId::of::<K>();
191        let mut keyed_states = self.keyed_states.borrow_mut();
192
193        let handle = if let Some(any) = keyed_states.get(&key) {
194            any.downcast_ref::<TextStreamHandle>()
195                .expect("TextStream type mismatch for keyed text stream state")
196                .clone()
197        } else {
198            let handle = TextStreamHandle::new();
199            keyed_states.insert(key, Rc::new(handle.clone()));
200            handle
201        };
202
203        drop(keyed_states);
204
205        // Reset if requested (for starting a new stream)
206        if restart {
207            handle.reset();
208        }
209
210        // Start the stream if not already started
211        handle.start(stream_fn);
212
213        // Poll and accumulate text
214        handle.poll_text();
215
216        handle
217    }
218
219    /// Get or create a terminal handle by TypeId key (order-independent).
220    pub fn use_terminal_keyed<K: 'static>(&self) -> crate::terminal_state::TerminalHandle {
221        let key = TypeId::of::<K>();
222        let mut keyed_states = self.keyed_states.borrow_mut();
223
224        if let Some(any) = keyed_states.get(&key) {
225            any.downcast_ref::<crate::terminal_state::TerminalHandle>()
226                .expect("TerminalHandle type mismatch for keyed terminal state")
227                .clone()
228        } else {
229            let handle = crate::terminal_state::TerminalHandle::new(24, 80);
230            keyed_states.insert(key, Rc::new(handle.clone()));
231            handle
232        }
233    }
234
235    // ========== Reducer (order-independent) ==========
236
237    /// Get or create a reducer by TypeId key.
238    ///
239    /// Returns `(state, dispatch)` where `dispatch` sends an action through
240    /// the reducer to produce a new state.
241    pub fn use_reducer_keyed<K: 'static, S: Clone + 'static, A: 'static>(
242        &self,
243        initial: S,
244        reducer: impl Fn(S, A) -> S + 'static,
245    ) -> (State<S>, Rc<dyn Fn(A)>) {
246        let state = self.use_state_keyed::<K, S>(|| initial);
247        let state_for_dispatch = state.clone();
248        let reducer = Rc::new(reducer);
249        let dispatch: Rc<dyn Fn(A)> = Rc::new(move |action: A| {
250            let current = state_for_dispatch.get();
251            let next = reducer(current, action);
252            state_for_dispatch.set(next);
253        });
254        (state, dispatch)
255    }
256
257    // ========== Channels / Ports (order-independent) ==========
258
259    /// Get or create a typed inbound channel by TypeId key.
260    pub fn use_channel_keyed<K: 'static, T: 'static>(&self) -> ChannelHandle<T> {
261        let key = TypeId::of::<K>();
262        let mut keyed_states = self.keyed_states.borrow_mut();
263
264        if let Some(any) = keyed_states.get(&key) {
265            any.downcast_ref::<ChannelHandle<T>>()
266                .expect("Channel type mismatch for keyed channel")
267                .clone()
268        } else {
269            let handle = ChannelHandle::new(Arc::clone(&self.wake_flag));
270            keyed_states.insert(key, Rc::new(handle.clone()));
271            // Register for run-loop draining
272            self.channels.borrow_mut().push(Rc::new(handle.clone()) as Rc<dyn ChannelDrain>);
273            handle
274        }
275    }
276
277    /// Get or create a bidirectional port by TypeId key.
278    pub fn use_port_keyed<K: 'static, In: 'static, Out: 'static>(&self) -> PortHandle<In, Out> {
279        let key = TypeId::of::<K>();
280        let mut keyed_states = self.keyed_states.borrow_mut();
281
282        if let Some(any) = keyed_states.get(&key) {
283            any.downcast_ref::<PortHandle<In, Out>>()
284                .expect("Port type mismatch for keyed port")
285                .clone()
286        } else {
287            let handle = PortHandle::new(Arc::clone(&self.wake_flag));
288            keyed_states.insert(key, Rc::new(handle.clone()));
289            // Register the inbound channel for run-loop draining
290            self.channels.borrow_mut().push(Rc::new(handle.rx.clone()) as Rc<dyn ChannelDrain>);
291            handle
292        }
293    }
294
295    /// Create or retrieve a periodic interval by TypeId key.
296    ///
297    /// Spawns a timer thread that fires at the given `duration`. The `callback`
298    /// is called on the main thread each frame that one or more ticks arrived.
299    pub fn use_interval_keyed<K: 'static>(
300        &self,
301        duration: std::time::Duration,
302        callback: impl Fn() + 'static,
303    ) {
304        let key = TypeId::of::<K>();
305        let keyed_states = self.keyed_states.borrow();
306
307        if keyed_states.contains_key(&key) {
308            // Already created — nothing to do. The timer thread is running
309            // and the drain callback is registered.
310            return;
311        }
312        drop(keyed_states);
313
314        let handle = IntervalHandle::new(duration, Rc::new(callback), Arc::clone(&self.wake_flag));
315        self.keyed_states
316            .borrow_mut()
317            .insert(key, Rc::new(handle.clone()));
318        self.channels
319            .borrow_mut()
320            .push(Rc::new(handle) as Rc<dyn ChannelDrain>);
321    }
322
323    /// Drain all registered channels (called by the run loop each frame).
324    pub fn drain_channels(&self) {
325        let channels = self.channels.borrow();
326        for ch in channels.iter() {
327            ch.drain();
328        }
329    }
330
331    /// Clear all channel frame buffers (called at start of each frame).
332    pub fn clear_channels(&self) {
333        let channels = self.channels.borrow();
334        for ch in channels.iter() {
335            ch.clear();
336        }
337    }
338
339    /// Check if any channel has messages this frame.
340    pub fn has_channel_data(&self) -> bool {
341        let channels = self.channels.borrow();
342        channels.iter().any(|ch| ch.has_messages())
343    }
344
345    // ========== Keyed Effects (order-independent) ==========
346
347    /// Schedule a keyed effect to run only once (on first render).
348    /// Order-independent - safe to use in conditionals.
349    pub fn use_effect_once_keyed<K: 'static, F, C>(&self, effect_fn: F)
350    where
351        F: FnOnce() -> C + 'static,
352        C: FnOnce() + 'static,
353    {
354        let key = TypeId::of::<K>();
355        let keyed_effects = self.keyed_effects.borrow();
356        let should_run = !keyed_effects.contains_key(&key)
357            || !keyed_effects.get(&key).map(|e| e.initialized).unwrap_or(false);
358        drop(keyed_effects);
359
360        if should_run {
361            self.pending_keyed_effects
362                .borrow_mut()
363                .push(PendingKeyedEffect {
364                    key,
365                    effect_fn: Box::new(move || {
366                        let cleanup = effect_fn();
367                        Some(Box::new(cleanup) as Box<dyn FnOnce()>)
368                    }),
369                    new_deps: None,
370                });
371        }
372    }
373
374    /// Schedule a keyed effect to run when dependencies change.
375    /// Order-independent - safe to use in conditionals.
376    pub fn use_effect_keyed<K: 'static, D, F, C>(&self, deps: D, effect_fn: F)
377    where
378        D: PartialEq + Clone + 'static,
379        F: FnOnce(&D) -> C + 'static,
380        C: FnOnce() + 'static,
381    {
382        let key = TypeId::of::<K>();
383        let keyed_effects = self.keyed_effects.borrow();
384        let should_run = match keyed_effects.get(&key) {
385            None => true, // First render, always run
386            Some(effect_state) => {
387                match &effect_state.last_deps {
388                    Some(last_deps) => {
389                        match last_deps.downcast_ref::<D>() {
390                            Some(last) => *last != deps,
391                            None => true, // Type mismatch, re-run
392                        }
393                    }
394                    None => true,
395                }
396            }
397        };
398        drop(keyed_effects);
399
400        if should_run {
401            let deps_for_effect = deps.clone();
402            let deps_to_store = deps;
403            self.pending_keyed_effects
404                .borrow_mut()
405                .push(PendingKeyedEffect {
406                    key,
407                    effect_fn: Box::new(move || {
408                        let cleanup = effect_fn(&deps_for_effect);
409                        Some(Box::new(cleanup) as Box<dyn FnOnce()>)
410                    }),
411                    new_deps: Some(Box::new(deps_to_store)),
412                });
413        }
414    }
415
416    /// Run all pending effects (called after render).
417    /// Returns true if any effects actually ran (state may have changed).
418    ///
419    /// # Panics
420    /// Panics if effects run more than MAX_EFFECT_RUNS_PER_WINDOW times within
421    /// EFFECT_WINDOW_FRAMES frames, indicating a likely infinite loop.
422    pub fn flush_effects(&self) -> bool {
423        let pending_keyed: Vec<_> = self.pending_keyed_effects.borrow_mut().drain(..).collect();
424        let ran_any = !pending_keyed.is_empty();
425
426        for pending_effect in pending_keyed {
427            // Cycle detection: check if we've exceeded the threshold
428            let run_count = {
429                let mut count = self.effect_run_count.borrow_mut();
430                *count += 1;
431                *count
432            };
433
434            if run_count > MAX_EFFECT_RUNS_PER_WINDOW {
435                panic!(
436                    "\n\
437                    ┌─ Telex Effect Cycle Detected ─────────────────────────────────┐\n\
438                    │                                                               │\n\
439                    │  An effect has run {} times in {} frames.             │\n\
440                    │  This usually means an effect is updating state that          │\n\
441                    │  triggers itself to run again (infinite loop).                │\n\
442                    │                                                               │\n\
443                    │  Common causes:                                               │\n\
444                    │    • effect! updating state without dependencies              │\n\
445                    │    • effect! updating its own dependency                      │\n\
446                    │                                                               │\n\
447                    │  Fix: Make sure effects don't write to their own deps.        │\n\
448                    │  Effects should flow outward (to external systems) or         │\n\
449                    │  sideways (to different state), not back to their triggers.   │\n\
450                    │                                                               │\n\
451                    └───────────────────────────────────────────────────────────────┘",
452                    run_count,
453                    EFFECT_WINDOW_FRAMES
454                );
455            }
456
457            // Run previous cleanup if this effect existed
458            {
459                let mut keyed_effects = self.keyed_effects.borrow_mut();
460                if let Some(effect_state) = keyed_effects.get_mut(&pending_effect.key) {
461                    if let Some(cleanup) = effect_state.cleanup.take() {
462                        drop(keyed_effects); // Release borrow before running cleanup
463                        cleanup();
464                    }
465                }
466            }
467
468            // Run effect, get cleanup
469            let cleanup = (pending_effect.effect_fn)();
470
471            // Store cleanup and mark initialized
472            let mut keyed_effects = self.keyed_effects.borrow_mut();
473            let effect_state = keyed_effects
474                .entry(pending_effect.key)
475                .or_insert_with(|| EffectState {
476                    cleanup: None,
477                    last_deps: None,
478                    initialized: false,
479                });
480            effect_state.cleanup = cleanup;
481            effect_state.initialized = true;
482            if let Some(new_deps) = pending_effect.new_deps {
483                effect_state.last_deps = Some(new_deps);
484            }
485        }
486
487        ran_any
488    }
489
490    /// Called once per frame to decay the effect run counter.
491    /// This implements a sliding window for cycle detection.
492    pub fn decay_effect_counter(&self) {
493        let mut frames = self.frames_since_decay.borrow_mut();
494        *frames += 1;
495
496        if *frames >= EFFECT_WINDOW_FRAMES {
497            // Reset the window
498            *frames = 0;
499            *self.effect_run_count.borrow_mut() = 0;
500        }
501    }
502
503    /// Run all cleanup functions (called on app exit).
504    pub fn cleanup_all_effects(&self) {
505        let mut keyed_effects = self.keyed_effects.borrow_mut();
506        for effect in keyed_effects.values_mut() {
507            if let Some(cleanup) = effect.cleanup.take() {
508                cleanup();
509            }
510        }
511    }
512}
513
514/// Context passed to components during rendering.
515///
516/// Provides access to hooks like `state!`, `effect!`, `stream!`, etc.
517#[derive(Clone)]
518pub struct Scope {
519    storage: Rc<StateStorage>,
520    commands: Option<Rc<CommandRegistry>>,
521    context: Rc<ContextStorage>,
522    /// Component identity for future memoization support.
523    /// Set by the view! macro when rendering child components.
524    component_id: Option<TypeId>,
525}
526
527impl Scope {
528    /// Create a new scope with fresh state storage.
529    pub fn new() -> Self {
530        Self {
531            storage: Rc::new(StateStorage::new()),
532            commands: None,
533            context: Rc::new(ContextStorage::new()),
534            component_id: None,
535        }
536    }
537
538    /// Create a scope with existing storage (for re-renders).
539    pub fn with_storage(storage: Rc<StateStorage>) -> Self {
540        Self {
541            storage,
542            commands: None,
543            context: Rc::new(ContextStorage::new()),
544            component_id: None,
545        }
546    }
547
548    /// Create a scope with existing storage and command registry.
549    pub fn with_storage_and_commands(
550        storage: Rc<StateStorage>,
551        commands: Rc<CommandRegistry>,
552    ) -> Self {
553        Self {
554            storage,
555            commands: Some(commands),
556            context: Rc::new(ContextStorage::new()),
557            component_id: None,
558        }
559    }
560
561    /// Create a scope with all dependencies.
562    pub fn with_all(
563        storage: Rc<StateStorage>,
564        commands: Rc<CommandRegistry>,
565        context: Rc<ContextStorage>,
566    ) -> Self {
567        Self {
568            storage,
569            commands: Some(commands),
570            context,
571            component_id: None,
572        }
573    }
574
575    /// Get the component identity (if set by the view! macro).
576    pub fn component_id(&self) -> Option<TypeId> {
577        self.component_id
578    }
579
580    /// Set the component identity (used by the view! macro).
581    pub fn with_component_id(mut self, id: TypeId) -> Self {
582        self.component_id = Some(id);
583        self
584    }
585
586    /// Get the underlying storage for persistence.
587    pub fn storage(&self) -> Rc<StateStorage> {
588        Rc::clone(&self.storage)
589    }
590
591    /// Create keyed state that persists across re-renders (order-independent).
592    ///
593    /// The type K acts as the key - same K always returns the same state.
594    /// Prefer the `state!` macro which auto-generates the key.
595    ///
596    /// # Example
597    /// ```rust,ignore
598    /// let count = state!(cx, || 0);
599    /// ```
600    pub fn use_state_keyed<K: 'static, T: 'static>(&self, init: impl FnOnce() -> T) -> State<T> {
601        self.storage.use_state_keyed::<K, T>(init)
602    }
603
604    /// Load keyed async data (order-independent).
605    /// Prefer the `async_data!` macro which auto-generates the key.
606    ///
607    /// # Example
608    /// ```rust,ignore
609    /// let data = async_data!(cx, || {
610    ///     Ok(fetch_data())
611    /// });
612    /// ```
613    pub fn use_async_keyed<K: 'static, T, F>(&self, f: F) -> Async<T>
614    where
615        T: Clone + Send + 'static,
616        F: FnOnce() -> Result<T, String> + Send + 'static,
617    {
618        self.storage.use_async_keyed::<K, T, F>(f)
619    }
620
621    /// Stream data with keyed state (order-independent).
622    /// Prefer the `stream!` macro which auto-generates the key.
623    ///
624    /// # Example
625    /// ```rust,ignore
626    /// let elapsed = stream!(cx, || {
627    ///     (0..).inspect(|_| std::thread::sleep(Duration::from_secs(1)))
628    /// });
629    /// ```
630    pub fn use_stream_keyed<K: 'static, T, F, I>(&self, stream_fn: F) -> StreamHandle<T>
631    where
632        T: Clone + Default + Send + 'static,
633        F: FnOnce() -> I + Send + 'static,
634        I: Iterator<Item = T> + Send + 'static,
635    {
636        self.storage.use_stream_keyed::<K, T, F, I>(stream_fn)
637    }
638
639    /// Stream text with keyed state (order-independent).
640    /// Prefer the `text_stream!` macro which auto-generates the key.
641    ///
642    /// # Example
643    /// ```rust,ignore
644    /// let logs = text_stream!(cx, || {
645    ///     generate_log_entries()
646    /// });
647    /// ```
648    pub fn use_text_stream_keyed<K: 'static, F, I>(&self, stream_fn: F) -> TextStreamHandle
649    where
650        F: FnOnce() -> I + Send + 'static,
651        I: Iterator<Item = String> + Send + 'static,
652    {
653        self.storage.use_text_stream_keyed::<K, F, I>(stream_fn)
654    }
655
656    /// Stream text with restart support, keyed state (order-independent).
657    /// Prefer the `text_stream_with_restart!` macro which auto-generates the key.
658    pub fn use_text_stream_with_restart_keyed<K: 'static, F, I>(
659        &self,
660        restart: bool,
661        stream_fn: F,
662    ) -> TextStreamHandle
663    where
664        F: FnOnce() -> I + Send + 'static,
665        I: Iterator<Item = String> + Send + 'static,
666    {
667        self.storage
668            .use_text_stream_with_restart_keyed::<K, F, I>(restart, stream_fn)
669    }
670
671    /// Create or get a keyed terminal handle (order-independent).
672    /// Prefer the `terminal!` macro which auto-generates the key.
673    ///
674    /// # Example
675    /// ```rust,ignore
676    /// let terminal = terminal!(cx);
677    /// ```
678    pub fn use_terminal_keyed<K: 'static>(&self) -> crate::terminal_state::TerminalHandle {
679        self.storage.use_terminal_keyed::<K>()
680    }
681
682    /// Create a reducer (order-independent).
683    /// Prefer the `reducer!` macro which auto-generates the key.
684    ///
685    /// Returns `(state, dispatch)` where `dispatch` sends actions through
686    /// the reducer function to produce new state.
687    ///
688    /// # Example
689    /// ```rust,ignore
690    /// let (state, dispatch) = reducer!(cx, AppState::Idle, |state, action| {
691    ///     match (state, action) {
692    ///         (_, Action::Reset) => AppState::Idle,
693    ///         (s, _) => s,
694    ///     }
695    /// });
696    /// ```
697    pub fn use_reducer_keyed<K: 'static, S: Clone + 'static, A: 'static>(
698        &self,
699        initial: S,
700        reducer: impl Fn(S, A) -> S + 'static,
701    ) -> (State<S>, Rc<dyn Fn(A)>) {
702        self.storage.use_reducer_keyed::<K, S, A>(initial, reducer)
703    }
704
705    /// Create a typed inbound channel (order-independent).
706    /// Prefer the `channel!` macro which auto-generates the key.
707    ///
708    /// # Example
709    /// ```rust,ignore
710    /// let ch = channel!(cx, String);
711    /// let tx = ch.tx();
712    /// // hand tx to an external thread
713    /// for msg in ch.get() { /* ... */ }
714    /// ```
715    pub fn use_channel_keyed<K: 'static, T: 'static>(&self) -> ChannelHandle<T> {
716        self.storage.use_channel_keyed::<K, T>()
717    }
718
719    /// Create a bidirectional port (order-independent).
720    /// Prefer the `port!` macro which auto-generates the key.
721    ///
722    /// # Example
723    /// ```rust,ignore
724    /// let midi = port!(cx, MidiIn, MidiOut);
725    /// let inbound_tx = midi.rx.tx();  // external sends MidiIn here
726    /// let outbound_tx = midi.tx();    // component sends MidiOut here
727    /// for msg in midi.rx.get() { /* ... */ }
728    /// ```
729    pub fn use_port_keyed<K: 'static, In: 'static, Out: 'static>(&self) -> PortHandle<In, Out> {
730        self.storage.use_port_keyed::<K, In, Out>()
731    }
732
733    /// Create a periodic interval (order-independent).
734    /// Prefer the `interval!` macro which auto-generates the key.
735    ///
736    /// The callback runs on the main thread each frame that the timer fired.
737    /// Internally uses a channel + timer thread.
738    ///
739    /// # Example
740    /// ```rust,ignore
741    /// let count = state!(cx, || 0u64);
742    /// let c = count.clone();
743    /// interval!(cx, Duration::from_secs(1), move || {
744    ///     c.update(|n| *n += 1);
745    /// });
746    /// ```
747    pub fn use_interval_keyed<K: 'static>(
748        &self,
749        duration: std::time::Duration,
750        callback: impl Fn() + 'static,
751    ) {
752        self.storage.use_interval_keyed::<K>(duration, callback)
753    }
754
755    /// Register a keyboard command/shortcut.
756    ///
757    /// The callback will be invoked when the key combination is pressed.
758    /// Commands registered later in the render tree take precedence.
759    ///
760    /// # Example
761    /// ```rust,ignore
762    /// fn App(cx: Scope) -> View {
763    ///     let count = state!(cx, || 0);
764    ///     let c = count.clone();
765    ///
766    ///     // Ctrl+R to reset counter
767    ///     cx.use_command(KeyBinding::ctrl('r'), move || {
768    ///         c.set(0);
769    ///     });
770    ///
771    ///     view! { <Text>{format!("Count: {}", count.get())}</Text> }
772    /// }
773    /// ```
774    pub fn use_command<F>(&self, binding: KeyBinding, callback: F)
775    where
776        F: Fn() + 'static,
777    {
778        if let Some(ref commands) = self.commands {
779            commands.register(binding, Rc::new(callback));
780        }
781    }
782
783    /// Provide a value in the context for child components to access.
784    ///
785    /// Values are stored by type, so each type can only have one value.
786    /// Providing a value of a type that already exists will replace it.
787    ///
788    /// # Example
789    /// ```rust,ignore
790    /// #[derive(Clone)]
791    /// struct UserState {
792    ///     name: String,
793    ///     logged_in: bool,
794    /// }
795    ///
796    /// fn App(cx: Scope) -> View {
797    ///     cx.provide_context(UserState {
798    ///         name: "Alice".to_string(),
799    ///         logged_in: true,
800    ///     });
801    ///
802    ///     view! { <Header /> }
803    /// }
804    /// ```
805    pub fn provide_context<T: Clone + 'static>(&self, value: T) {
806        self.context.provide(value);
807    }
808
809    /// Get a value from the context.
810    ///
811    /// Returns None if no value of this type has been provided by a parent.
812    ///
813    /// # Example
814    /// ```rust,ignore
815    /// fn Header(cx: Scope) -> View {
816    ///     let user = cx.use_context::<UserState>();
817    ///
818    ///     match user {
819    ///         Some(u) => view! { <Text>{format!("Hello, {}", u.name)}</Text> },
820    ///         None => view! { <Text>"Not logged in"</Text> },
821    ///     }
822    /// }
823    /// ```
824    pub fn use_context<T: Clone + 'static>(&self) -> Option<T> {
825        self.context.get::<T>()
826    }
827
828    /// Get the context storage (for passing to child scopes).
829    pub fn context(&self) -> Rc<ContextStorage> {
830        Rc::clone(&self.context)
831    }
832
833    // ========== Keyed Effects (order-independent) ==========
834    //
835    // Use the effect!() and effect_once!() macros for convenient access.
836
837    /// Run a keyed side effect only once (on first render).
838    /// Order-independent - safe to use in conditionals.
839    ///
840    /// Prefer the `effect_once!` macro which auto-generates the key:
841    /// ```rust,ignore
842    /// effect_once!(cx, || {
843    ///     println!("initialized");
844    ///     || { println!("cleanup"); }
845    /// });
846    /// ```
847    pub fn use_effect_once_keyed<K: 'static, F, C>(&self, effect_fn: F)
848    where
849        F: FnOnce() -> C + 'static,
850        C: FnOnce() + 'static,
851    {
852        self.storage.use_effect_once_keyed::<K, F, C>(effect_fn)
853    }
854
855    /// Run a keyed side effect when dependencies change.
856    /// Order-independent - safe to use in conditionals.
857    ///
858    /// Prefer the `effect!` macro which auto-generates the key:
859    /// ```rust,ignore
860    /// effect!(cx, count.get(), |&c| {
861    ///     println!("count changed to {}", c);
862    ///     || {}  // cleanup
863    /// });
864    /// ```
865    pub fn use_effect_keyed<K: 'static, D, F, C>(&self, deps: D, effect_fn: F)
866    where
867        D: PartialEq + Clone + 'static,
868        F: FnOnce(&D) -> C + 'static,
869        C: FnOnce() + 'static,
870    {
871        self.storage.use_effect_keyed::<K, D, F, C>(deps, effect_fn)
872    }
873}
874
875impl Default for Scope {
876    fn default() -> Self {
877        Self::new()
878    }
879}