Make tester work with daemon (only 1 window for now!)

This commit is contained in:
Héctor Ramón Jiménez 2025-07-08 01:19:37 +02:00
parent 1deb87694d
commit f3a4e44314
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
3 changed files with 113 additions and 84 deletions

View file

@ -361,7 +361,7 @@ where
match &self.mode { match &self.mode {
Mode::Open { tester } => { Mode::Open { tester } => {
tester.view(program, window, view, Event::Tester) tester.view(program, view, Event::Tester)
} }
_ => view(), _ => view(),
} }
@ -444,19 +444,9 @@ where
} }
pub fn subscription(&self, program: &P) -> Subscription<Event<P>> { pub fn subscription(&self, program: &P) -> Subscription<Event<P>> {
let subscription = match &self.mode { let subscription =
Mode::Open { tester } if !tester.is_idle() => { program.subscription(&self.state).map(Event::Program);
tester.subscription(program).map(Event::Tester) debug::subscriptions_tracked(subscription.units());
}
_ => {
let subscription =
program.subscription(&self.state).map(Event::Program);
debug::subscriptions_tracked(subscription.units());
subscription
}
};
let hotkeys = let hotkeys =
futures::keyboard::on_key_press(|key, _modifiers| match key { futures::keyboard::on_key_press(|key, _modifiers| match key {

View file

@ -10,7 +10,6 @@ use crate::core::border;
use crate::core::window; use crate::core::window;
use crate::core::{Element, Event, Font, Size, Theme}; use crate::core::{Element, Event, Font, Size, Theme};
use crate::executor; use crate::executor;
use crate::futures::Subscription;
use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::mpsc;
use crate::icon; use crate::icon;
use crate::program; use crate::program;
@ -36,10 +35,11 @@ pub struct Tester<P: Program> {
enum State<P: Program> { enum State<P: Program> {
Idle, Idle,
Recording { Recording {
state: P::State, emulator: Emulator<P>,
}, },
Ready { Ready {
state: P::State, state: P::State,
window: window::Id,
}, },
Playing { Playing {
emulator: Emulator<P>, emulator: Emulator<P>,
@ -98,10 +98,6 @@ impl<P: Program + 'static> Tester<P> {
} }
} }
pub fn is_idle(&self) -> bool {
matches!(self.state, State::Idle)
}
pub fn is_busy(&self) -> bool { pub fn is_busy(&self) -> bool {
matches!( matches!(
self.state, self.state,
@ -134,24 +130,30 @@ impl<P: Program + 'static> Tester<P> {
self.edit = None; self.edit = None;
self.instructions.clear(); self.instructions.clear();
let (state, task) = if let Some(preset) = self.preset(program) { let (sender, receiver) = mpsc::channel(1);
preset.boot()
} else {
program.boot()
};
self.state = State::Recording { state }; let emulator = Emulator::with_preset(
sender,
program,
self.mode,
self.viewport,
self.preset(program),
);
task.map(Tick::Program) self.state = State::Recording { emulator };
Task::run(receiver, Tick::Emulator)
} }
Message::Stop => { Message::Stop => {
let State::Recording { state } = let State::Recording { emulator } =
std::mem::replace(&mut self.state, State::Idle) std::mem::replace(&mut self.state, State::Idle)
else { else {
return Task::none(); return Task::none();
}; };
self.state = State::Ready { state }; let (state, window) = emulator.into_state();
self.state = State::Ready { state, window };
Task::none() Task::none()
} }
@ -301,11 +303,13 @@ impl<P: Program + 'static> Tester<P> {
match tick { match tick {
Tick::Tester(message) => self.update(program, message), Tick::Tester(message) => self.update(program, message),
Tick::Program(message) => { Tick::Program(message) => {
let State::Recording { state } = &mut self.state else { let State::Recording { emulator } = &mut self.state else {
return Task::none(); return Task::none();
}; };
program.update(state, message).map(Tick::Program) emulator.update(program, message);
Task::none()
} }
Tick::Recorder(event) => { Tick::Recorder(event) => {
let mut interaction = let mut interaction =
@ -337,35 +341,38 @@ impl<P: Program + 'static> Tester<P> {
Task::none() Task::none()
} }
Tick::Emulator(event) => { Tick::Emulator(event) => {
let State::Playing { match &mut self.state {
emulator, State::Recording { emulator } => {
current, if let emulator::Event::Action(action) = event {
outcome, emulator.perform(program, action);
} = &mut self.state
else {
return Task::none();
};
match event {
emulator::Event::Action(action) => {
emulator.perform(program, action);
}
emulator::Event::Failed => {
*outcome = Outcome::Failed;
}
emulator::Event::Ready => {
*current += 1;
if let Some(instruction) =
self.instructions.get(*current - 1).cloned()
{
emulator.run(program, instruction);
}
if *current >= self.instructions.len() {
*outcome = Outcome::Success;
} }
} }
State::Playing {
emulator,
current,
outcome,
} => match event {
emulator::Event::Action(action) => {
emulator.perform(program, action);
}
emulator::Event::Failed => {
*outcome = Outcome::Failed;
}
emulator::Event::Ready => {
*current += 1;
if let Some(instruction) =
self.instructions.get(*current - 1).cloned()
{
emulator.run(program, instruction);
}
if *current >= self.instructions.len() {
*outcome = Outcome::Success;
}
}
},
State::Idle | State::Ready { .. } => {}
} }
Task::none() Task::none()
@ -373,21 +380,9 @@ impl<P: Program + 'static> Tester<P> {
} }
} }
pub fn subscription(&self, program: &P) -> Subscription<Tick<P>> {
match &self.state {
State::Idle | State::Playing { .. } | State::Ready { .. } => {
Subscription::none()
}
State::Recording { state } => {
program.subscription(state).map(Tick::Program)
}
}
}
pub fn view<'a, T: 'static>( pub fn view<'a, T: 'static>(
&'a self, &'a self,
program: &P, program: &P,
window: window::Id,
current: impl FnOnce() -> Element<'a, T, Theme, P::Renderer>, current: impl FnOnce() -> Element<'a, T, Theme, P::Renderer>,
emulate: impl Fn(Tick<P>) -> T + 'a, emulate: impl Fn(Tick<P>) -> T + 'a,
) -> Element<'a, T, Theme, P::Renderer> { ) -> Element<'a, T, Theme, P::Renderer> {
@ -435,10 +430,9 @@ impl<P: Program + 'static> Tester<P> {
scrollable( scrollable(
container(match &self.state { container(match &self.state {
State::Idle => current(), State::Idle => current(),
State::Recording { state } => { State::Recording { emulator } => {
let theme = program.theme(state, window); let theme = emulator.theme(program);
let view = let view = emulator.view(program).map(Tick::Program);
program.view(state, window).map(Tick::Program);
Element::from( Element::from(
recorder(themer(theme, view)) recorder(themer(theme, view))
@ -446,10 +440,10 @@ impl<P: Program + 'static> Tester<P> {
) )
.map(emulate) .map(emulate)
} }
State::Ready { state } => { State::Ready { state, window } => {
let theme = program.theme(state, window); let theme = program.theme(state, *window);
let view = let view =
program.view(state, window).map(Tick::Program); program.view(state, *window).map(Tick::Program);
Element::from(themer(theme, view)).map(emulate) Element::from(themer(theme, view)).map(emulate)
} }

View file

@ -3,8 +3,7 @@ use crate::core;
use crate::core::mouse; use crate::core::mouse;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget; use crate::core::widget;
use crate::core::window; use crate::core::{Element, Point, Size};
use crate::core::{Element, Size};
use crate::instruction; use crate::instruction;
use crate::program; use crate::program;
use crate::program::Program; use crate::program::Program;
@ -15,6 +14,7 @@ use crate::runtime::futures::subscription;
use crate::runtime::futures::{Executor, Runtime}; use crate::runtime::futures::{Executor, Runtime};
use crate::runtime::task; use crate::runtime::task;
use crate::runtime::user_interface; use crate::runtime::user_interface;
use crate::runtime::window;
use crate::runtime::{Action, Task, UserInterface}; use crate::runtime::{Action, Task, UserInterface};
use std::fmt; use std::fmt;
@ -26,7 +26,7 @@ pub struct Emulator<P: Program> {
renderer: P::Renderer, renderer: P::Renderer,
mode: Mode, mode: Mode,
size: Size, size: Size,
window: window::Id, window: core::window::Id,
cursor: mouse::Cursor, cursor: mouse::Cursor,
clipboard: Clipboard, clipboard: Clipboard,
cache: Option<user_interface::Cache>, cache: Option<user_interface::Cache>,
@ -87,7 +87,7 @@ impl<P: Program + 'static> Emulator<P> {
size, size,
clipboard: Clipboard { content: None }, clipboard: Clipboard { content: None },
cursor: mouse::Cursor::Unavailable, cursor: mouse::Cursor::Unavailable,
window: window::Id::unique(), window: core::window::Id::unique(),
cache: Some(user_interface::Cache::default()), cache: Some(user_interface::Cache::default()),
}; };
@ -143,9 +143,50 @@ impl<P: Program + 'static> Emulator<P> {
// TODO // TODO
dbg!(action); dbg!(action);
} }
Action::Window(_action) => { Action::Window(action) => match action {
// TODO window::Action::Open(_settings, sender) => {
} self.window = core::window::Id::unique();
let _ = sender.send(self.window);
}
window::Action::GetOldest(sender)
| window::Action::GetLatest(sender) => {
let _ = sender.send(Some(self.window));
}
window::Action::GetSize(id, sender) => {
if id == self.window {
let _ = sender.send(self.size);
}
}
window::Action::GetMaximized(id, sender) => {
if id == self.window {
let _ = sender.send(false);
}
}
window::Action::GetMinimized(id, sender) => {
if id == self.window {
let _ = sender.send(None);
}
}
window::Action::GetPosition(id, sender) => {
if id == self.window {
let _ = sender.send(Some(Point::ORIGIN));
}
}
window::Action::GetScaleFactor(id, sender) => {
if id == self.window {
let _ = sender.send(1.0);
}
}
window::Action::GetMode(id, sender) => {
if id == self.window {
let _ = sender.send(core::window::Mode::Windowed);
}
}
_ => {
// Ignored
}
},
Action::System(action) => { Action::System(action) => {
// TODO // TODO
dbg!(action); dbg!(action);
@ -265,6 +306,10 @@ impl<P: Program + 'static> Emulator<P> {
pub fn theme(&self, program: &P) -> P::Theme { pub fn theme(&self, program: &P) -> P::Theme {
program.theme(&self.state, self.window) program.theme(&self.state, self.window)
} }
pub fn into_state(self) -> (P::State, core::window::Id) {
(self.state, self.window)
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]