Skip to main content

iced_test/
emulator.rs

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