1use crate::app::BrepApp;
41use eframe::UserEvent;
42use std::cell::RefCell;
43use std::rc::Rc;
44use std::sync::{Arc, Mutex};
45use std::time::{Duration, Instant};
46use winit::application::ApplicationHandler;
47use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
48
49pub type Waker = Arc<dyn Fn() + Send + Sync>;
51
52const WAKE: u64 = u64::MAX;
56
57const SERVICE_PACE: Duration = Duration::from_millis(16);
63
64#[derive(Clone, Default)]
68pub struct Slot(Rc<RefCell<Option<Installed>>>);
69
70#[derive(Clone)]
71struct Installed {
72 app: Rc<RefCell<BrepApp>>,
73 ctx: egui::Context,
74 render: Option<eframe::egui_wgpu::RenderState>,
77 pass: Rc<RefCell<PassInput>>,
78}
79
80struct PassInput {
84 clock: Option<(f64, Instant)>,
86 started: Instant,
88 size: Option<winit::dpi::PhysicalSize<u32>>,
90 scale: Option<f64>,
92 events: Vec<egui::Event>,
94}
95
96impl PassInput {
97 fn new() -> Self {
98 PassInput { clock: None, started: Instant::now(), size: None, scale: None, events: Vec::new() }
99 }
100
101 fn now(&self) -> f64 {
102 match self.clock {
103 Some((time, at)) => time + at.elapsed().as_secs_f64(),
104 None => self.started.elapsed().as_secs_f64(),
105 }
106 }
107}
108
109impl Slot {
110 pub fn install(&self, app: BrepApp, ctx: &egui::Context, render: Option<eframe::egui_wgpu::RenderState>) -> Shell {
113 let app = Rc::new(RefCell::new(app));
114 let pass = Rc::new(RefCell::new(PassInput::new()));
115 *self.0.borrow_mut() = Some(Installed { app: app.clone(), ctx: ctx.clone(), render, pass: pass.clone() });
116 Shell(app, pass)
117 }
118
119 fn get(&self) -> Option<Installed> {
120 self.0.borrow().clone()
121 }
122}
123
124pub struct Shell(Rc<RefCell<BrepApp>>, Rc<RefCell<PassInput>>);
126
127impl eframe::App for Shell {
128 fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
129 eframe::App::logic(&mut *self.0.borrow_mut(), ctx, frame)
130 }
131 fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
132 eframe::App::ui(&mut *self.0.borrow_mut(), ui, frame)
133 }
134 fn save(&mut self, storage: &mut dyn eframe::Storage) {
135 eframe::App::save(&mut *self.0.borrow_mut(), storage)
136 }
137 fn on_exit(&mut self) {
138 eframe::App::on_exit(&mut *self.0.borrow_mut())
139 }
140 fn auto_save_interval(&self) -> Duration {
141 eframe::App::auto_save_interval(&*self.0.borrow())
142 }
143 fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] {
144 eframe::App::clear_color(&*self.0.borrow(), visuals)
145 }
146 fn persist_egui_memory(&self) -> bool {
147 eframe::App::persist_egui_memory(&*self.0.borrow())
148 }
149 fn raw_input_hook(&mut self, ctx: &egui::Context, raw_input: &mut egui::RawInput) {
150 {
151 let mut pass = self.1.borrow_mut();
154 if let Some(time) = raw_input.time {
155 pass.clock = Some((time, Instant::now()));
156 }
157 raw_input.events.append(&mut pass.events);
158 }
159 eframe::App::raw_input_hook(&mut *self.0.borrow_mut(), ctx, raw_input)
160 }
161}
162
163static ON_DEMAND_PASSES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
165
166pub fn on_demand_passes() -> u64 {
168 ON_DEMAND_PASSES.load(std::sync::atomic::Ordering::SeqCst)
169}
170
171const PASS_PACE: Duration = SERVICE_PACE;
176
177const FALLBACK_SIZE: egui::Vec2 = egui::vec2(800.0, 600.0);
180
181#[cfg(feature = "automation")]
184fn run_pass(app: &mut BrepApp, installed: &Installed) {
185 let ctx = &installed.ctx;
186 let mut raw = {
187 let mut pass = installed.pass.borrow_mut();
188 let mut raw = ctx.input(|i| i.raw.clone());
189 raw.events = std::mem::take(&mut pass.events);
192 raw.hovered_files.clear();
193 raw.dropped_files.clear();
194 raw.time = Some(pass.now());
195 let root = raw.viewports.entry(egui::ViewportId::ROOT).or_default();
196 root.events.clear();
197 if let Some(scale) = pass.scale {
198 root.native_pixels_per_point = Some(scale as f32);
199 }
200 let pixels_per_point = root.native_pixels_per_point.unwrap_or(1.0) * ctx.zoom_factor();
202 if let Some(size) = pass.size.filter(|s| s.width > 0 && s.height > 0) {
203 raw.screen_rect = Some(egui::Rect::from_min_size(
204 egui::Pos2::ZERO,
205 egui::vec2(size.width as f32, size.height as f32) / pixels_per_point,
206 ));
207 }
208 if raw.screen_rect.is_none() {
209 raw.screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, FALLBACK_SIZE));
212 }
213 if raw.max_texture_side.is_none() {
214 raw.max_texture_side = installed.render.as_ref().map(|r| r.device.limits().max_texture_dimension_2d as usize);
215 }
216 raw
217 };
218 ON_DEMAND_PASSES.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
220 eframe::App::raw_input_hook(app, ctx, &mut raw);
221 let mut frame = eframe::Frame::_new_kittest();
222 let output = ctx.run_ui(raw, |ui| {
223 eframe::App::logic(app, ui.ctx(), &mut frame);
224 eframe::App::ui(app, ui, &mut frame);
225 });
226 let egui::FullOutput { platform_output, textures_delta, shapes, pixels_per_point, mut viewport_output } = output;
227 if let Some(render) = &installed.render {
228 crate::offscreen::apply_texture_sets(render, &textures_delta);
229 }
230 let mut captures = Vec::new();
233 for (id, viewport) in viewport_output.iter_mut() {
234 for command in std::mem::take(&mut viewport.commands) {
235 match command {
236 egui::ViewportCommand::Screenshot(user_data) if *id == egui::ViewportId::ROOT => captures.push(user_data),
237 other => ctx.send_viewport_cmd_to(*id, other),
238 }
239 }
240 }
241 if !captures.is_empty() {
242 let size = ctx.content_rect().size() * pixels_per_point;
243 let size_px = [size.x.round() as u32, size.y.round() as u32];
244 let clear = eframe::App::clear_color(app, &ctx.global_style().visuals);
245 let image = match &installed.render {
246 Some(render) => crate::offscreen::capture(render, ctx, shapes, pixels_per_point, size_px, clear),
247 None => Err("offscreen capture: the window has no wgpu render state".to_string()),
248 };
249 let mut pass = installed.pass.borrow_mut();
250 match image {
251 Ok(image) => {
252 let image = std::sync::Arc::new(image);
253 for user_data in captures {
254 pass.events.push(egui::Event::Screenshot { viewport_id: egui::ViewportId::ROOT, user_data, image: image.clone() });
255 }
256 }
257 Err(error) => {
258 log::error!("{error}");
259 drop(pass);
260 app.automation().fail_captures(&error);
261 }
262 }
263 }
264 if let Some(render) = &installed.render {
265 crate::offscreen::apply_texture_frees(render, &textures_delta);
266 }
267 if !platform_output.commands.is_empty() {
270 ctx.output_mut(|o| o.commands.extend(platform_output.commands));
271 }
272 ctx.request_repaint();
274}
275
276pub fn waker(proxy: EventLoopProxy<UserEvent>) -> Waker {
279 let proxy = Mutex::new(proxy);
280 Arc::new(move || {
281 let event = UserEvent::RequestRepaint { viewport_id: egui::ViewportId::ROOT, when: Instant::now(), cumulative_pass_nr: WAKE };
282 let _ = proxy.lock().unwrap_or_else(|p| p.into_inner()).send_event(event);
283 })
284}
285
286fn is_wake(event: &UserEvent) -> bool {
287 matches!(event, UserEvent::RequestRepaint { cumulative_pass_nr: WAKE, .. })
288}
289
290pub struct DesktopLoop<H> {
292 inner: H,
293 slot: Slot,
294 busy: bool,
296 paced: Option<Instant>,
298 polled: bool,
300 redrawn: bool,
302 last_pass: Option<Instant>,
304}
305
306impl<H: ApplicationHandler<UserEvent>> DesktopLoop<H> {
307 pub fn new(inner: H, slot: Slot) -> Self {
308 DesktopLoop { inner, slot, busy: false, paced: None, polled: false, redrawn: false, last_pass: None }
309 }
310
311 fn with_pass(&self, f: impl FnOnce(&mut PassInput)) {
312 if let Some(installed) = self.slot.get() {
313 f(&mut installed.pass.borrow_mut());
314 }
315 }
316
317 fn unstick_poll(&mut self, event_loop: &ActiveEventLoop) {
321 if event_loop.control_flow() == ControlFlow::Poll {
322 if self.polled && !self.redrawn {
323 event_loop.set_control_flow(ControlFlow::Wait);
324 self.polled = false;
325 } else {
326 self.polled = true;
327 }
328 } else {
329 self.polled = false;
330 }
331 self.redrawn = false;
332 }
333
334 fn service(&mut self, event_loop: &ActiveEventLoop) {
337 #[cfg(feature = "automation")]
338 self.step();
339 #[cfg(not(feature = "automation"))]
340 let _ = &self.slot;
341 let ours = self.paced.take().filter(|at| event_loop.control_flow() == ControlFlow::WaitUntil(*at));
344 if self.busy {
345 let next = Instant::now() + SERVICE_PACE;
346 match event_loop.control_flow() {
347 ControlFlow::Poll => {}
348 ControlFlow::WaitUntil(at) if at <= next && ours.is_none() => {}
350 _ => {
351 event_loop.set_control_flow(ControlFlow::WaitUntil(next));
352 self.paced = Some(next);
353 }
354 }
355 } else if ours.is_some() {
356 event_loop.set_control_flow(ControlFlow::Wait);
359 }
360 }
361
362 #[cfg(feature = "automation")]
366 fn step(&mut self) {
367 let Some(installed) = self.slot.get() else { return };
368 let Ok(mut app) = installed.app.try_borrow_mut() else { return };
370 let queue = app.automation().clone();
371 let owed = loop {
372 if self.busy || queue.has_frameless_work() {
373 self.busy = app.service_automation(&installed.ctx);
374 }
375 let events = !installed.pass.borrow().events.is_empty();
376 let owed = queue.needs_pass() || events || app.spinning();
378 if !owed || self.last_pass.is_some_and(|at| at.elapsed() < PASS_PACE) {
379 break owed;
380 }
381 run_pass(&mut app, &installed);
382 self.last_pass = Some(Instant::now());
383 };
384 self.busy |= owed;
385 }
386}
387
388impl<H: ApplicationHandler<UserEvent>> ApplicationHandler<UserEvent> for DesktopLoop<H> {
389 fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
390 self.inner.new_events(event_loop, cause);
391 }
392
393 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
394 self.inner.resumed(event_loop);
395 }
396
397 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
398 if !is_wake(&event) {
401 self.inner.user_event(event_loop, event);
402 }
403 }
404
405 fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: winit::event::WindowEvent) {
406 self.redrawn |= matches!(event, winit::event::WindowEvent::RedrawRequested);
407 let resized = match &event {
410 winit::event::WindowEvent::Resized(size) => {
411 self.with_pass(|pass| pass.size = Some(*size));
412 true
413 }
414 winit::event::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
415 let scale = *scale_factor;
416 self.with_pass(|pass| pass.scale = Some(scale));
417 true
418 }
419 _ => false,
420 };
421 self.inner.window_event(event_loop, window_id, event);
422 #[cfg(feature = "automation")]
423 if resized {
424 if let Some(installed) = self.slot.get() {
425 if let Ok(app) = installed.app.try_borrow() {
426 app.automation().mark_layout_dirty();
427 }
428 }
429 }
430 #[cfg(not(feature = "automation"))]
431 let _ = resized;
432 }
433
434 fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: winit::event::DeviceEvent) {
435 self.inner.device_event(event_loop, device_id, event);
436 }
437
438 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
442 self.inner.about_to_wait(event_loop);
443 self.unstick_poll(event_loop);
444 if !event_loop.exiting() {
445 self.service(event_loop);
446 }
447 }
448
449 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
450 self.inner.suspended(event_loop);
451 }
452
453 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
454 self.inner.exiting(event_loop);
455 }
456
457 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
458 self.inner.memory_warning(event_loop);
459 }
460}
461
462pub type Create<'a> = Box<dyn 'a + FnOnce(&eframe::CreationContext<'_>, Waker) -> Result<BrepApp, String>>;
465
466pub fn run(app_name: &str, options: eframe::NativeOptions, create: Create<'_>) -> Result<(), String> {
468 let event_loop = EventLoop::<UserEvent>::with_user_event().build().map_err(|e| format!("event loop: {e}"))?;
469 run_on(event_loop, app_name, options, create, |eframe| eframe)
470}
471
472pub fn run_on<'a, H: ApplicationHandler<UserEvent>>(
476 event_loop: EventLoop<UserEvent>,
477 app_name: &str,
478 options: eframe::NativeOptions,
479 create: Create<'a>,
480 wrap: impl FnOnce(eframe::EframeWinitApplication<'a>) -> H,
481) -> Result<(), String> {
482 let slot = Slot::default();
483 let failure: Rc<RefCell<Option<String>>> = Rc::default();
484 let waker = waker(event_loop.create_proxy());
485 let (installer, failed) = (slot.clone(), failure.clone());
486 brep_render::runner::set_reply_waker(Some(waker.clone()));
489 let creator: eframe::AppCreator<'a> = Box::new(move |cc| match create(cc, waker) {
490 Ok(app) => Ok(Box::new(installer.install(app, &cc.egui_ctx, cc.wgpu_render_state.clone())) as Box<dyn eframe::App>),
491 Err(error) => {
492 *failed.borrow_mut() = Some(error.clone());
493 Err(error.into())
494 }
495 });
496 let eframe = eframe::create_native(app_name, options, creator, &event_loop);
497 let mut handler = DesktopLoop::new(wrap(eframe), slot);
498 event_loop.run_app(&mut handler).map_err(|e| format!("event loop: {e}"))?;
499 let failed = failure.borrow_mut().take();
500 match failed {
501 Some(error) => Err(error),
502 None => Ok(()),
503 }
504}