diff --git a/Cargo.toml b/Cargo.toml index 2ea64300..3874b403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ members = [ "highlighter", "renderer", "runtime", - "sentinel", + "beacon", "tiny_skia", "wgpu", "widget", @@ -126,6 +126,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] [workspace.dependencies] iced = { version = "0.13.0-dev", path = "." } +iced_beacon = { version = "0.13.0-dev", path = "beacon" } iced_core = { version = "0.13.0-dev", path = "core" } iced_debug = { version = "0.13.0-dev", path = "debug" } iced_futures = { version = "0.13.0-dev", path = "futures" } @@ -133,7 +134,6 @@ iced_graphics = { version = "0.13.0-dev", path = "graphics" } iced_highlighter = { version = "0.13.0-dev", path = "highlighter" } iced_renderer = { version = "0.13.0-dev", path = "renderer" } iced_runtime = { version = "0.13.0-dev", path = "runtime" } -iced_sentinel = { version = "0.13.0-dev", path = "sentinel" } iced_tiny_skia = { version = "0.13.0-dev", path = "tiny_skia" } iced_wgpu = { version = "0.13.0-dev", path = "wgpu" } iced_widget = { version = "0.13.0-dev", path = "widget" } diff --git a/sentinel/Cargo.toml b/beacon/Cargo.toml similarity index 93% rename from sentinel/Cargo.toml rename to beacon/Cargo.toml index d8ec8e64..f141fabe 100644 --- a/sentinel/Cargo.toml +++ b/beacon/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "iced_sentinel" +name = "iced_beacon" description = "A client/server protocol to monitor and supervise iced applications" version.workspace = true edition.workspace = true @@ -17,6 +17,7 @@ iced_core.features = ["serde"] bincode.workspace = true futures.workspace = true log.workspace = true +thiserror.workspace = true tokio.workspace = true tokio.features = ["rt", "rt-multi-thread", "net", "sync", "time", "io-util", "macros"] diff --git a/beacon/src/client.rs b/beacon/src/client.rs new file mode 100644 index 00000000..28cb2eeb --- /dev/null +++ b/beacon/src/client.rs @@ -0,0 +1,123 @@ +use crate::core::time::{Duration, SystemTime}; +use crate::span; +use crate::theme; + +use semver::Version; +use serde::{Deserialize, Serialize}; +use tokio::io::{self, AsyncWriteExt}; +use tokio::net; +use tokio::sync::mpsc; +use tokio::time; + +use std::sync::Arc; +use std::thread; + +pub const SERVER_ADDRESS: &str = "127.0.0.1:9167"; + +#[derive(Debug, Clone)] +pub struct Client { + sender: mpsc::Sender, + _handle: Arc>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Message { + Connected { + at: SystemTime, + name: String, + version: Version, + }, + EventLogged { + at: SystemTime, + event: Event, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Event { + ThemeChanged(theme::Palette), + SpanStarted(span::Stage), + SpanFinished(span::Stage, Duration), +} + +impl Client { + pub fn log(&self, event: Event) { + let _ = self.sender.try_send(Message::EventLogged { + at: SystemTime::now(), + event, + }); + } +} + +#[must_use] +pub fn connect(name: String) -> Client { + let (sender, receiver) = mpsc::channel(100); + + let handle = std::thread::spawn(move || run(name, receiver)); + + Client { + sender, + _handle: Arc::new(handle), + } +} + +#[tokio::main] +async fn run(name: String, mut receiver: mpsc::Receiver) { + let version = semver::Version::parse(env!("CARGO_PKG_VERSION")) + .expect("Parse package version"); + + loop { + match _connect().await { + Ok(mut stream) => { + let _ = send( + &mut stream, + Message::Connected { + at: SystemTime::now(), + name: name.clone(), + version: version.clone(), + }, + ) + .await; + + while let Some(output) = receiver.recv().await { + match send(&mut stream, output).await { + Ok(()) => {} + Err(error) => { + log::warn!( + "Error sending message to server: {error}" + ); + break; + } + } + } + } + Err(_) => { + time::sleep(time::Duration::from_secs(2)).await; + } + } + } +} + +async fn _connect() -> Result { + log::debug!("Attempting to connect to server..."); + let stream = net::TcpStream::connect(SERVER_ADDRESS).await?; + + stream.set_nodelay(true)?; + stream.writable().await?; + + Ok(stream) +} + +async fn send( + stream: &mut net::TcpStream, + message: Message, +) -> Result<(), io::Error> { + let bytes = bincode::serialize(&message).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(()) +} diff --git a/beacon/src/lib.rs b/beacon/src/lib.rs new file mode 100644 index 00000000..3149d8b5 --- /dev/null +++ b/beacon/src/lib.rs @@ -0,0 +1,184 @@ +pub use iced_core as core; +pub use semver::Version; + +pub mod client; +pub mod span; + +mod stream; + +pub use client::Client; +pub use span::Span; + +use crate::core::theme; +use crate::core::time::{Duration, SystemTime}; + +use futures::{SinkExt, Stream}; +use tokio::io::{self, AsyncReadExt}; +use tokio::net; + +#[derive(Debug, Clone)] +pub enum Event { + Connected { + at: SystemTime, + name: String, + version: Version, + }, + Disconnected { + at: SystemTime, + }, + ThemeChanged { + at: SystemTime, + palette: theme::Palette, + }, + SpanFinished { + at: SystemTime, + duration: Duration, + span: Span, + }, +} + +impl Event { + pub fn at(&self) -> SystemTime { + match self { + Self::Connected { at, .. } + | Self::Disconnected { at, .. } + | Self::ThemeChanged { at, .. } + | Self::SpanFinished { at, .. } => *at, + } + } +} + +pub fn run() -> impl Stream { + stream::channel(|mut output| async move { + let mut buffer = Vec::new(); + + loop { + let Ok(mut stream) = connect().await else { + delay().await; + continue; + }; + + loop { + match receive(&mut stream, &mut buffer).await { + Ok(message) => { + match message { + client::Message::Connected { + at, + name, + version, + } => { + let _ = output + .send(Event::Connected { + at, + name, + version, + }) + .await; + } + client::Message::EventLogged { at, event } => { + match event { + client::Event::ThemeChanged(palette) => { + let _ = output + .send(Event::ThemeChanged { + at, + palette, + }) + .await; + } + client::Event::SpanStarted(_) => {} + client::Event::SpanFinished( + stage, + duration, + ) => { + let span = match stage { + span::Stage::Boot => Span::Boot, + span::Stage::Update => Span::Update, + span::Stage::View(window) => { + Span::View { window } + } + span::Stage::Layout(window) => { + Span::Layout { window } + } + span::Stage::Interact(window) => { + Span::Interact { window } + } + span::Stage::Draw(window) => { + Span::Draw { window } + } + span::Stage::Present(window) => { + Span::Present { window } + } + span::Stage::Custom( + window, + name, + ) => Span::Custom { window, name }, + }; + + let _ = output + .send(Event::SpanFinished { + at, + duration, + span, + }) + .await; + } + } + } + }; + } + Err(Error::IOFailed(_)) => { + let _ = output + .send(Event::Disconnected { + at: SystemTime::now(), + }) + .await; + + delay().await; + break; + } + Err(Error::DecodingFailed(error)) => { + log::warn!("Error decoding beacon output: {error}") + } + } + } + } + }) +} + +async fn connect() -> Result { + let listener = net::TcpListener::bind(client::SERVER_ADDRESS).await?; + + let (stream, _) = listener.accept().await?; + + stream.set_nodelay(true)?; + stream.readable().await?; + + Ok(stream) +} + +async fn receive( + stream: &mut net::TcpStream, + buffer: &mut Vec, +) -> Result { + 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)?) +} + +async fn delay() { + 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), +} diff --git a/beacon/src/span.rs b/beacon/src/span.rs new file mode 100644 index 00000000..7d673663 --- /dev/null +++ b/beacon/src/span.rs @@ -0,0 +1,61 @@ +use crate::core::window; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Span { + Boot, + Update, + View { window: window::Id }, + Layout { window: window::Id }, + Interact { window: window::Id }, + Draw { window: window::Id }, + Present { window: window::Id }, + Custom { window: window::Id, name: String }, +} + +impl Span { + pub fn stage(&self) -> Stage { + match self { + Span::Boot => Stage::Boot, + Span::Update => Stage::Update, + Span::View { window } => Stage::View(*window), + Span::Layout { window } => Stage::Layout(*window), + Span::Interact { window } => Stage::Interact(*window), + Span::Draw { window } => Stage::Draw(*window), + Span::Present { window } => Stage::Present(*window), + Span::Custom { window, name } => { + Stage::Custom(*window, name.clone()) + } + } + } +} + +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, +)] +pub enum Stage { + Boot, + Update, + View(window::Id), + Layout(window::Id), + Interact(window::Id), + Draw(window::Id), + Present(window::Id), + Custom(window::Id, String), +} + +impl std::fmt::Display for Stage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Stage::Boot => "Boot", + Stage::Update => "Update", + Stage::View(_) => "View", + Stage::Layout(_) => "Layout", + Stage::Interact(_) => "Interact", + Stage::Draw(_) => "Draw", + Stage::Present(_) => "Present", + Stage::Custom(_, name) => name, + }) + } +} diff --git a/beacon/src/stream.rs b/beacon/src/stream.rs new file mode 100644 index 00000000..855576e7 --- /dev/null +++ b/beacon/src/stream.rs @@ -0,0 +1,15 @@ +use futures::channel::mpsc; +use futures::stream::{self, Stream, StreamExt}; +use futures::Future; + +pub fn channel(f: impl Fn(mpsc::Sender) -> F) -> impl Stream +where + F: Future, +{ + let (sender, receiver) = mpsc::channel(1); + + stream::select( + receiver, + stream::once(f(sender)).filter_map(|_| async { None }), + ) +} diff --git a/debug/Cargo.toml b/debug/Cargo.toml index 4e3e0a61..99ee1ea1 100644 --- a/debug/Cargo.toml +++ b/debug/Cargo.toml @@ -11,13 +11,13 @@ categories.workspace = true keywords.workspace = true [features] -enable = ["dep:iced_sentinel", "dep:once_cell"] +enable = ["dep:iced_beacon", "dep:once_cell"] [dependencies] iced_core.workspace = true -iced_sentinel.workspace = true -iced_sentinel.optional = true +iced_beacon.workspace = true +iced_beacon.optional = true once_cell.workspace = true once_cell.optional = true diff --git a/debug/src/lib.rs b/debug/src/lib.rs index 4fc9a9a1..779cbb2c 100644 --- a/debug/src/lib.rs +++ b/debug/src/lib.rs @@ -3,45 +3,49 @@ pub use iced_core as core; use crate::core::theme; use crate::core::window; -pub use internal::Timer; +pub use internal::Span; -pub fn open_axe() {} +pub fn init(name: &str) { + internal::init(name); +} + +pub fn open_comet() {} pub fn log_message(_message: &impl std::fmt::Debug) {} -pub fn theme_changed(palette: theme::Palette) { - internal::theme_changed(palette); +pub fn theme_changed(f: impl FnOnce() -> Option) { + internal::theme_changed(f); } -pub fn boot_time() -> Timer { - internal::boot_time() +pub fn boot() -> Span { + internal::boot() } -pub fn update_time() -> Timer { - internal::update_time() +pub fn update() -> Span { + internal::update() } -pub fn view_time(window: window::Id) -> Timer { - internal::view_time(window) +pub fn view(window: window::Id) -> Span { + internal::view(window) } -pub fn layout_time(window: window::Id) -> Timer { - internal::layout_time(window) +pub fn layout(window: window::Id) -> Span { + internal::layout(window) } -pub fn interact_time(window: window::Id) -> Timer { - internal::interact_time(window) +pub fn interact(window: window::Id) -> Span { + internal::interact(window) } -pub fn draw_time(window: window::Id) -> Timer { - internal::draw_time(window) +pub fn draw(window: window::Id) -> Span { + internal::draw(window) } -pub fn render_time(window: window::Id) -> Timer { - internal::render_time(window) +pub fn present(window: window::Id) -> Span { + internal::present(window) } -pub fn time(window: window::Id, name: impl AsRef) -> Timer { +pub fn time(window: window::Id, name: impl AsRef) -> Span { internal::time(window, name) } @@ -52,158 +56,156 @@ pub fn skip_next_timing() { #[cfg(feature = "enable")] mod internal { use crate::core::theme; - use crate::core::time::{Instant, SystemTime}; + use crate::core::time::Instant; use crate::core::window; - use iced_sentinel::client::{self, Client}; - use iced_sentinel::timing::{self, Timing}; + use iced_beacon as beacon; + + use beacon::client::{self, Client}; + use beacon::span; use once_cell::sync::Lazy; - use std::sync::{Mutex, MutexGuard}; + use std::sync::atomic::{self, AtomicBool}; + use std::sync::RwLock; - pub fn theme_changed(palette: theme::Palette) { - let mut debug = lock(); + pub fn init(name: &str) { + name.clone_into(&mut NAME.write().expect("Write application name")); + } - if debug.last_palette.as_ref() != Some(&palette) { - debug.sentinel.report_theme_change(palette); + pub fn theme_changed(f: impl FnOnce() -> Option) { + let Some(palette) = f() else { + return; + }; - debug.last_palette = Some(palette); + if LAST_PALETTE.read().expect("Read last palette").as_ref() + != Some(&palette) + { + BEACON.log(client::Event::ThemeChanged(palette)); + + *LAST_PALETTE.write().expect("Write last palette") = Some(palette); } } - pub fn boot_time() -> Timer { - timer(timing::Stage::Boot) + pub fn boot() -> Span { + span(span::Stage::Boot) } - pub fn update_time() -> Timer { - timer(timing::Stage::Update) + pub fn update() -> Span { + span(span::Stage::Update) } - pub fn view_time(window: window::Id) -> Timer { - timer(timing::Stage::View(window)) + pub fn view(window: window::Id) -> Span { + span(span::Stage::View(window)) } - pub fn layout_time(window: window::Id) -> Timer { - timer(timing::Stage::Layout(window)) + pub fn layout(window: window::Id) -> Span { + span(span::Stage::Layout(window)) } - pub fn interact_time(window: window::Id) -> Timer { - timer(timing::Stage::Interact(window)) + pub fn interact(window: window::Id) -> Span { + span(span::Stage::Interact(window)) } - pub fn draw_time(window: window::Id) -> Timer { - timer(timing::Stage::Draw(window)) + pub fn draw(window: window::Id) -> Span { + span(span::Stage::Draw(window)) } - pub fn render_time(window: window::Id) -> Timer { - timer(timing::Stage::Render(window)) + pub fn present(window: window::Id) -> Span { + span(span::Stage::Present(window)) } - pub fn time(window: window::Id, name: impl AsRef) -> Timer { - timer(timing::Stage::Custom(window, name.as_ref().to_owned())) + pub fn time(window: window::Id, name: impl AsRef) -> Span { + span(span::Stage::Custom(window, name.as_ref().to_owned())) } pub fn skip_next_timing() { - lock().skip_next_timing = true; + SKIP_NEXT_SPAN.store(true, atomic::Ordering::Relaxed); } - fn timer(stage: timing::Stage) -> Timer { - Timer { - stage, + fn span(span: span::Stage) -> Span { + BEACON.log(client::Event::SpanStarted(span.clone())); + + Span { + span, start: Instant::now(), - start_system_time: SystemTime::now(), } } #[derive(Debug)] - pub struct Timer { - stage: timing::Stage, + pub struct Span { + span: span::Stage, start: Instant, - start_system_time: SystemTime, } - impl Timer { + impl Span { pub fn finish(self) { - let mut debug = lock(); - - if debug.skip_next_timing { - debug.skip_next_timing = false; + if SKIP_NEXT_SPAN.fetch_and(false, atomic::Ordering::Relaxed) { return; } - debug.sentinel.report_timing(Timing { - stage: self.stage, - start: self.start_system_time, - duration: self.start.elapsed(), - }); + BEACON.log(client::Event::SpanFinished( + self.span, + self.start.elapsed(), + )); } } - #[derive(Debug)] - struct Debug { - sentinel: Client, - last_palette: Option, - skip_next_timing: bool, - } + static BEACON: Lazy = Lazy::new(|| { + client::connect(NAME.read().expect("Read application name").to_owned()) + }); - fn lock() -> MutexGuard<'static, Debug> { - static DEBUG: Lazy> = Lazy::new(|| { - Mutex::new(Debug { - sentinel: client::connect(), - last_palette: None, - skip_next_timing: false, - }) - }); - - DEBUG.lock().expect("Acquire debug lock") - } + static NAME: RwLock = RwLock::new(String::new()); + static LAST_PALETTE: RwLock> = RwLock::new(None); + static SKIP_NEXT_SPAN: AtomicBool = AtomicBool::new(false); } #[cfg(not(feature = "enable"))] mod internal { + use crate::core::theme; use crate::core::window; - use crate::style::theme; - pub fn theme_changed(_palette: theme::Palette) {} + pub fn init(_name: &str) {} - pub fn boot_time() -> Timer { - Timer + pub fn theme_changed(_f: impl FnOnce() -> Option) {} + + pub fn boot() -> Span { + Span } - pub fn update_time() -> Timer { - Timer + pub fn update() -> Span { + Span } - pub fn view_time(_window: window::Id) -> Timer { - Timer + pub fn view(_window: window::Id) -> Span { + Span } - pub fn layout_time(_window: window::Id) -> Timer { - Timer + pub fn layout(_window: window::Id) -> Span { + Span } - pub fn interact_time(_window: window::Id) -> Timer { - Timer + pub fn interact(_window: window::Id) -> Span { + Span } - pub fn draw_time(_window: window::Id) -> Timer { - Timer + pub fn draw(_window: window::Id) -> Span { + Span } - pub fn render_time(_window: window::Id) -> Timer { - Timer + pub fn present(_window: window::Id) -> Span { + Span } - pub fn time(_window: window::Id, _name: impl AsRef) -> Timer { - Timer + pub fn time(_window: window::Id, _name: impl AsRef) -> Span { + Span } pub fn skip_next_timing() {} #[derive(Debug)] - pub struct Timer; + pub struct Span; - impl Timer { + impl Span { pub fn finish(self) {} } } diff --git a/docs/logo-no-shadow.svg b/docs/logo-no-shadow.svg new file mode 100644 index 00000000..459b7fbb --- /dev/null +++ b/docs/logo-no-shadow.svg @@ -0,0 +1,2 @@ + + diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 182169be..129f2449 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -102,7 +102,7 @@ where bounds, ); - let interact_timer = debug::interact_time(window::Id::MAIN); + let interact_span = debug::interact(window::Id::MAIN); let mut messages = Vec::new(); let (_, event_statuses) = user_interface.update( @@ -125,13 +125,13 @@ where self.queued_events.clear(); messages.append(&mut self.queued_messages); - drop(interact_timer); + interact_span.finish(); let command = if messages.is_empty() { - let draw_timer = debug::draw_time(window::Id::MAIN); + let draw_span = debug::draw(window::Id::MAIN); self.mouse_interaction = user_interface.draw(renderer, theme, style, cursor); - drop(draw_timer); + draw_span.finish(); self.cache = Some(user_interface.into_cache()); @@ -145,9 +145,9 @@ where Command::batch(messages.into_iter().map(|message| { debug::log_message(&message); - let update_timer = debug::update_time(); + let update_span = debug::update(); let command = self.program.update(message); - drop(update_timer); + update_span.finish(); command })); @@ -159,10 +159,10 @@ where bounds, ); - let draw_timer = debug::draw_time(window::Id::MAIN); + let draw_spawn = debug::draw(window::Id::MAIN); self.mouse_interaction = user_interface.draw(renderer, theme, style, cursor); - drop(draw_timer); + draw_spawn.finish(); self.cache = Some(user_interface.into_cache()); @@ -214,13 +214,13 @@ fn build_user_interface<'a, P: Program>( renderer: &mut P::Renderer, size: Size, ) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> { - let view_timer = debug::view_time(window::Id::MAIN); + let view_span = debug::view(window::Id::MAIN); let view = program.view(); - drop(view_timer); + view_span.finish(); - let layout_timer = debug::layout_time(window::Id::MAIN); + let layout_span = debug::layout(window::Id::MAIN); let user_interface = UserInterface::build(view, size, cache, renderer); - drop(layout_timer); + layout_span.finish(); user_interface } diff --git a/sentinel/src/client.rs b/sentinel/src/client.rs deleted file mode 100644 index 79e3dca4..00000000 --- a/sentinel/src/client.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::core::time::SystemTime; -use crate::theme; -use crate::{Input, Timing, SOCKET_ADDRESS}; - -use tokio::io::{self, AsyncWriteExt}; -use tokio::net; -use tokio::sync::mpsc; -use tokio::time; - -#[derive(Debug, Clone)] -pub struct Client { - sender: mpsc::Sender, -} - -impl Client { - pub fn report_theme_change(&mut self, palette: theme::Palette) { - let _ = self.sender.try_send(Input::ThemeChanged { - at: SystemTime::now(), - palette, - }); - } - - pub fn report_timing(&mut self, timing: Timing) { - let _ = self.sender.try_send(Input::TimingMeasured(timing)); - } -} - -#[must_use] -pub fn connect() -> Client { - let (sender, receiver) = mpsc::channel(1_000); - - std::thread::spawn(move || run(receiver)); - - Client { sender } -} - -#[tokio::main] -async fn run(mut receiver: mpsc::Receiver) { - let version = semver::Version::parse(env!("CARGO_PKG_VERSION")) - .expect("Parse package version"); - - loop { - match _connect().await { - Ok(mut stream) => { - let _ = send( - &mut stream, - Input::Connected { - at: SystemTime::now(), - version: version.clone(), - }, - ) - .await; - - while let Some(input) = receiver.recv().await { - match send(&mut stream, input).await { - Ok(()) => {} - Err(error) => { - log::warn!("Error sending message to sentinel server: {error}"); - break; - } - } - } - } - Err(_) => { - time::sleep(time::Duration::from_secs(2)).await; - } - } - } -} - -async fn _connect() -> Result, io::Error> { - log::debug!("Attempting to connect sentinel to server..."); - let stream = net::TcpStream::connect(SOCKET_ADDRESS).await?; - - stream.set_nodelay(true)?; - stream.writable().await?; - - Ok(io::BufStream::new(stream)) -} - -async fn send( - stream: &mut io::BufStream, - input: Input, -) -> Result<(), io::Error> { - let bytes = bincode::serialize(&input).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(()) -} diff --git a/sentinel/src/lib.rs b/sentinel/src/lib.rs deleted file mode 100644 index e7377861..00000000 --- a/sentinel/src/lib.rs +++ /dev/null @@ -1,137 +0,0 @@ -pub use iced_core as core; -pub use semver::Version; - -pub mod client; -pub mod timing; - -use crate::core::theme; -use crate::core::time::SystemTime; -use crate::timing::Timing; - -use futures::future; -use futures::stream::{self, Stream, StreamExt}; -use serde::{Deserialize, Serialize}; -use tokio::io::{self, AsyncReadExt, BufStream}; -use tokio::net; - -pub const SOCKET_ADDRESS: &str = "127.0.0.1:9167"; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Input { - Connected { - at: SystemTime, - version: Version, - }, - ThemeChanged { - at: SystemTime, - palette: theme::Palette, - }, - TimingMeasured(Timing), -} - -#[derive(Debug, Clone)] -pub enum Event { - Connected { - at: SystemTime, - version: Version, - }, - Disconnected { - at: SystemTime, - }, - ThemeChanged { - at: SystemTime, - palette: theme::Palette, - }, - TimingMeasured(Timing), -} - -impl Event { - pub fn at(&self) -> SystemTime { - match self { - Self::Connected { at, .. } - | Self::Disconnected { at } - | Self::ThemeChanged { at, .. } => *at, - Self::TimingMeasured(timing) => timing.start, - } - } -} - -pub fn run() -> impl Stream { - enum State { - Disconnected, - Connected(BufStream), - } - - stream::unfold(State::Disconnected, |state| async { - match state { - State::Disconnected => match connect().await { - Ok(stream) => { - let stream = BufStream::new(stream); - - Some((None, State::Connected(stream))) - } - Err(_error) => Some((None, State::Disconnected)), - }, - State::Connected(stream) => match receive(stream).await { - Ok((stream, input)) => { - let event = match input { - Input::Connected { at, version } => { - Event::Connected { at, version } - } - Input::TimingMeasured(timing) => { - Event::TimingMeasured(timing) - } - Input::ThemeChanged { at, palette } => { - Event::ThemeChanged { at, palette } - } - }; - - Some((Some(event), State::Connected(stream))) - } - Err(_) => Some(( - Some(Event::Disconnected { - at: SystemTime::now(), - }), - State::Disconnected, - )), - }, - } - }) - .filter_map(future::ready) -} - -async fn connect() -> Result { - let listener = net::TcpListener::bind(SOCKET_ADDRESS).await?; - - let (stream, _) = listener.accept().await?; - - stream.set_nodelay(true)?; - stream.readable().await?; - - Ok(stream) -} - -async fn receive( - mut stream: BufStream, -) -> Result<(BufStream, Input), io::Error> { - let mut bytes = Vec::new(); - - loop { - let size = stream.read_u64().await? as usize; - - if bytes.len() < size { - bytes.resize(size, 0); - } - - let _n = stream.read_exact(&mut bytes[..size]).await?; - - match bincode::deserialize(&bytes) { - Ok(input) => { - return Ok((stream, input)); - } - Err(_) => { - log::warn!("Error decoding sentinel message"); - } - } - } -} diff --git a/sentinel/src/timing.rs b/sentinel/src/timing.rs deleted file mode 100644 index ffbc7e46..00000000 --- a/sentinel/src/timing.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::core::time::{Duration, SystemTime}; -use crate::core::window; - -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct Timing { - pub stage: Stage, - pub start: SystemTime, - pub duration: Duration, -} - -#[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub enum Stage { - Boot, - Update, - View(window::Id), - Layout(window::Id), - Interact(window::Id), - Draw(window::Id), - Render(window::Id), - Custom(window::Id, String), -} - -impl fmt::Display for Stage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Boot => write!(f, "Boot"), - Self::Update => write!(f, "Update"), - Self::View(_) => write!(f, "View"), - Self::Layout(_) => write!(f, "Layout"), - Self::Interact(_) => write!(f, "Interact"), - Self::Draw(_) => write!(f, "Draw"), - Self::Render(_) => write!(f, "Render"), - Self::Custom(_, name) => f.write_str(name), - } - } -} diff --git a/src/application.rs b/src/application.rs index d12ba73d..7bdef972 100644 --- a/src/application.rs +++ b/src/application.rs @@ -118,6 +118,9 @@ where /// The data needed to initialize your [`Application`]. type Flags; + /// Returns the unique name of the [`Application`]. + fn name() -> &'static str; + /// Initializes the [`Application`] with the flags provided to /// [`run`] as part of the [`Settings`]. /// @@ -250,6 +253,10 @@ where { type Flags = A::Flags; + fn name() -> &'static str { + A::name() + } + fn new(flags: Self::Flags) -> (Self, Command) { let (app, command) = A::new(flags); diff --git a/src/program.rs b/src/program.rs index d4c2a266..70d3bd51 100644 --- a/src/program.rs +++ b/src/program.rs @@ -106,6 +106,12 @@ where type Renderer = Renderer; type Executor = executor::Default; + fn name() -> &'static str { + let type_name = std::any::type_name::(); + + type_name.split("::").next().unwrap_or(type_name) + } + fn load(&self) -> Command { Command::none() } @@ -211,6 +217,10 @@ impl Program

{ ) } + fn name() -> &'static str { + P::name() + } + fn title(&self) -> String { self.program.title(&self.state) } @@ -431,6 +441,8 @@ pub trait Definition: Sized { /// The executor of the program. type Executor: Executor; + fn name() -> &'static str; + fn load(&self) -> Command; fn update( @@ -484,12 +496,16 @@ fn with_title( type Renderer = P::Renderer; type Executor = P::Executor; + fn title(&self, state: &Self::State) -> String { + self.title.title(state) + } + fn load(&self) -> Command { self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.title.title(state) + fn name() -> &'static str { + P::name() } fn update( @@ -553,6 +569,10 @@ fn with_load( Command::batch([self.program.load(), (self.load)()]) } + fn name() -> &'static str { + P::name() + } + fn update( &self, state: &mut Self::State, @@ -621,6 +641,10 @@ fn with_subscription( (self.subscription)(state) } + fn name() -> &'static str { + P::name() + } + fn load(&self) -> Command { self.program.load() } @@ -686,6 +710,10 @@ fn with_theme( (self.theme)(state) } + fn name() -> &'static str { + P::name() + } + fn load(&self) -> Command { self.program.load() } @@ -755,6 +783,10 @@ fn with_style( (self.style)(state, theme) } + fn name() -> &'static str { + P::name() + } + fn load(&self) -> Command { self.program.load() } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 095e1f1b..9d6c09f5 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -61,7 +61,7 @@ pub use settings::Settings; pub use geometry::Geometry; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, + Background, Color, Font, Pixels, Point, Rectangle, Transformation, }; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; @@ -477,7 +477,7 @@ impl core::text::Renderer for Renderer { impl core::image::Renderer for Renderer { type Handle = core::image::Handle; - fn measure_image(&self, handle: &Self::Handle) -> Size { + fn measure_image(&self, handle: &Self::Handle) -> core::Size { self.image_cache.borrow_mut().measure_image(handle) } @@ -503,7 +503,7 @@ impl core::image::Renderer for Renderer { #[cfg(feature = "svg")] impl core::svg::Renderer for Renderer { - fn measure_svg(&self, handle: &core::svg::Handle) -> Size { + fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size { self.image_cache.borrow_mut().measure_svg(handle) } @@ -539,7 +539,7 @@ impl graphics::geometry::Renderer for Renderer { type Geometry = Geometry; type Frame = geometry::Frame; - fn new_frame(&self, size: Size) -> Self::Frame { + fn new_frame(&self, size: core::Size) -> Self::Frame { geometry::Frame::new(size) } diff --git a/winit/src/application.rs b/winit/src/application.rs index 3d11bd4a..49ccb61c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -48,6 +48,9 @@ where /// The data needed to initialize your [`Application`]. type Flags; + /// Returns the unique name of the [`Application`]. + fn name() -> &'static str; + /// Initializes the [`Application`] with the flags provided to /// [`run`] as part of the [`Settings`]. /// @@ -156,7 +159,8 @@ where use futures::Future; use winit::event_loop::EventLoop; - let boot_timer = debug::boot_time(); + debug::init(A::name()); + let boot_span = debug::boot(); let event_loop = EventLoop::with_user_event() .build() @@ -193,7 +197,7 @@ where control_sender, init_command, settings.fonts, - boot_timer, + boot_span, )); let context = task::Context::from_waker(task::noop_waker_ref()); @@ -498,7 +502,7 @@ async fn run_instance( mut control_sender: mpsc::UnboundedSender, init_command: Command, fonts: Vec>, - boot_timer: debug::Timer, + boot_span: debug::Span, ) where A: Application + 'static, E: Executor + 'static, @@ -554,7 +558,7 @@ async fn run_instance( &window, ); runtime.track(application.subscription().into_recipes()); - boot_timer.finish(); + boot_span.finish(); let mut user_interface = ManuallyDrop::new(build_user_interface( &application, @@ -608,12 +612,12 @@ async fn run_instance( if viewport_version != current_viewport_version { let logical_size = state.logical_size(); - let layout_timer = debug::layout_time(window::Id::MAIN); + let layout_span = debug::layout(window::Id::MAIN); user_interface = ManuallyDrop::new( ManuallyDrop::into_inner(user_interface) .relayout(logical_size, &mut renderer), ); - layout_timer.finish(); + layout_span.finish(); compositor.configure_surface( &mut surface, @@ -660,7 +664,7 @@ async fn run_instance( runtime.broadcast(redraw_event, core::event::Status::Ignored); - let draw_timer = debug::draw_time(window::Id::MAIN); + let draw_span = debug::draw(window::Id::MAIN); let new_mouse_interaction = user_interface.draw( &mut renderer, state.theme(), @@ -670,7 +674,7 @@ async fn run_instance( state.cursor(), ); redraw_pending = false; - draw_timer.finish(); + draw_span.finish(); if new_mouse_interaction != mouse_interaction { window.set_cursor(conversion::mouse_interaction( @@ -680,7 +684,7 @@ async fn run_instance( mouse_interaction = new_mouse_interaction; } - let render_timer = debug::render_time(window::Id::MAIN); + let present_span = debug::present(window::Id::MAIN); match compositor.present( &mut renderer, &mut surface, @@ -688,7 +692,7 @@ async fn run_instance( state.background_color(), ) { Ok(()) => { - render_timer.finish(); + present_span.finish(); } Err(error) => match error { // This is an unrecoverable error. @@ -733,7 +737,7 @@ async fn run_instance( redraw_request: None, } } else { - let interact_timer = debug::interact_time(window::Id::MAIN); + let interact_span = debug::interact(window::Id::MAIN); let (interface_state, statuses) = user_interface.update( &events, state.cursor(), @@ -747,7 +751,7 @@ async fn run_instance( { runtime.broadcast(event, status); } - interact_timer.finish(); + interact_span.finish(); interface_state }; @@ -842,13 +846,13 @@ pub fn build_user_interface<'a, A: Application>( where A::Theme: DefaultStyle, { - let view_timer = debug::view_time(window::Id::MAIN); + let view_span = debug::view(window::Id::MAIN); let view = application.view(); - view_timer.finish(); + view_span.finish(); - let layout_timer = debug::layout_time(window::Id::MAIN); + let layout_span = debug::layout(window::Id::MAIN); let user_interface = UserInterface::build(view, size, cache, renderer); - layout_timer.finish(); + layout_span.finish(); user_interface } @@ -875,9 +879,9 @@ pub fn update( for message in messages.drain(..) { debug::log_message(&message); - let update_timer = debug::update_time(); + let update_span = debug::update(); let command = runtime.enter(|| application.update(message)); - update_timer.finish(); + update_span.finish(); run_command( application, diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index eae9c3a8..da9519ee 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -38,8 +38,7 @@ where let theme = application.theme(); let appearance = application.style(&theme); - let _ = application::DefaultStyle::palette(&theme) - .map(debug::theme_changed); + debug::theme_changed(|| application::DefaultStyle::palette(&theme)); let viewport = { let physical_size = window.inner_size(); @@ -216,7 +215,8 @@ where self.theme = application.theme(); self.appearance = application.style(&self.theme); - let _ = application::DefaultStyle::palette(&self.theme) - .map(debug::theme_changed); + debug::theme_changed(|| { + application::DefaultStyle::palette(&self.theme) + }); } } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 61c0f736..473913bc 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -117,7 +117,7 @@ where { use winit::event_loop::EventLoop; - let boot_timer = debug::boot_time(); + let boot_span = debug::boot(); let event_loop = EventLoop::with_user_event() .build() @@ -153,7 +153,7 @@ where event_receiver, control_sender, init_command, - boot_timer, + boot_span, )); let context = task::Context::from_waker(task::noop_waker_ref()); @@ -452,7 +452,7 @@ async fn run_instance( mut event_receiver: mpsc::UnboundedReceiver>, mut control_sender: mpsc::UnboundedSender, init_command: Command, - boot_timer: debug::Timer, + boot_span: debug::Span, ) where A: Application + 'static, E: Executor + 'static, @@ -524,7 +524,7 @@ async fn run_instance( ); runtime.track(application.subscription().into_recipes()); - boot_timer.finish(); + boot_span.finish(); let mut messages = Vec::new(); let mut user_events = 0; @@ -636,7 +636,7 @@ async fn run_instance( &mut messages, ); - let draw_timer = debug::draw_time(id); + let draw_span = debug::draw(id); let new_mouse_interaction = ui.draw( &mut window.renderer, window.state.theme(), @@ -645,7 +645,7 @@ async fn run_instance( }, cursor, ); - draw_timer.finish(); + draw_span.finish(); if new_mouse_interaction != window.mouse_interaction { window.raw.set_cursor( @@ -692,7 +692,7 @@ async fn run_instance( { let logical_size = window.state.logical_size(); - let layout_time = debug::layout_time(id); + let layout = debug::layout(id); let ui = user_interfaces .remove(&id) .expect("Remove user interface"); @@ -701,9 +701,9 @@ async fn run_instance( id, ui.relayout(logical_size, &mut window.renderer), ); - layout_time.finish(); + layout.finish(); - let draw_time = debug::draw_time(id); + let draw = debug::draw(id); let new_mouse_interaction = user_interfaces .get_mut(&id) .expect("Get user interface") @@ -715,7 +715,7 @@ async fn run_instance( }, window.state.cursor(), ); - draw_time.finish(); + draw.finish(); if new_mouse_interaction != window.mouse_interaction { @@ -739,7 +739,7 @@ async fn run_instance( window.state.viewport_version(); } - let render_time = debug::render_time(id); + let present_span = debug::present(id); match compositor.present( &mut window.renderer, &mut window.surface, @@ -747,7 +747,7 @@ async fn run_instance( window.state.background_color(), ) { Ok(()) => { - render_time.finish(); + present_span.finish(); // TODO: Handle animations! // Maybe we can use `ControlFlow::WaitUntil` for this. @@ -821,7 +821,7 @@ async fn run_instance( let mut uis_stale = false; for (id, window) in window_manager.iter_mut() { - let interact_time = debug::interact_time(id); + let interact = debug::interact(id); let mut window_events = vec![]; events.retain(|(window_id, event)| { @@ -864,7 +864,7 @@ async fn run_instance( { runtime.broadcast(event, status); } - interact_time.finish(); + interact.finish(); } // TODO mw application update returns which window IDs to update @@ -938,13 +938,13 @@ fn build_user_interface<'a, A: Application>( where A::Theme: DefaultStyle, { - let view_timer = debug::view_time(id); + let view_span = debug::view(id); let view = application.view(id); - view_timer.finish(); + view_span.finish(); - let layout_timer = debug::layout_time(id); + let layout_span = debug::layout(id); let user_interface = UserInterface::build(view, size, cache, renderer); - layout_timer.finish(); + layout_span.finish(); user_interface } @@ -968,9 +968,9 @@ fn update( for message in messages.drain(..) { debug::log_message(&message); - let update_timer = debug::update_time(); + let update_span = debug::update(); let command = runtime.enter(|| application.update(message)); - update_timer.finish(); + update_span.finish(); run_command( application,