Rename iced_sentinel to iced_beacon and refactor its API

This commit is contained in:
Héctor Ramón Jiménez 2024-05-10 20:08:09 +02:00
parent aaf396256e
commit 57033dc4d0
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
19 changed files with 596 additions and 438 deletions

View file

@ -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" }

View file

@ -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"]

123
beacon/src/client.rs Normal file
View file

@ -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<Message>,
_handle: Arc<thread::JoinHandle<()>>,
}
#[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<Message>) {
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<net::TcpStream, io::Error> {
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(())
}

184
beacon/src/lib.rs Normal file
View file

@ -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<Item = Event> {
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<net::TcpStream, io::Error> {
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<u8>,
) -> Result<client::Message, 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)?)
}
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<bincode::ErrorKind>),
}

61
beacon/src/span.rs Normal file
View file

@ -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,
})
}
}

15
beacon/src/stream.rs Normal file
View file

@ -0,0 +1,15 @@
use futures::channel::mpsc;
use futures::stream::{self, Stream, StreamExt};
use futures::Future;
pub fn channel<T, F>(f: impl Fn(mpsc::Sender<T>) -> F) -> impl Stream<Item = T>
where
F: Future<Output = ()>,
{
let (sender, receiver) = mpsc::channel(1);
stream::select(
receiver,
stream::once(f(sender)).filter_map(|_| async { None }),
)
}

View file

@ -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

View file

@ -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<theme::Palette>) {
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<str>) -> Timer {
pub fn time(window: window::Id, name: impl AsRef<str>) -> 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<theme::Palette>) {
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<str>) -> Timer {
timer(timing::Stage::Custom(window, name.as_ref().to_owned()))
pub fn time(window: window::Id, name: impl AsRef<str>) -> 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<theme::Palette>,
skip_next_timing: bool,
}
fn lock() -> MutexGuard<'static, Debug> {
static DEBUG: Lazy<Mutex<Debug>> = Lazy::new(|| {
Mutex::new(Debug {
sentinel: client::connect(),
last_palette: None,
skip_next_timing: false,
})
static BEACON: Lazy<Client> = Lazy::new(|| {
client::connect(NAME.read().expect("Read application name").to_owned())
});
DEBUG.lock().expect("Acquire debug lock")
}
static NAME: RwLock<String> = RwLock::new(String::new());
static LAST_PALETTE: RwLock<Option<theme::Palette>> = 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<theme::Palette>) {}
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<str>) -> Timer {
Timer
pub fn time(_window: window::Id, _name: impl AsRef<str>) -> Span {
Span
}
pub fn skip_next_timing() {}
#[derive(Debug)]
pub struct Timer;
pub struct Span;
impl Timer {
impl Span {
pub fn finish(self) {}
}
}

2
docs/logo-no-shadow.svg Normal file
View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140" fill="none" version="1.1" viewBox="35 31 179 171"><rect x="42" y="31.001" width="169.9" height="169.9" rx="49.815" fill="url(#paint1_linear)"/><path d="m182.62 65.747-28.136 28.606-6.13-6.0291 28.136-28.606 6.13 6.0291zm-26.344 0.218-42.204 42.909-6.13-6.029 42.204-42.909 6.13 6.0291zm-61.648 23.913c5.3254-5.3831 10.65-10.765 21.569-21.867l6.13 6.0291c-10.927 11.11-16.258 16.498-21.587 21.885-4.4007 4.4488-8.8009 8.8968-16.359 16.573l31.977 8.358 25.968-26.402 6.13 6.0292-25.968 26.402 8.907 31.908 42.138-42.087 6.076 6.083-49.109 49.05-45.837-12.628-13.394-45.646 1.7714-1.801c10.928-11.111 16.258-16.499 21.588-21.886zm28.419 70.99-8.846-31.689-31.831-8.32 9.1945 31.335 31.482 8.674zm47.734-56.517 7.122-7.1221-6.08-6.0797-7.147 7.1474-30.171 30.674 6.13 6.029 30.146-30.649z" clip-rule="evenodd" fill="url(#paint2_linear)" fill-rule="evenodd"/><defs><filter id="filter0_f" x="55" y="47.001" width="144" height="168" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur" stdDeviation="2"/></filter><linearGradient id="paint0_linear" x1="127" x2="127" y1="51.001" y2="211" gradientUnits="userSpaceOnUse"><stop offset=".052083"/><stop stop-opacity=".08" offset="1"/></linearGradient><linearGradient id="paint1_linear" x1="212" x2="57.5" y1="31.001" y2="189" gradientUnits="userSpaceOnUse"><stop stop-color="#00A3FF" offset="0"/><stop stop-color="#30f" offset="1"/></linearGradient><linearGradient id="paint2_linear" x1="86.098" x2="206.01" y1="158.28" y2="35.327" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" offset="0"/><stop stop-color="#fff" offset="1"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -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
}

View file

@ -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<Input>,
}
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<Input>) {
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::BufStream<net::TcpStream>, 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<net::TcpStream>,
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(())
}

View file

@ -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<Item = Event> {
enum State {
Disconnected,
Connected(BufStream<net::TcpStream>),
}
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<net::TcpStream, io::Error> {
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<net::TcpStream>,
) -> Result<(BufStream<net::TcpStream>, 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");
}
}
}
}

View file

@ -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),
}
}
}

View file

@ -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<A::Message>) {
let (app, command) = A::new(flags);

View file

@ -106,6 +106,12 @@ where
type Renderer = Renderer;
type Executor = executor::Default;
fn name() -> &'static str {
let type_name = std::any::type_name::<State>();
type_name.split("::").next().unwrap_or(type_name)
}
fn load(&self) -> Command<Self::Message> {
Command::none()
}
@ -211,6 +217,10 @@ impl<P: Definition> Program<P> {
)
}
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<Self::Message>;
fn update(
@ -484,12 +496,16 @@ fn with_title<P: Definition>(
type Renderer = P::Renderer;
type Executor = P::Executor;
fn title(&self, state: &Self::State) -> String {
self.title.title(state)
}
fn load(&self) -> Command<Self::Message> {
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<P: Definition>(
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<P: Definition>(
(self.subscription)(state)
}
fn name() -> &'static str {
P::name()
}
fn load(&self) -> Command<Self::Message> {
self.program.load()
}
@ -686,6 +710,10 @@ fn with_theme<P: Definition>(
(self.theme)(state)
}
fn name() -> &'static str {
P::name()
}
fn load(&self) -> Command<Self::Message> {
self.program.load()
}
@ -755,6 +783,10 @@ fn with_style<P: Definition>(
(self.style)(state, theme)
}
fn name() -> &'static str {
P::name()
}
fn load(&self) -> Command<Self::Message> {
self.program.load()
}

View file

@ -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<u32> {
fn measure_image(&self, handle: &Self::Handle) -> core::Size<u32> {
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<u32> {
fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size<u32> {
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)
}

View file

@ -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<A, E, C>(
mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>,
init_command: Command<A::Message>,
fonts: Vec<Cow<'static, [u8]>>,
boot_timer: debug::Timer,
boot_span: debug::Span,
) where
A: Application + 'static,
E: Executor + 'static,
@ -554,7 +558,7 @@ async fn run_instance<A, E, C>(
&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<A, E, C>(
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<A, E, C>(
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<A, E, C>(
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<A, E, C>(
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<A, E, C>(
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<A, E, C>(
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<A, E, C>(
{
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<A: Application, C, E: Executor>(
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,

View file

@ -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)
});
}
}

View file

@ -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<A, E, C>(
mut event_receiver: mpsc::UnboundedReceiver<Event<A::Message>>,
mut control_sender: mpsc::UnboundedSender<Control>,
init_command: Command<A::Message>,
boot_timer: debug::Timer,
boot_span: debug::Span,
) where
A: Application + 'static,
E: Executor + 'static,
@ -524,7 +524,7 @@ async fn run_instance<A, E, C>(
);
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<A, E, C>(
&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<A, E, C>(
},
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<A, E, C>(
{
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<A, E, C>(
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<A, E, C>(
},
window.state.cursor(),
);
draw_time.finish();
draw.finish();
if new_mouse_interaction != window.mouse_interaction
{
@ -739,7 +739,7 @@ async fn run_instance<A, E, C>(
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<A, E, C>(
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<A, E, C>(
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<A, E, C>(
{
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<A: Application, C, E: Executor>(
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,