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 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 let render = installed.render.as_ref();
221 let pass = crate::on_demand::run(app, ctx, render, raw);
222 if !pass.captures.is_empty() {
225 let (size_px, clear) = crate::on_demand::capture_target(app, ctx, pass.pixels_per_point);
226 let image = match render {
227 Some(render) => crate::offscreen::capture(render, ctx, pass.shapes, pass.pixels_per_point, size_px, clear),
228 None => Err("offscreen capture: the window has no wgpu render state".to_string()),
229 };
230 match image {
231 Ok(image) => installed.pass.borrow_mut().events.extend(crate::on_demand::screenshot_events(pass.captures, image)),
232 Err(error) => {
233 log::error!("{error}");
234 app.automation().fail_captures(&error);
235 }
236 }
237 }
238 if let Some(render) = render {
239 crate::offscreen::apply_texture_frees(render, &pass.textures_delta);
240 }
241}
242
243pub fn waker(proxy: EventLoopProxy<UserEvent>) -> Waker {
246 let proxy = Mutex::new(proxy);
247 Arc::new(move || {
248 let event = UserEvent::RequestRepaint { viewport_id: egui::ViewportId::ROOT, when: Instant::now(), cumulative_pass_nr: WAKE };
249 let _ = proxy.lock().unwrap_or_else(|p| p.into_inner()).send_event(event);
250 })
251}
252
253fn is_wake(event: &UserEvent) -> bool {
254 matches!(event, UserEvent::RequestRepaint { cumulative_pass_nr: WAKE, .. })
255}
256
257pub struct DesktopLoop<H> {
259 inner: H,
260 slot: Slot,
261 busy: bool,
263 paced: Option<Instant>,
265 polled: bool,
267 redrawn: bool,
269 last_pass: Option<Instant>,
271}
272
273impl<H: ApplicationHandler<UserEvent>> DesktopLoop<H> {
274 pub fn new(inner: H, slot: Slot) -> Self {
275 DesktopLoop { inner, slot, busy: false, paced: None, polled: false, redrawn: false, last_pass: None }
276 }
277
278 fn with_pass(&self, f: impl FnOnce(&mut PassInput)) {
279 if let Some(installed) = self.slot.get() {
280 f(&mut installed.pass.borrow_mut());
281 }
282 }
283
284 fn unstick_poll(&mut self, event_loop: &ActiveEventLoop) {
288 if event_loop.control_flow() == ControlFlow::Poll {
289 if self.polled && !self.redrawn {
290 event_loop.set_control_flow(ControlFlow::Wait);
291 self.polled = false;
292 } else {
293 self.polled = true;
294 }
295 } else {
296 self.polled = false;
297 }
298 self.redrawn = false;
299 }
300
301 fn service(&mut self, event_loop: &ActiveEventLoop) {
304 #[cfg(feature = "automation")]
305 self.step();
306 #[cfg(not(feature = "automation"))]
307 let _ = &self.slot;
308 let ours = self.paced.take().filter(|at| event_loop.control_flow() == ControlFlow::WaitUntil(*at));
311 if self.busy {
312 let next = Instant::now() + SERVICE_PACE;
313 match event_loop.control_flow() {
314 ControlFlow::Poll => {}
315 ControlFlow::WaitUntil(at) if at <= next && ours.is_none() => {}
317 _ => {
318 event_loop.set_control_flow(ControlFlow::WaitUntil(next));
319 self.paced = Some(next);
320 }
321 }
322 } else if ours.is_some() {
323 event_loop.set_control_flow(ControlFlow::Wait);
326 }
327 }
328
329 #[cfg(feature = "automation")]
333 fn step(&mut self) {
334 let Some(installed) = self.slot.get() else { return };
335 let Ok(mut app) = installed.app.try_borrow_mut() else { return };
337 let queue = app.automation().clone();
338 let owed = loop {
339 if self.busy || queue.has_frameless_work() {
340 self.busy = app.service_automation(&installed.ctx);
341 }
342 let events = !installed.pass.borrow().events.is_empty();
343 let owed = queue.needs_pass() || events || app.spinning();
345 if !owed || self.last_pass.is_some_and(|at| at.elapsed() < PASS_PACE) {
346 break owed;
347 }
348 run_pass(&mut app, &installed);
349 self.last_pass = Some(Instant::now());
350 };
351 self.busy |= owed;
352 }
353}
354
355impl<H: ApplicationHandler<UserEvent>> ApplicationHandler<UserEvent> for DesktopLoop<H> {
356 fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
357 self.inner.new_events(event_loop, cause);
358 }
359
360 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
361 self.inner.resumed(event_loop);
362 }
363
364 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
365 if !is_wake(&event) {
368 self.inner.user_event(event_loop, event);
369 }
370 }
371
372 fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: winit::event::WindowEvent) {
373 self.redrawn |= matches!(event, winit::event::WindowEvent::RedrawRequested);
374 let resized = match &event {
377 winit::event::WindowEvent::Resized(size) => {
378 self.with_pass(|pass| pass.size = Some(*size));
379 true
380 }
381 winit::event::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
382 let scale = *scale_factor;
383 self.with_pass(|pass| pass.scale = Some(scale));
384 true
385 }
386 _ => false,
387 };
388 self.inner.window_event(event_loop, window_id, event);
389 #[cfg(feature = "automation")]
390 if resized {
391 if let Some(installed) = self.slot.get() {
392 if let Ok(app) = installed.app.try_borrow() {
393 app.automation().mark_layout_dirty();
394 }
395 }
396 }
397 #[cfg(not(feature = "automation"))]
398 let _ = resized;
399 }
400
401 fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: winit::event::DeviceEvent) {
402 self.inner.device_event(event_loop, device_id, event);
403 }
404
405 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
409 self.inner.about_to_wait(event_loop);
410 self.unstick_poll(event_loop);
411 if !event_loop.exiting() {
412 self.service(event_loop);
413 }
414 }
415
416 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
417 self.inner.suspended(event_loop);
418 }
419
420 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
421 self.inner.exiting(event_loop);
422 }
423
424 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
425 self.inner.memory_warning(event_loop);
426 }
427}
428
429pub type Create<'a> = Box<dyn 'a + FnOnce(&eframe::CreationContext<'_>, Waker) -> Result<BrepApp, String>>;
432
433pub fn run(app_name: &str, options: eframe::NativeOptions, create: Create<'_>) -> Result<(), String> {
435 let event_loop = EventLoop::<UserEvent>::with_user_event().build().map_err(|e| format!("event loop: {e}"))?;
436 run_on(event_loop, app_name, options, create, |eframe| eframe)
437}
438
439pub fn run_on<'a, H: ApplicationHandler<UserEvent>>(
443 event_loop: EventLoop<UserEvent>,
444 app_name: &str,
445 options: eframe::NativeOptions,
446 create: Create<'a>,
447 wrap: impl FnOnce(eframe::EframeWinitApplication<'a>) -> H,
448) -> Result<(), String> {
449 let slot = Slot::default();
450 let failure: Rc<RefCell<Option<String>>> = Rc::default();
451 let waker = waker(event_loop.create_proxy());
452 let (installer, failed) = (slot.clone(), failure.clone());
453 brep_render::runner::set_reply_waker(Some(waker.clone()));
456 let creator: eframe::AppCreator<'a> = Box::new(move |cc| match create(cc, waker) {
457 Ok(app) => Ok(Box::new(installer.install(app, &cc.egui_ctx, cc.wgpu_render_state.clone())) as Box<dyn eframe::App>),
458 Err(error) => {
459 *failed.borrow_mut() = Some(error.clone());
460 Err(error.into())
461 }
462 });
463 let eframe = eframe::create_native(app_name, options, creator, &event_loop);
464 let mut handler = DesktopLoop::new(wrap(eframe), slot);
465 event_loop.run_app(&mut handler).map_err(|e| format!("event loop: {e}"))?;
466 let failed = failure.borrow_mut().take();
467 match failed {
468 Some(error) => Err(error),
469 None => Ok(()),
470 }
471}