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