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::widget;
6use crate::core::{Element, Point, Size};
7use crate::instruction;
8use crate::program;
9use crate::program::Program;
10use crate::runtime;
11use crate::runtime::futures::futures::StreamExt;
12use crate::runtime::futures::futures::channel::mpsc;
13use crate::runtime::futures::futures::stream;
14use crate::runtime::futures::subscription;
15use crate::runtime::futures::{Executor, Runtime};
16use crate::runtime::task;
17use crate::runtime::user_interface;
18use crate::runtime::window;
19use crate::runtime::{Task, UserInterface};
20use crate::{Instruction, Selector};
21
22use std::fmt;
23
24/// A headless runtime that can run iced applications and execute
25/// [instructions](crate::Instruction).
26///
27/// An [`Emulator`] runs its program as faithfully as possible to the real thing.
28/// It will run subscriptions and tasks with the [`Executor`](Program::Executor) of
29/// the [`Program`].
30///
31/// If you want to run a simulation without side effects, use a [`Simulator`](crate::Simulator)
32/// instead.
33pub struct Emulator<P: Program> {
34    state: P::State,
35    runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
36    renderer: P::Renderer,
37    mode: Mode,
38    size: Size,
39    window: core::window::Id,
40    cursor: mouse::Cursor,
41    clipboard: Clipboard,
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(
71        sender: mpsc::Sender<Event<P>>,
72        program: &P,
73        mode: Mode,
74        size: Size,
75    ) -> Emulator<P> {
76        Self::with_preset(sender, program, mode, size, None)
77    }
78
79    /// Creates a new [`Emulator`] analogously to [`new`](Self::new), but it also takes a
80    /// [`program::Preset`] that will be used as the initial state.
81    ///
82    /// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
83    pub fn with_preset(
84        sender: mpsc::Sender<Event<P>>,
85        program: &P,
86        mode: Mode,
87        size: Size,
88        preset: Option<&program::Preset<P::State, P::Message>>,
89    ) -> Emulator<P> {
90        use renderer::Headless;
91
92        let settings = program.settings();
93
94        // TODO: Error handling
95        let executor = P::Executor::new().expect("Create emulator executor");
96
97        let renderer = executor
98            .block_on(P::Renderer::new(
99                settings.default_font,
100                settings.default_text_size,
101                None,
102            ))
103            .expect("Create emulator renderer");
104
105        let runtime = Runtime::new(executor, sender);
106
107        let (state, task) = runtime.enter(|| {
108            if let Some(preset) = preset {
109                preset.boot()
110            } else {
111                program.boot()
112            }
113        });
114
115        let mut emulator = Self {
116            state,
117            runtime,
118            renderer,
119            mode,
120            size,
121            clipboard: Clipboard { content: None },
122            cursor: mouse::Cursor::Unavailable,
123            window: core::window::Id::unique(),
124            cache: Some(user_interface::Cache::default()),
125            pending_tasks: 0,
126        };
127
128        emulator.resubscribe(program);
129        emulator.wait_for(task);
130
131        emulator
132    }
133
134    /// Updates the state of the [`Emulator`] program.
135    ///
136    /// This is equivalent to calling the [`Program::update`] function,
137    /// resubscribing to any subscriptions, and running the resulting tasks
138    /// concurrently.
139    pub fn update(&mut self, program: &P, message: P::Message) {
140        let task = self
141            .runtime
142            .enter(|| program.update(&mut self.state, message));
143
144        self.resubscribe(program);
145
146        match self.mode {
147            Mode::Zen if self.pending_tasks > 0 => self.wait_for(task),
148            _ => {
149                if let Some(stream) = task::into_stream(task) {
150                    self.runtime.run(
151                        stream
152                            .map(Action_::Runtime)
153                            .map(Action)
154                            .map(Event::Action)
155                            .boxed(),
156                    );
157                }
158            }
159        }
160    }
161
162    /// Performs an [`Action`].
163    ///
164    /// Whenever an [`Emulator`] sends an [`Event::Action`], this
165    /// method must be called to proceed with the execution.
166    pub fn perform(&mut self, program: &P, action: Action<P>) {
167        match action.0 {
168            Action_::CountDown => {
169                if self.pending_tasks > 0 {
170                    self.pending_tasks -= 1;
171
172                    if self.pending_tasks == 0 {
173                        self.runtime.send(Event::Ready);
174                    }
175                }
176            }
177            Action_::Runtime(action) => match action {
178                runtime::Action::Output(message) => {
179                    self.update(program, message);
180                }
181                runtime::Action::LoadFont { .. } => {
182                    // TODO
183                }
184                runtime::Action::Widget(operation) => {
185                    let mut user_interface = UserInterface::build(
186                        program.view(&self.state, self.window),
187                        self.size,
188                        self.cache.take().unwrap(),
189                        &mut self.renderer,
190                    );
191
192                    let mut operation = Some(operation);
193
194                    while let Some(mut current) = operation.take() {
195                        user_interface.operate(&self.renderer, &mut current);
196
197                        match current.finish() {
198                            widget::operation::Outcome::None => {}
199                            widget::operation::Outcome::Some(()) => {}
200                            widget::operation::Outcome::Chain(next) => {
201                                operation = Some(next);
202                            }
203                        }
204                    }
205
206                    self.cache = Some(user_interface.into_cache());
207                }
208                runtime::Action::Clipboard(action) => {
209                    // TODO
210                    dbg!(action);
211                }
212                runtime::Action::Window(action) => match action {
213                    window::Action::Open(id, _settings, sender) => {
214                        self.window = id;
215
216                        let _ = sender.send(self.window);
217                    }
218                    window::Action::GetOldest(sender)
219                    | window::Action::GetLatest(sender) => {
220                        let _ = sender.send(Some(self.window));
221                    }
222                    window::Action::GetSize(id, sender) => {
223                        if id == self.window {
224                            let _ = sender.send(self.size);
225                        }
226                    }
227                    window::Action::GetMaximized(id, sender) => {
228                        if id == self.window {
229                            let _ = sender.send(false);
230                        }
231                    }
232                    window::Action::GetMinimized(id, sender) => {
233                        if id == self.window {
234                            let _ = sender.send(None);
235                        }
236                    }
237                    window::Action::GetPosition(id, sender) => {
238                        if id == self.window {
239                            let _ = sender.send(Some(Point::ORIGIN));
240                        }
241                    }
242                    window::Action::GetScaleFactor(id, sender) => {
243                        if id == self.window {
244                            let _ = sender.send(1.0);
245                        }
246                    }
247                    window::Action::GetMode(id, sender) => {
248                        if id == self.window {
249                            let _ = sender.send(core::window::Mode::Windowed);
250                        }
251                    }
252                    _ => {
253                        // Ignored
254                    }
255                },
256                runtime::Action::System(action) => {
257                    // TODO
258                    dbg!(action);
259                }
260                iced_runtime::Action::Image(action) => {
261                    // TODO
262                    dbg!(action);
263                }
264                runtime::Action::Exit => {
265                    // TODO
266                }
267                runtime::Action::Reload => {
268                    // TODO
269                }
270            },
271        }
272    }
273
274    /// Runs an [`Instruction`].
275    ///
276    /// If the [`Instruction`] executes successfully, an [`Event::Ready`] will be
277    /// produced by the [`Emulator`].
278    ///
279    /// Otherwise, an [`Event::Failed`] will be triggered.
280    pub fn run(&mut self, program: &P, instruction: Instruction) {
281        let mut user_interface = UserInterface::build(
282            program.view(&self.state, self.window),
283            self.size,
284            self.cache.take().unwrap(),
285            &mut self.renderer,
286        );
287
288        let mut messages = Vec::new();
289
290        match &instruction {
291            Instruction::Interact(interaction) => {
292                let Some(events) = interaction.events(|target| match target {
293                    instruction::Target::Point(position) => Some(*position),
294                    instruction::Target::Text(text) => {
295                        use widget::Operation;
296
297                        let mut operation = Selector::find(text.as_str());
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(text) => {
306                                Some(text?.visible_bounds()?.center())
307                            }
308                            _ => None,
309                        }
310                    }
311                }) else {
312                    self.runtime.send(Event::Failed(instruction));
313                    self.cache = Some(user_interface.into_cache());
314                    return;
315                };
316
317                for event in &events {
318                    if let core::Event::Mouse(mouse::Event::CursorMoved {
319                        position,
320                    }) = event
321                    {
322                        self.cursor = mouse::Cursor::Available(*position);
323                    }
324                }
325
326                let (_state, _status) = user_interface.update(
327                    &events,
328                    self.cursor,
329                    &mut self.renderer,
330                    &mut self.clipboard,
331                    &mut messages,
332                );
333
334                self.cache = Some(user_interface.into_cache());
335
336                let task = self.runtime.enter(|| {
337                    Task::batch(messages.into_iter().map(|message| {
338                        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));
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(
419                        runtime::Action::Output(message),
420                    )))
421                })
422            })));
423    }
424
425    /// Returns the current view of the [`Emulator`].
426    pub fn view(
427        &self,
428        program: &P,
429    ) -> Element<'_, P::Message, P::Theme, P::Renderer> {
430        program.view(&self.state, self.window)
431    }
432
433    /// Returns the current theme of the [`Emulator`].
434    pub fn theme(&self, program: &P) -> Option<P::Theme> {
435        program.theme(&self.state, self.window)
436    }
437
438    /// Turns the [`Emulator`] into its internal state.
439    pub fn into_state(self) -> (P::State, core::window::Id) {
440        (self.state, self.window)
441    }
442}
443
444/// The strategy used by an [`Emulator`] when waiting for tasks to finish.
445///
446/// A [`Mode`] can be used to make an [`Emulator`] wait for side effects to finish before
447/// continuing execution.
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
449pub enum Mode {
450    /// Waits for all tasks spawned by an [`Instruction`], as well as all tasks indirectly
451    /// spawned by the the results of those tasks.
452    ///
453    /// This is the default.
454    #[default]
455    Zen,
456    /// Waits only for the tasks directly spawned by an [`Instruction`].
457    Patient,
458    /// Never waits for any tasks to finish.
459    Immediate,
460}
461
462impl Mode {
463    /// A list of all the available modes.
464    pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Immediate];
465}
466
467impl fmt::Display for Mode {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        f.write_str(match self {
470            Self::Zen => "Zen",
471            Self::Patient => "Patient",
472            Self::Immediate => "Immediate",
473        })
474    }
475}
476
477struct Clipboard {
478    content: Option<String>,
479}
480
481impl core::Clipboard for Clipboard {
482    fn read(&self, _kind: core::clipboard::Kind) -> Option<String> {
483        self.content.clone()
484    }
485
486    fn write(&mut self, _kind: core::clipboard::Kind, contents: String) {
487        self.content = Some(contents);
488    }
489}