1use super::{Application, WindowHandle, profiling::GpuResourceSummary};
9use incular_config::Constraints;
10use incular_core::{
11 Code, InputEvent, KeyState, KeyboardEvent, KeyboardKey, Location, Modifiers, NamedKey, Offset,
12 PointerPhase, Rect,
13};
14use incular_platform::WindowId;
15use incular_semantics::SemanticActionKind;
16use std::collections::{HashMap, HashSet};
17use std::fmt;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, Mutex, mpsc};
20use std::time::{Duration, Instant};
21
22const SIMULATION_REPLY_TIMEOUT: Duration = Duration::from_secs(30);
23const MAX_SIMULATION_REQUESTS_PER_TURN: usize = 64;
24
25#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct Screenshot {
32 width: u32,
33 height: u32,
34 rgba8: Vec<u8>,
35}
36
37impl Screenshot {
38 pub fn from_rgba8(width: u32, height: u32, rgba8: Vec<u8>) -> Result<Self, SimulationError> {
40 let expected = usize::try_from(width)
41 .ok()
42 .and_then(|width| {
43 usize::try_from(height)
44 .ok()
45 .and_then(|height| width.checked_mul(height))
46 })
47 .and_then(|pixels| pixels.checked_mul(4))
48 .ok_or_else(|| {
49 SimulationError::InvalidInput("screenshot dimensions overflow".into())
50 })?;
51 if expected == 0 || rgba8.len() != expected {
52 return Err(SimulationError::InvalidInput(format!(
53 "screenshot pixel length {} does not match {}x{} RGBA8",
54 rgba8.len(),
55 width,
56 height
57 )));
58 }
59 Ok(Self {
60 width,
61 height,
62 rgba8,
63 })
64 }
65
66 #[must_use]
67 pub const fn width(&self) -> u32 {
68 self.width
69 }
70
71 #[must_use]
72 pub const fn height(&self) -> u32 {
73 self.height
74 }
75
76 #[must_use]
78 pub fn pixels(&self) -> &[u8] {
79 &self.rgba8
80 }
81
82 #[must_use]
84 pub fn into_pixels(self) -> Vec<u8> {
85 self.rgba8
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq)]
91pub enum SimulationError {
92 Disconnected,
95 Timeout,
97 WindowNotFound(WindowId),
99 WindowClosed(WindowId),
101 ElementNotFound(String),
103 InvalidInput(String),
105 CaptureUnavailable(String),
107 FrameFailed(String),
109}
110
111impl fmt::Display for SimulationError {
112 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 Self::Disconnected => {
115 formatter.write_str("the Incular simulation bridge is disconnected")
116 }
117 Self::Timeout => formatter.write_str("the Incular simulation request timed out"),
118 Self::WindowNotFound(id) => write!(formatter, "simulation window {id} was not found"),
119 Self::WindowClosed(id) => write!(formatter, "simulation window {id} was closed"),
120 Self::ElementNotFound(label) => {
121 write!(formatter, "no semantic element labeled {label:?} was found")
122 }
123 Self::InvalidInput(message) => write!(formatter, "invalid simulation input: {message}"),
124 Self::CaptureUnavailable(message) => {
125 write!(formatter, "window capture unavailable: {message}")
126 }
127 Self::FrameFailed(message) => write!(formatter, "simulation frame failed: {message}"),
128 }
129 }
130}
131
132impl std::error::Error for SimulationError {}
133
134type Reply<T> = mpsc::SyncSender<Result<T, SimulationError>>;
135
136pub(crate) enum SimulationRequest {
137 Input {
138 window_id: WindowId,
139 event: InputEvent,
140 reply: Reply<()>,
141 },
142 ClickAt {
143 window_id: WindowId,
144 position: Offset,
145 reply: Reply<()>,
146 },
147 ClickLabel {
148 window_id: WindowId,
149 label: String,
150 reply: Reply<()>,
151 },
152 WaitForFrame {
153 window_id: WindowId,
154 reply: Reply<()>,
155 },
156 Capture {
157 window_id: WindowId,
158 reply: Reply<Screenshot>,
159 },
160 GpuResources {
161 window_id: WindowId,
162 reply: Reply<GpuResourceSummary>,
163 },
164}
165
166pub(crate) struct SimulationBridge {
167 sender: mpsc::Sender<SimulationRequest>,
168 wake: Mutex<Option<Arc<dyn super::RuntimeWake>>>,
169 accepting: AtomicBool,
170}
171
172impl SimulationBridge {
173 pub(crate) fn new(sender: mpsc::Sender<SimulationRequest>) -> Self {
174 Self {
175 sender,
176 wake: Mutex::new(None),
177 accepting: AtomicBool::new(true),
178 }
179 }
180
181 fn enqueue(&self, request: SimulationRequest) -> Result<(), SimulationError> {
182 if !self.accepting.load(Ordering::Acquire) {
183 return Err(SimulationError::Disconnected);
184 }
185 self.sender
186 .send(request)
187 .map_err(|_| SimulationError::Disconnected)?;
188 if let Some(wake) = self.wake.lock().expect("simulation wake mutex").as_ref() {
189 wake.wake();
190 }
191 Ok(())
192 }
193
194 pub(crate) fn set_wake(&self, wake: Arc<dyn super::RuntimeWake>) {
195 *self.wake.lock().expect("simulation wake mutex") = Some(wake);
196 }
197
198 pub(crate) fn stop(&self) {
199 self.accepting.store(false, Ordering::Release);
200 }
201}
202
203#[derive(Default)]
204struct KeyboardState {
205 modifiers: Modifiers,
206 pressed: HashSet<Code>,
207}
208
209#[derive(Default)]
216pub(crate) struct SimulationWaiters {
217 frame: HashMap<WindowId, Vec<mpsc::SyncSender<Result<(), SimulationError>>>>,
218 capture: HashMap<WindowId, Vec<mpsc::SyncSender<Result<Screenshot, SimulationError>>>>,
219 gpu: HashMap<WindowId, Vec<mpsc::SyncSender<Result<GpuResourceSummary, SimulationError>>>>,
220}
221
222impl SimulationWaiters {
223 fn settle<T: Clone>(
224 waiters: &mut HashMap<WindowId, Vec<mpsc::SyncSender<Result<T, SimulationError>>>>,
225 window_id: WindowId,
226 result: Result<T, SimulationError>,
227 ) {
228 if let Some(pending) = waiters.remove(&window_id) {
229 for reply in pending {
230 let _ = reply.send(result.clone());
231 }
232 }
233 }
234
235 #[must_use]
237 pub(crate) fn frame_pending(&self, window_id: WindowId) -> bool {
238 self.frame
239 .get(&window_id)
240 .is_some_and(|waiters| !waiters.is_empty())
241 }
242
243 #[must_use]
245 pub(crate) fn capture_pending(&self, window_id: WindowId) -> bool {
246 self.capture
247 .get(&window_id)
248 .is_some_and(|waiters| !waiters.is_empty())
249 }
250
251 pub(crate) fn insert_frame(
253 &mut self,
254 window_id: WindowId,
255 reply: mpsc::SyncSender<Result<(), SimulationError>>,
256 ) {
257 self.frame.entry(window_id).or_default().push(reply);
258 }
259
260 pub(crate) fn insert_capture(
262 &mut self,
263 window_id: WindowId,
264 reply: mpsc::SyncSender<Result<Screenshot, SimulationError>>,
265 ) {
266 self.capture.entry(window_id).or_default().push(reply);
267 }
268
269 pub(crate) fn insert_gpu(
271 &mut self,
272 window_id: WindowId,
273 reply: mpsc::SyncSender<Result<GpuResourceSummary, SimulationError>>,
274 ) {
275 self.gpu.entry(window_id).or_default().push(reply);
276 }
277
278 pub(crate) fn complete_frame(
282 &mut self,
283 window_id: WindowId,
284 presented: bool,
285 capture: Option<Result<Screenshot, String>>,
286 ) {
287 if !presented {
288 return;
289 }
290 Self::settle(&mut self.frame, window_id, Ok(()));
291 let result = capture
292 .map(|result| result.map_err(SimulationError::CaptureUnavailable))
293 .unwrap_or_else(|| {
294 Err(SimulationError::CaptureUnavailable(
295 "the renderer did not return a capture".into(),
296 ))
297 });
298 Self::settle(&mut self.capture, window_id, result);
299 }
300
301 pub(crate) fn fail_frame(&mut self, window_id: WindowId, message: impl Into<String>) {
303 let error = SimulationError::FrameFailed(message.into());
304 Self::settle(&mut self.frame, window_id, Err(error.clone()));
305 let capture_error = SimulationError::CaptureUnavailable(error.to_string());
306 Self::settle(&mut self.capture, window_id, Err(capture_error));
307 }
308
309 pub(crate) fn fail_window(&mut self, window_id: WindowId) {
312 let error = SimulationError::WindowClosed(window_id);
313 Self::settle(&mut self.frame, window_id, Err(error.clone()));
314 Self::settle(&mut self.capture, window_id, Err(error.clone()));
315 Self::settle(&mut self.gpu, window_id, Err(error));
319 }
320
321 pub(crate) fn complete_gpu(&mut self, window_id: WindowId, summary: GpuResourceSummary) {
324 Self::settle(&mut self.gpu, window_id, Ok(summary));
325 }
326
327 pub(crate) fn gpu_windows(&self) -> Vec<WindowId> {
330 self.gpu.keys().copied().collect()
331 }
332
333 pub(crate) fn take_gpu_waiters(
336 &mut self,
337 window_id: WindowId,
338 ) -> Vec<Reply<GpuResourceSummary>> {
339 self.gpu.remove(&window_id).unwrap_or_default()
340 }
341}
342
343#[derive(Clone)]
349pub struct Simulation {
350 bridge: Arc<SimulationBridge>,
351 window_id: WindowId,
352 keyboard: Arc<Mutex<KeyboardState>>,
353}
354
355impl Simulation {
356 pub(crate) fn new(bridge: Arc<SimulationBridge>, window_id: WindowId) -> Self {
357 Self {
358 bridge,
359 window_id,
360 keyboard: Arc::new(Mutex::new(KeyboardState::default())),
361 }
362 }
363
364 #[must_use]
366 pub fn window(&self, handle: &WindowHandle) -> Self {
367 self.for_window(handle.id())
368 }
369
370 #[must_use]
372 pub fn for_window(&self, window_id: WindowId) -> Self {
373 Self {
374 bridge: self.bridge.clone(),
375 window_id,
376 keyboard: self.keyboard.clone(),
377 }
378 }
379
380 #[must_use]
381 pub const fn window_id(&self) -> WindowId {
382 self.window_id
383 }
384
385 #[must_use]
387 pub fn mouse(&self) -> SimulationMouse {
388 SimulationMouse {
389 simulation: self.clone(),
390 }
391 }
392
393 #[must_use]
395 pub fn keyboard(&self) -> SimulationKeyboard {
396 SimulationKeyboard {
397 simulation: self.clone(),
398 }
399 }
400
401 pub fn click(&self, label: impl Into<String>) -> Result<(), SimulationError> {
404 self.mouse().click(label)
405 }
406
407 pub fn click_at(&self, position: Offset) -> Result<(), SimulationError> {
408 self.mouse().click_at(position)
409 }
410
411 pub fn move_mouse_to(&self, position: Offset) -> Result<(), SimulationError> {
412 self.mouse().move_to(position)
413 }
414
415 pub fn scroll(&self, delta: Offset) -> Result<(), SimulationError> {
416 self.mouse().scroll(delta)
417 }
418
419 pub fn key_down(&self, code: Code) -> Result<(), SimulationError> {
420 self.keyboard().key_down(code)
421 }
422
423 pub fn key_up(&self, code: Code) -> Result<(), SimulationError> {
424 self.keyboard().key_up(code)
425 }
426
427 pub fn press(&self, code: Code) -> Result<(), SimulationError> {
428 self.keyboard().press(code)
429 }
430
431 pub fn press_key(&self, key: KeyboardKey, code: Code) -> Result<(), SimulationError> {
432 self.keyboard().press_key(key, code)
433 }
434
435 pub fn type_text(&self, text: impl Into<String>) -> Result<(), SimulationError> {
436 self.keyboard().type_text(text)
437 }
438
439 pub fn wait_for_frame(&self) -> Result<(), SimulationError> {
441 self.request(|reply| SimulationRequest::WaitForFrame {
442 window_id: self.window_id,
443 reply,
444 })
445 }
446
447 pub fn capture(&self) -> Result<Screenshot, SimulationError> {
449 self.request(|reply| SimulationRequest::Capture {
450 window_id: self.window_id,
451 reply,
452 })
453 }
454
455 pub fn query_gpu_resources(&self) -> Result<GpuResourceSummary, SimulationError> {
459 self.request(|reply| SimulationRequest::GpuResources {
460 window_id: self.window_id,
461 reply,
462 })
463 }
464
465 pub fn release_all_keys(&self) -> Result<(), SimulationError> {
467 self.keyboard().release_all()
468 }
469
470 fn request<T>(
471 &self,
472 build: impl FnOnce(Reply<T>) -> SimulationRequest,
473 ) -> Result<T, SimulationError> {
474 let (sender, receiver) = mpsc::sync_channel(1);
475 self.bridge.enqueue(build(sender))?;
476 match receiver.recv_timeout(SIMULATION_REPLY_TIMEOUT) {
477 Ok(result) => result,
478 Err(mpsc::RecvTimeoutError::Timeout) => Err(SimulationError::Timeout),
479 Err(mpsc::RecvTimeoutError::Disconnected) => Err(SimulationError::Disconnected),
480 }
481 }
482
483 fn input(&self, event: InputEvent) -> Result<(), SimulationError> {
484 self.request(|reply| SimulationRequest::Input {
485 window_id: self.window_id,
486 event,
487 reply,
488 })
489 }
490}
491
492#[derive(Clone)]
496pub struct SimulationMouse {
497 simulation: Simulation,
498}
499
500impl SimulationMouse {
501 pub fn move_to(&self, position: Offset) -> Result<(), SimulationError> {
502 self.simulation.input(InputEvent::Pointer {
503 phase: PointerPhase::Move,
504 position,
505 })
506 }
507
508 pub fn down(&self, position: Offset) -> Result<(), SimulationError> {
509 self.simulation.input(InputEvent::Pointer {
510 phase: PointerPhase::Down,
511 position,
512 })
513 }
514
515 pub fn up(&self, position: Offset) -> Result<(), SimulationError> {
516 self.simulation.input(InputEvent::Pointer {
517 phase: PointerPhase::Up,
518 position,
519 })
520 }
521
522 pub fn click(&self, label: impl Into<String>) -> Result<(), SimulationError> {
523 self.simulation
524 .request(|reply| SimulationRequest::ClickLabel {
525 window_id: self.simulation.window_id,
526 label: label.into(),
527 reply,
528 })
529 }
530
531 pub fn click_at(&self, position: Offset) -> Result<(), SimulationError> {
532 self.simulation.request(|reply| SimulationRequest::ClickAt {
533 window_id: self.simulation.window_id,
534 position,
535 reply,
536 })
537 }
538
539 pub fn scroll(&self, delta: Offset) -> Result<(), SimulationError> {
540 self.simulation.input(InputEvent::Scroll { delta })
541 }
542}
543
544#[derive(Clone)]
549pub struct SimulationKeyboard {
550 simulation: Simulation,
551}
552
553impl SimulationKeyboard {
554 pub fn key_down(&self, code: Code) -> Result<(), SimulationError> {
555 self.send_key(KeyState::Down, None, code)
556 }
557
558 pub fn key_up(&self, code: Code) -> Result<(), SimulationError> {
559 self.send_key(KeyState::Up, None, code)
560 }
561
562 pub fn press(&self, code: Code) -> Result<(), SimulationError> {
563 self.key_down(code)?;
564 self.key_up(code)
565 }
566
567 pub fn press_key(&self, key: KeyboardKey, code: Code) -> Result<(), SimulationError> {
568 self.send_key(KeyState::Down, Some(key.clone()), code)?;
569 self.send_key(KeyState::Up, Some(key), code)
570 }
571
572 pub fn type_text(&self, text: impl Into<String>) -> Result<(), SimulationError> {
573 let text = text.into();
574 if text.chars().any(char::is_control) {
575 return Err(SimulationError::InvalidInput(
576 "type_text accepts printable committed text; use press for commands".into(),
577 ));
578 }
579 self.simulation.input(InputEvent::Text(text))
580 }
581
582 pub fn release_all(&self) -> Result<(), SimulationError> {
583 let pressed = self
584 .simulation
585 .keyboard
586 .lock()
587 .expect("simulation keyboard mutex")
588 .pressed
589 .iter()
590 .copied()
591 .collect::<Vec<_>>();
592 for code in pressed {
593 self.key_up(code)?;
594 }
595 Ok(())
596 }
597
598 fn send_key(
599 &self,
600 state: KeyState,
601 explicit_key: Option<KeyboardKey>,
602 code: Code,
603 ) -> Result<(), SimulationError> {
604 let (key, modifiers, location, repeat, was_pressed) = {
605 let mut keyboard = self
606 .simulation
607 .keyboard
608 .lock()
609 .expect("simulation keyboard mutex");
610 let was_pressed = keyboard.pressed.contains(&code);
611 if state.is_down() {
612 if let Some(modifier) = modifier_for_code(code) {
613 keyboard.modifiers.insert(modifier);
614 }
615 keyboard.pressed.insert(code);
616 }
617 let values = (
618 explicit_key.unwrap_or_else(|| logical_key(code)),
619 keyboard.modifiers,
620 location_for_code(code),
621 state.is_down() && was_pressed,
622 was_pressed,
623 );
624 if state.is_up() {
625 keyboard.pressed.remove(&code);
628 if let Some(modifier) = modifier_for_code(code) {
629 keyboard.modifiers.remove(modifier);
630 }
631 }
632 values
633 };
634 let event = KeyboardEvent {
635 state,
636 key,
637 code,
638 location,
639 modifiers,
640 repeat,
641 is_composing: false,
642 };
643 let result = self.simulation.input(InputEvent::Key(event));
644 if result.is_err() {
645 let mut keyboard = self
647 .simulation
648 .keyboard
649 .lock()
650 .expect("simulation keyboard mutex");
651 if state.is_down() {
652 if !was_pressed {
653 keyboard.pressed.remove(&code);
654 }
655 } else {
656 keyboard.pressed.insert(code);
657 }
658 keyboard.modifiers = modifiers_for_pressed(&keyboard.pressed);
659 }
660 result
661 }
662}
663
664fn modifier_for_code(code: Code) -> Option<Modifiers> {
665 match code {
666 Code::AltLeft | Code::AltRight => Some(Modifiers::ALT),
667 Code::ControlLeft | Code::ControlRight => Some(Modifiers::CONTROL),
668 Code::MetaLeft | Code::MetaRight => Some(Modifiers::META),
669 Code::ShiftLeft | Code::ShiftRight => Some(Modifiers::SHIFT),
670 _ => None,
671 }
672}
673
674fn modifiers_for_pressed(pressed: &HashSet<Code>) -> Modifiers {
675 pressed
676 .iter()
677 .fold(Modifiers::default(), |mut modifiers, code| {
678 if let Some(modifier) = modifier_for_code(*code) {
679 modifiers.insert(modifier);
680 }
681 modifiers
682 })
683}
684
685fn location_for_code(code: Code) -> Location {
686 match code {
687 Code::AltLeft | Code::ControlLeft | Code::MetaLeft | Code::ShiftLeft => Location::Left,
688 Code::AltRight | Code::ControlRight | Code::MetaRight | Code::ShiftRight => Location::Right,
689 Code::NumpadEnter => Location::Numpad,
690 _ => Location::Standard,
691 }
692}
693
694fn logical_key(code: Code) -> KeyboardKey {
695 let character = match code {
696 Code::Backquote => Some("`"),
697 Code::Backslash => Some("\\"),
698 Code::BracketLeft => Some("["),
699 Code::BracketRight => Some("]"),
700 Code::Comma => Some(","),
701 Code::Digit0 => Some("0"),
702 Code::Digit1 => Some("1"),
703 Code::Digit2 => Some("2"),
704 Code::Digit3 => Some("3"),
705 Code::Digit4 => Some("4"),
706 Code::Digit5 => Some("5"),
707 Code::Digit6 => Some("6"),
708 Code::Digit7 => Some("7"),
709 Code::Digit8 => Some("8"),
710 Code::Digit9 => Some("9"),
711 Code::Equal => Some("="),
712 Code::KeyA => Some("a"),
713 Code::KeyB => Some("b"),
714 Code::KeyC => Some("c"),
715 Code::KeyD => Some("d"),
716 Code::KeyE => Some("e"),
717 Code::KeyF => Some("f"),
718 Code::KeyG => Some("g"),
719 Code::KeyH => Some("h"),
720 Code::KeyI => Some("i"),
721 Code::KeyJ => Some("j"),
722 Code::KeyK => Some("k"),
723 Code::KeyL => Some("l"),
724 Code::KeyM => Some("m"),
725 Code::KeyN => Some("n"),
726 Code::KeyO => Some("o"),
727 Code::KeyP => Some("p"),
728 Code::KeyQ => Some("q"),
729 Code::KeyR => Some("r"),
730 Code::KeyS => Some("s"),
731 Code::KeyT => Some("t"),
732 Code::KeyU => Some("u"),
733 Code::KeyV => Some("v"),
734 Code::KeyW => Some("w"),
735 Code::KeyX => Some("x"),
736 Code::KeyY => Some("y"),
737 Code::KeyZ => Some("z"),
738 Code::Minus => Some("-"),
739 Code::Period => Some("."),
740 Code::Quote => Some("'"),
741 Code::Semicolon => Some(";"),
742 Code::Slash => Some("/"),
743 Code::Space => Some(" "),
744 Code::Numpad0 => Some("0"),
745 Code::Numpad1 => Some("1"),
746 Code::Numpad2 => Some("2"),
747 Code::Numpad3 => Some("3"),
748 Code::Numpad4 => Some("4"),
749 Code::Numpad5 => Some("5"),
750 Code::Numpad6 => Some("6"),
751 Code::Numpad7 => Some("7"),
752 Code::Numpad8 => Some("8"),
753 Code::Numpad9 => Some("9"),
754 Code::NumpadAdd => Some("+"),
755 Code::NumpadComma => Some(","),
756 Code::NumpadDecimal => Some("."),
757 Code::NumpadDivide => Some("/"),
758 Code::NumpadEqual => Some("="),
759 Code::NumpadMultiply | Code::NumpadStar => Some("*"),
760 Code::NumpadSubtract => Some("-"),
761 _ => None,
762 };
763 if let Some(character) = character {
764 return KeyboardKey::Character(character.into());
765 }
766 let named = match code {
767 Code::AltLeft | Code::AltRight => NamedKey::Alt,
768 Code::CapsLock => NamedKey::CapsLock,
769 Code::ControlLeft | Code::ControlRight => NamedKey::Control,
770 Code::Enter | Code::NumpadEnter => NamedKey::Enter,
771 Code::Escape => NamedKey::Escape,
772 Code::MetaLeft | Code::MetaRight => NamedKey::Meta,
773 Code::NumLock => NamedKey::NumLock,
774 Code::ScrollLock => NamedKey::ScrollLock,
775 Code::ShiftLeft | Code::ShiftRight => NamedKey::Shift,
776 Code::Tab => NamedKey::Tab,
777 Code::Backspace => NamedKey::Backspace,
778 Code::Delete => NamedKey::Delete,
779 Code::Insert => NamedKey::Insert,
780 Code::PageDown => NamedKey::PageDown,
781 Code::PageUp => NamedKey::PageUp,
782 Code::ArrowDown => NamedKey::ArrowDown,
783 Code::ArrowLeft => NamedKey::ArrowLeft,
784 Code::ArrowRight => NamedKey::ArrowRight,
785 Code::ArrowUp => NamedKey::ArrowUp,
786 Code::End => NamedKey::End,
787 Code::Home => NamedKey::Home,
788 _ => NamedKey::Unidentified,
789 };
790 KeyboardKey::Named(named)
791}
792
793fn valid_offset(offset: Offset) -> Result<(), SimulationError> {
794 if offset.x.is_finite() && offset.y.is_finite() {
795 Ok(())
796 } else {
797 Err(SimulationError::InvalidInput(
798 "pointer and scroll offsets must be finite".into(),
799 ))
800 }
801}
802
803impl Application {
804 pub fn process_simulation_requests(&mut self) {
807 for _ in 0..MAX_SIMULATION_REQUESTS_PER_TURN {
808 let request = match self.simulation_receiver.try_recv() {
809 Ok(request) => request,
810 Err(mpsc::TryRecvError::Empty | mpsc::TryRecvError::Disconnected) => break,
811 };
812 self.handle_simulation_request(request);
813 }
814 }
815
816 fn handle_simulation_request(&mut self, request: SimulationRequest) {
817 match request {
818 SimulationRequest::Input {
819 window_id,
820 event,
821 reply,
822 } => {
823 let result = self
824 .ensure_simulation_layout(window_id)
825 .and_then(|_| self.dispatch_simulation_input(window_id, event));
826 let _ = reply.send(result);
827 }
828 SimulationRequest::ClickAt {
829 window_id,
830 position,
831 reply,
832 } => {
833 let _ = reply.send(self.dispatch_simulation_click(window_id, position));
834 }
835 SimulationRequest::ClickLabel {
836 window_id,
837 label,
838 reply,
839 } => {
840 let trigger = format!("click({label:?})");
841 let result = self
842 .simulation_label_bounds(window_id, &label)
843 .and_then(|bounds| {
844 self.dispatch_simulation_click_prepared(
845 window_id,
846 bounds.center(),
847 &trigger,
848 )
849 });
850 let _ = reply.send(result);
851 }
852 SimulationRequest::WaitForFrame { window_id, reply } => {
853 if self.contains_window(window_id) {
854 let _ =
855 self.with_window_mut(window_id, |record| record.runtime.request_frame());
856 self.simulation_waiters.insert_frame(window_id, reply);
857 } else {
858 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
859 }
860 }
861 SimulationRequest::Capture { window_id, reply } => {
862 if self.contains_window(window_id) {
863 let _ =
864 self.with_window_mut(window_id, |record| record.runtime.request_frame());
865 self.simulation_waiters.insert_capture(window_id, reply);
866 } else {
867 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
868 }
869 }
870 SimulationRequest::GpuResources { window_id, reply } => {
871 if self.contains_window(window_id) {
875 self.simulation_waiters.insert_gpu(window_id, reply);
876 } else {
877 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
878 }
879 }
880 }
881 }
882
883 fn dispatch_simulation_input(
884 &mut self,
885 window_id: WindowId,
886 event: InputEvent,
887 ) -> Result<(), SimulationError> {
888 let trigger = format!("simulation::{}", super::diagnostic_input_trigger(&event));
889 self.dispatch_simulation_input_with_trigger(window_id, event, &trigger)
890 }
891
892 fn dispatch_simulation_input_with_trigger(
893 &mut self,
894 window_id: WindowId,
895 event: InputEvent,
896 trigger: &str,
897 ) -> Result<(), SimulationError> {
898 if let InputEvent::Pointer { position, .. } | InputEvent::PointerWithId { position, .. } =
899 &event
900 {
901 valid_offset(*position)?;
902 }
903 if let InputEvent::Scroll { delta } = &event {
904 valid_offset(*delta)?;
905 }
906 if !self.contains_window(window_id) {
907 return Err(SimulationError::WindowNotFound(window_id));
908 }
909 let applied = self.with_window_mut(window_id, |record| {
910 record.input_events = record.input_events.wrapping_add(1);
911 let _ = record.runtime.handle_input_with_trigger(event, trigger);
912 });
913 applied
914 .map(|_| ())
915 .ok_or(SimulationError::WindowNotFound(window_id))
916 }
917
918 fn dispatch_simulation_click(
919 &mut self,
920 window_id: WindowId,
921 position: Offset,
922 ) -> Result<(), SimulationError> {
923 valid_offset(position)?;
924 self.ensure_simulation_layout(window_id)?;
925 self.dispatch_simulation_click_prepared(
926 window_id,
927 position,
928 &format!("click_at({:.1}, {:.1})", position.x, position.y),
929 )
930 }
931
932 fn dispatch_simulation_click_prepared(
933 &mut self,
934 window_id: WindowId,
935 position: Offset,
936 trigger: &str,
937 ) -> Result<(), SimulationError> {
938 self.dispatch_simulation_input_with_trigger(
939 window_id,
940 InputEvent::Pointer {
941 phase: PointerPhase::Move,
942 position,
943 },
944 trigger,
945 )?;
946 self.dispatch_simulation_input_with_trigger(
947 window_id,
948 InputEvent::Pointer {
949 phase: PointerPhase::Down,
950 position,
951 },
952 trigger,
953 )?;
954 self.dispatch_simulation_input_with_trigger(
955 window_id,
956 InputEvent::Pointer {
957 phase: PointerPhase::Up,
958 position,
959 },
960 trigger,
961 )
962 }
963
964 fn ensure_simulation_layout(&mut self, window_id: WindowId) -> Result<(), SimulationError> {
965 let diagnostics = self
966 .window_diagnostics(window_id)
967 .ok_or(SimulationError::WindowNotFound(window_id))?;
968 if diagnostics.physical_size.is_zero() {
969 return Err(SimulationError::FrameFailed(
970 "the target window has a zero-sized surface".into(),
971 ));
972 }
973 self.run_window_frame_at(
974 window_id,
975 Constraints::tight(diagnostics.logical_size),
976 Instant::now(),
977 )
978 .map_err(|error| SimulationError::FrameFailed(format!("{error:?}")))?;
979 let _ = self.with_window_mut(window_id, |record| record.runtime.request_frame());
982 Ok(())
983 }
984
985 fn simulation_label_bounds(
986 &mut self,
987 window_id: WindowId,
988 label: &str,
989 ) -> Result<Rect, SimulationError> {
990 self.set_semantics_enabled(window_id, true);
993 self.ensure_simulation_layout(window_id)?;
994 let viewport = self
995 .window_diagnostics(window_id)
996 .map(|diagnostics| Rect::from_origin_size(Offset::ZERO, diagnostics.logical_size))
997 .ok_or(SimulationError::WindowNotFound(window_id))?;
998 let result = self.registry.borrow().get(window_id).and_then(|record| {
999 let mut fallback = None;
1000 record
1001 .runtime
1002 .tree()
1003 .semantics()
1004 .iter()
1005 .find_map(|(_, node)| {
1006 if node.label.as_deref() != Some(label) || !node.state.enabled {
1007 return None;
1008 }
1009 let visible_bounds = node.bounds.intersection(viewport)?;
1015 if visible_bounds.size.width <= 0.0 || visible_bounds.size.height <= 0.0 {
1016 return None;
1017 }
1018 if node.actions.contains(&SemanticActionKind::Activate) {
1019 return Some(visible_bounds);
1020 }
1021 fallback = Some(visible_bounds);
1022 None
1023 })
1024 .or(fallback)
1025 });
1026 result.ok_or_else(|| SimulationError::ElementNotFound(label.to_owned()))
1027 }
1028
1029 pub fn simulation_capture_pending(&self, window_id: WindowId) -> bool {
1030 self.simulation_waiters.capture_pending(window_id)
1031 }
1032
1033 pub fn take_gpu_resource_queries(&mut self) -> Vec<WindowId> {
1042 let mut live = Vec::new();
1043 for window_id in self.simulation_waiters.gpu_windows() {
1044 if self.contains_window(window_id) {
1045 live.push(window_id);
1046 } else {
1047 for reply in self.simulation_waiters.take_gpu_waiters(window_id) {
1048 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
1049 }
1050 }
1051 }
1052 live
1053 }
1054
1055 pub fn complete_gpu_resource_query(
1058 &mut self,
1059 window_id: WindowId,
1060 summary: GpuResourceSummary,
1061 ) {
1062 self.simulation_waiters.complete_gpu(window_id, summary);
1063 }
1064
1065 #[must_use]
1067 pub fn simulation_frame_pending(&self, window_id: WindowId) -> bool {
1068 self.simulation_waiters.frame_pending(window_id)
1069 }
1070
1071 pub fn complete_simulation_frame(
1075 &mut self,
1076 window_id: WindowId,
1077 presented: bool,
1078 capture: Option<Result<Screenshot, String>>,
1079 ) {
1080 self.simulation_waiters
1081 .complete_frame(window_id, presented, capture);
1082 }
1083
1084 pub fn fail_simulation_frame(&mut self, window_id: WindowId, message: impl Into<String>) {
1086 self.simulation_waiters.fail_frame(window_id, message);
1087 }
1088
1089 pub(crate) fn fail_simulation_window(&mut self, window_id: WindowId) {
1090 self.simulation_waiters.fail_window(window_id);
1091 }
1092
1093 pub(crate) fn stop_simulation(&self) {
1094 self.simulation_bridge.stop();
1095 }
1096}
1097
1098trait RectCenter {
1099 fn center(self) -> Offset;
1100}
1101
1102impl RectCenter for Rect {
1103 fn center(self) -> Offset {
1104 Offset::new(
1105 self.origin.x + self.size.width * 0.5,
1106 self.origin.y + self.size.height * 0.5,
1107 )
1108 }
1109}
1110
1111