use std::collections::VecDeque;
use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use ashlar_control::{Reply, Request};
use egui::{Event, Rect, ViewportCommand, ViewportId, pos2, vec2};
use serde_json::Value;
use crate::runtime::Runtime;
use crate::session::Document;
#[derive(Default)]
struct Wakes {
pending: Vec<(Duration, u64)>,
}
pub struct Driver<D: Document> {
pub runtime: Runtime<D>,
ctx: egui::Context,
time: f64,
wakes: Arc<Mutex<Wakes>>,
next: Option<f64>,
input: VecDeque<Event>,
screen: Rect,
frames: u64,
closed: bool,
close_requested: bool,
screenshots: bool,
hidden: bool,
}
impl<D: Document> Driver<D> {
pub fn new(runtime: Runtime<D>) -> Self {
let ctx = egui::Context::default();
let wakes: Arc<Mutex<Wakes>> = Arc::default();
let w = Arc::clone(&wakes);
ctx.set_request_repaint_callback(move |info| {
if info.viewport_id == ViewportId::ROOT {
w.lock().unwrap_or_else(PoisonError::into_inner).pending.push((info.delay, info.current_cumulative_pass_nr));
}
});
Driver {
runtime,
ctx,
time: 0.0,
wakes,
next: Some(0.0),
input: VecDeque::new(),
screen: Rect::from_min_size(pos2(0.0, 0.0), vec2(1280.0, 800.0)),
frames: 0,
closed: false,
close_requested: false,
screenshots: true,
hidden: false,
}
}
pub fn into_runtime(self) -> Runtime<D> {
self.runtime
}
pub fn ctx(&self) -> &egui::Context {
&self.ctx
}
pub fn set_screen(&mut self, width: f32, height: f32) {
self.screen = Rect::from_min_size(pos2(0.0, 0.0), vec2(width, height));
self.next = Some(self.time);
}
pub fn time(&self) -> f64 {
self.time
}
pub fn frames(&self) -> u64 {
self.frames
}
pub fn is_closed(&self) -> bool {
self.closed
}
pub fn push_event(&mut self, e: Event) {
self.input.push_back(e);
}
pub fn set_screenshots(&mut self, deliver: bool) {
self.screenshots = deliver;
}
pub fn set_hidden(&mut self, hidden: bool) {
self.hidden = hidden;
self.next = Some(self.time);
}
pub fn is_hidden(&self) -> bool {
self.hidden
}
pub fn request_close(&mut self) {
self.close_requested = true;
}
pub fn frame(&mut self) -> egui::FullOutput {
if self.hidden {
self.logic_only();
return egui::FullOutput::default();
}
let mut raw = egui::RawInput { screen_rect: Some(self.screen), time: Some(self.time), focused: true, ..Default::default() };
raw.events.extend(self.input.drain(..));
if std::mem::take(&mut self.close_requested) {
raw.viewports.entry(ViewportId::ROOT).or_default().events.push(egui::ViewportEvent::Close);
}
let ctx = self.ctx.clone();
self.runtime.raw_input_hook(&ctx, &mut raw);
let rt = &mut self.runtime;
let mut out = ctx.run_ui(raw, |ui| {
rt.logic(ui.ctx());
rt.ui(ui);
});
out.textures_delta.clear();
self.frames = self.frames.saturating_add(1);
let done = self.ctx.cumulative_pass_nr();
self.next = None;
if let Some(vp) = out.viewport_output.get(&ViewportId::ROOT) {
if vp.repaint_delay < Duration::MAX {
self.next = Some(self.time + vp.repaint_delay.as_secs_f64());
}
for c in &vp.commands {
match c {
ViewportCommand::Screenshot(_) if !self.screenshots => {}
ViewportCommand::Screenshot(user_data) => {
let [w, h] = [self.screen.width() as usize, self.screen.height() as usize];
let image = Arc::new(egui::ColorImage::new([w, h], vec![egui::Color32::from_gray(30); w * h]));
self.input.push_back(Event::Screenshot { viewport_id: ViewportId::ROOT, user_data: user_data.clone(), image });
}
ViewportCommand::Close => self.closed = true,
_ => {}
}
}
}
self.wakes.lock().unwrap_or_else(PoisonError::into_inner).pending.retain(|(_, nr)| *nr >= done);
out
}
fn logic_only(&mut self) {
let mut raw = egui::RawInput { screen_rect: Some(self.screen), time: Some(self.time), focused: false, ..Default::default() };
raw.viewports.entry(ViewportId::ROOT).or_default().minimized = Some(true);
let (shots, rest): (Vec<Event>, Vec<Event>) = self.input.drain(..).partition(|e| matches!(e, Event::Screenshot { .. }));
self.input.extend(rest);
let done = self.ctx.cumulative_pass_nr();
let (rt, time) = (&mut self.runtime, self.time);
let out = self.ctx.run_logic(&raw, |ctx| {
rt.logic(ctx);
if let Some(mut out) = rt.hidden_pass(ctx, Some(time), shots) {
out.textures_delta.clear();
}
});
self.frames = self.frames.saturating_add(1);
self.next = None;
for c in out.viewport_commands.get(&ViewportId::ROOT).into_iter().flatten() {
match c {
ViewportCommand::Screenshot(user_data) if self.screenshots => {
let [w, h] = [self.screen.width() as usize, self.screen.height() as usize];
let image = Arc::new(egui::ColorImage::new([w, h], vec![egui::Color32::from_gray(30); w * h]));
self.input.push_back(Event::Screenshot { viewport_id: ViewportId::ROOT, user_data: user_data.clone(), image });
self.next = Some(self.time);
}
ViewportCommand::Focus | ViewportCommand::Minimized(false) | ViewportCommand::Visible(true) => {
self.hidden = false;
self.next = Some(self.time);
}
ViewportCommand::Close => self.closed = true,
_ => {}
}
}
self.wakes.lock().unwrap_or_else(PoisonError::into_inner).pending.retain(|(_, nr)| *nr >= done);
}
fn due(&self) -> Option<f64> {
let wake =
self.wakes.lock().unwrap_or_else(PoisonError::into_inner).pending.iter().map(|(d, _)| self.time + d.as_secs_f64()).reduce(f64::min);
let input = ((!self.input.is_empty() && !self.hidden) || self.close_requested).then_some(self.time);
[self.next, wake, input].into_iter().flatten().reduce(f64::min)
}
pub fn advance(&mut self, secs: f64) -> u64 {
let end = self.time + secs.max(0.0);
let start = self.frames;
let min_dt = 1.0 / 120.0;
let mut last_frame = f64::NEG_INFINITY;
while !self.closed {
let Some(due) = self.due() else { break };
let at = due.max(last_frame + min_dt).max(self.time);
if at > end {
break;
}
self.time = at;
self.frame();
last_frame = at;
}
self.time = self.time.max(end);
self.frames - start
}
pub fn settle(&mut self, max: u64) -> u64 {
let start = self.frames;
let horizon = 0.25;
while self.frames - start < max && !self.closed {
match self.due() {
Some(t) if t <= self.time + horizon => {
self.time = t.max(self.time) + 1.0 / 120.0;
self.frame();
}
_ => break,
}
}
self.frames - start
}
pub fn request(&mut self, method: &str, params: Value) -> Reply {
let (req, rx) = Request::new(method, params);
self.runtime.inject_request(req);
self.time += 1.0 / 120.0;
self.frame();
let limit = self.time + 600.0;
for _ in 0..10_000 {
if let Ok(r) = rx.try_recv() {
return r;
}
if self.closed {
break;
}
let Some(due) = self.due().filter(|d| *d <= limit) else { break };
self.time = due.max(self.time + 1.0 / 120.0);
self.frame();
}
rx.try_recv().unwrap_or_else(|_| Reply::err(format!("`{method}` was not answered, and no frame is pending")))
}
pub fn call(&mut self, method: &str, params: Value) -> Result<Value, String> {
self.request(method, params).into_result().map_err(|(e, _)| e)
}
}