Draft time-travel debugging feature

This commit is contained in:
Héctor Ramón Jiménez 2025-04-17 03:24:17 +02:00
parent 388a419ed5
commit d5d4479a53
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
20 changed files with 330 additions and 63 deletions

1
Cargo.lock generated
View file

@ -2453,6 +2453,7 @@ version = "0.14.0-dev"
dependencies = [ dependencies = [
"iced_beacon", "iced_beacon",
"iced_core", "iced_core",
"iced_futures",
"log", "log",
] ]

View file

@ -43,6 +43,8 @@ markdown = ["iced_widget/markdown"]
lazy = ["iced_widget/lazy"] lazy = ["iced_widget/lazy"]
# Enables a debug view in native platforms (press F12) # Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug", "iced_devtools"] debug = ["iced_winit/debug", "iced_devtools"]
# Enables time-travel debugging (very experimental!)
time-travel = ["debug", "iced_devtools/time-travel"]
# Enables the `thread-pool` futures executor as the `executor::Default` on native platforms # Enables the `thread-pool` futures executor as the `executor::Default` on native platforms
thread-pool = ["iced_futures/thread-pool"] thread-pool = ["iced_futures/thread-pool"]
# Enables `tokio` as the `executor::Default` on native platforms # Enables `tokio` as the `executor::Default` on native platforms

View file

@ -1,10 +1,12 @@
use crate::Error;
use crate::core::time::{Duration, SystemTime}; use crate::core::time::{Duration, SystemTime};
use crate::span; use crate::span;
use crate::theme; use crate::theme;
use futures::{FutureExt, select};
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::io::{self, AsyncWriteExt}; use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net; use tokio::net;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::time; use tokio::time;
@ -17,7 +19,7 @@ pub const SERVER_ADDRESS: &str = "127.0.0.1:9167";
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Client { pub struct Client {
sender: mpsc::Sender<Message>, sender: mpsc::Sender<Action>,
is_connected: Arc<AtomicBool>, is_connected: Arc<AtomicBool>,
_handle: Arc<thread::JoinHandle<()>>, _handle: Arc<thread::JoinHandle<()>>,
} }
@ -43,17 +45,17 @@ pub enum Event {
ThemeChanged(theme::Palette), ThemeChanged(theme::Palette),
SpanStarted(span::Stage), SpanStarted(span::Stage),
SpanFinished(span::Stage, Duration), SpanFinished(span::Stage, Duration),
MessageLogged(String), MessageLogged { number: usize, message: String },
CommandsSpawned(usize), CommandsSpawned(usize),
SubscriptionsTracked(usize), SubscriptionsTracked(usize),
} }
impl Client { impl Client {
pub fn log(&self, event: Event) { pub fn log(&self, event: Event) {
let _ = self.sender.try_send(Message::EventLogged { let _ = self.sender.try_send(Action::Send(Message::EventLogged {
at: SystemTime::now(), at: SystemTime::now(),
event, event,
}); }));
} }
pub fn is_connected(&self) -> bool { pub fn is_connected(&self) -> bool {
@ -61,21 +63,28 @@ impl Client {
} }
pub fn quit(&self) { pub fn quit(&self) {
let _ = self.sender.try_send(Message::Quit { let _ = self.sender.try_send(Action::Send(Message::Quit {
at: SystemTime::now(), at: SystemTime::now(),
}); }));
}
pub fn subscribe(&self) -> mpsc::Receiver<Command> {
let (sender, receiver) = mpsc::channel(100);
let _ = self.sender.try_send(Action::Forward(sender));
receiver
} }
} }
#[must_use] #[must_use]
pub fn connect(name: String) -> Client { pub fn connect(name: String) -> Client {
let (sender, receiver) = mpsc::channel(100); let (sender, receiver) = mpsc::channel(10_000);
let is_connected = Arc::new(AtomicBool::new(false)); let is_connected = Arc::new(AtomicBool::new(false));
let handle = { let handle = {
let is_connected = is_connected.clone(); let is_connected = is_connected.clone();
std::thread::spawn(move || run(name, is_connected.clone(), receiver)) std::thread::spawn(move || run(name, is_connected, receiver))
}; };
Client { Client {
@ -85,16 +94,30 @@ pub fn connect(name: String) -> Client {
} }
} }
enum Action {
Send(Message),
Forward(mpsc::Sender<Command>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Command {
RewindTo { message: usize },
}
#[tokio::main] #[tokio::main]
async fn run( async fn run(
name: String, name: String,
is_connected: Arc<AtomicBool>, is_connected: Arc<AtomicBool>,
mut receiver: mpsc::Receiver<Message>, mut receiver: mpsc::Receiver<Action>,
) { ) {
let version = semver::Version::parse(env!("CARGO_PKG_VERSION")) let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
.expect("Parse package version"); .expect("Parse package version");
let mut buffer = Vec::new();
loop { loop {
let mut command_sender = None;
match _connect().await { match _connect().await {
Ok(mut stream) => { Ok(mut stream) => {
is_connected.store(true, atomic::Ordering::Relaxed); is_connected.store(true, atomic::Ordering::Relaxed);
@ -109,16 +132,37 @@ async fn run(
) )
.await; .await;
while let Some(output) = receiver.recv().await { loop {
match send(&mut stream, output).await { select! {
Ok(()) => {} action = receiver.recv().fuse() => {
Err(error) => { let Some(action) = action else { break; };
if error.kind() != io::ErrorKind::BrokenPipe {
log::warn!( match action {
"Error sending message to server: {error}" Action::Send(message) => {
); match send(&mut stream, message).await {
Ok(()) => {}
Err(error) => {
if error.kind() != io::ErrorKind::BrokenPipe
{
log::warn!(
"Error sending message to server: {error}"
);
}
break;
}
}
}
Action::Forward(sender) => {
command_sender = Some(sender);
}
}
}
command = receive(&mut stream, &mut buffer).fuse() => {
let Ok(command) = command else { continue; };
if let Some(sender) = command_sender.as_mut() {
let _ = sender.send(command).await;
} }
break;
} }
} }
} }
@ -154,3 +198,18 @@ async fn send(
Ok(()) Ok(())
} }
async fn receive(
stream: &mut net::TcpStream,
buffer: &mut Vec<u8>,
) -> Result<Command, Error> {
let size = stream.read_u64().await? as usize;
if buffer.len() < size {
buffer.resize(size, 0);
}
let _n = stream.read_exact(&mut buffer[..size]).await?;
Ok(bincode::deserialize(buffer)?)
}

9
beacon/src/error.rs Normal file
View file

@ -0,0 +1,9 @@
use std::io;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("input/output operation failed: {0}")]
IOFailed(#[from] io::Error),
#[error("decoding failed: {0}")]
DecodingFailed(#[from] Box<bincode::ErrorKind>),
}

View file

@ -4,6 +4,7 @@ pub use semver::Version;
pub mod client; pub mod client;
pub mod span; pub mod span;
mod error;
mod stream; mod stream;
pub use client::Client; pub use client::Client;
@ -11,14 +12,36 @@ pub use span::Span;
use crate::core::theme; use crate::core::theme;
use crate::core::time::{Duration, SystemTime}; use crate::core::time::{Duration, SystemTime};
use crate::error::Error;
use futures::{SinkExt, Stream}; use futures::{SinkExt, Stream};
use tokio::io::{self, AsyncReadExt}; use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net; use tokio::net;
use tokio::sync::mpsc;
use tokio::task;
#[derive(Debug, Clone)]
pub struct Connection {
commands: mpsc::Sender<client::Command>,
}
impl Connection {
pub fn rewind_to<'a>(
&self,
message: usize,
) -> impl Future<Output = ()> + 'a {
let commands = self.commands.clone();
async move {
let _ = commands.send(client::Command::RewindTo { message }).await;
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Event { pub enum Event {
Connected { Connected {
connection: Connection,
at: SystemTime, at: SystemTime,
name: String, name: String,
version: Version, version: Version,
@ -86,18 +109,42 @@ pub fn run() -> impl Stream<Item = Event> {
}; };
loop { loop {
let Ok((mut stream, _)) = server.accept().await else { let Ok((stream, _)) = server.accept().await else {
continue; continue;
}; };
let _ = stream.set_nodelay(true); let (mut reader, mut writer) = {
let _ = stream.set_nodelay(true);
stream.into_split()
};
let (command_sender, mut command_receiver) = mpsc::channel(1);
let mut last_message = String::new(); let mut last_message = String::new();
let mut last_update_number = 0;
let mut last_commands_spawned = 0; let mut last_commands_spawned = 0;
let mut last_present_window = None; let mut last_present_window = None;
drop(task::spawn(async move {
let mut last_message_number = None;
while let Some(command) = command_receiver.recv().await {
let client::Command::RewindTo { message } = command;
if Some(message) == last_message_number {
continue;
}
last_message_number = Some(message);
let _ =
send(&mut writer, command).await.inspect_err(|error| {
log::error!("Error when sending command: {error}")
});
}
}));
loop { loop {
match receive(&mut stream, &mut buffer).await { match receive(&mut reader, &mut buffer).await {
Ok(message) => { Ok(message) => {
match message { match message {
client::Message::Connected { client::Message::Connected {
@ -107,6 +154,9 @@ pub fn run() -> impl Stream<Item = Event> {
} => { } => {
let _ = output let _ = output
.send(Event::Connected { .send(Event::Connected {
connection: Connection {
commands: command_sender.clone(),
},
at, at,
name, name,
version, version,
@ -133,7 +183,11 @@ pub fn run() -> impl Stream<Item = Event> {
}) })
.await; .await;
} }
client::Event::MessageLogged(message) => { client::Event::MessageLogged {
number,
message,
} => {
last_update_number = number;
last_message = message; last_message = message;
} }
client::Event::CommandsSpawned( client::Event::CommandsSpawned(
@ -161,6 +215,7 @@ pub fn run() -> impl Stream<Item = Event> {
span::Stage::Boot => Span::Boot, span::Stage::Boot => Span::Boot,
span::Stage::Update => { span::Stage::Update => {
Span::Update { Span::Update {
number: last_update_number,
message: last_message message: last_message
.clone(), .clone(),
commands_spawned: commands_spawned:
@ -246,7 +301,7 @@ pub fn run() -> impl Stream<Item = Event> {
} }
async fn receive( async fn receive(
stream: &mut net::TcpStream, stream: &mut net::tcp::OwnedReadHalf,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
) -> Result<client::Message, Error> { ) -> Result<client::Message, Error> {
let size = stream.read_u64().await? as usize; let size = stream.read_u64().await? as usize;
@ -260,14 +315,20 @@ async fn receive(
Ok(bincode::deserialize(buffer)?) Ok(bincode::deserialize(buffer)?)
} }
async fn send(
stream: &mut net::tcp::OwnedWriteHalf,
command: client::Command,
) -> Result<(), io::Error> {
let bytes = bincode::serialize(&command).expect("Encode input message");
let size = bytes.len() as u64;
stream.write_all(&size.to_be_bytes()).await?;
stream.write_all(&bytes).await?;
stream.flush().await?;
Ok(())
}
async fn delay() { async fn delay() {
tokio::time::sleep(Duration::from_secs(2)).await; tokio::time::sleep(Duration::from_secs(2)).await;
} }
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("input/output operation failed: {0}")]
IOFailed(#[from] io::Error),
#[error("decoding failed: {0}")]
DecodingFailed(#[from] Box<bincode::ErrorKind>),
}

View file

@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
pub enum Span { pub enum Span {
Boot, Boot,
Update { Update {
number: usize,
message: String, message: String,
commands_spawned: usize, commands_spawned: usize,
}, },

View file

@ -15,6 +15,7 @@ enable = ["dep:iced_beacon"]
[dependencies] [dependencies]
iced_core.workspace = true iced_core.workspace = true
iced_futures.workspace = true
log.workspace = true log.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View file

@ -1,7 +1,9 @@
pub use iced_core as core; pub use iced_core as core;
pub use iced_futures as futures;
use crate::core::theme; use crate::core::theme;
use crate::core::window; use crate::core::window;
use crate::futures::Subscription;
pub use internal::Span; pub use internal::Span;
@ -16,6 +18,11 @@ pub enum Primitive {
Text, Text,
} }
#[derive(Debug, Clone, Copy)]
pub enum Command {
RewindTo { message: usize },
}
pub fn init(name: &str) { pub fn init(name: &str) {
internal::init(name); internal::init(name);
} }
@ -88,12 +95,18 @@ pub fn skip_next_timing() {
internal::skip_next_timing(); internal::skip_next_timing();
} }
pub fn commands() -> Subscription<Command> {
internal::commands()
}
#[cfg(all(feature = "enable", not(target_arch = "wasm32")))] #[cfg(all(feature = "enable", not(target_arch = "wasm32")))]
mod internal { mod internal {
use crate::Primitive;
use crate::core::theme; use crate::core::theme;
use crate::core::time::Instant; use crate::core::time::Instant;
use crate::core::window; use crate::core::window;
use crate::futures::Subscription;
use crate::futures::futures::Stream;
use crate::{Command, Primitive};
use iced_beacon as beacon; use iced_beacon as beacon;
@ -102,7 +115,7 @@ mod internal {
use std::io; use std::io;
use std::process; use std::process;
use std::sync::atomic::{self, AtomicBool}; use std::sync::atomic::{self, AtomicBool, AtomicUsize};
use std::sync::{LazyLock, RwLock}; use std::sync::{LazyLock, RwLock};
pub fn init(name: &str) { pub fn init(name: &str) {
@ -162,6 +175,8 @@ mod internal {
pub fn update(message: &impl std::fmt::Debug) -> Span { pub fn update(message: &impl std::fmt::Debug) -> Span {
let span = span(span::Stage::Update); let span = span(span::Stage::Update);
let number = LAST_UPDATE.fetch_add(1, atomic::Ordering::Relaxed);
let start = Instant::now(); let start = Instant::now();
let message = format!("{message:?}"); let message = format!("{message:?}");
let elapsed = start.elapsed(); let elapsed = start.elapsed();
@ -172,11 +187,13 @@ mod internal {
); );
} }
BEACON.log(client::Event::MessageLogged(if message.len() > 49 { let message = if message.len() > 49 {
format!("{}...", &message[..49]) format!("{}...", &message[..49])
} else { } else {
message message
})); };
BEACON.log(client::Event::MessageLogged { number, message });
span span
} }
@ -217,6 +234,24 @@ mod internal {
SKIP_NEXT_SPAN.store(true, atomic::Ordering::Relaxed); SKIP_NEXT_SPAN.store(true, atomic::Ordering::Relaxed);
} }
pub fn commands() -> Subscription<Command> {
fn listen_for_commands() -> impl Stream<Item = Command> {
use crate::futures::futures::stream;
stream::unfold(BEACON.subscribe(), async move |mut receiver| {
let command = match receiver.recv().await? {
client::Command::RewindTo { message } => {
Command::RewindTo { message }
}
};
Some((command, receiver))
})
}
Subscription::run(listen_for_commands)
}
fn span(span: span::Stage) -> Span { fn span(span: span::Stage) -> Span {
BEACON.log(client::Event::SpanStarted(span.clone())); BEACON.log(client::Event::SpanStarted(span.clone()));
@ -260,15 +295,17 @@ mod internal {
}); });
static NAME: RwLock<String> = RwLock::new(String::new()); static NAME: RwLock<String> = RwLock::new(String::new());
static LAST_UPDATE: AtomicUsize = AtomicUsize::new(0);
static LAST_PALETTE: RwLock<Option<theme::Palette>> = RwLock::new(None); static LAST_PALETTE: RwLock<Option<theme::Palette>> = RwLock::new(None);
static SKIP_NEXT_SPAN: AtomicBool = AtomicBool::new(false); static SKIP_NEXT_SPAN: AtomicBool = AtomicBool::new(false);
} }
#[cfg(any(not(feature = "enable"), target_arch = "wasm32"))] #[cfg(any(not(feature = "enable"), target_arch = "wasm32"))]
mod internal { mod internal {
use crate::Primitive;
use crate::core::theme; use crate::core::theme;
use crate::core::window; use crate::core::window;
use crate::futures::Subscription;
use crate::{Command, Primitive};
use std::io; use std::io;
@ -326,6 +363,10 @@ mod internal {
pub fn skip_next_timing() {} pub fn skip_next_timing() {}
pub fn commands() -> Subscription<Command> {
Subscription::none()
}
#[derive(Debug)] #[derive(Debug)]
pub struct Span; pub struct Span;

View file

@ -13,6 +13,9 @@ rust-version.workspace = true
[lints] [lints]
workspace = true workspace = true
[features]
time-travel = ["iced_program/time-travel"]
[dependencies] [dependencies]
iced_program.workspace = true iced_program.workspace = true
iced_widget.workspace = true iced_widget.workspace = true

View file

@ -115,6 +115,8 @@ where
state: P::State, state: P::State,
mode: Mode, mode: Mode,
show_notification: bool, show_notification: bool,
rewind: Option<P::State>,
log: Vec<P::Message>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -147,6 +149,8 @@ where
state, state,
mode: Mode::None, mode: Mode::None,
show_notification: true, show_notification: true,
rewind: None,
log: Vec::new(),
}, },
executor::spawn_blocking(|mut sender| { executor::spawn_blocking(|mut sender| {
thread::sleep(seconds(2)); thread::sleep(seconds(2));
@ -250,7 +254,46 @@ where
} }
}, },
Event::Program(message) => { Event::Program(message) => {
program.update(&mut self.state, message).map(Event::Program) if self.rewind.is_some() {
return Task::none();
}
#[cfg(feature = "time-travel")]
{
self.log.push(message.clone());
}
let span = debug::update(&message);
let task = program.update(&mut self.state, message);
debug::tasks_spawned(task.units());
span.finish();
task.map(Event::Program)
}
Event::Command(command) => {
match command {
debug::Command::RewindTo { message } => {
#[cfg(feature = "time-travel")]
{
let (mut state, _) = program.boot();
if message < self.log.len() {
// TODO: Run concurrently (?)
for message in &self.log[0..message] {
let _ = program
.update(&mut state, message.clone());
}
}
self.rewind = Some(state);
}
#[cfg(not(feature = "time-travel"))]
let _ = message;
}
}
Task::none()
} }
} }
} }
@ -260,8 +303,10 @@ where
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 view = program.view(&self.state, window).map(Event::Program); let state = self.rewind.as_ref().unwrap_or(&self.state);
let theme = program.theme(&self.state, window);
let view = program.view(state, window).map(Event::Program);
let theme = program.theme(state, window);
let derive_theme = move || { let derive_theme = move || {
theme theme
@ -363,6 +408,7 @@ where
}); });
stack![view] stack![view]
.height(Fill)
.push_maybe(mode.map(opaque)) .push_maybe(mode.map(opaque))
.push_maybe(notification) .push_maybe(notification)
.into() .into()
@ -372,6 +418,8 @@ where
let subscription = let subscription =
program.subscription(&self.state).map(Event::Program); program.subscription(&self.state).map(Event::Program);
debug::subscriptions_tracked(subscription.units());
let hotkeys = let hotkeys =
futures::keyboard::on_key_press(|key, _modifiers| match key { futures::keyboard::on_key_press(|key, _modifiers| match key {
keyboard::Key::Named(keyboard::key::Named::F12) => { keyboard::Key::Named(keyboard::key::Named::F12) => {
@ -381,19 +429,22 @@ where
}) })
.map(Event::Message); .map(Event::Message);
Subscription::batch([subscription, hotkeys]) let commands = debug::commands().map(Event::Command);
Subscription::batch([subscription, hotkeys, commands])
} }
pub fn theme(&self, program: &P, window: window::Id) -> P::Theme { pub fn theme(&self, program: &P, window: window::Id) -> P::Theme {
program.theme(&self.state, window) program.theme(self.rewind.as_ref().unwrap_or(&self.state), window)
} }
pub fn style(&self, program: &P, theme: &P::Theme) -> theme::Style { pub fn style(&self, program: &P, theme: &P::Theme) -> theme::Style {
program.style(&self.state, theme) program.style(self.rewind.as_ref().unwrap_or(&self.state), theme)
} }
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 { pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 {
program.scale_factor(&self.state, window) program
.scale_factor(self.rewind.as_ref().unwrap_or(&self.state), window)
} }
} }
@ -403,6 +454,7 @@ where
{ {
Message(Message), Message(Message),
Program(P::Message), Program(P::Message),
Command(debug::Command),
} }
impl<P> fmt::Debug for Event<P> impl<P> fmt::Debug for Event<P>
@ -413,6 +465,21 @@ where
match self { match self {
Self::Message(message) => message.fmt(f), Self::Message(message) => message.fmt(f),
Self::Program(message) => message.fmt(f), Self::Program(message) => message.fmt(f),
Self::Command(command) => command.fmt(f),
}
}
}
#[cfg(feature = "time-travel")]
impl<P> Clone for Event<P>
where
P: Program,
{
fn clone(&self) -> Self {
match self {
Event::Message(message) => Event::Message(message.clone()),
Event::Program(message) => Event::Program(message.clone()),
Event::Command(command) => Event::Command(*command),
} }
} }
} }

View file

@ -23,7 +23,7 @@ struct Multitouch {
fingers: HashMap<touch::Finger, Point>, fingers: HashMap<touch::Finger, Point>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
enum Message { enum Message {
FingerPressed { id: touch::Finger, position: Point }, FingerPressed { id: touch::Finger, position: Point },
FingerLifted { id: touch::Finger }, FingerLifted { id: touch::Finger },

View file

@ -105,6 +105,10 @@ impl State {
} }
pub fn update(&mut self, now: Instant) { pub fn update(&mut self, now: Instant) {
if self.start > now {
self.start = now;
}
self.now = now; self.now = now;
self.system_cache.clear(); self.system_cache.clear();
} }

View file

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["tokio", "debug"] iced.features = ["tokio", "debug", "time-travel"]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View file

@ -285,6 +285,11 @@ impl<T> Subscription<T> {
.collect(), .collect(),
} }
} }
/// Returns the amount of recipe units in this [`Subscription`].
pub fn units(&self) -> usize {
self.recipes.len()
}
} }
/// Creates a [`Subscription`] from a [`Recipe`] describing it. /// Creates a [`Subscription`] from a [`Recipe`] describing it.

View file

@ -10,9 +10,12 @@ categories.workspace = true
keywords.workspace = true keywords.workspace = true
rust-version.workspace = true rust-version.workspace = true
[lints]
workspace = true
[features]
time-travel = []
[dependencies] [dependencies]
iced_graphics.workspace = true iced_graphics.workspace = true
iced_runtime.workspace = true iced_runtime.workspace = true
[lints]
workspace = true

View file

@ -22,7 +22,7 @@ pub trait Program: Sized {
type State; type State;
/// The message of the program. /// The message of the program.
type Message: Send + std::fmt::Debug + 'static; type Message: Message + 'static;
/// The theme of the program. /// The theme of the program.
type Theme: Default + theme::Base; type Theme: Default + theme::Base;
@ -642,3 +642,17 @@ impl<P: Program> Instance<P> {
self.program.scale_factor(&self.state, window) self.program.scale_factor(&self.state, window)
} }
} }
/// A trait alias for the [`Message`](Program::Message) of a [`Program`].
#[cfg(feature = "time-travel")]
pub trait Message: Send + std::fmt::Debug + Clone {}
#[cfg(feature = "time-travel")]
impl<T: Send + std::fmt::Debug + Clone> Message for T {}
/// A trait alias for the [`Message`](Program::Message) of a [`Program`].
#[cfg(not(feature = "time-travel"))]
pub trait Message: Send + std::fmt::Debug {}
#[cfg(not(feature = "time-travel"))]
impl<T: Send + std::fmt::Debug> Message for T {}

View file

@ -75,7 +75,7 @@ pub fn application<State, Message, Theme, Renderer>(
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>> ) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
where where
State: 'static, State: 'static,
Message: Send + std::fmt::Debug + 'static, Message: program::Message + 'static,
Theme: Default + theme::Base, Theme: Default + theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
{ {
@ -94,7 +94,7 @@ where
impl<State, Message, Theme, Renderer, Boot, Update, View> Program impl<State, Message, Theme, Renderer, Boot, Update, View> Program
for Instance<State, Message, Theme, Renderer, Boot, Update, View> for Instance<State, Message, Theme, Renderer, Boot, Update, View>
where where
Message: Send + std::fmt::Debug + 'static, Message: program::Message + 'static,
Theme: Default + theme::Base, Theme: Default + theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
Boot: self::Boot<State, Message>, Boot: self::Boot<State, Message>,

View file

@ -25,7 +25,7 @@ pub fn daemon<State, Message, Theme, Renderer>(
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>> ) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
where where
State: 'static, State: 'static,
Message: Send + std::fmt::Debug + 'static, Message: program::Message + 'static,
Theme: Default + theme::Base, Theme: Default + theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
{ {
@ -44,7 +44,7 @@ where
impl<State, Message, Theme, Renderer, Boot, Update, View> Program impl<State, Message, Theme, Renderer, Boot, Update, View> Program
for Instance<State, Message, Theme, Renderer, Boot, Update, View> for Instance<State, Message, Theme, Renderer, Boot, Update, View>
where where
Message: Send + std::fmt::Debug + 'static, Message: program::Message + 'static,
Theme: Default + theme::Base, Theme: Default + theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
Boot: application::Boot<State, Message>, Boot: application::Boot<State, Message>,

View file

@ -343,7 +343,7 @@
//! use iced::window; //! use iced::window;
//! use iced::{Size, Subscription}; //! use iced::{Size, Subscription};
//! //!
//! #[derive(Debug)] //! #[derive(Debug, Clone)]
//! enum Message { //! enum Message {
//! WindowResized(Size), //! WindowResized(Size),
//! } //! }
@ -387,7 +387,7 @@
//! # pub fn update(&mut self, message: Message) -> Action { unimplemented!() } //! # pub fn update(&mut self, message: Message) -> Action { unimplemented!() }
//! # pub fn view(&self) -> Element<Message> { unimplemented!() } //! # pub fn view(&self) -> Element<Message> { unimplemented!() }
//! # } //! # }
//! # #[derive(Debug)] //! # #[derive(Debug, Clone)]
//! # pub enum Message {} //! # pub enum Message {}
//! # pub enum Action { None, Run(Task<Message>), Chat(()) } //! # pub enum Action { None, Run(Task<Message>), Chat(()) }
//! # } //! # }
@ -399,7 +399,7 @@
//! # pub fn update(&mut self, message: Message) -> Task<Message> { unimplemented!() } //! # pub fn update(&mut self, message: Message) -> Task<Message> { unimplemented!() }
//! # pub fn view(&self) -> Element<Message> { unimplemented!() } //! # pub fn view(&self) -> Element<Message> { unimplemented!() }
//! # } //! # }
//! # #[derive(Debug)] //! # #[derive(Debug, Clone)]
//! # pub enum Message {} //! # pub enum Message {}
//! # } //! # }
//! use contacts::Contacts; //! use contacts::Contacts;
@ -697,7 +697,7 @@ pub fn run<State, Message, Theme, Renderer>(
) -> Result ) -> Result
where where
State: Default + 'static, State: Default + 'static,
Message: std::fmt::Debug + Send + 'static, Message: program::Message + 'static,
Theme: Default + theme::Base + 'static, Theme: Default + theme::Base + 'static,
Renderer: program::Renderer + 'static, Renderer: program::Renderer + 'static,
{ {

View file

@ -1069,10 +1069,7 @@ fn update<P: Program, E: Executor>(
P::Theme: theme::Base, P::Theme: theme::Base,
{ {
for message in messages.drain(..) { for message in messages.drain(..) {
let update_span = debug::update(&message);
let task = runtime.enter(|| program.update(message)); let task = runtime.enter(|| program.update(message));
debug::tasks_spawned(task.units());
update_span.finish();
if let Some(stream) = runtime::task::into_stream(task) { if let Some(stream) = runtime::task::into_stream(task) {
runtime.run(stream); runtime.run(stream);
@ -1082,7 +1079,6 @@ fn update<P: Program, E: Executor>(
let subscription = runtime.enter(|| program.subscription()); let subscription = runtime.enter(|| program.subscription());
let recipes = subscription::into_recipes(subscription.map(Action::Output)); let recipes = subscription::into_recipes(subscription.map(Action::Output));
debug::subscriptions_tracked(recipes.len());
runtime.track(recipes); runtime.track(recipes);
} }