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