use crate::app::BrepApp;
use eframe::UserEvent;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use winit::application::ApplicationHandler;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
pub type Waker = Arc<dyn Fn() + Send + Sync>;
const WAKE: u64 = u64::MAX;
const SERVICE_PACE: Duration = Duration::from_millis(16);
#[derive(Clone, Default)]
pub struct Slot(Rc<RefCell<Option<Installed>>>);
#[derive(Clone)]
struct Installed {
app: Rc<RefCell<BrepApp>>,
ctx: egui::Context,
render: Option<eframe::egui_wgpu::RenderState>,
pass: Rc<RefCell<PassInput>>,
}
struct PassInput {
clock: Option<(f64, Instant)>,
started: Instant,
size: Option<winit::dpi::PhysicalSize<u32>>,
scale: Option<f64>,
events: Vec<egui::Event>,
}
impl PassInput {
fn new() -> Self {
PassInput { clock: None, started: Instant::now(), size: None, scale: None, events: Vec::new() }
}
fn now(&self) -> f64 {
match self.clock {
Some((time, at)) => time + at.elapsed().as_secs_f64(),
None => self.started.elapsed().as_secs_f64(),
}
}
}
impl Slot {
pub fn install(&self, app: BrepApp, ctx: &egui::Context, render: Option<eframe::egui_wgpu::RenderState>) -> Shell {
let app = Rc::new(RefCell::new(app));
let pass = Rc::new(RefCell::new(PassInput::new()));
*self.0.borrow_mut() = Some(Installed { app: app.clone(), ctx: ctx.clone(), render, pass: pass.clone() });
Shell(app, pass)
}
fn get(&self) -> Option<Installed> {
self.0.borrow().clone()
}
}
pub struct Shell(Rc<RefCell<BrepApp>>, Rc<RefCell<PassInput>>);
impl eframe::App for Shell {
fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
eframe::App::logic(&mut *self.0.borrow_mut(), ctx, frame)
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
eframe::App::ui(&mut *self.0.borrow_mut(), ui, frame)
}
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::App::save(&mut *self.0.borrow_mut(), storage)
}
fn on_exit(&mut self) {
eframe::App::on_exit(&mut *self.0.borrow_mut())
}
fn auto_save_interval(&self) -> Duration {
eframe::App::auto_save_interval(&*self.0.borrow())
}
fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] {
eframe::App::clear_color(&*self.0.borrow(), visuals)
}
fn persist_egui_memory(&self) -> bool {
eframe::App::persist_egui_memory(&*self.0.borrow())
}
fn raw_input_hook(&mut self, ctx: &egui::Context, raw_input: &mut egui::RawInput) {
{
let mut pass = self.1.borrow_mut();
if let Some(time) = raw_input.time {
pass.clock = Some((time, Instant::now()));
}
raw_input.events.append(&mut pass.events);
}
eframe::App::raw_input_hook(&mut *self.0.borrow_mut(), ctx, raw_input)
}
}
static ON_DEMAND_PASSES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub fn on_demand_passes() -> u64 {
ON_DEMAND_PASSES.load(std::sync::atomic::Ordering::SeqCst)
}
const PASS_PACE: Duration = SERVICE_PACE;
const FALLBACK_SIZE: egui::Vec2 = egui::vec2(800.0, 600.0);
#[cfg(feature = "automation")]
fn run_pass(app: &mut BrepApp, installed: &Installed) {
let ctx = &installed.ctx;
let raw = {
let mut pass = installed.pass.borrow_mut();
let mut raw = ctx.input(|i| i.raw.clone());
raw.events = std::mem::take(&mut pass.events);
raw.hovered_files.clear();
raw.dropped_files.clear();
raw.time = Some(pass.now());
let root = raw.viewports.entry(egui::ViewportId::ROOT).or_default();
root.events.clear();
if let Some(scale) = pass.scale {
root.native_pixels_per_point = Some(scale as f32);
}
let pixels_per_point = root.native_pixels_per_point.unwrap_or(1.0) * ctx.zoom_factor();
if let Some(size) = pass.size.filter(|s| s.width > 0 && s.height > 0) {
raw.screen_rect = Some(egui::Rect::from_min_size(
egui::Pos2::ZERO,
egui::vec2(size.width as f32, size.height as f32) / pixels_per_point,
));
}
if raw.screen_rect.is_none() {
raw.screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, FALLBACK_SIZE));
}
if raw.max_texture_side.is_none() {
raw.max_texture_side = installed.render.as_ref().map(|r| r.device.limits().max_texture_dimension_2d as usize);
}
raw
};
ON_DEMAND_PASSES.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let render = installed.render.as_ref();
let pass = crate::on_demand::run(app, ctx, render, raw);
if !pass.captures.is_empty() {
let (size_px, clear) = crate::on_demand::capture_target(app, ctx, pass.pixels_per_point);
let image = match render {
Some(render) => crate::offscreen::capture(render, ctx, pass.shapes, pass.pixels_per_point, size_px, clear),
None => Err("offscreen capture: the window has no wgpu render state".to_string()),
};
match image {
Ok(image) => installed.pass.borrow_mut().events.extend(crate::on_demand::screenshot_events(pass.captures, image)),
Err(error) => {
log::error!("{error}");
app.automation().fail_captures(&error);
}
}
}
if let Some(render) = render {
crate::offscreen::apply_texture_frees(render, &pass.textures_delta);
}
}
pub fn waker(proxy: EventLoopProxy<UserEvent>) -> Waker {
let proxy = Mutex::new(proxy);
Arc::new(move || {
let event = UserEvent::RequestRepaint { viewport_id: egui::ViewportId::ROOT, when: Instant::now(), cumulative_pass_nr: WAKE };
let _ = proxy.lock().unwrap_or_else(|p| p.into_inner()).send_event(event);
})
}
fn is_wake(event: &UserEvent) -> bool {
matches!(event, UserEvent::RequestRepaint { cumulative_pass_nr: WAKE, .. })
}
pub struct DesktopLoop<H> {
inner: H,
slot: Slot,
busy: bool,
paced: Option<Instant>,
polled: bool,
redrawn: bool,
last_pass: Option<Instant>,
}
impl<H: ApplicationHandler<UserEvent>> DesktopLoop<H> {
pub fn new(inner: H, slot: Slot) -> Self {
DesktopLoop { inner, slot, busy: false, paced: None, polled: false, redrawn: false, last_pass: None }
}
fn with_pass(&self, f: impl FnOnce(&mut PassInput)) {
if let Some(installed) = self.slot.get() {
f(&mut installed.pass.borrow_mut());
}
}
fn unstick_poll(&mut self, event_loop: &ActiveEventLoop) {
if event_loop.control_flow() == ControlFlow::Poll {
if self.polled && !self.redrawn {
event_loop.set_control_flow(ControlFlow::Wait);
self.polled = false;
} else {
self.polled = true;
}
} else {
self.polled = false;
}
self.redrawn = false;
}
fn service(&mut self, event_loop: &ActiveEventLoop) {
#[cfg(feature = "automation")]
self.step();
#[cfg(not(feature = "automation"))]
let _ = &self.slot;
let ours = self.paced.take().filter(|at| event_loop.control_flow() == ControlFlow::WaitUntil(*at));
if self.busy {
let next = Instant::now() + SERVICE_PACE;
match event_loop.control_flow() {
ControlFlow::Poll => {}
ControlFlow::WaitUntil(at) if at <= next && ours.is_none() => {}
_ => {
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
self.paced = Some(next);
}
}
} else if ours.is_some() {
event_loop.set_control_flow(ControlFlow::Wait);
}
}
#[cfg(feature = "automation")]
fn step(&mut self) {
let Some(installed) = self.slot.get() else { return };
let Ok(mut app) = installed.app.try_borrow_mut() else { return };
let queue = app.automation().clone();
let owed = loop {
if self.busy || queue.has_frameless_work() {
self.busy = app.service_automation(&installed.ctx);
}
let events = !installed.pass.borrow().events.is_empty();
let owed = queue.needs_pass() || events || app.spinning();
if !owed || self.last_pass.is_some_and(|at| at.elapsed() < PASS_PACE) {
break owed;
}
run_pass(&mut app, &installed);
self.last_pass = Some(Instant::now());
};
self.busy |= owed;
}
}
impl<H: ApplicationHandler<UserEvent>> ApplicationHandler<UserEvent> for DesktopLoop<H> {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
self.inner.new_events(event_loop, cause);
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.inner.resumed(event_loop);
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
if !is_wake(&event) {
self.inner.user_event(event_loop, event);
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: winit::event::WindowEvent) {
self.redrawn |= matches!(event, winit::event::WindowEvent::RedrawRequested);
let resized = match &event {
winit::event::WindowEvent::Resized(size) => {
self.with_pass(|pass| pass.size = Some(*size));
true
}
winit::event::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let scale = *scale_factor;
self.with_pass(|pass| pass.scale = Some(scale));
true
}
_ => false,
};
self.inner.window_event(event_loop, window_id, event);
#[cfg(feature = "automation")]
if resized {
if let Some(installed) = self.slot.get() {
if let Ok(app) = installed.app.try_borrow() {
app.automation().mark_layout_dirty();
}
}
}
#[cfg(not(feature = "automation"))]
let _ = resized;
}
fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: winit::event::DeviceEvent) {
self.inner.device_event(event_loop, device_id, event);
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.inner.about_to_wait(event_loop);
self.unstick_poll(event_loop);
if !event_loop.exiting() {
self.service(event_loop);
}
}
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
self.inner.suspended(event_loop);
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
self.inner.exiting(event_loop);
}
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
self.inner.memory_warning(event_loop);
}
}
pub type Create<'a> = Box<dyn 'a + FnOnce(&eframe::CreationContext<'_>, Waker) -> Result<BrepApp, String>>;
pub fn run(app_name: &str, options: eframe::NativeOptions, create: Create<'_>) -> Result<(), String> {
let event_loop = EventLoop::<UserEvent>::with_user_event().build().map_err(|e| format!("event loop: {e}"))?;
run_on(event_loop, app_name, options, create, |eframe| eframe)
}
pub fn run_on<'a, H: ApplicationHandler<UserEvent>>(
event_loop: EventLoop<UserEvent>,
app_name: &str,
options: eframe::NativeOptions,
create: Create<'a>,
wrap: impl FnOnce(eframe::EframeWinitApplication<'a>) -> H,
) -> Result<(), String> {
let slot = Slot::default();
let failure: Rc<RefCell<Option<String>>> = Rc::default();
let waker = waker(event_loop.create_proxy());
let (installer, failed) = (slot.clone(), failure.clone());
brep_render::runner::set_reply_waker(Some(waker.clone()));
let creator: eframe::AppCreator<'a> = Box::new(move |cc| match create(cc, waker) {
Ok(app) => Ok(Box::new(installer.install(app, &cc.egui_ctx, cc.wgpu_render_state.clone())) as Box<dyn eframe::App>),
Err(error) => {
*failed.borrow_mut() = Some(error.clone());
Err(error.into())
}
});
let eframe = eframe::create_native(app_name, options, creator, &event_loop);
let mut handler = DesktopLoop::new(wrap(eframe), slot);
event_loop.run_app(&mut handler).map_err(|e| format!("event loop: {e}"))?;
let failed = failure.borrow_mut().take();
match failed {
Some(error) => Err(error),
None => Ok(()),
}
}