1use 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 notices_replied: usize,
34 pointer: Pointer,
35 next_token: u64,
36 frame: u64,
37 poisoned: Option<String>,
38}
39
40const MAX_NOTICES: usize = 256;
45
46impl Inner {
47 fn reply_notices(&mut self) -> Vec<Notice> {
59 let from = self.notices_replied.min(self.notices.len());
60 self.notices_replied = self.notices.len();
61 self.notices[from..].to_vec()
62 }
63}
64
65#[derive(Default)]
66pub struct AutomationQueue {
67 inner: Mutex<Inner>,
68 ctx: Mutex<Option<egui::Context>>,
69}
70
71impl AutomationQueue {
72 pub fn new() -> Arc<Self> {
73 Arc::new(Self::default())
74 }
75
76 pub fn attach(&self, ctx: &egui::Context) {
78 *self.ctx.lock().unwrap() = Some(ctx.clone());
79 }
80
81 pub fn submit(&self, envelope: Envelope) -> Receiver<Reply> {
85 let (tx, rx) = channel();
86 let mut inner = self.inner.lock().unwrap();
87 if let Some(why) = &inner.poisoned {
88 let _ = tx.send(Reply::err(envelope.id, inner.frame, format!("session poisoned: {why}"), vec![]));
89 return rx;
90 }
91 if lookup(&envelope.cmd).is_none() {
92 let _ = tx.send(Reply::err(envelope.id, inner.frame, format!("unknown command `{}`", envelope.cmd), vec![]));
93 return rx;
94 }
95 inner.pending.push_back(Pending { envelope, reply: tx });
96 drop(inner);
97 if let Some(ctx) = self.ctx.lock().unwrap().as_ref() {
98 ctx.request_repaint();
99 }
100 rx
101 }
102
103 pub fn push_notice(&self, kind: NoticeKind, text: impl Into<String>) {
107 let mut inner = self.inner.lock().unwrap();
108 let frame = inner.frame;
109 inner.notices.push(Notice { kind, frame, text: text.into() });
110 let overflow = inner.notices.len().saturating_sub(MAX_NOTICES);
111 if overflow > 0 {
112 inner.notices.drain(0..overflow);
113 inner.notices_replied = inner.notices_replied.saturating_sub(overflow);
114 }
115 }
116
117 pub fn take_notices(&self) -> Vec<Notice> {
121 let mut inner = self.inner.lock().unwrap();
122 inner.notices_replied = 0;
123 std::mem::take(&mut inner.notices)
124 }
125
126 pub fn pointer(&self) -> Pointer {
127 self.inner.lock().unwrap().pointer.clone()
128 }
129
130 pub fn frame(&self) -> u64 {
131 self.inner.lock().unwrap().frame
132 }
133
134 pub fn has_pending(&self) -> bool {
135 let inner = self.inner.lock().unwrap();
136 !inner.pending.is_empty() || !inner.awaiting.is_empty()
137 }
138
139 pub fn poison(&self, why: impl Into<String>) {
142 let why = why.into();
143 let mut inner = self.inner.lock().unwrap();
144 inner.poisoned = Some(why.clone());
145 let frame = inner.frame;
146 for p in inner.pending.drain(..) {
147 let _ = p.reply.send(Reply::err(p.envelope.id, frame, format!("session poisoned: {why}"), vec![]));
148 }
149 for a in inner.awaiting.drain(..) {
150 let _ = a.pending.reply.send(Reply::err(a.pending.envelope.id, frame, format!("session poisoned: {why}"), vec![]));
151 }
152 }
153
154 pub fn is_poisoned(&self) -> bool {
155 self.inner.lock().unwrap().poisoned.is_some()
156 }
157
158 pub fn drain_input(&self, raw: &mut egui::RawInput, frame: u64, ppp: f32, view: Option<egui::Rect>, ctx: &egui::Context) {
165 let mut inner = self.inner.lock().unwrap();
166 inner.frame = frame;
167 inner.pointer.now = raw.time.unwrap_or_else(|| ctx.input(|i| i.time) + f64::from(raw.predicted_dt));
168
169 if !inner.awaiting.is_empty() {
171 let mut done = Vec::new();
172 for event in &raw.events {
173 if let egui::Event::Screenshot { user_data, image, .. } = event {
174 let token = user_data.data.as_ref().and_then(|d| d.downcast_ref::<u64>()).copied();
175 if let Some(token) = token {
176 if let Some(pos) = inner.awaiting.iter().position(|a| a.token == token) {
177 let a = inner.awaiting.remove(pos);
178 done.push((a, image.clone()));
179 }
180 }
181 }
182 }
183 for (a, image) in done {
184 let notices = inner.reply_notices();
185 let reply = match crate::automation::cmd_capture::encode_capture(&image, ppp, view, &a.region) {
186 Ok((json, png)) => {
187 let mut r = Reply::ok(a.pending.envelope.id, frame, json, notices);
188 r.blob = Some(png);
189 r
190 }
191 Err(e) => Reply::err(a.pending.envelope.id, frame, e, notices),
192 };
193 let _ = a.pending.reply.send(reply);
194 }
195 }
196
197 let mut keep = VecDeque::new();
199 let mut events = Vec::new();
200 while let Some(p) = inner.pending.pop_front() {
201 let Some(spec) = lookup(&p.envelope.cmd) else { continue };
202 match (&spec.handler, spec.phase) {
203 (Handler::Input(f), Phase::Input) => {
204 let result = f(&mut inner.pointer, p.envelope.args.clone(), &mut events);
205 let notices = inner.reply_notices();
206 let reply = match result {
207 Ok(v) => Reply::ok(p.envelope.id, frame, v, notices),
208 Err(e) => Reply::err(p.envelope.id, frame, e, notices),
209 };
210 let _ = p.reply.send(reply);
211 }
212 _ => keep.push_back(p),
213 }
214 }
215 inner.pending = keep;
216 if std::mem::take(&mut inner.pointer.reset_clicks) {
220 ctx.input_mut(|i| i.pointer = egui::PointerState::default());
221 }
222 raw.modifiers = inner.pointer.modifiers.egui();
223 raw.events.extend(events);
224 }
225
226 pub fn drain_app(&self, phase: Phase, ctx: &mut Ctx<'_>, frame: u64) {
229 let batch: Vec<Pending> = {
230 let mut inner = self.inner.lock().unwrap();
231 inner.frame = frame;
232 let mut keep = VecDeque::new();
233 let mut batch = Vec::new();
234 while let Some(p) = inner.pending.pop_front() {
235 match lookup(&p.envelope.cmd) {
236 Some(spec) if spec.phase == phase => batch.push(p),
237 _ => keep.push_back(p),
238 }
239 }
240 inner.pending = keep;
241 batch
242 };
243 for p in batch {
244 let spec = lookup(&p.envelope.cmd).expect("checked at submit");
245 let Handler::App(f) = &spec.handler else { continue };
246 let before = refused_counts(&ctx.app.docs);
247 let result = f(ctx, p.envelope.args.clone());
248 let result = refused_by_lock(&ctx.app.docs, &before, result);
249 let mut inner = self.inner.lock().unwrap();
250 match result {
251 Ok(Outcome::Done(v)) => {
252 let notices = inner.reply_notices();
253 let _ = p.reply.send(Reply::ok(p.envelope.id, frame, v, notices));
254 }
255 Ok(Outcome::Blob { json, bytes }) => {
256 let notices = inner.reply_notices();
257 let mut r = Reply::ok(p.envelope.id, frame, json, notices);
258 r.blob = Some(bytes);
259 let _ = p.reply.send(r);
260 }
261 Ok(Outcome::AwaitScreenshot { token, region }) => {
264 inner.awaiting.push(Awaiting { token, region, pending: p });
265 }
266 Err(e) => {
267 let notices = inner.reply_notices();
268 let _ = p.reply.send(Reply::err(p.envelope.id, frame, e, notices));
269 }
270 }
271 }
272 }
273
274 pub fn next_token(&self) -> u64 {
276 let mut inner = self.inner.lock().unwrap();
277 inner.next_token += 1;
278 inner.next_token
279 }
280}
281
282fn refused_counts(docs: &crate::document::Documents) -> Vec<(u64, u64)> {
284 docs.iter().map(|d| (d.id(), d.engine.history.refused_user_edits())).collect()
285}
286
287fn refused_by_lock(
293 docs: &crate::document::Documents,
294 before: &[(u64, u64)],
295 result: Result<Outcome, String>,
296) -> Result<Outcome, String> {
297 if result.is_err() {
298 return result;
299 }
300 for doc in docs.iter() {
301 let was = before.iter().find(|(id, _)| *id == doc.id()).map(|(_, n)| *n);
302 if was.is_some_and(|was| doc.engine.history.refused_user_edits() > was) {
303 if let crate::document::Access::ReadOnly { reason } = doc.access() {
304 return Err(format!("{} is read-only: {reason}", doc.title()));
305 }
306 }
307 }
308 result
309}
310