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