1use crate::command::{Command, DumpSpec, EventSpec, QuerySpec};
5use crate::framebuffer::FramebufferReader;
6use crate::protocol::{format_event_spec, format_response, parse_command, write_hex_u32};
7use crate::recorder::EventRecorder;
8use crate::response::{Response, StatusData};
9use crate::tag::{find_by_tag, find_by_tag_mut};
10use crate::transport::PlayitTransport;
11use rlvgl_core::WidgetNode;
12use rlvgl_core::event::Event;
13
14const MAX_LINE: usize = 128;
16
17pub trait EventPipeline {
57 fn process(&mut self, event: Event) -> (Option<Event>, Option<Event>);
59 fn tick(&mut self) -> (Option<Event>, Option<Event>);
61}
62
63pub struct NullPipeline;
65
66impl EventPipeline for NullPipeline {
67 fn process(&mut self, event: Event) -> (Option<Event>, Option<Event>) {
68 (Some(event), None)
69 }
70 fn tick(&mut self) -> (Option<Event>, Option<Event>) {
71 (None, None)
72 }
73}
74
75struct DumpState {
77 spec: DumpSpec,
78 remaining: u8,
79 last_present_seen: u32,
80}
81
82pub struct PlayitExecutor<T: PlayitTransport, const REC_CAP: usize = 256> {
90 transport: T,
91 line_buf: [u8; MAX_LINE],
92 line_len: usize,
93 dump: Option<DumpState>,
94 recorder: EventRecorder<REC_CAP>,
95}
96
97impl<T: PlayitTransport, const REC_CAP: usize> PlayitExecutor<T, REC_CAP> {
98 pub fn new(transport: T) -> Self {
100 Self {
101 transport,
102 line_buf: [0; MAX_LINE],
103 line_len: 0,
104 dump: None,
105 recorder: EventRecorder::new(),
106 }
107 }
108
109 pub fn poll<P: EventPipeline, F>(
118 &mut self,
119 root: &mut WidgetNode,
120 status: &StatusData,
121 fb: Option<&dyn FramebufferReader>,
122 pipeline: &mut P,
123 on_extension: F,
124 ) where
125 F: FnMut(&[u8]),
126 {
127 self.poll_with_callback(root, status, fb, pipeline, on_extension, |_event| {});
128 }
129
130 pub fn poll_with_callback<P: EventPipeline, F, A>(
133 &mut self,
134 root: &mut WidgetNode,
135 status: &StatusData,
136 fb: Option<&dyn FramebufferReader>,
137 pipeline: &mut P,
138 mut on_extension: F,
139 mut after_dispatch: A,
140 ) where
141 F: FnMut(&[u8]),
142 A: FnMut(&Event),
143 {
144 while let Some(byte) = self.transport.read_byte() {
146 if byte == b'\n' || byte == b'\r' {
147 if self.line_len > 0 {
148 let len = self.line_len;
149 let mut line = [0u8; MAX_LINE];
150 line[..len].copy_from_slice(&self.line_buf[..len]);
151 self.line_len = 0;
152 self.handle_line(
153 &line[..len],
154 root,
155 status,
156 pipeline,
157 &mut on_extension,
158 &mut after_dispatch,
159 );
160 }
161 } else if self.line_len < MAX_LINE {
162 self.line_buf[self.line_len] = byte;
163 self.line_len += 1;
164 }
165 }
166
167 self.dispatch_output_events(root, pipeline.tick(), &mut after_dispatch);
169
170 self.recorder.tick();
172
173 if let Some(reader) = fb {
175 self.emit_dump_if_ready(reader);
176 }
177 }
178
179 pub fn dispatch_event<P: EventPipeline, A>(
182 &mut self,
183 event: Event,
184 root: &mut WidgetNode,
185 pipeline: &mut P,
186 mut after_dispatch: A,
187 ) where
188 A: FnMut(&Event),
189 {
190 self.dispatch_output_events(root, pipeline.process(event), &mut after_dispatch);
191 }
192
193 fn handle_line<P: EventPipeline, F, A>(
198 &mut self,
199 line: &[u8],
200 root: &mut WidgetNode,
201 status: &StatusData,
202 pipeline: &mut P,
203 on_extension: &mut F,
204 after_dispatch: &mut A,
205 ) where
206 F: FnMut(&[u8]),
207 A: FnMut(&Event),
208 {
209 let Some(cmd) = parse_command(line) else {
210 self.send_response(&Response::Error("parse error"));
211 return;
212 };
213
214 match cmd {
215 Command::Status => {
216 self.send_response(&Response::Status(*status));
217 }
218
219 Command::Inject(spec) => {
220 self.inject_event(spec, root, pipeline, after_dispatch);
221 self.send_response(&Response::Ok);
222 }
223
224 Command::InjectTagged(tag, spec) => {
225 if let Some(node) = find_by_tag_mut(root, tag) {
226 let event = spec.to_event();
228 if self.recorder.is_running() {
229 self.recorder.record(spec);
230 }
231 node.dispatch_event(&event);
232 after_dispatch(&event);
233 self.send_response(&Response::Ok);
234 } else {
235 self.send_response(&Response::Error("tag not found"));
236 }
237 }
238
239 Command::Query(q) => self.handle_query(root, &q),
240
241 Command::DumpPixels(spec) => {
242 let present = spec.frames;
243 self.dump = Some(DumpState {
244 spec,
245 remaining: present,
246 last_present_seen: 0,
247 });
248 self.send_str(b"DUMP:queued\r\n");
249 }
250
251 Command::RecordStart => {
252 self.recorder.start();
253 self.send_str(b"REC:recording\r\n");
254 }
255
256 Command::RecordStop => {
257 self.recorder.stop();
258 self.dump_recording();
259 }
260
261 Command::RecordDump => {
262 self.dump_recording();
263 }
264
265 Command::Extension(payload) => {
266 on_extension(payload);
267 self.send_response(&Response::Ok);
268 }
269 }
270 }
271
272 fn inject_event<P: EventPipeline, A>(
275 &mut self,
276 spec: EventSpec,
277 root: &mut WidgetNode,
278 pipeline: &mut P,
279 after_dispatch: &mut A,
280 ) where
281 A: FnMut(&Event),
282 {
283 if self.recorder.is_running() {
284 self.recorder.record(spec);
285 }
286
287 let event = spec.to_event();
288 self.dispatch_output_events(root, pipeline.process(event), after_dispatch);
289 }
290
291 fn dispatch_output_events<A>(
292 &mut self,
293 root: &mut WidgetNode,
294 outputs: (Option<Event>, Option<Event>),
295 after_dispatch: &mut A,
296 ) where
297 A: FnMut(&Event),
298 {
299 if let Some(evt) = outputs.0 {
300 root.dispatch_event(&evt);
301 after_dispatch(&evt);
302 }
303 if let Some(evt) = outputs.1 {
304 root.dispatch_event(&evt);
305 after_dispatch(&evt);
306 }
307 }
308
309 fn handle_query(&mut self, root: &WidgetNode, q: &QuerySpec<'_>) {
310 match q {
311 QuerySpec::Bounds(tag) => {
312 if let Some(node) = find_by_tag(root, tag) {
313 let b = node.widget.borrow().bounds();
314 self.send_response(&Response::Bounds {
315 x: b.x,
316 y: b.y,
317 width: b.width,
318 height: b.height,
319 });
320 } else {
321 self.send_response(&Response::Error("tag not found"));
322 }
323 }
324 QuerySpec::Exists(tag) => {
325 let found = find_by_tag(root, tag).is_some();
326 self.send_response(&Response::Exists(found));
327 }
328 QuerySpec::ChildCount(tag) => {
329 if let Some(node) = find_by_tag(root, tag) {
330 let count = node.children.len().min(u16::MAX as usize) as u16;
331 self.send_response(&Response::ChildCount(count));
332 } else {
333 self.send_response(&Response::Error("tag not found"));
334 }
335 }
336 }
337 }
338
339 fn dump_recording(&mut self) {
344 let len = self.recorder.len();
345 {
347 let mut hdr = [0u8; 32];
348 let mut w = crate::protocol::BufWriter::new(&mut hdr);
349 w.write_str("REC:START,");
350 w.write_i32(len as i32);
351 w.write_str("\r\n");
352 let n = w.pos;
353 self.transport.write_bytes(&hdr[..n]);
354 }
355
356 for entry in self.recorder.drain() {
359 let mut prefix = [0u8; 16];
361 let pn = {
362 let mut w = crate::protocol::BufWriter::new(&mut prefix);
363 w.write_byte(b'@');
364 w.write_i32(entry.tick_delta as i32);
365 w.write_byte(b' ');
366 w.pos
367 };
368 self.transport.write_bytes(&prefix[..pn]);
369
370 let mut spec_buf = [0u8; 128];
372 let sn = format_event_spec(&entry.spec, &mut spec_buf);
373 self.transport.write_bytes(&spec_buf[..sn]);
374 self.transport.write_bytes(b"\r\n");
375 }
376
377 self.transport.write_bytes(b"REC:END\r\n");
378 }
379
380 fn emit_dump_if_ready(&mut self, reader: &dyn FramebufferReader) {
385 let Some(state) = self.dump.as_mut() else {
386 return;
387 };
388
389 let current_present = reader.present_count();
390 if state.last_present_seen == 0 && state.remaining == state.spec.frames {
391 state.last_present_seen = current_present;
392 return;
393 }
394 if current_present == state.last_present_seen {
395 return;
396 }
397
398 self.transport.write_bytes(b"F\r\n");
399
400 let spec = state.spec;
401 let mut row_buf = [0u32; 40];
402 for row in 0..spec.height {
403 let n = reader.read_row(
404 spec.x,
405 spec.y + row as i32,
406 spec.width,
407 &mut row_buf[..spec.width as usize],
408 );
409 for (i, &pixel) in row_buf[..n].iter().enumerate() {
410 let mut hex = [0u8; 8];
411 write_hex_u32(pixel, &mut hex);
412 self.transport.write_bytes(&hex);
413 if i + 1 < n {
414 self.transport.write_bytes(b" ");
415 }
416 }
417 self.transport.write_bytes(b"\r\n");
418 }
419
420 state.last_present_seen = current_present;
421 state.remaining -= 1;
422
423 if state.remaining == 0 {
424 self.dump = None;
425 let mut buf = [0u8; 16];
426 let n = format_response(&Response::DumpEnd, &mut buf);
427 self.transport.write_bytes(&buf[..n]);
428 }
429 }
430
431 fn send_response(&mut self, resp: &Response<'_>) {
436 let mut buf = [0u8; 128];
437 let n = format_response(resp, &mut buf);
438 self.transport.write_bytes(&buf[..n]);
439 }
440
441 fn send_str(&mut self, s: &[u8]) {
442 self.transport.write_bytes(s);
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449 use crate::transport::PlayitTransport;
450 use rlvgl_core::renderer::Renderer;
451 use rlvgl_core::widget::{Rect, Widget};
452 use std::cell::RefCell;
453 use std::collections::VecDeque;
454 use std::rc::Rc;
455
456 #[derive(Default)]
457 struct VecTransport {
458 incoming: VecDeque<u8>,
459 outgoing: Vec<u8>,
460 }
461
462 impl VecTransport {
463 fn with_input(input: &[u8]) -> Self {
464 Self {
465 incoming: input.iter().copied().collect(),
466 outgoing: Vec::new(),
467 }
468 }
469 }
470
471 impl PlayitTransport for VecTransport {
472 fn read_byte(&mut self) -> Option<u8> {
473 self.incoming.pop_front()
474 }
475
476 fn write_bytes(&mut self, bytes: &[u8]) {
477 self.outgoing.extend_from_slice(bytes);
478 }
479 }
480
481 type TestExecutor = PlayitExecutor<VecTransport, 16>;
482
483 struct RecordingWidget {
484 bounds: Rect,
485 events: Rc<RefCell<Vec<Event>>>,
486 }
487
488 impl Widget for RecordingWidget {
489 fn bounds(&self) -> Rect {
490 self.bounds
491 }
492
493 fn draw(&self, _renderer: &mut dyn Renderer) {}
494
495 fn handle_event(&mut self, event: &Event) -> bool {
496 self.events.borrow_mut().push(event.clone());
497 false
498 }
499 }
500
501 fn recording_node(
502 tag: Option<&'static str>,
503 events: Rc<RefCell<Vec<Event>>>,
504 bounds: Rect,
505 ) -> WidgetNode {
506 WidgetNode {
507 widget: Rc::new(RefCell::new(RecordingWidget { bounds, events })),
508 children: Vec::new(),
509 tag,
510 }
511 }
512
513 struct TickPipeline {
514 emit_tick: bool,
515 }
516
517 impl EventPipeline for TickPipeline {
518 fn process(&mut self, event: Event) -> (Option<Event>, Option<Event>) {
519 (Some(event), None)
520 }
521
522 fn tick(&mut self) -> (Option<Event>, Option<Event>) {
523 if self.emit_tick {
524 self.emit_tick = false;
525 (Some(Event::Tick), None)
526 } else {
527 (None, None)
528 }
529 }
530 }
531
532 #[test]
533 fn poll_with_callback_reports_dispatches_from_transport_and_pipeline_tick() {
534 let widget_events = Rc::new(RefCell::new(Vec::new()));
535 let mut root = recording_node(
536 Some("root"),
537 widget_events.clone(),
538 Rect {
539 x: 0,
540 y: 0,
541 width: 10,
542 height: 10,
543 },
544 );
545 let mut executor = TestExecutor::new(VecTransport::with_input(b"T10,20\r\n"));
546 let mut pipeline = TickPipeline { emit_tick: true };
547 let mut callback_events = Vec::new();
548
549 executor.poll_with_callback(
550 &mut root,
551 &StatusData::default(),
552 None,
553 &mut pipeline,
554 |_payload| {},
555 |event| callback_events.push(event.clone()),
556 );
557
558 assert_eq!(
559 callback_events,
560 vec![Event::PressRelease { x: 10, y: 20 }, Event::Tick]
561 );
562 assert_eq!(*widget_events.borrow(), callback_events);
563 }
564
565 #[test]
566 fn tagged_inject_and_runtime_dispatch_both_invoke_after_dispatch() {
567 let root_events = Rc::new(RefCell::new(Vec::new()));
568 let child_events = Rc::new(RefCell::new(Vec::new()));
569 let mut root = recording_node(
570 Some("root"),
571 root_events,
572 Rect {
573 x: 0,
574 y: 0,
575 width: 10,
576 height: 10,
577 },
578 );
579 root.children.push(recording_node(
580 Some("target"),
581 child_events.clone(),
582 Rect {
583 x: 5,
584 y: 6,
585 width: 20,
586 height: 30,
587 },
588 ));
589
590 let mut executor = TestExecutor::new(VecTransport::with_input(b"T@target:15,16\r\n"));
591 let mut pipeline = TickPipeline { emit_tick: false };
592 let mut callback_events = Vec::new();
593
594 executor.poll_with_callback(
595 &mut root,
596 &StatusData::default(),
597 None,
598 &mut pipeline,
599 |_payload| {},
600 |event| callback_events.push(event.clone()),
601 );
602 executor.dispatch_event(
603 Event::KeyDown {
604 key: rlvgl_core::event::Key::Enter,
605 },
606 &mut root,
607 &mut pipeline,
608 |event| callback_events.push(event.clone()),
609 );
610
611 assert_eq!(
612 callback_events,
613 vec![
614 Event::PressRelease { x: 15, y: 16 },
615 Event::KeyDown {
616 key: rlvgl_core::event::Key::Enter
617 }
618 ]
619 );
620 assert_eq!(
621 *child_events.borrow(),
622 vec![
623 Event::PressRelease { x: 15, y: 16 },
624 Event::KeyDown {
625 key: rlvgl_core::event::Key::Enter
626 }
627 ]
628 );
629 }
630}