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