workflow_egui/runtime/
runtime.rs1use super::*;
2use crate::imports::*;
3use workflow_wasm::callback::CallbackMap;
4
5pub use payload::Payload;
6use repaint::RepaintService;
7pub use service::{Service, ServiceResult};
8
9pub struct Inner {
12 egui_ctx: egui::Context,
13 events: ApplicationEventsChannel,
14 services: RwLock<AHashMap<String, Arc<dyn Service>>>,
15 repaint_service: Arc<RepaintService>,
16 is_running: Arc<AtomicBool>,
17 start_time: Instant,
18 #[allow(dead_code)]
19 callbacks: CallbackMap,
20}
21
22#[derive(Clone)]
25pub struct Runtime {
26 inner: Arc<Inner>,
27}
28
29impl Runtime {
30 pub fn new(egui_ctx: &egui::Context, events: Option<ApplicationEventsChannel>) -> Self {
34 let events = events.unwrap_or_else(channel::Channel::unbounded);
35
36 let repaint_service = Arc::new(RepaintService::default());
37
38 let runtime = Self {
39 inner: Arc::new(Inner {
40 services: Default::default(),
41 events,
42 repaint_service: repaint_service.clone(),
43 egui_ctx: egui_ctx.clone(),
44 is_running: Arc::new(AtomicBool::new(false)),
45 start_time: Instant::now(),
46 callbacks: Default::default(),
47 }),
48 };
49 register_global(Some(runtime.clone()));
50
51 runtime.bind(repaint_service);
52
53 #[cfg(target_arch = "wasm32")]
54 runtime.register_visibility_handler();
55
56 runtime
57 }
58
59 pub fn bind(&self, service: Arc<dyn Service>) {
62 self.inner
63 .services
64 .write()
65 .unwrap()
66 .insert(service.name().to_string(), service.clone());
67 let runtime = self.clone();
68 spawn(async move { service.spawn(runtime).await });
69 }
70
71 pub fn uptime(&self) -> Duration {
73 self.inner.start_time.elapsed()
74 }
75
76 pub fn start_services(&self) {
78 let services = self.services();
79 for service in services {
80 let runtime = self.clone();
81 spawn(async move { service.spawn(runtime).await });
83 }
84 }
85
86 pub fn services(&self) -> Vec<Arc<dyn Service>> {
88 self.inner
89 .services
90 .read()
91 .unwrap()
92 .values()
93 .cloned()
94 .collect()
95 }
96
97 pub fn stop_services(&self) {
99 self.services()
100 .into_iter()
101 .for_each(|service| service.terminate());
102 }
103
104 pub async fn join_services(&self) {
106 let futures = self
114 .services()
115 .into_iter()
116 .map(|service| service.join())
117 .collect::<Vec<_>>();
118 join_all(futures).await;
119 }
120
121 pub fn drop(&self) {
123 register_global(None);
124 }
125
126 pub fn start(&self) {
129 self.inner.is_running.store(true, Ordering::SeqCst);
130 self.start_services();
131 }
132
133 pub async fn shutdown(&self) {
135 if self.inner.is_running.load(Ordering::SeqCst) {
136 self.inner.is_running.store(false, Ordering::SeqCst);
137 self.stop_services();
138 self.join_services().await;
139 register_global(None);
140 }
141 }
142
143 pub fn repaint_service(&self) -> &Arc<RepaintService> {
145 &self.inner.repaint_service
146 }
147
148 pub fn events(&self) -> &ApplicationEventsChannel {
150 &self.inner.events
151 }
152
153 pub async fn send<T>(&self, msg: T) -> Result<()>
155 where
156 T: Any + Send + Sync + 'static,
157 {
158 self.inner.events.send(RuntimeEvent::new(msg)).await?;
159 Ok(())
160 }
161
162 pub async fn send_runtime_event(&self, msg: RuntimeEvent) -> Result<()> {
164 self.inner.events.send(msg).await?;
165 Ok(())
166 }
167
168 pub fn try_send<T>(&self, msg: T) -> Result<()>
170 where
171 T: Any + Send + Sync + 'static,
172 {
173 self.inner.events.sender.try_send(RuntimeEvent::new(msg))?;
174 Ok(())
175 }
176
177 pub fn try_send_runtime_event(&self, msg: RuntimeEvent) -> Result<()> {
179 self.inner.events.sender.try_send(msg)?;
180 Ok(())
181 }
182
183 pub fn spawn_task<F>(&self, task: F)
186 where
187 F: Future<Output = Result<()>> + Send + 'static,
188 {
189 let sender = self.events().sender.clone();
190 workflow_core::task::spawn(async move {
191 if let Err(err) = task.await {
192 sender
193 .send(RuntimeEvent::Error(err.to_string()))
194 .await
195 .unwrap();
196 }
197 });
198 }
199
200 pub fn spawn_task_with_result<R, F>(
204 &self,
205 payload: &Payload<std::result::Result<R, Error>>,
206 task: F,
207 ) where
208 R: Clone + Send + 'static,
209 F: Future<Output = Result<R>> + Send + 'static,
210 {
211 let payload = (*payload).clone();
212 payload.mark_pending();
213 workflow_core::task::spawn(async move {
214 let result = task.await;
215 match result {
216 Ok(r) => payload.store(Ok(r)),
217 Err(err) => {
218 payload.store(Err(err));
219 }
220 }
221 });
222 }
223
224 pub fn egui_ctx(&self) -> &egui::Context {
226 &self.inner.egui_ctx
227 }
228
229 pub fn request_repaint(&self) {
231 self.repaint_service().trigger();
232 }
233
234 #[cfg(target_arch = "wasm32")]
235 pub fn register_visibility_handler(&self) {
236 use workflow_dom::utils::*;
237 use workflow_wasm::callback::*;
238
239 let sender = self.events().sender.clone();
240 let callback = callback!(move || {
241 let visibility_state = document().visibility_state();
242 sender
243 .try_send(RuntimeEvent::VisibilityState(visibility_state))
244 .unwrap();
245 runtime().egui_ctx().request_repaint();
246 });
247
248 document().set_onvisibilitychange(Some(callback.as_ref()));
249 self.inner.callbacks.retain(callback).unwrap();
250 }
251}
252
253static RUNTIME: Mutex<Option<Runtime>> = Mutex::new(None);
254
255pub fn runtime() -> Runtime {
259 if let Some(runtime) = RUNTIME.lock().unwrap().as_ref() {
260 runtime.clone()
261 } else {
262 panic!("Error: `Runtime` is not initialized")
263 }
264}
265
266pub fn try_runtime() -> Option<Runtime> {
269 RUNTIME.lock().unwrap().clone()
270}
271
272fn register_global(runtime: Option<Runtime>) {
273 match runtime {
274 Some(runtime) => {
275 let mut global = RUNTIME.lock().unwrap();
276 if global.is_some() {
277 panic!("runtime already initialized");
278 }
279 global.replace(runtime);
280 }
281 None => {
282 RUNTIME.lock().unwrap().take();
283 }
284 };
285}
286
287pub fn spawn<F>(task: F)
290where
291 F: Future<Output = Result<()>> + Send + 'static,
292{
293 runtime().spawn_task(task);
294}
295
296pub fn spawn_with_result<R, F>(payload: &Payload<std::result::Result<R, Error>>, task: F)
301where
302 R: Clone + Send + 'static,
303 F: Future<Output = Result<R>> + Send + 'static,
304{
305 runtime().spawn_task_with_result(payload, task);
306}
307
308#[cfg(not(target_arch = "wasm32"))]
312impl Runtime {
313 pub fn halt() {
316 if let Some(runtime) = try_runtime() {
317 runtime.try_send(RuntimeEvent::Exit).ok();
318 let handle = tokio::spawn(async move { runtime.shutdown().await });
321
322 while !handle.is_finished() {
323 std::thread::sleep(std::time::Duration::from_millis(50));
324 }
325 }
326 }
327
328 pub fn abort() {
333 const TIMEOUT: u128 = 5000;
334 let flag = Arc::new(AtomicBool::new(false));
335 let flag_ = flag.clone();
336 let thread = std::thread::Builder::new()
337 .name("halt".to_string())
338 .spawn(move || {
339 let start = std::time::Instant::now();
340 while !flag_.load(Ordering::SeqCst) {
341 if start.elapsed().as_millis() > TIMEOUT {
342 println!("halting...");
343 std::process::exit(1);
344 }
345 std::thread::sleep(std::time::Duration::from_millis(50));
346 }
347 })
348 .ok();
349
350 Self::halt();
351
352 flag.store(true, Ordering::SeqCst);
353 if let Some(thread) = thread {
354 thread.join().unwrap();
355 }
356
357 #[cfg(feature = "console")]
358 {
359 println!("Press Enter to exit...");
360 let mut input = String::new();
361 let _ = std::io::stdin().read_line(&mut input);
362 }
363
364 std::process::exit(1);
365 }
366}