tessera_ui/
entry_point.rs

1//! Application entry builder for registering packages and launching the
2//! renderer.
3//!
4//! ## Usage
5//!
6//! Configure app startup with packages and plugins before launching the
7//! renderer.
8
9use std::sync::Arc;
10
11use parking_lot::RwLock;
12
13use crate::{
14    entry_registry::{EntryRegistry, TesseraPackage},
15    plugin::Plugin,
16    render_module::RenderModule,
17    renderer::{Renderer, TesseraConfig},
18};
19
20#[cfg(target_os = "android")]
21use winit::platform::android::activity::AndroidApp;
22
23/// Builder for application entry configuration and startup.
24pub struct EntryPoint {
25    entry: Box<dyn Fn()>,
26    registry: EntryRegistry,
27    config: TesseraConfig,
28}
29
30impl EntryPoint {
31    /// Creates a new entry point builder from the root UI function.
32    pub fn new(entry: impl Fn() + 'static) -> Self {
33        Self {
34            entry: Box::new(entry),
35            registry: EntryRegistry::new(),
36            config: TesseraConfig::default(),
37        }
38    }
39
40    /// Adds a render module to the entry registry.
41    pub fn module(mut self, module: impl RenderModule + 'static) -> Self {
42        self.registry.add_module(module);
43        self
44    }
45
46    /// Registers a plugin instance with the global plugin registry.
47    pub fn plugin(mut self, plugin: impl Plugin) -> Self {
48        self.registry.register_plugin(plugin);
49        self
50    }
51
52    /// Registers a boxed plugin instance with the global plugin registry.
53    pub fn plugin_boxed<P: Plugin>(mut self, plugin: Arc<RwLock<P>>) -> Self {
54        self.registry.register_plugin_boxed(plugin);
55        self
56    }
57
58    /// Registers a package into the entry registry.
59    pub fn package(mut self, package: impl TesseraPackage) -> Self {
60        self.registry.register_package(package);
61        self
62    }
63
64    /// Overrides the renderer configuration for this entry.
65    pub fn config(mut self, config: TesseraConfig) -> Self {
66        self.config = config;
67        self
68    }
69
70    /// Runs the entry point on desktop platforms.
71    #[cfg(all(not(target_os = "android"), not(target_family = "wasm")))]
72    pub fn run_desktop(self) -> Result<(), winit::error::EventLoopError> {
73        init_tracing();
74        init_deadlock_detection();
75        Renderer::run_with_config(self.entry, self.registry.finish(), self.config)
76    }
77
78    /// Runs the entry point on web platforms.
79    #[cfg(target_family = "wasm")]
80    pub fn run_web(self) -> Result<(), winit::error::EventLoopError> {
81        init_tracing();
82        Renderer::run_web_with_config(self.entry, self.registry.finish(), self.config)
83    }
84
85    /// Runs the entry point on Android.
86    #[cfg(target_os = "android")]
87    pub fn run_android(self, android_app: AndroidApp) -> Result<(), winit::error::EventLoopError> {
88        init_tracing();
89        init_deadlock_detection();
90        Renderer::run_with_config(self.entry, self.registry.finish(), android_app, self.config)
91    }
92}
93
94#[cfg(all(debug_assertions, not(target_family = "wasm")))]
95fn init_deadlock_detection() {
96    use std::{sync::Once, thread, time::Duration};
97
98    static INIT: Once = Once::new();
99    INIT.call_once(|| {
100        thread::spawn(|| {
101            loop {
102                thread::sleep(Duration::from_secs(10));
103                let deadlocks = parking_lot::deadlock::check_deadlock();
104                if deadlocks.is_empty() {
105                    continue;
106                }
107
108                eprintln!("{} deadlocks detected", deadlocks.len());
109                for (idx, threads) in deadlocks.iter().enumerate() {
110                    eprintln!("Deadlock #{}", idx);
111                    for thread in threads {
112                        eprintln!("Thread Id {:#?}", thread.thread_id());
113                        eprintln!("{:?}", thread.backtrace());
114                    }
115                }
116            }
117        });
118    });
119}
120
121fn init_tracing() {
122    #[cfg(target_family = "wasm")]
123    {
124        let _ = tracing_subscriber::fmt()
125            .with_max_level(tracing::Level::INFO)
126            .without_time()
127            .try_init();
128    }
129
130    #[cfg(target_os = "android")]
131    {
132        let _ = tracing_subscriber::fmt()
133            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
134            .with_max_level(tracing::Level::INFO)
135            .try_init();
136    }
137
138    #[cfg(all(not(target_os = "android"), not(target_family = "wasm")))]
139    {
140        let filter = match tracing_subscriber::EnvFilter::try_from_default_env() {
141            Ok(filter) => filter,
142            Err(_) => match tracing_subscriber::EnvFilter::try_new("error,tessera_ui=info") {
143                Ok(filter) => filter,
144                Err(_) => tracing_subscriber::EnvFilter::new("error"),
145            },
146        };
147
148        let _ = tracing_subscriber::fmt()
149            .pretty()
150            .with_env_filter(filter)
151            .with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
152            .try_init();
153    }
154}