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