Skip to main content

brep_app/automation/
queue.rs

1//! The command queue: the channel between a host and the app.
2//!
3//! A host calls [`AutomationQueue::submit`] with an [`Envelope`] and gets a
4//! receiver for the [`Reply`]. The app drains the queue at three fixed points
5//! in the frame (§4.4): [`drain_input`](AutomationQueue::drain_input) in
6//! `raw_input_hook`, then [`drain_app`](AutomationQueue::drain_app) for
7//! `Phase::Mutate` at the top of `ui` and for `Phase::Read` at the bottom. A
8//! command waits in the queue until its phase comes round.
9use crate::automation::command::{lookup, Ctx, Envelope, Handler, Notice, NoticeKind, Outcome, Phase, Reply};
10use crate::automation::pointer::Pointer;
11use std::collections::VecDeque;
12use std::sync::mpsc::{channel, Receiver, Sender};
13use std::sync::{Arc, Mutex};
14
15pub struct Pending {
16    pub envelope: Envelope,
17    pub reply: Sender<Reply>,
18}
19
20struct Awaiting {
21    token: u64,
22    region: crate::automation::cmd_capture::Region,
23    pending: Pending,
24}
25
26#[derive(Default)]
27struct Inner {
28    pending: VecDeque<Pending>,
29    awaiting: Vec<Awaiting>,
30    notices: Vec<Notice>,
31    pointer: Pointer,
32    next_token: u64,
33    frame: u64,
34    poisoned: Option<String>,
35}
36
37#[derive(Default)]
38pub struct AutomationQueue {
39    inner: Mutex<Inner>,
40    ctx: Mutex<Option<egui::Context>>,
41}
42
43impl AutomationQueue {
44    pub fn new() -> Arc<Self> {
45        Arc::new(Self::default())
46    }
47
48    /// Give the queue the egui context so a submit can wake an idle frame loop.
49    pub fn attach(&self, ctx: &egui::Context) {
50        *self.ctx.lock().unwrap() = Some(ctx.clone());
51    }
52
53    /// Enqueue a command. The reply arrives on the returned receiver once the
54    /// app has reached the command's phase (or at once, for an unknown command
55    /// or a poisoned session).
56    pub fn submit(&self, envelope: Envelope) -> Receiver<Reply> {
57        let (tx, rx) = channel();
58        let mut inner = self.inner.lock().unwrap();
59        if let Some(why) = &inner.poisoned {
60            let _ = tx.send(Reply::err(envelope.id, inner.frame, format!("session poisoned: {why}"), vec![]));
61            return rx;
62        }
63        if lookup(&envelope.cmd).is_none() {
64            let _ = tx.send(Reply::err(envelope.id, inner.frame, format!("unknown command `{}`", envelope.cmd), vec![]));
65            return rx;
66        }
67        inner.pending.push_back(Pending { envelope, reply: tx });
68        drop(inner);
69        if let Some(ctx) = self.ctx.lock().unwrap().as_ref() {
70            ctx.request_repaint();
71        }
72        rx
73    }
74
75    pub fn push_notice(&self, kind: NoticeKind, text: impl Into<String>) {
76        let mut inner = self.inner.lock().unwrap();
77        let frame = inner.frame;
78        inner.notices.push(Notice { kind, frame, text: text.into() });
79    }
80
81    pub fn take_notices(&self) -> Vec<Notice> {
82        std::mem::take(&mut self.inner.lock().unwrap().notices)
83    }
84
85    pub fn pointer(&self) -> Pointer {
86        self.inner.lock().unwrap().pointer.clone()
87    }
88
89    pub fn frame(&self) -> u64 {
90        self.inner.lock().unwrap().frame
91    }
92
93    pub fn has_pending(&self) -> bool {
94        let inner = self.inner.lock().unwrap();
95        !inner.pending.is_empty() || !inner.awaiting.is_empty()
96    }
97
98    /// Mark the session unusable (a panic unwound through the app). Every
99    /// pending and future command is answered with the reason.
100    pub fn poison(&self, why: impl Into<String>) {
101        let why = why.into();
102        let mut inner = self.inner.lock().unwrap();
103        inner.poisoned = Some(why.clone());
104        let frame = inner.frame;
105        for p in inner.pending.drain(..) {
106            let _ = p.reply.send(Reply::err(p.envelope.id, frame, format!("session poisoned: {why}"), vec![]));
107        }
108        for a in inner.awaiting.drain(..) {
109            let _ = a.pending.reply.send(Reply::err(a.pending.envelope.id, frame, format!("session poisoned: {why}"), vec![]));
110        }
111    }
112
113    pub fn is_poisoned(&self) -> bool {
114        self.inner.lock().unwrap().poisoned.is_some()
115    }
116
117    /// Phase 1 of the frame: apply input commands as `egui::Event`s and
118    /// complete captures whose `Event::Screenshot` arrived. `view` is the
119    /// viewport rect of the last frame (for `region: viewport` crops).
120    pub fn drain_input(&self, raw: &mut egui::RawInput, frame: u64, ppp: f32, view: Option<egui::Rect>) {
121        let mut inner = self.inner.lock().unwrap();
122        inner.frame = frame;
123
124        // Screenshots that landed in this frame's input.
125        if !inner.awaiting.is_empty() {
126            let mut done = Vec::new();
127            for event in &raw.events {
128                if let egui::Event::Screenshot { user_data, image, .. } = event {
129                    let token = user_data.data.as_ref().and_then(|d| d.downcast_ref::<u64>()).copied();
130                    if let Some(token) = token {
131                        if let Some(pos) = inner.awaiting.iter().position(|a| a.token == token) {
132                            let a = inner.awaiting.remove(pos);
133                            done.push((a, image.clone()));
134                        }
135                    }
136                }
137            }
138            for (a, image) in done {
139                let notices = std::mem::take(&mut inner.notices);
140                let reply = match crate::automation::cmd_capture::encode_capture(&image, ppp, view, &a.region) {
141                    Ok((json, png)) => {
142                        let mut r = Reply::ok(a.pending.envelope.id, frame, json, notices);
143                        r.blob = Some(png);
144                        r
145                    }
146                    Err(e) => Reply::err(a.pending.envelope.id, frame, e, notices),
147                };
148                let _ = a.pending.reply.send(reply);
149            }
150        }
151
152        // Input-phase commands, in order.
153        let mut keep = VecDeque::new();
154        let mut events = Vec::new();
155        while let Some(p) = inner.pending.pop_front() {
156            let Some(spec) = lookup(&p.envelope.cmd) else { continue };
157            match (&spec.handler, spec.phase) {
158                (Handler::Input(f), Phase::Input) => {
159                    let result = f(&mut inner.pointer, p.envelope.args.clone(), &mut events);
160                    let notices = std::mem::take(&mut inner.notices);
161                    let reply = match result {
162                        Ok(v) => Reply::ok(p.envelope.id, frame, v, notices),
163                        Err(e) => Reply::err(p.envelope.id, frame, e, notices),
164                    };
165                    let _ = p.reply.send(reply);
166                }
167                _ => keep.push_back(p),
168            }
169        }
170        inner.pending = keep;
171        raw.modifiers = inner.pointer.modifiers.egui();
172        raw.events.extend(events);
173    }
174
175    /// Phases 2 and 3 of the frame. Runs every pending command registered for
176    /// `phase`, in order.
177    pub fn drain_app(&self, phase: Phase, ctx: &mut Ctx<'_>, frame: u64) {
178        let batch: Vec<Pending> = {
179            let mut inner = self.inner.lock().unwrap();
180            inner.frame = frame;
181            let mut keep = VecDeque::new();
182            let mut batch = Vec::new();
183            while let Some(p) = inner.pending.pop_front() {
184                match lookup(&p.envelope.cmd) {
185                    Some(spec) if spec.phase == phase => batch.push(p),
186                    _ => keep.push_back(p),
187                }
188            }
189            inner.pending = keep;
190            batch
191        };
192        for p in batch {
193            let spec = lookup(&p.envelope.cmd).expect("checked at submit");
194            let Handler::App(f) = &spec.handler else { continue };
195            let result = f(ctx, p.envelope.args.clone());
196            let mut inner = self.inner.lock().unwrap();
197            let notices = std::mem::take(&mut inner.notices);
198            match result {
199                Ok(Outcome::Done(v)) => {
200                    let _ = p.reply.send(Reply::ok(p.envelope.id, frame, v, notices));
201                }
202                Ok(Outcome::Blob { json, bytes }) => {
203                    let mut r = Reply::ok(p.envelope.id, frame, json, notices);
204                    r.blob = Some(bytes);
205                    let _ = p.reply.send(r);
206                }
207                Ok(Outcome::AwaitScreenshot { token, region }) => {
208                    inner.notices = notices;
209                    inner.awaiting.push(Awaiting { token, region, pending: p });
210                }
211                Err(e) => {
212                    let _ = p.reply.send(Reply::err(p.envelope.id, frame, e, notices));
213                }
214            }
215        }
216    }
217
218    /// A fresh token for a screenshot request.
219    pub fn next_token(&self) -> u64 {
220        let mut inner = self.inner.lock().unwrap();
221        inner.next_token += 1;
222        inner.next_token
223    }
224}