Abstract time-travel feature in TimeMachine struct

This commit is contained in:
Héctor Ramón Jiménez 2025-04-20 22:11:24 +02:00
parent 7c6155242c
commit 5ce3892a1e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
2 changed files with 116 additions and 48 deletions

View file

@ -7,6 +7,7 @@ use iced_widget::runtime;
use iced_widget::runtime::futures; use iced_widget::runtime::futures;
mod executor; mod executor;
mod time_machine;
use crate::core::keyboard; use crate::core::keyboard;
use crate::core::theme::{self, Base, Theme}; use crate::core::theme::{self, Base, Theme};
@ -16,6 +17,7 @@ use crate::core::{Color, Element, Length::Fill};
use crate::futures::Subscription; use crate::futures::Subscription;
use crate::program::Program; use crate::program::Program;
use crate::runtime::Task; use crate::runtime::Task;
use crate::time_machine::TimeMachine;
use crate::widget::{ use crate::widget::{
bottom_right, button, center, column, container, horizontal_space, opaque, bottom_right, button, center, column, container, horizontal_space, opaque,
row, scrollable, stack, text, themer, row, scrollable, stack, text, themer,
@ -115,8 +117,7 @@ where
state: P::State, state: P::State,
mode: Mode, mode: Mode,
show_notification: bool, show_notification: bool,
rewind: Option<P::State>, time_machine: TimeMachine<P>,
messages: Vec<P::Message>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -143,14 +144,13 @@ impl<P> DevTools<P>
where where
P: Program + 'static, P: Program + 'static,
{ {
pub fn new(state: P::State) -> (Self, Task<Message>) { fn new(state: P::State) -> (Self, Task<Message>) {
( (
Self { Self {
state, state,
mode: Mode::None, mode: Mode::None,
show_notification: true, show_notification: true,
rewind: None, time_machine: TimeMachine::new(),
messages: Vec::new(),
}, },
executor::spawn_blocking(|mut sender| { executor::spawn_blocking(|mut sender| {
thread::sleep(seconds(2)); thread::sleep(seconds(2));
@ -160,11 +160,11 @@ where
) )
} }
pub fn title(&self, program: &P, window: window::Id) -> String { fn title(&self, program: &P, window: window::Id) -> String {
program.title(&self.state, window) program.title(&self.state, window)
} }
pub fn update(&mut self, program: &P, event: Event<P>) -> Task<Event<P>> { fn update(&mut self, program: &P, event: Event<P>) -> Task<Event<P>> {
match event { match event {
Event::Message(message) => match message { Event::Message(message) => match message {
Message::HideNotification => { Message::HideNotification => {
@ -254,13 +254,12 @@ where
} }
}, },
Event::Program(message) => { Event::Program(message) => {
#[cfg(feature = "time-travel")]
{ {
self.messages.push(message.clone()); self.time_machine.push(&message);
}
if self.rewind.is_some() { if self.time_machine.is_rewinding() {
debug::enable(); debug::enable();
}
} }
let span = debug::update(&message); let span = debug::update(&message);
@ -268,7 +267,7 @@ where
debug::tasks_spawned(task.units()); debug::tasks_spawned(task.units());
span.finish(); span.finish();
if self.rewind.is_some() { if self.time_machine.is_rewinding() {
debug::disable(); debug::disable();
} }
@ -277,31 +276,10 @@ where
Event::Command(command) => { Event::Command(command) => {
match command { match command {
debug::Command::RewindTo { message } => { debug::Command::RewindTo { message } => {
#[cfg(feature = "time-travel")] self.time_machine.rewind(program, message);
{
let (mut state, _) = program.boot();
if message < self.messages.len() {
// TODO: Run concurrently (?)
for message in &self.messages[0..message] {
let _ = program
.update(&mut state, message.clone());
}
}
self.rewind = Some(state);
debug::disable();
}
#[cfg(not(feature = "time-travel"))]
let _ = message;
} }
debug::Command::GoLive => { debug::Command::GoLive => {
#[cfg(feature = "time-travel")] self.time_machine.go_to_present();
{
self.rewind = None;
debug::enable();
}
} }
} }
@ -311,17 +289,17 @@ where
} }
} }
pub fn view( fn view(
&self, &self,
program: &P, program: &P,
window: window::Id, window: window::Id,
) -> Element<'_, Event<P>, P::Theme, P::Renderer> { ) -> Element<'_, Event<P>, P::Theme, P::Renderer> {
let state = self.rewind.as_ref().unwrap_or(&self.state); let state = self.state();
let view = { let view = {
let view = program.view(state, window); let view = program.view(state, window);
if self.rewind.is_some() { if self.time_machine.is_rewinding() {
view.map(|_| Event::Discard) view.map(|_| Event::Discard)
} else { } else {
view.map(Event::Program) view.map(Event::Program)
@ -436,10 +414,9 @@ where
.into() .into()
} }
pub fn subscription(&self, program: &P) -> Subscription<Event<P>> { fn subscription(&self, program: &P) -> Subscription<Event<P>> {
let subscription = let subscription =
program.subscription(&self.state).map(Event::Program); program.subscription(&self.state).map(Event::Program);
debug::subscriptions_tracked(subscription.units()); debug::subscriptions_tracked(subscription.units());
let hotkeys = let hotkeys =
@ -456,17 +433,20 @@ where
Subscription::batch([subscription, hotkeys, commands]) Subscription::batch([subscription, hotkeys, commands])
} }
pub fn theme(&self, program: &P, window: window::Id) -> P::Theme { fn theme(&self, program: &P, window: window::Id) -> P::Theme {
program.theme(self.rewind.as_ref().unwrap_or(&self.state), window) program.theme(self.state(), window)
} }
pub fn style(&self, program: &P, theme: &P::Theme) -> theme::Style { fn style(&self, program: &P, theme: &P::Theme) -> theme::Style {
program.style(self.rewind.as_ref().unwrap_or(&self.state), theme) program.style(self.state(), theme)
} }
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 { fn scale_factor(&self, program: &P, window: window::Id) -> f64 {
program program.scale_factor(self.state(), window)
.scale_factor(self.rewind.as_ref().unwrap_or(&self.state), window) }
fn state(&self) -> &P::State {
self.time_machine.state().unwrap_or(&self.state)
} }
} }

View file

@ -0,0 +1,88 @@
use crate::Program;
#[cfg(feature = "time-travel")]
pub struct TimeMachine<P>
where
P: Program,
{
state: Option<P::State>,
messages: Vec<P::Message>,
}
#[cfg(feature = "time-travel")]
impl<P> TimeMachine<P>
where
P: Program,
{
pub fn new() -> Self {
Self {
state: None,
messages: Vec::new(),
}
}
pub fn is_rewinding(&self) -> bool {
self.state.is_some()
}
pub fn push(&mut self, message: &P::Message) {
self.messages.push(message.clone());
}
pub fn rewind(&mut self, program: &P, message: usize) {
let (mut state, _) = program.boot();
if message < self.messages.len() {
// TODO: Run concurrently (?)
for message in &self.messages[0..message] {
let _ = program.update(&mut state, message.clone());
}
}
self.state = Some(state);
crate::debug::disable();
}
pub fn go_to_present(&mut self) {
self.state = None;
crate::debug::enable();
}
pub fn state(&self) -> Option<&P::State> {
self.state.as_ref()
}
}
#[cfg(not(feature = "time-travel"))]
pub struct TimeMachine<P>
where
P: Program,
{
_program: std::marker::PhantomData<P>,
}
#[cfg(not(feature = "time-travel"))]
impl<P> TimeMachine<P>
where
P: Program,
{
pub fn new() -> Self {
Self {
_program: std::marker::PhantomData,
}
}
pub fn is_rewinding(&self) -> bool {
false
}
pub fn push(&mut self, _message: &P::Message) {}
pub fn rewind(&mut self, _program: &P, _message: usize) {}
pub fn go_to_present(&mut self) {}
pub fn state(&self) -> Option<&P::State> {
None
}
}