Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_agent/worker/
provider.rs

1use std::any::type_name;
2use std::cell::RefCell;
3use std::fmt;
4use std::rc::Rc;
5
6use gloo_worker::{Bincode, Codec, Spawnable};
7use serde::{Deserialize, Serialize};
8use yew::prelude::*;
9
10use super::{Worker, WorkerBridge};
11use crate::reach::Reach;
12use crate::utils::get_next_id;
13
14/// Properties for [WorkerProvider].
15#[derive(Debug, Properties, PartialEq, Clone)]
16pub struct WorkerProviderProps {
17    /// The path to an agent.
18    pub path: AttrValue,
19
20    /// The reachability of an agent.
21    ///
22    /// Default: [`Public`](Reach::Public).
23    #[prop_or(Reach::Public)]
24    pub reach: Reach,
25
26    /// Whether the agent should be created
27    /// with type `Module`.
28    #[prop_or(false)]
29    pub module: bool,
30
31    /// Lazily spawn the agent.
32    ///
33    /// The agent will be spawned when the first time a hook requests a bridge.
34    ///
35    /// Does not affect private agents.
36    ///
37    /// Default: `true`
38    #[prop_or(true)]
39    pub lazy: bool,
40
41    /// Children of the provider.
42    #[prop_or_default]
43    pub children: Html,
44}
45
46pub(crate) struct WorkerProviderState<W>
47where
48    W: Worker,
49{
50    id: usize,
51    spawn_bridge_fn: Rc<dyn Fn() -> WorkerBridge<W>>,
52    reach: Reach,
53    held_bridge: RefCell<Option<Rc<WorkerBridge<W>>>>,
54}
55
56impl<W> fmt::Debug for WorkerProviderState<W>
57where
58    W: Worker,
59{
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.debug_struct(type_name::<Self>()).finish_non_exhaustive()
62    }
63}
64
65impl<W> WorkerProviderState<W>
66where
67    W: Worker<Output: 'static>,
68{
69    fn get_held_bridge(&self) -> Rc<WorkerBridge<W>> {
70        let mut held_bridge = self.held_bridge.borrow_mut();
71
72        match held_bridge.as_mut() {
73            Some(m) => m.clone(),
74            None => {
75                let bridge = Rc::new((self.spawn_bridge_fn)());
76                *held_bridge = Some(bridge.clone());
77                bridge
78            }
79        }
80    }
81
82    /// Creates a bridge, uses "fork" for public agents.
83    pub fn create_bridge(&self, cb: Callback<W::Output>) -> WorkerBridge<W> {
84        match self.reach {
85            Reach::Public => {
86                let held_bridge = self.get_held_bridge();
87                held_bridge.fork(Some(move |m| cb.emit(m)))
88            }
89            Reach::Private => (self.spawn_bridge_fn)(),
90        }
91    }
92}
93
94impl<W> PartialEq for WorkerProviderState<W>
95where
96    W: Worker,
97{
98    fn eq(&self, rhs: &Self) -> bool {
99        self.id == rhs.id
100    }
101}
102
103/// The Worker Agent Provider.
104///
105/// This component provides its children access to a worker agent.
106#[component]
107pub fn WorkerProvider<W, C = Bincode>(props: &WorkerProviderProps) -> Html
108where
109    W: Worker<
110            Input: Serialize + for<'de> Deserialize<'de> + 'static,
111            Output: Serialize + for<'de> Deserialize<'de> + 'static,
112        > + 'static,
113    C: Codec + 'static,
114{
115    let WorkerProviderProps {
116        children,
117        path,
118        lazy,
119        module,
120        reach,
121    } = props.clone();
122
123    // Creates a spawning function so Codec is can be erased from contexts.
124    let spawn_bridge_fn: Rc<dyn Fn() -> WorkerBridge<W>> = {
125        let path = path.clone();
126        Rc::new(move || W::spawner().as_module(module).encoding::<C>().spawn(&path))
127    };
128
129    let state = {
130        use_memo((path, lazy, reach), move |(_path, lazy, reach)| {
131            let state = WorkerProviderState::<W> {
132                id: get_next_id(),
133                spawn_bridge_fn,
134                reach: *reach,
135                held_bridge: Default::default(),
136            };
137
138            if *reach == Reach::Public && !*lazy {
139                state.get_held_bridge();
140            }
141            state
142        })
143    };
144
145    html! {
146        <ContextProvider<Rc<WorkerProviderState<W>>> context={state.clone()}>
147            {children}
148        </ContextProvider<Rc<WorkerProviderState<W>>>>
149    }
150}