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