pub use iced_core as core;
pub use iced_futures as futures;
use crate::core::theme::palette;
use crate::core::window;
use crate::futures::Subscription;
pub use internal::Span;
#[derive(Debug, Clone, Copy)]
pub struct Metadata {
pub name: &'static str,
pub theme: Option<palette::Seed>,
pub can_time_travel: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum Primitive {
Quad,
Triangle,
Shader,
Image,
Text,
}
#[derive(Debug, Clone, Copy)]
pub enum Command {
RewindTo { message: usize },
GoLive,
}
pub fn enable() {
internal::enable();
}
pub fn disable() {
internal::disable();
}
pub fn init(metadata: Metadata) {
internal::init(metadata);
hot::init();
}
pub fn quit() -> bool {
internal::quit()
}
pub fn theme_changed(f: impl FnOnce() -> Option<palette::Seed>) {
internal::theme_changed(f);
}
pub fn tasks_spawned(amount: usize) {
internal::tasks_spawned(amount);
}
pub fn subscriptions_tracked(amount: usize) {
internal::subscriptions_tracked(amount);
}
pub fn layers_rendered(amount: impl FnOnce() -> usize) {
internal::layers_rendered(amount);
}
pub fn boot() -> Span {
internal::boot()
}
pub fn update(message: &impl std::fmt::Debug) -> Span {
internal::update(message)
}
pub fn view(window: window::Id) -> Span {
internal::view(window)
}
pub fn layout(window: window::Id) -> Span {
internal::layout(window)
}
pub fn interact(window: window::Id) -> Span {
internal::interact(window)
}
pub fn draw(window: window::Id) -> Span {
internal::draw(window)
}
pub fn prepare(primitive: Primitive) -> Span {
internal::prepare(primitive)
}
pub fn render(primitive: Primitive) -> Span {
internal::render(primitive)
}
pub fn present(window: window::Id) -> Span {
internal::present(window)
}
pub fn time(name: impl Into<String>) -> Span {
internal::time(name)
}
pub fn time_with<T>(name: impl Into<String>, f: impl FnOnce() -> T) -> T {
let span = time(name);
let result = f();
span.finish();
result
}
pub fn commands() -> Subscription<Command> {
internal::commands()
}
pub fn hot<O>(f: impl FnOnce() -> O) -> O {
hot::call(f)
}
pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) {
hot::on_hotpatch(f)
}
pub fn is_stale() -> bool {
hot::is_stale()
}
#[cfg(all(feature = "enable", not(target_arch = "wasm32")))]
mod internal {
use crate::core::theme::palette;
use crate::core::time::Instant;
use crate::core::window;
use crate::futures::Subscription;
use crate::futures::futures::Stream;
use crate::{Command, Metadata, Primitive};
use iced_beacon as beacon;
use beacon::client::{self, Client};
use beacon::span;
use beacon::span::present;
use std::sync::atomic::{self, AtomicBool, AtomicUsize};
use std::sync::{LazyLock, RwLock};
pub fn init(metadata: Metadata) {
let name = metadata.name.split("::").next().unwrap_or(metadata.name);
*METADATA.write().expect("Write application metadata") = client::Metadata {
name,
theme: metadata.theme,
can_time_travel: metadata.can_time_travel,
};
}
pub fn quit() -> bool {
if BEACON.is_connected() {
BEACON.quit();
true
} else {
false
}
}
pub fn theme_changed(f: impl FnOnce() -> Option<palette::Seed>) {
let Some(palette) = f() else {
return;
};
if METADATA.read().expect("Read last palette").theme.as_ref() != Some(&palette) {
log(client::Event::ThemeChanged(palette));
METADATA.write().expect("Write last palette").theme = Some(palette);
}
}
pub fn tasks_spawned(amount: usize) {
log(client::Event::CommandsSpawned(amount));
}
pub fn subscriptions_tracked(amount: usize) {
log(client::Event::SubscriptionsTracked(amount));
}
pub fn layers_rendered(amount: impl FnOnce() -> usize) {
log(client::Event::LayersRendered(amount()));
}
pub fn boot() -> Span {
span(span::Stage::Boot)
}
pub fn update(message: &impl std::fmt::Debug) -> Span {
let span = span(span::Stage::Update);
let number = LAST_UPDATE.fetch_add(1, atomic::Ordering::Relaxed);
let start = Instant::now();
let message = format!("{message:?}");
let elapsed = start.elapsed();
if elapsed.as_millis() >= 1 {
log::warn!("Slow `Debug` implementation of `Message` (took {elapsed:?})!");
}
let message = if message.len() > 49 {
message
.chars()
.take(49)
.chain("...".chars())
.collect::<String>()
} else {
message
};
log(client::Event::MessageLogged { number, message });
span
}
pub fn view(window: window::Id) -> Span {
span(span::Stage::View(window))
}
pub fn layout(window: window::Id) -> Span {
span(span::Stage::Layout(window))
}
pub fn interact(window: window::Id) -> Span {
span(span::Stage::Interact(window))
}
pub fn draw(window: window::Id) -> Span {
span(span::Stage::Draw(window))
}
pub fn prepare(primitive: Primitive) -> Span {
span(span::Stage::Prepare(to_primitive(primitive)))
}
pub fn render(primitive: Primitive) -> Span {
span(span::Stage::Render(to_primitive(primitive)))
}
pub fn present(window: window::Id) -> Span {
span(span::Stage::Present(window))
}
pub fn time(name: impl Into<String>) -> Span {
span(span::Stage::Custom(name.into()))
}
pub fn enable() {
ENABLED.store(true, atomic::Ordering::Relaxed);
}
pub fn disable() {
ENABLED.store(false, atomic::Ordering::Relaxed);
}
pub fn commands() -> Subscription<Command> {
fn listen_for_commands() -> impl Stream<Item = Command> {
use crate::futures::futures::stream;
stream::unfold(BEACON.subscribe(), async move |mut receiver| {
let command = match receiver.recv().await? {
client::Command::RewindTo { message } => Command::RewindTo { message },
client::Command::GoLive => Command::GoLive,
};
Some((command, receiver))
})
}
Subscription::run(listen_for_commands)
}
fn span(span: span::Stage) -> Span {
log(client::Event::SpanStarted(span.clone()));
Span {
span,
start: Instant::now(),
}
}
fn to_primitive(primitive: Primitive) -> present::Primitive {
match primitive {
Primitive::Quad => present::Primitive::Quad,
Primitive::Triangle => present::Primitive::Triangle,
Primitive::Shader => present::Primitive::Shader,
Primitive::Text => present::Primitive::Text,
Primitive::Image => present::Primitive::Image,
}
}
fn log(event: client::Event) {
if ENABLED.load(atomic::Ordering::Relaxed) {
BEACON.log(event);
}
}
#[derive(Debug)]
pub struct Span {
span: span::Stage,
start: Instant,
}
impl Span {
pub fn finish(self) {
log(client::Event::SpanFinished(self.span, self.start.elapsed()));
}
}
static BEACON: LazyLock<Client> = LazyLock::new(|| {
let metadata = METADATA.read().expect("Read application metadata");
client::connect(metadata.clone())
});
static METADATA: RwLock<client::Metadata> = RwLock::new(client::Metadata {
name: "",
theme: None,
can_time_travel: false,
});
static LAST_UPDATE: AtomicUsize = AtomicUsize::new(0);
static ENABLED: AtomicBool = AtomicBool::new(true);
}
#[cfg(any(not(feature = "enable"), target_arch = "wasm32"))]
mod internal {
use crate::core::theme::palette;
use crate::core::window;
use crate::futures::Subscription;
use crate::{Command, Metadata, Primitive};
pub fn enable() {}
pub fn disable() {}
pub fn init(_metadata: Metadata) {}
pub fn quit() -> bool {
false
}
pub fn theme_changed(_f: impl FnOnce() -> Option<palette::Seed>) {}
pub fn tasks_spawned(_amount: usize) {}
pub fn subscriptions_tracked(_amount: usize) {}
pub fn layers_rendered(_amount: impl FnOnce() -> usize) {}
pub fn boot() -> Span {
Span
}
pub fn update(_message: &impl std::fmt::Debug) -> Span {
Span
}
pub fn view(_window: window::Id) -> Span {
Span
}
pub fn layout(_window: window::Id) -> Span {
Span
}
pub fn interact(_window: window::Id) -> Span {
Span
}
pub fn draw(_window: window::Id) -> Span {
Span
}
pub fn prepare(_primitive: Primitive) -> Span {
Span
}
pub fn render(_primitive: Primitive) -> Span {
Span
}
pub fn present(_window: window::Id) -> Span {
Span
}
pub fn time(_name: impl Into<String>) -> Span {
Span
}
pub fn commands() -> Subscription<Command> {
Subscription::none()
}
#[derive(Debug)]
pub struct Span;
impl Span {
pub fn finish(self) {}
}
}
#[cfg(all(feature = "hot", not(target_arch = "wasm32")))]
mod hot {
use std::collections::BTreeSet;
use std::sync::atomic::{self, AtomicBool};
use std::sync::{Arc, Mutex, OnceLock};
static IS_STALE: AtomicBool = AtomicBool::new(false);
static HOT_FUNCTIONS_PENDING: Mutex<BTreeSet<u64>> = Mutex::new(BTreeSet::new());
static HOT_FUNCTIONS: OnceLock<BTreeSet<u64>> = OnceLock::new();
pub fn init() {
cargo_hot::connect();
cargo_hot::subsecond::register_handler(Arc::new(|| {
if HOT_FUNCTIONS.get().is_none() {
HOT_FUNCTIONS
.set(std::mem::take(
&mut HOT_FUNCTIONS_PENDING.lock().expect("Lock hot functions"),
))
.expect("Set hot functions");
}
IS_STALE.store(false, atomic::Ordering::Relaxed);
}));
}
pub fn call<O>(f: impl FnOnce() -> O) -> O {
let mut f = Some(f);
let mut f = cargo_hot::subsecond::HotFn::current(move || {
f.take().expect("Hot function is stale")()
});
let address = f.ptr_address().0;
if let Some(hot_functions) = HOT_FUNCTIONS.get() {
if hot_functions.contains(&address) {
IS_STALE.store(true, atomic::Ordering::Relaxed);
}
} else {
let _ = HOT_FUNCTIONS_PENDING
.lock()
.expect("Lock hot functions")
.insert(address);
}
f.call(())
}
pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) {
cargo_hot::subsecond::register_handler(Arc::new(f));
}
pub fn is_stale() -> bool {
IS_STALE.load(atomic::Ordering::Relaxed)
}
}
#[cfg(any(not(feature = "hot"), target_arch = "wasm32"))]
mod hot {
pub fn init() {}
pub fn call<O>(f: impl FnOnce() -> O) -> O {
f()
}
pub fn on_hotpatch(_f: impl Fn()) {}
pub fn is_stale() -> bool {
false
}
}