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    hot::init();
43}
44
45pub fn quit() -> bool {
46    internal::quit()
47}
48
49pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
50    internal::theme_changed(f);
51}
52
53pub fn tasks_spawned(amount: usize) {
54    internal::tasks_spawned(amount);
55}
56
57pub fn subscriptions_tracked(amount: usize) {
58    internal::subscriptions_tracked(amount);
59}
60
61pub fn layers_rendered(amount: impl FnOnce() -> usize) {
62    internal::layers_rendered(amount);
63}
64
65pub fn boot() -> Span {
66    internal::boot()
67}
68
69pub fn update(message: &impl std::fmt::Debug) -> Span {
70    internal::update(message)
71}
72
73pub fn view(window: window::Id) -> Span {
74    internal::view(window)
75}
76
77pub fn layout(window: window::Id) -> Span {
78    internal::layout(window)
79}
80
81pub fn interact(window: window::Id) -> Span {
82    internal::interact(window)
83}
84
85pub fn draw(window: window::Id) -> Span {
86    internal::draw(window)
87}
88
89pub fn prepare(primitive: Primitive) -> Span {
90    internal::prepare(primitive)
91}
92
93pub fn render(primitive: Primitive) -> Span {
94    internal::render(primitive)
95}
96
97pub fn present(window: window::Id) -> Span {
98    internal::present(window)
99}
100
101pub fn time(name: impl Into<String>) -> Span {
102    internal::time(name)
103}
104
105pub fn time_with<T>(name: impl Into<String>, f: impl FnOnce() -> T) -> T {
106    let span = time(name);
107    let result = f();
108    span.finish();
109
110    result
111}
112
113pub fn commands() -> Subscription<Command> {
114    internal::commands()
115}
116
117pub fn hot<O>(f: impl FnOnce() -> O) -> O {
118    hot::call(f)
119}
120
121pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) {
122    hot::on_hotpatch(f)
123}
124
125pub fn is_stale() -> bool {
126    hot::is_stale()
127}
128
129#[cfg(all(feature = "enable", not(target_arch = "wasm32")))]
130mod internal {
131    use crate::core::theme;
132    use crate::core::time::Instant;
133    use crate::core::window;
134    use crate::futures::Subscription;
135    use crate::futures::futures::Stream;
136    use crate::{Command, Metadata, Primitive};
137
138    use iced_beacon as beacon;
139
140    use beacon::client::{self, Client};
141    use beacon::span;
142    use beacon::span::present;
143
144    use std::sync::atomic::{self, AtomicBool, AtomicUsize};
145    use std::sync::{LazyLock, RwLock};
146
147    pub fn init(metadata: Metadata) {
148        let name = metadata.name.split("::").next().unwrap_or(metadata.name);
149
150        *METADATA.write().expect("Write application metadata") =
151            client::Metadata {
152                name,
153                theme: metadata.theme,
154                can_time_travel: metadata.can_time_travel,
155            };
156    }
157
158    pub fn quit() -> bool {
159        if BEACON.is_connected() {
160            BEACON.quit();
161
162            true
163        } else {
164            false
165        }
166    }
167
168    pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
169        let Some(palette) = f() else {
170            return;
171        };
172
173        if METADATA.read().expect("Read last palette").theme.as_ref()
174            != Some(&palette)
175        {
176            log(client::Event::ThemeChanged(palette));
177
178            METADATA.write().expect("Write last palette").theme = Some(palette);
179        }
180    }
181
182    pub fn tasks_spawned(amount: usize) {
183        log(client::Event::CommandsSpawned(amount));
184    }
185
186    pub fn subscriptions_tracked(amount: usize) {
187        log(client::Event::SubscriptionsTracked(amount));
188    }
189
190    pub fn layers_rendered(amount: impl FnOnce() -> usize) {
191        log(client::Event::LayersRendered(amount()));
192    }
193
194    pub fn boot() -> Span {
195        span(span::Stage::Boot)
196    }
197
198    pub fn update(message: &impl std::fmt::Debug) -> Span {
199        let span = span(span::Stage::Update);
200
201        let number = LAST_UPDATE.fetch_add(1, atomic::Ordering::Relaxed);
202
203        let start = Instant::now();
204        let message = format!("{message:?}");
205        let elapsed = start.elapsed();
206
207        if elapsed.as_millis() >= 1 {
208            log::warn!(
209                "Slow `Debug` implementation of `Message` (took {elapsed:?})!"
210            );
211        }
212
213        let message = if message.len() > 49 {
214            message
215                .chars()
216                .take(49)
217                .chain("...".chars())
218                .collect::<String>()
219        } else {
220            message
221        };
222
223        log(client::Event::MessageLogged { number, message });
224
225        span
226    }
227
228    pub fn view(window: window::Id) -> Span {
229        span(span::Stage::View(window))
230    }
231
232    pub fn layout(window: window::Id) -> Span {
233        span(span::Stage::Layout(window))
234    }
235
236    pub fn interact(window: window::Id) -> Span {
237        span(span::Stage::Interact(window))
238    }
239
240    pub fn draw(window: window::Id) -> Span {
241        span(span::Stage::Draw(window))
242    }
243
244    pub fn prepare(primitive: Primitive) -> Span {
245        span(span::Stage::Prepare(to_primitive(primitive)))
246    }
247
248    pub fn render(primitive: Primitive) -> Span {
249        span(span::Stage::Render(to_primitive(primitive)))
250    }
251
252    pub fn present(window: window::Id) -> Span {
253        span(span::Stage::Present(window))
254    }
255
256    pub fn time(name: impl Into<String>) -> Span {
257        span(span::Stage::Custom(name.into()))
258    }
259
260    pub fn enable() {
261        ENABLED.store(true, atomic::Ordering::Relaxed);
262    }
263
264    pub fn disable() {
265        ENABLED.store(false, atomic::Ordering::Relaxed);
266    }
267
268    pub fn commands() -> Subscription<Command> {
269        fn listen_for_commands() -> impl Stream<Item = Command> {
270            use crate::futures::futures::stream;
271
272            stream::unfold(BEACON.subscribe(), async move |mut receiver| {
273                let command = match receiver.recv().await? {
274                    client::Command::RewindTo { message } => {
275                        Command::RewindTo { message }
276                    }
277                    client::Command::GoLive => Command::GoLive,
278                };
279
280                Some((command, receiver))
281            })
282        }
283
284        Subscription::run(listen_for_commands)
285    }
286
287    fn span(span: span::Stage) -> Span {
288        log(client::Event::SpanStarted(span.clone()));
289
290        Span {
291            span,
292            start: Instant::now(),
293        }
294    }
295
296    fn to_primitive(primitive: Primitive) -> present::Primitive {
297        match primitive {
298            Primitive::Quad => present::Primitive::Quad,
299            Primitive::Triangle => present::Primitive::Triangle,
300            Primitive::Shader => present::Primitive::Shader,
301            Primitive::Text => present::Primitive::Text,
302            Primitive::Image => present::Primitive::Image,
303        }
304    }
305
306    fn log(event: client::Event) {
307        if ENABLED.load(atomic::Ordering::Relaxed) {
308            BEACON.log(event);
309        }
310    }
311
312    #[derive(Debug)]
313    pub struct Span {
314        span: span::Stage,
315        start: Instant,
316    }
317
318    impl Span {
319        pub fn finish(self) {
320            log(client::Event::SpanFinished(self.span, self.start.elapsed()));
321        }
322    }
323
324    static BEACON: LazyLock<Client> = LazyLock::new(|| {
325        let metadata = METADATA.read().expect("Read application metadata");
326
327        client::connect(metadata.clone())
328    });
329
330    static METADATA: RwLock<client::Metadata> = RwLock::new(client::Metadata {
331        name: "",
332        theme: None,
333        can_time_travel: false,
334    });
335
336    static LAST_UPDATE: AtomicUsize = AtomicUsize::new(0);
337    static ENABLED: AtomicBool = AtomicBool::new(true);
338}
339
340#[cfg(any(not(feature = "enable"), target_arch = "wasm32"))]
341mod internal {
342    use crate::core::theme;
343    use crate::core::window;
344    use crate::futures::Subscription;
345    use crate::{Command, Metadata, Primitive};
346
347    pub fn enable() {}
348    pub fn disable() {}
349
350    pub fn init(_metadata: Metadata) {}
351
352    pub fn quit() -> bool {
353        false
354    }
355
356    pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
357
358    pub fn tasks_spawned(_amount: usize) {}
359
360    pub fn subscriptions_tracked(_amount: usize) {}
361
362    pub fn layers_rendered(_amount: impl FnOnce() -> usize) {}
363
364    pub fn boot() -> Span {
365        Span
366    }
367
368    pub fn update(_message: &impl std::fmt::Debug) -> Span {
369        Span
370    }
371
372    pub fn view(_window: window::Id) -> Span {
373        Span
374    }
375
376    pub fn layout(_window: window::Id) -> Span {
377        Span
378    }
379
380    pub fn interact(_window: window::Id) -> Span {
381        Span
382    }
383
384    pub fn draw(_window: window::Id) -> Span {
385        Span
386    }
387
388    pub fn prepare(_primitive: Primitive) -> Span {
389        Span
390    }
391
392    pub fn render(_primitive: Primitive) -> Span {
393        Span
394    }
395
396    pub fn present(_window: window::Id) -> Span {
397        Span
398    }
399
400    pub fn time(_name: impl Into<String>) -> Span {
401        Span
402    }
403
404    pub fn commands() -> Subscription<Command> {
405        Subscription::none()
406    }
407
408    #[derive(Debug)]
409    pub struct Span;
410
411    impl Span {
412        pub fn finish(self) {}
413    }
414}
415
416#[cfg(feature = "hot")]
417mod hot {
418    use std::collections::BTreeSet;
419    use std::sync::atomic::{self, AtomicBool};
420    use std::sync::{Arc, Mutex, OnceLock};
421
422    static IS_STALE: AtomicBool = AtomicBool::new(false);
423
424    static HOT_FUNCTIONS_PENDING: Mutex<BTreeSet<u64>> =
425        Mutex::new(BTreeSet::new());
426
427    static HOT_FUNCTIONS: OnceLock<BTreeSet<u64>> = OnceLock::new();
428
429    pub fn init() {
430        cargo_hot::connect();
431
432        cargo_hot::subsecond::register_handler(Arc::new(|| {
433            if HOT_FUNCTIONS.get().is_none() {
434                HOT_FUNCTIONS
435                    .set(std::mem::take(
436                        &mut HOT_FUNCTIONS_PENDING
437                            .lock()
438                            .expect("Lock hot functions"),
439                    ))
440                    .expect("Set hot functions");
441            }
442
443            IS_STALE.store(false, atomic::Ordering::Relaxed);
444        }));
445    }
446
447    pub fn call<O>(f: impl FnOnce() -> O) -> O {
448        let mut f = Some(f);
449
450        // The `move` here is important. Hotpatching will not work
451        // otherwise.
452        let mut f = cargo_hot::subsecond::HotFn::current(move || {
453            f.take().expect("Hot function is stale")()
454        });
455
456        let address = f.ptr_address().0;
457
458        if let Some(hot_functions) = HOT_FUNCTIONS.get() {
459            if hot_functions.contains(&address) {
460                IS_STALE.store(true, atomic::Ordering::Relaxed);
461            }
462        } else {
463            let _ = HOT_FUNCTIONS_PENDING
464                .lock()
465                .expect("Lock hot functions")
466                .insert(address);
467        }
468
469        f.call(())
470    }
471
472    pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) {
473        cargo_hot::subsecond::register_handler(Arc::new(f));
474    }
475
476    pub fn is_stale() -> bool {
477        IS_STALE.load(atomic::Ordering::Relaxed)
478    }
479}
480
481#[cfg(not(feature = "hot"))]
482mod hot {
483    pub fn init() {}
484
485    pub fn call<O>(f: impl FnOnce() -> O) -> O {
486        f()
487    }
488
489    pub fn on_hotpatch(_f: impl Fn() + Send + Sync + 'static) {}
490
491    pub fn is_stale() -> bool {
492        false
493    }
494}