Skip to main content

iced_test/
emulator.rs

1//! Run your application in a headless runtime.
2use crate::core;
3use crate::core::mouse;
4use crate::core::renderer;
5use crate::core::shell;
6use crate::core::time::Instant;
7use crate::core::widget;
8use crate::core::window;
9use crate::core::{Bytes, Element, Point, Size};
10use crate::instruction;
11use crate::program;
12use crate::program::Program;
13use crate::runtime;
14use crate::runtime::futures::futures::StreamExt;
15use crate::runtime::futures::futures::channel::mpsc;
16use crate::runtime::futures::futures::stream;
17use crate::runtime::futures::subscription;
18use crate::runtime::futures::{Executor, Runtime};
19use crate::runtime::task;
20use crate::runtime::user_interface;
21use crate::runtime::{Task, UserInterface};
22use crate::{Instruction, Selector};
23
24use std::fmt;
25
26/// A headless runtime that can run iced applications and execute
27/// [instructions](crate::Instruction).
28///
29/// An [`Emulator`] runs its program as faithfully as possible to the real thing.
30/// It will run subscriptions and tasks with the [`Executor`](Program::Executor) of
31/// the [`Program`].
32///
33/// If you want to run a simulation without side effects, use a [`Simulator`](crate::Simulator)
34/// instead.
35pub struct Emulator<P: Program> {
36    state: P::State,
37    runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
38    renderer: P::Renderer,
39    mode: Mode,
40    size: Size,
41    window: core::window::Id,
42    cursor: mouse::Cursor,
43    cache: Option<user_interface::Cache>,
44    pending_tasks: usize,
45}
46
47/// An emulation event.
48pub enum Event<P: Program> {
49    /// An action that must be [performed](Emulator::perform) by the [`Emulator`].
50    Action(Action<P>),
51    /// An [`Instruction`] failed to be executed.
52    Failed(Instruction),
53    /// The [`Emulator`] is ready.
54    Ready,
55}
56
57/// An action that must be [performed](Emulator::perform) by the [`Emulator`].
58pub struct Action<P: Program>(Action_<P>);
59
60enum Action_<P: Program> {
61    Runtime(runtime::Action<P::Message>),
62    CountDown,
63}
64
65impl<P: Program + 'static> Emulator<P> {
66    /// Creates a new [`Emulator`] of the [`Program`] with the given [`Mode`] and [`Size`].
67    ///
68    /// The [`Emulator`] will send [`Event`] notifications through the provided [`mpsc::Sender`].
69    ///
70    /// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
71    pub fn new(sender: mpsc::Sender<Event<P>>, program: &P, mode: Mode, size: Size) -> Emulator<P> {
72        Self::with_preset(sender, program, mode, size, None)
73    }
74
75    /// Creates a new [`Emulator`] analogously to [`new`](Self::new), but it also takes a
76    /// [`program::Preset`] that will be used as the initial state.
77    ///
78    /// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
79    pub fn with_preset(
80        sender: mpsc::Sender<Event<P>>,
81        program: &P,
82        mode: Mode,
83        size: Size,
84        preset: Option<&program::Preset<P::State, P::Message>>,
85    ) -> Emulator<P> {
86        use renderer::Headless;
87
88        let settings = program.settings();
89
90        // TODO: Error handling
91        let executor = P::Executor::new().expect("Create emulator executor");
92
93        let renderer = executor
94            .block_on(P::Renderer::new(renderer::Settings::from(&settings), None))
95            .expect("Create emulator renderer");
96
97        let runtime = Runtime::new(executor, sender);
98
99        let (state, task) = runtime.enter(|| {
100            if let Some(preset) = preset {
101                preset.boot()
102            } else {
103                program.boot()
104            }
105        });
106
107        let mut emulator = Self {
108            state,
109            runtime,
110            renderer,
111            mode,
112            size,
113            cursor: mouse::Cursor::Unavailable,
114            window: core::window::Id::unique(),
115            cache: Some(user_interface::Cache::default()),
116            pending_tasks: 0,
117        };
118
119        emulator.resubscribe(program);
120        emulator.wait_for(task);
121
122        emulator
123    }
124
125    /// Updates the state of the [`Emulator`] program.
126    ///
127    /// This is equivalent to calling the [`Program::update`] function,
128    /// resubscribing to any subscriptions, and running the resulting tasks
129    /// concurrently.
130    pub fn update(&mut self, program: &P, message: P::Message) {
131        let task = self
132            .runtime
133            .enter(|| program.update(&mut self.state, message));
134
135        self.resubscribe(program);
136
137        match self.mode {
138            Mode::Zen if self.pending_tasks > 0 => self.wait_for(task),
139            _ => {
140                if let Some(stream) = task::into_stream(task) {
141                    self.runtime.run(
142                        stream
143                            .map(Action_::Runtime)
144                            .map(Action)
145                            .map(Event::Action)
146                            .boxed(),
147                    );
148                }
149            }
150        }
151    }
152
153    /// Performs an [`Action`].
154    ///
155    /// Whenever an [`Emulator`] sends an [`Event::Action`], this
156    /// method must be called to proceed with the execution.
157    pub fn perform(&mut self, program: &P, action: Action<P>) {
158        match action.0 {
159            Action_::CountDown => {
160                if self.pending_tasks > 0 {
161                    self.pending_tasks -= 1;
162
163                    if self.pending_tasks == 0 {
164                        self.runtime.send(Event::Ready);
165                    }
166                }
167            }
168            Action_::Runtime(action) => match action {
169                runtime::Action::Output(message) => {
170                    self.update(program, message);
171                }
172                runtime::Action::Widget(operation) => {
173                    let mut user_interface = UserInterface::build(
174                        program.view(&self.state, self.window),
175                        self.size,
176                        self.cache.take().unwrap(),
177                        &mut self.renderer,
178                    );
179
180                    let mut operation = Some(operation);
181
182                    while let Some(mut current) = operation.take() {
183                        user_interface.operate(&self.renderer, &mut current);
184
185                        match current.finish() {
186                            widget::operation::Outcome::None => {}
187                            widget::operation::Outcome::Some(()) => {}
188                            widget::operation::Outcome::Chain(next) => {
189                                operation = Some(next);
190                            }
191                        }
192                    }
193
194                    self.cache = Some(user_interface.into_cache());
195                }
196                runtime::Action::Clipboard(action) => {
197                    // TODO
198                    dbg!(action);
199                }
200                runtime::Action::Window(action) => {
201                    use crate::runtime::window;
202
203                    match action {
204                        window::Action::Open(id, _settings, sender) => {
205                            self.window = id;
206
207                            let _ = sender.send(self.window);
208                        }
209                        window::Action::GetOldest(sender) | window::Action::GetLatest(sender) => {
210                            let _ = sender.send(Some(self.window));
211                        }
212                        window::Action::GetSize(id, sender) if id == self.window => {
213                            let _ = sender.send(self.size);
214                        }
215                        window::Action::GetMaximized(id, sender) if id == self.window => {
216                            let _ = sender.send(false);
217                        }
218                        window::Action::GetMinimized(id, sender) if id == self.window => {
219                            let _ = sender.send(None);
220                        }
221                        window::Action::GetPosition(id, sender) if id == self.window => {
222                            let _ = sender.send(Some(Point::ORIGIN));
223                        }
224                        window::Action::GetScaleFactor(id, sender) if id == self.window => {
225                            let _ = sender.send(1.0);
226                        }
227                        window::Action::GetMode(id, sender) if id == self.window => {
228                            let _ = sender.send(core::window::Mode::Windowed);
229                        }
230                        _ => {
231                            // Ignored
232                        }
233                    }
234                }
235                runtime::Action::System(action) => {
236                    // TODO
237                    dbg!(action);
238                }
239                runtime::Action::Font(action) => {
240                    // TODO
241                    dbg!(action);
242                }
243                runtime::Action::Image(action) => {
244                    // TODO
245                    dbg!(action);
246                }
247                runtime::Action::Backend(action) => {
248                    // TODO
249                    dbg!(action);
250                }
251                runtime::Action::Event { window, event } => {
252                    // TODO
253                    dbg!(window, event);
254                }
255                runtime::Action::Tick => {
256                    // TODO
257                }
258                runtime::Action::Exit => {
259                    // TODO
260                }
261                runtime::Action::Reload => {
262                    // TODO
263                }
264            },
265        }
266    }
267
268    /// Runs an [`Instruction`].
269    ///
270    /// If the [`Instruction`] executes successfully, an [`Event::Ready`] will be
271    /// produced by the [`Emulator`].
272    ///
273    /// Otherwise, an [`Event::Failed`] will be triggered.
274    pub fn run(&mut self, program: &P, instruction: &Instruction) {
275        let mut user_interface = UserInterface::build(
276            program.view(&self.state, self.window),
277            self.size,
278            self.cache.take().unwrap(),
279            &mut self.renderer,
280        );
281
282        let mut messages = Vec::new();
283
284        match instruction {
285            Instruction::Interact(interaction) => {
286                let Some(events) = interaction.events(|target| match target {
287                    instruction::Target::Id(id) => {
288                        use widget::Operation;
289
290                        let mut operation = Selector::find(widget::Id::from(id.to_owned()));
291
292                        user_interface.operate(
293                            &self.renderer,
294                            &mut widget::operation::black_box(&mut operation),
295                        );
296
297                        match operation.finish() {
298                            widget::operation::Outcome::Some(widget) => {
299                                Some(widget?.visible_bounds()?.center())
300                            }
301                            _ => None,
302                        }
303                    }
304                    instruction::Target::Text(text) => {
305                        use widget::Operation;
306
307                        let mut operation = Selector::find(text.as_str());
308
309                        user_interface.operate(
310                            &self.renderer,
311                            &mut widget::operation::black_box(&mut operation),
312                        );
313
314                        match operation.finish() {
315                            widget::operation::Outcome::Some(text) => {
316                                Some(text?.visible_bounds()?.center())
317                            }
318                            _ => None,
319                        }
320                    }
321                    instruction::Target::Point(position) => Some(*position),
322                }) else {
323                    self.runtime.send(Event::Failed(instruction.clone()));
324                    self.cache = Some(user_interface.into_cache());
325                    return;
326                };
327
328                for event in &events {
329                    if let core::Event::Mouse(mouse::Event::CursorMoved { position }) = event {
330                        self.cursor = mouse::Cursor::Available(*position);
331                    }
332                }
333
334                let (_state, _status) = user_interface.update(
335                    &window::Headless,
336                    &shell::Waker::noop(),
337                    &events,
338                    self.cursor,
339                    &mut self.renderer,
340                    &mut messages,
341                );
342
343                self.cache = Some(user_interface.into_cache());
344
345                let task = self.runtime.enter(|| {
346                    Task::batch(
347                        messages
348                            .into_iter()
349                            .map(|message| program.update(&mut self.state, message)),
350                    )
351                });
352
353                self.resubscribe(program);
354                self.wait_for(task);
355            }
356            Instruction::Expect(expectation) => match expectation {
357                instruction::Expectation::Text(text) => {
358                    use widget::Operation;
359
360                    let mut operation = Selector::find(text.as_str());
361
362                    user_interface.operate(
363                        &self.renderer,
364                        &mut widget::operation::black_box(&mut operation),
365                    );
366
367                    match operation.finish() {
368                        widget::operation::Outcome::Some(Some(_text)) => {
369                            self.runtime.send(Event::Ready);
370                        }
371                        _ => {
372                            self.runtime.send(Event::Failed(instruction.clone()));
373                        }
374                    }
375
376                    self.cache = Some(user_interface.into_cache());
377                }
378            },
379        }
380    }
381
382    fn wait_for(&mut self, task: Task<P::Message>) {
383        if let Some(stream) = task::into_stream(task) {
384            match self.mode {
385                Mode::Zen => {
386                    self.pending_tasks += 1;
387
388                    self.runtime.run(
389                        stream
390                            .map(Action_::Runtime)
391                            .map(Action)
392                            .map(Event::Action)
393                            .chain(stream::once(async {
394                                Event::Action(Action(Action_::CountDown))
395                            }))
396                            .boxed(),
397                    );
398                }
399                Mode::Patient => {
400                    self.runtime.run(
401                        stream
402                            .map(Action_::Runtime)
403                            .map(Action)
404                            .map(Event::Action)
405                            .chain(stream::once(async { Event::Ready }))
406                            .boxed(),
407                    );
408                }
409                Mode::Immediate => {
410                    self.runtime.run(
411                        stream
412                            .map(Action_::Runtime)
413                            .map(Action)
414                            .map(Event::Action)
415                            .boxed(),
416                    );
417                    self.runtime.send(Event::Ready);
418                }
419            }
420        } else if self.pending_tasks == 0 {
421            self.runtime.send(Event::Ready);
422        }
423    }
424
425    fn resubscribe(&mut self, program: &P) {
426        self.runtime
427            .track(subscription::into_recipes(self.runtime.enter(|| {
428                program.subscription(&self.state).map(|message| {
429                    Event::Action(Action(Action_::Runtime(runtime::Action::Output(message))))
430                })
431            })));
432    }
433
434    /// Returns the current view of the [`Emulator`].
435    pub fn view(&self, program: &P) -> Element<'_, P::Message, P::Theme, P::Renderer> {
436        program.view(&self.state, self.window)
437    }
438
439    /// Returns the current theme of the [`Emulator`].
440    pub fn theme(&self, program: &P) -> Option<P::Theme> {
441        program.theme(&self.state, self.window)
442    }
443
444    /// Takes a [`window::Screenshot`] of the current state of the [`Emulator`].
445    pub fn screenshot(
446        &mut self,
447        program: &P,
448        theme: &P::Theme,
449        scale_factor: f32,
450    ) -> window::Screenshot {
451        use core::renderer::Headless;
452
453        let style = program.style(&self.state, theme);
454
455        let mut user_interface = UserInterface::build(
456            program.view(&self.state, self.window),
457            self.size,
458            self.cache.take().unwrap(),
459            &mut self.renderer,
460        );
461
462        // TODO: Nested redraws!
463        let _ = user_interface.update(
464            &window::Headless,
465            &shell::Waker::noop(),
466            &[core::Event::Window(window::Event::RedrawRequested(
467                Instant::now(),
468            ))],
469            mouse::Cursor::Unavailable,
470            &mut self.renderer,
471            &mut Vec::new(),
472        );
473
474        user_interface.draw(
475            &mut self.renderer,
476            theme,
477            &renderer::Style {
478                text_color: style.text_color,
479            },
480            mouse::Cursor::Unavailable,
481        );
482
483        let physical_size = Size::new(
484            (self.size.width * scale_factor).round() as u32,
485            (self.size.height * scale_factor).round() as u32,
486        );
487
488        let rgba = self
489            .renderer
490            .screenshot(physical_size, scale_factor, style.background_color);
491
492        window::Screenshot {
493            rgba: Bytes::from(rgba),
494            size: physical_size,
495            scale_factor,
496        }
497    }
498
499    /// Turns the [`Emulator`] into its internal state.
500    pub fn into_state(self) -> (P::State, core::window::Id) {
501        (self.state, self.window)
502    }
503}
504
505/// The strategy used by an [`Emulator`] when waiting for tasks to finish.
506///
507/// A [`Mode`] can be used to make an [`Emulator`] wait for side effects to finish before
508/// continuing execution.
509#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
510pub enum Mode {
511    /// Waits for all tasks spawned by an [`Instruction`], as well as all tasks indirectly
512    /// spawned by the the results of those tasks.
513    ///
514    /// This is the default.
515    #[default]
516    Zen,
517    /// Waits only for the tasks directly spawned by an [`Instruction`].
518    Patient,
519    /// Never waits for any tasks to finish.
520    Immediate,
521}
522
523impl Mode {
524    /// A list of all the available modes.
525    pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Immediate];
526}
527
528impl fmt::Display for Mode {
529    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530        f.write_str(match self {
531            Self::Zen => "Zen",
532            Self::Patient => "Patient",
533            Self::Immediate => "Immediate",
534        })
535    }
536}