use crate::automation::command::{lookup, Ctx, Envelope, Handler, Notice, NoticeKind, Outcome, Phase, Reply};
use crate::automation::pointer::Pointer;
use std::collections::VecDeque;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
pub struct Pending {
pub envelope: Envelope,
pub reply: Sender<Reply>,
}
struct Awaiting {
token: u64,
region: crate::automation::cmd_capture::Region,
pending: Pending,
}
#[derive(Default)]
struct Inner {
pending: VecDeque<Pending>,
awaiting: Vec<Awaiting>,
notices: Vec<Notice>,
pointer: Pointer,
next_token: u64,
frame: u64,
poisoned: Option<String>,
}
#[derive(Default)]
pub struct AutomationQueue {
inner: Mutex<Inner>,
ctx: Mutex<Option<egui::Context>>,
}
impl AutomationQueue {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub fn attach(&self, ctx: &egui::Context) {
*self.ctx.lock().unwrap() = Some(ctx.clone());
}
pub fn submit(&self, envelope: Envelope) -> Receiver<Reply> {
let (tx, rx) = channel();
let mut inner = self.inner.lock().unwrap();
if let Some(why) = &inner.poisoned {
let _ = tx.send(Reply::err(envelope.id, inner.frame, format!("session poisoned: {why}"), vec![]));
return rx;
}
if lookup(&envelope.cmd).is_none() {
let _ = tx.send(Reply::err(envelope.id, inner.frame, format!("unknown command `{}`", envelope.cmd), vec![]));
return rx;
}
inner.pending.push_back(Pending { envelope, reply: tx });
drop(inner);
if let Some(ctx) = self.ctx.lock().unwrap().as_ref() {
ctx.request_repaint();
}
rx
}
pub fn push_notice(&self, kind: NoticeKind, text: impl Into<String>) {
let mut inner = self.inner.lock().unwrap();
let frame = inner.frame;
inner.notices.push(Notice { kind, frame, text: text.into() });
}
pub fn take_notices(&self) -> Vec<Notice> {
std::mem::take(&mut self.inner.lock().unwrap().notices)
}
pub fn pointer(&self) -> Pointer {
self.inner.lock().unwrap().pointer.clone()
}
pub fn frame(&self) -> u64 {
self.inner.lock().unwrap().frame
}
pub fn has_pending(&self) -> bool {
let inner = self.inner.lock().unwrap();
!inner.pending.is_empty() || !inner.awaiting.is_empty()
}
pub fn poison(&self, why: impl Into<String>) {
let why = why.into();
let mut inner = self.inner.lock().unwrap();
inner.poisoned = Some(why.clone());
let frame = inner.frame;
for p in inner.pending.drain(..) {
let _ = p.reply.send(Reply::err(p.envelope.id, frame, format!("session poisoned: {why}"), vec![]));
}
for a in inner.awaiting.drain(..) {
let _ = a.pending.reply.send(Reply::err(a.pending.envelope.id, frame, format!("session poisoned: {why}"), vec![]));
}
}
pub fn is_poisoned(&self) -> bool {
self.inner.lock().unwrap().poisoned.is_some()
}
pub fn drain_input(&self, raw: &mut egui::RawInput, frame: u64, ppp: f32, view: Option<egui::Rect>) {
let mut inner = self.inner.lock().unwrap();
inner.frame = frame;
if !inner.awaiting.is_empty() {
let mut done = Vec::new();
for event in &raw.events {
if let egui::Event::Screenshot { user_data, image, .. } = event {
let token = user_data.data.as_ref().and_then(|d| d.downcast_ref::<u64>()).copied();
if let Some(token) = token {
if let Some(pos) = inner.awaiting.iter().position(|a| a.token == token) {
let a = inner.awaiting.remove(pos);
done.push((a, image.clone()));
}
}
}
}
for (a, image) in done {
let notices = std::mem::take(&mut inner.notices);
let reply = match crate::automation::cmd_capture::encode_capture(&image, ppp, view, &a.region) {
Ok((json, png)) => {
let mut r = Reply::ok(a.pending.envelope.id, frame, json, notices);
r.blob = Some(png);
r
}
Err(e) => Reply::err(a.pending.envelope.id, frame, e, notices),
};
let _ = a.pending.reply.send(reply);
}
}
let mut keep = VecDeque::new();
let mut events = Vec::new();
while let Some(p) = inner.pending.pop_front() {
let Some(spec) = lookup(&p.envelope.cmd) else { continue };
match (&spec.handler, spec.phase) {
(Handler::Input(f), Phase::Input) => {
let result = f(&mut inner.pointer, p.envelope.args.clone(), &mut events);
let notices = std::mem::take(&mut inner.notices);
let reply = match result {
Ok(v) => Reply::ok(p.envelope.id, frame, v, notices),
Err(e) => Reply::err(p.envelope.id, frame, e, notices),
};
let _ = p.reply.send(reply);
}
_ => keep.push_back(p),
}
}
inner.pending = keep;
raw.modifiers = inner.pointer.modifiers.egui();
raw.events.extend(events);
}
pub fn drain_app(&self, phase: Phase, ctx: &mut Ctx<'_>, frame: u64) {
let batch: Vec<Pending> = {
let mut inner = self.inner.lock().unwrap();
inner.frame = frame;
let mut keep = VecDeque::new();
let mut batch = Vec::new();
while let Some(p) = inner.pending.pop_front() {
match lookup(&p.envelope.cmd) {
Some(spec) if spec.phase == phase => batch.push(p),
_ => keep.push_back(p),
}
}
inner.pending = keep;
batch
};
for p in batch {
let spec = lookup(&p.envelope.cmd).expect("checked at submit");
let Handler::App(f) = &spec.handler else { continue };
let result = f(ctx, p.envelope.args.clone());
let mut inner = self.inner.lock().unwrap();
let notices = std::mem::take(&mut inner.notices);
match result {
Ok(Outcome::Done(v)) => {
let _ = p.reply.send(Reply::ok(p.envelope.id, frame, v, notices));
}
Ok(Outcome::Blob { json, bytes }) => {
let mut r = Reply::ok(p.envelope.id, frame, json, notices);
r.blob = Some(bytes);
let _ = p.reply.send(r);
}
Ok(Outcome::AwaitScreenshot { token, region }) => {
inner.notices = notices;
inner.awaiting.push(Awaiting { token, region, pending: p });
}
Err(e) => {
let _ = p.reply.send(Reply::err(p.envelope.id, frame, e, notices));
}
}
}
}
pub fn next_token(&self) -> u64 {
let mut inner = self.inner.lock().unwrap();
inner.next_token += 1;
inner.next_token
}
}