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) if id == self.window => {
212                            let _ = sender.send(self.size);
213                        }
214                        window::Action::GetMaximized(id, sender) if id == self.window => {
215                            let _ = sender.send(false);
216                        }
217                        window::Action::GetMinimized(id, sender) if id == self.window => {
218                            let _ = sender.send(None);
219                        }
220                        window::Action::GetPosition(id, sender) if id == self.window => {
221                            let _ = sender.send(Some(Point::ORIGIN));
222                        }
223                        window::Action::GetScaleFactor(id, sender) if id == self.window => {
224                            let _ = sender.send(1.0);
225                        }
226                        window::Action::GetMode(id, sender) if id == self.window => {
227                            let _ = sender.send(core::window::Mode::Windowed);
228                        }
229                        _ => {
230                            // Ignored
231                        }
232                    }
233                }
234                runtime::Action::System(action) => {
235                    // TODO
236                    dbg!(action);
237                }
238                runtime::Action::Font(action) => {
239                    // TODO
240                    dbg!(action);
241                }
242                runtime::Action::Image(action) => {
243                    // TODO
244                    dbg!(action);
245                }
246                iced_runtime::Action::Event { window, event } => {
247                    // TODO
248                    dbg!(window, event);
249                }
250                runtime::Action::Tick => {
251                    // TODO
252                }
253                runtime::Action::Exit => {
254                    // TODO
255                }
256                runtime::Action::Reload => {
257                    // TODO
258                }
259            },
260        }
261    }
262
263    /// Runs an [`Instruction`].
264    ///
265    /// If the [`Instruction`] executes successfully, an [`Event::Ready`] will be
266    /// produced by the [`Emulator`].
267    ///
268    /// Otherwise, an [`Event::Failed`] will be triggered.
269    pub fn run(&mut self, program: &P, instruction: &Instruction) {
270        let mut user_interface = UserInterface::build(
271            program.view(&self.state, self.window),
272            self.size,
273            self.cache.take().unwrap(),
274            &mut self.renderer,
275        );
276
277        let mut messages = Vec::new();
278
279        match instruction {
280            Instruction::Interact(interaction) => {
281                let Some(events) = interaction.events(|target| match target {
282                    instruction::Target::Id(id) => {
283                        use widget::Operation;
284
285                        let mut operation = Selector::find(widget::Id::from(id.to_owned()));
286
287                        user_interface.operate(
288                            &self.renderer,
289                            &mut widget::operation::black_box(&mut operation),
290                        );
291
292                        match operation.finish() {
293                            widget::operation::Outcome::Some(widget) => {
294                                Some(widget?.visible_bounds()?.center())
295                            }
296                            _ => None,
297                        }
298                    }
299                    instruction::Target::Text(text) => {
300                        use widget::Operation;
301
302                        let mut operation = Selector::find(text.as_str());
303
304                        user_interface.operate(
305                            &self.renderer,
306                            &mut widget::operation::black_box(&mut operation),
307                        );
308
309                        match operation.finish() {
310                            widget::operation::Outcome::Some(text) => {
311                                Some(text?.visible_bounds()?.center())
312                            }
313                            _ => None,
314                        }
315                    }
316                    instruction::Target::Point(position) => Some(*position),
317                }) else {
318                    self.runtime.send(Event::Failed(instruction.clone()));
319                    self.cache = Some(user_interface.into_cache());
320                    return;
321                };
322
323                for event in &events {
324                    if let core::Event::Mouse(mouse::Event::CursorMoved { position }) = event {
325                        self.cursor = mouse::Cursor::Available(*position);
326                    }
327                }
328
329                let (_state, _status) =
330                    user_interface.update(&events, self.cursor, &mut self.renderer, &mut messages);
331
332                self.cache = Some(user_interface.into_cache());
333
334                let task = self.runtime.enter(|| {
335                    Task::batch(
336                        messages
337                            .into_iter()
338                            .map(|message| program.update(&mut self.state, message)),
339                    )
340                });
341
342                self.resubscribe(program);
343                self.wait_for(task);
344            }
345            Instruction::Expect(expectation) => match expectation {
346                instruction::Expectation::Text(text) => {
347                    use widget::Operation;
348
349                    let mut operation = Selector::find(text.as_str());
350
351                    user_interface.operate(
352                        &self.renderer,
353                        &mut widget::operation::black_box(&mut operation),
354                    );
355
356                    match operation.finish() {
357                        widget::operation::Outcome::Some(Some(_text)) => {
358                            self.runtime.send(Event::Ready);
359                        }
360                        _ => {
361                            self.runtime.send(Event::Failed(instruction.clone()));
362                        }
363                    }
364
365                    self.cache = Some(user_interface.into_cache());
366                }
367            },
368        }
369    }
370
371    fn wait_for(&mut self, task: Task<P::Message>) {
372        if let Some(stream) = task::into_stream(task) {
373            match self.mode {
374                Mode::Zen => {
375                    self.pending_tasks += 1;
376
377                    self.runtime.run(
378                        stream
379                            .map(Action_::Runtime)
380                            .map(Action)
381                            .map(Event::Action)
382                            .chain(stream::once(async {
383                                Event::Action(Action(Action_::CountDown))
384                            }))
385                            .boxed(),
386                    );
387                }
388                Mode::Patient => {
389                    self.runtime.run(
390                        stream
391                            .map(Action_::Runtime)
392                            .map(Action)
393                            .map(Event::Action)
394                            .chain(stream::once(async { Event::Ready }))
395                            .boxed(),
396                    );
397                }
398                Mode::Immediate => {
399                    self.runtime.run(
400                        stream
401                            .map(Action_::Runtime)
402                            .map(Action)
403                            .map(Event::Action)
404                            .boxed(),
405                    );
406                    self.runtime.send(Event::Ready);
407                }
408            }
409        } else if self.pending_tasks == 0 {
410            self.runtime.send(Event::Ready);
411        }
412    }
413
414    fn resubscribe(&mut self, program: &P) {
415        self.runtime
416            .track(subscription::into_recipes(self.runtime.enter(|| {
417                program.subscription(&self.state).map(|message| {
418                    Event::Action(Action(Action_::Runtime(runtime::Action::Output(message))))
419                })
420            })));
421    }
422
423    /// Returns the current view of the [`Emulator`].
424    pub fn view(&self, program: &P) -> Element<'_, P::Message, P::Theme, P::Renderer> {
425        program.view(&self.state, self.window)
426    }
427
428    /// Returns the current theme of the [`Emulator`].
429    pub fn theme(&self, program: &P) -> Option<P::Theme> {
430        program.theme(&self.state, self.window)
431    }
432
433    /// Takes a [`window::Screenshot`] of the current state of the [`Emulator`].
434    pub fn screenshot(
435        &mut self,
436        program: &P,
437        theme: &P::Theme,
438        scale_factor: f32,
439    ) -> window::Screenshot {
440        use core::renderer::Headless;
441
442        let style = program.style(&self.state, theme);
443
444        let mut user_interface = UserInterface::build(
445            program.view(&self.state, self.window),
446            self.size,
447            self.cache.take().unwrap(),
448            &mut self.renderer,
449        );
450
451        // TODO: Nested redraws!
452        let _ = user_interface.update(
453            &[core::Event::Window(window::Event::RedrawRequested(
454                Instant::now(),
455            ))],
456            mouse::Cursor::Unavailable,
457            &mut self.renderer,
458            &mut Vec::new(),
459        );
460
461        user_interface.draw(
462            &mut self.renderer,
463            theme,
464            &renderer::Style {
465                text_color: style.text_color,
466            },
467            mouse::Cursor::Unavailable,
468        );
469
470        let physical_size = Size::new(
471            (self.size.width * scale_factor).round() as u32,
472            (self.size.height * scale_factor).round() as u32,
473        );
474
475        let rgba = self
476            .renderer
477            .screenshot(physical_size, scale_factor, style.background_color);
478
479        window::Screenshot {
480            rgba: Bytes::from(rgba),
481            size: physical_size,
482            scale_factor,
483        }
484    }
485
486    /// Turns the [`Emulator`] into its internal state.
487    pub fn into_state(self) -> (P::State, core::window::Id) {
488        (self.state, self.window)
489    }
490}
491
492/// The strategy used by an [`Emulator`] when waiting for tasks to finish.
493///
494/// A [`Mode`] can be used to make an [`Emulator`] wait for side effects to finish before
495/// continuing execution.
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
497pub enum Mode {
498    /// Waits for all tasks spawned by an [`Instruction`], as well as all tasks indirectly
499    /// spawned by the the results of those tasks.
500    ///
501    /// This is the default.
502    #[default]
503    Zen,
504    /// Waits only for the tasks directly spawned by an [`Instruction`].
505    Patient,
506    /// Never waits for any tasks to finish.
507    Immediate,
508}
509
510impl Mode {
511    /// A list of all the available modes.
512    pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Immediate];
513}
514
515impl fmt::Display for Mode {
516    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517        f.write_str(match self {
518            Self::Zen => "Zen",
519            Self::Patient => "Patient",
520            Self::Immediate => "Immediate",
521        })
522    }
523}