iced_debug/
lib.rs

1pub use iced_core as core;
2pub use iced_futures as futures;
3
4use crate::core::theme;
5use crate::core::window;
6use crate::futures::Subscription;
7
8pub use internal::Span;
9
10#[derive(Debug, Clone, Copy)]
11pub struct Metadata {
12    pub name: &'static str,
13    pub theme: Option<theme::Palette>,
14    pub can_time_travel: bool,
15}
16
17#[derive(Debug, Clone, Copy)]
18pub enum Primitive {
19    Quad,
20    Triangle,
21    Shader,
22    Image,
23    Text,
24}
25
26#[derive(Debug, Clone, Copy)]
27pub enum Command {
28    RewindTo { message: usize },
29    GoLive,
30}
31
32pub fn enable() {
33    internal::enable();
34}
35
36pub fn disable() {
37    internal::disable();
38}
39
40pub fn init(metadata: Metadata) {
41    internal::init(metadata);
42}
43
44pub fn quit() -> bool {
45    internal::quit()
46}
47
48pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
49    internal::theme_changed(f);
50}
51
52pub fn tasks_spawned(amount: usize) {
53    internal::tasks_spawned(amount);
54}
55
56pub fn subscriptions_tracked(amount: usize) {
57    internal::subscriptions_tracked(amount);
58}
59
60pub fn layers_rendered(amount: impl FnOnce() -> usize) {
61    internal::layers_rendered(amount);
62}
63
64pub fn boot() -> Span {
65    internal::boot()
66}
67
68pub fn update(message: &impl std::fmt::Debug) -> Span {
69    internal::update(message)
70}
71
72pub fn view(window: window::Id) -> Span {
73    internal::view(window)
74}
75
76pub fn layout(window: window::Id) -> Span {
77    internal::layout(window)
78}
79
80pub fn interact(window: window::Id) -> Span {
81    internal::interact(window)
82}
83
84pub fn draw(window: window::Id) -> Span {
85    internal::draw(window)
86}
87
88pub fn prepare(primitive: Primitive) -> Span {
89    internal::prepare(primitive)
90}
91
92pub fn render(primitive: Primitive) -> Span {
93    internal::render(primitive)
94}
95
96pub fn present(window: window::Id) -> Span {
97    internal::present(window)
98}
99
100pub fn time(name: impl Into<String>) -> Span {
101    internal::time(name)
102}
103
104pub fn time_with<T>(name: impl Into<String>, f: impl FnOnce() -> T) -> T {
105    let span = time(name);
106    let result = f();
107    span.finish();
108
109    result
110}
111
112pub fn commands() -> Subscription<Command> {
113    internal::commands()
114}
115
116#[cfg(all(feature = "enable", not(target_arch = "wasm32")))]
117mod internal {
118    use crate::core::theme;
119    use crate::core::time::Instant;
120    use crate::core::window;
121    use crate::futures::Subscription;
122    use crate::futures::futures::Stream;
123    use crate::{Command, Metadata, Primitive};
124
125    use iced_beacon as beacon;
126
127    use beacon::client::{self, Client};
128    use beacon::span;
129    use beacon::span::present;
130
131    use std::sync::atomic::{self, AtomicBool, AtomicUsize};
132    use std::sync::{LazyLock, RwLock};
133
134    pub fn init(metadata: Metadata) {
135        let name = metadata.name.split("::").next().unwrap_or(metadata.name);
136
137        *METADATA.write().expect("Write application metadata") =
138            client::Metadata {
139                name,
140                theme: metadata.theme,
141                can_time_travel: metadata.can_time_travel,
142            };
143    }
144
145    pub fn quit() -> bool {
146        if BEACON.is_connected() {
147            BEACON.quit();
148
149            true
150        } else {
151            false
152        }
153    }
154
155    pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
156        let Some(palette) = f() else {
157            return;
158        };
159
160        if METADATA.read().expect("Read last palette").theme.as_ref()
161            != Some(&palette)
162        {
163            log(client::Event::ThemeChanged(palette));
164
165            METADATA.write().expect("Write last palette").theme = Some(palette);
166        }
167    }
168
169    pub fn tasks_spawned(amount: usize) {
170        log(client::Event::CommandsSpawned(amount));
171    }
172
173    pub fn subscriptions_tracked(amount: usize) {
174        log(client::Event::SubscriptionsTracked(amount));
175    }
176
177    pub fn layers_rendered(amount: impl FnOnce() -> usize) {
178        log(client::Event::LayersRendered(amount()));
179    }
180
181    pub fn boot() -> Span {
182        span(span::Stage::Boot)
183    }
184
185    pub fn update(message: &impl std::fmt::Debug) -> Span {
186        let span = span(span::Stage::Update);
187
188        let number = LAST_UPDATE.fetch_add(1, atomic::Ordering::Relaxed);
189
190        let start = Instant::now();
191        let message = format!("{message:?}");
192        let elapsed = start.elapsed();
193
194        if elapsed.as_millis() >= 1 {
195            log::warn!(
196                "Slow `Debug` implementation of `Message` (took {elapsed:?})!"
197            );
198        }
199
200        let message = if message.len() > 49 {
201            message
202                .chars()
203                .take(49)
204                .chain("...".chars())
205                .collect::<String>()
206        } else {
207            message
208        };
209
210        log(client::Event::MessageLogged { number, message });
211
212        span
213    }
214
215    pub fn view(window: window::Id) -> Span {
216        span(span::Stage::View(window))
217    }
218
219    pub fn layout(window: window::Id) -> Span {
220        span(span::Stage::Layout(window))
221    }
222
223    pub fn interact(window: window::Id) -> Span {
224        span(span::Stage::Interact(window))
225    }
226
227    pub fn draw(window: window::Id) -> Span {
228        span(span::Stage::Draw(window))
229    }
230
231    pub fn prepare(primitive: Primitive) -> Span {
232        span(span::Stage::Prepare(to_primitive(primitive)))
233    }
234
235    pub fn render(primitive: Primitive) -> Span {
236        span(span::Stage::Render(to_primitive(primitive)))
237    }
238
239    pub fn present(window: window::Id) -> Span {
240        span(span::Stage::Present(window))
241    }
242
243    pub fn time(name: impl Into<String>) -> Span {
244        span(span::Stage::Custom(name.into()))
245    }
246
247    pub fn enable() {
248        ENABLED.store(true, atomic::Ordering::Relaxed);
249    }
250
251    pub fn disable() {
252        ENABLED.store(false, atomic::Ordering::Relaxed);
253    }
254
255    pub fn commands() -> Subscription<Command> {
256        fn listen_for_commands() -> impl Stream<Item = Command> {
257            use crate::futures::futures::stream;
258
259            stream::unfold(BEACON.subscribe(), async move |mut receiver| {
260                let command = match receiver.recv().await? {
261                    client::Command::RewindTo { message } => {
262                        Command::RewindTo { message }
263                    }
264                    client::Command::GoLive => Command::GoLive,
265                };
266
267                Some((command, receiver))
268            })
269        }
270
271        Subscription::run(listen_for_commands)
272    }
273
274    fn span(span: span::Stage) -> Span {
275        log(client::Event::SpanStarted(span.clone()));
276
277        Span {
278            span,
279            start: Instant::now(),
280        }
281    }
282
283    fn to_primitive(primitive: Primitive) -> present::Primitive {
284        match primitive {
285            Primitive::Quad => present::Primitive::Quad,
286            Primitive::Triangle => present::Primitive::Triangle,
287            Primitive::Shader => present::Primitive::Shader,
288            Primitive::Text => present::Primitive::Text,
289            Primitive::Image => present::Primitive::Image,
290        }
291    }
292
293    fn log(event: client::Event) {
294        if ENABLED.load(atomic::Ordering::Relaxed) {
295            BEACON.log(event);
296        }
297    }
298
299    #[derive(Debug)]
300    pub struct Span {
301        span: span::Stage,
302        start: Instant,
303    }
304
305    impl Span {
306        pub fn finish(self) {
307            log(client::Event::SpanFinished(self.span, self.start.elapsed()));
308        }
309    }
310
311    static BEACON: LazyLock<Client> = LazyLock::new(|| {
312        let metadata = METADATA.read().expect("Read application metadata");
313
314        client::connect(metadata.clone())
315    });
316
317    static METADATA: RwLock<client::Metadata> = RwLock::new(client::Metadata {
318        name: "",
319        theme: None,
320        can_time_travel: false,
321    });
322
323    static LAST_UPDATE: AtomicUsize = AtomicUsize::new(0);
324    static ENABLED: AtomicBool = AtomicBool::new(true);
325}
326
327#[cfg(any(not(feature = "enable"), target_arch = "wasm32"))]
328mod internal {
329    use crate::core::theme;
330    use crate::core::window;
331    use crate::futures::Subscription;
332    use crate::{Command, Metadata, Primitive};
333
334    pub fn enable() {}
335    pub fn disable() {}
336
337    pub fn init(_metadata: Metadata) {}
338
339    pub fn quit() -> bool {
340        false
341    }
342
343    pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
344
345    pub fn tasks_spawned(_amount: usize) {}
346
347    pub fn subscriptions_tracked(_amount: usize) {}
348
349    pub fn layers_rendered(_amount: impl FnOnce() -> usize) {}
350
351    pub fn boot() -> Span {
352        Span
353    }
354
355    pub fn update(_message: &impl std::fmt::Debug) -> Span {
356        Span
357    }
358
359    pub fn view(_window: window::Id) -> Span {
360        Span
361    }
362
363    pub fn layout(_window: window::Id) -> Span {
364        Span
365    }
366
367    pub fn interact(_window: window::Id) -> Span {
368        Span
369    }
370
371    pub fn draw(_window: window::Id) -> Span {
372        Span
373    }
374
375    pub fn prepare(_primitive: Primitive) -> Span {
376        Span
377    }
378
379    pub fn render(_primitive: Primitive) -> Span {
380        Span
381    }
382
383    pub fn present(_window: window::Id) -> Span {
384        Span
385    }
386
387    pub fn time(_name: impl Into<String>) -> Span {
388        Span
389    }
390
391    pub fn commands() -> Subscription<Command> {
392        Subscription::none()
393    }
394
395    #[derive(Debug)]
396    pub struct Span;
397
398    impl Span {
399        pub fn finish(self) {}
400    }
401}