zng/app.rs
1//! App extensions, context, events and commands API.
2//!
3//! # Runtime
4//!
5//! A typical app instance has two processes, the initial process called the *app-process*, and a second process called the
6//! *view-process*. The app-process implements the event loop and updates, the view-process implements the platform integration and
7//! renderer, the app-process controls the view-process, most of the time app implementers don't interact directly with it, except
8//! at the start where the view-process is spawned.
9//!
10//! The reason for this dual process architecture is mostly for resilience, the unsafe interactions with the operating system and
11//! graphics driver are isolated in a different process, in case of crashes the view-process is respawned automatically and
12//! all windows are recreated. It is possible to run the app in a single process, in this case the view runs in the main thread
13//! and the app main loop in another.
14//!
15//! ## View-Process
16//!
17//! To simplify distribution the view-process is an instance of the same app executable, the view-process crate injects
18//! their own "main" in the [`zng::env::init!`] call, automatically taking over the process if the executable spawns as a view-process.
19//!
20//! On the first instance of the app executable the `init` only inits the env and returns, the app init spawns a second process
21//! marked as the view-process, on this second instance the init call never returns, for this reason the init
22//! must be called early in main, all code before the `init` call runs in both the app and view processes.
23//!
24//! ```toml
25//! [dependencies]
26//! zng = { version = "0.14.2", features = ["view_prebuilt"] }
27//! ```
28//!
29//! ```no_run
30//! use zng::prelude::*;
31//!
32//! fn main() {
33//! app_and_view();
34//! zng::env::init!(); // init only returns if it is not called in the view-process.
35//! app();
36//! }
37//!
38//! fn app_and_view() {
39//! // code here runs in the app-process and view-process.
40//! }
41//!
42//! fn app() {
43//! // code here only runs in the app-process.
44//!
45//! APP.defaults().run(async {
46//! // ..
47//! })
48//! }
49//! ```
50//!
51//! ## Same Process
52//!
53//! You can also run the view in the same process, this mode of execution is slightly more efficient, but
54//! your app will not be resilient to crashes caused by the operating system or graphics driver, the app code
55//! will also run in a different thread, not the main.
56//!
57//! ```no_run
58//! use zng::prelude::*;
59//!
60//! fn main() {
61//! zng::env::init!();
62//! zng::view_process::prebuilt::run_same_process(app);
63//! }
64//!
65//! fn app() {
66//! // code here runs in a different thread, the main thread becomes the view.
67//! APP.defaults().run(async {
68//! // ..
69//! })
70//! }
71//! ```
72//!
73//! Note that you must still call `init!` as it also initializes the app metadata and directories.
74//!
75//! # Headless
76//!
77//! The app can also run *headless*, where no window is actually created, optionally with real rendering.
78//! This mode is useful for running integration tests, or for rendering images.
79//!
80//! ```
81//! use zng::prelude::*;
82//!
83//! let mut app = APP.defaults().run_headless(/* with_renderer: */ false);
84//! app.run_window(async {
85//! Window! {
86//! child = Text!("Some text");
87//! auto_size = true;
88//!
89//! render_mode = window::RenderMode::Software;
90//! frame_capture_mode = window::FrameCaptureMode::Next;
91//!
92//! on_frame_image_ready = async_hn!(|args: window::FrameImageReadyArgs| {
93//! if let Some(img) = args.frame_image {
94//! // if the app runs with `run_headless(/* with_renderer: */ true)` an image is captured
95//! // and saved here.
96//! img.save("screenshot.png").await.unwrap();
97//! }
98//!
99//! // close the window, causing the app to exit.
100//! WINDOW.close();
101//! });
102//! }
103//! });
104//! ```
105//!
106//! You can also run multiple headless apps in the same process, one per thread, if the crate is build using the `"multi_app"` feature.
107//!
108//! # App Extension
109//!
110//! Apps can be extended to provide new services and events, in fact all default services and events are implemented as extensions
111//! loaded by [`APP.defaults()`]. The app extension API is [`AppExtension`]. Usually extensions are named with suffix `Manager`, but
112//! that is not a requirement.
113//!
114//! ```
115//! use zng::{app::{AppExtended, AppExtension}, APP};
116//!
117//! #[derive(Default)]
118//! pub struct HelloManager {}
119//! impl AppExtension for HelloManager {
120//! fn init(&mut self) {
121//! println!("Hello init!");
122//! }
123//!
124//! fn update_preview(&mut self) {
125//! println!("Hello before UI!");
126//! }
127//!
128//! fn update(&mut self) {
129//! println!("Hello after UI!");
130//! }
131//! }
132//!
133//! pub fn app() -> AppExtended<impl AppExtension> {
134//! APP.defaults().extend(HelloManager::default())
135//! }
136//! ```
137//!
138//! ## Services
139//!
140//! App services are defined by convention, there is no service trait or struct. Proper service implementations follow
141//! these rules:
142//!
143//! #### App services are an unit struct named like a static
144//!
145//! This is because services are a kind of *singleton*. The service API is implemented as methods on the service struct.
146//!
147//! ```
148//! # use zng::var::*;
149//! #[expect(non_camel_case_types)]
150//! pub struct SCREAMING_CASE;
151//! impl SCREAMING_CASE {
152//! pub fn state(&self) -> impl Var<bool> {
153//! # var(true)
154//! }
155//! }
156//! ```
157//!
158//! Note that you need to suppress a lint if the service name has more then one word.
159//!
160//! Service state and config methods should prefer variables over direct values. The use of variables allows the service state
161//! to be plugged directly into the UI. Async operations should prefer using [`ResponseVar<R>`] over `async` methods for
162//! the same reason.
163//!
164//! #### App services lifetime is the current app lifetime
165//!
166//! Unlike a simple singleton app services must only live for the duration of the app and must support
167//! multiple parallel instances if built with the `"multi_app"` feature. You can use private
168//! [`app_local!`] static variables as backing storage to fulfill this requirement.
169//!
170//! A common pattern in the zng services is to name the app locals with a `_SV` suffix.
171//!
172//! Services do not expose the app local locking, all state output is cloned the state is only locked
173//! for the duration of the service method call.
174//!
175//! #### App services don't change public state mid update
176//!
177//! All widgets using the service during the same update see the same state. State change requests are scheduled
178//! for the next update, just like variable updates or event notifications. Services also request
179//! an [`UPDATES.update`] after scheduling to wake-up the app in case the service request was made from a [`task`] thread.
180//!
181//! This is even true for the [`INSTANT`] service, although this can be configured for this service using [`APP.pause_time_for_update`].
182//!
183//! [`APP.pause_time_for_update`]: zng_app::APP::pause_time_for_update
184//!
185//! ### Examples
186//!
187//! Fulfilling service requests is where the [`AppExtension`] comes in, it is possible to declare a simple standalone
188//! service using only variables, `Event::on_event` and `UPDATES.run_hn_once`, but an app extension is more efficient
189//! and more easy to implement.
190//!
191//! If the service request can fail or be delayed it is common for the request method to return a [`ResponseVar<R>`]
192//! that is updated once the request is finished. You can also make the method `async`, but a response var is superior
193//! because it can be plugged directly into any UI property, and it can still be awaited using the variable async methods.
194//!
195//! If the service request cannot fail and it is guaranteed to affect an observable change in the service state in the
196//! next update a response var is not needed.
197//!
198//! The example below demonstrates an app extension implementation that provides a service.
199//!
200//! ```
201//! use zng::{prelude_wgt::*, app::AppExtension};
202//!
203//! /// Foo service.
204//! pub struct FOO;
205//!
206//! impl FOO {
207//! /// Foo read-write var.
208//! pub fn config(&self) -> impl Var<bool> {
209//! FOO_SV.read().config.clone()
210//! }
211//!
212//! /// Foo request.
213//! pub fn request(&self, request: char) -> ResponseVar<char> {
214//! UPDATES.update(None);
215//!
216//! let mut foo = FOO_SV.write();
217//! let (responder, response) = response_var();
218//! foo.requests.push((request, responder));
219//! response
220//! }
221//! }
222//!
223//! struct FooService {
224//! config: ArcVar<bool>,
225//! requests: Vec<(char, ResponderVar<char>)>,
226//! }
227//!
228//! app_local! {
229//! static FOO_SV: FooService = FooService { config: var(false), requests: vec![] };
230//! }
231//!
232//! /// Foo app extension.
233//! ///
234//! /// # Services
235//! ///
236//! /// Services provided by this extension.
237//! ///
238//! /// * [`FOO`]
239//! #[derive(Default)]
240//! pub struct FooManager { }
241//!
242//! impl AppExtension for FooManager {
243//! fn update(&mut self) {
244//! let mut foo = FOO_SV.write();
245//!
246//! if let Some(cfg) = foo.config.get_new() {
247//! println!("foo cfg={cfg}");
248//! }
249//!
250//! for (request, responder) in foo.requests.drain(..) {
251//! println!("foo request {request:?}");
252//! responder.respond(request);
253//! }
254//! }
255//! }
256//! ```
257//!
258//! Note that in the example requests are processed in the [`AppExtension::update`] update that is called
259//! after all widgets have had a chance to make requests. Requests can also be made from parallel [`task`] threads so
260//! the service also requests an [`UPDATES.update`] just in case there is no update running. If you expect to receive many
261//! requests from parallel tasks you can also process requests in the [`AppExtension::update`] instead, but there is probably
262//! little practical difference.
263//!
264//! # Init & Main Loop
265//!
266//! A headed app initializes in this sequence:
267//!
268//! 1. [`AppExtension::register`] is called.
269//! 2. [`AppExtension::enable_device_events`] is queried.
270//! 3. Spawn view-process.
271//! 4. [`AppExtension::init`] is called.
272//! 5. Schedule the app run future to run in the first preview update.
273//! 6. Does [updates loop](#updates-loop).
274//! 7. Does [update events loop](#update-events-loop).
275//! 6. Does [main loop](#main-loop).
276//!
277//! #### Main Loop
278//!
279//! The main loop coordinates view-process events, timers, app events and updates. There is no scheduler, update and event requests
280//! are captured and coalesced to various buffers that are drained in known sequential order. App extensions update one at a time
281//! in the order they are registered. Windows and widgets update in parallel by default, this is controlled by [`WINDOWS.parallel`] and [`parallel`].
282//!
283//! 1. Sleep if there are not pending events or updates.
284//! * If the view-process is busy blocks until it sends a message, this is a mechanism to stop the app-process
285//! from overwhelming the view-process.
286//! * Block until a message is received, from the view-process or from other app threads.
287//! * If there are [`TIMERS`] or [`VARS`] animations the message block has a deadline to the nearest timer or animation frame.
288//! * Animations have a fixed frame-rate defined in [`VARS.frame_duration`], it is 60 frames-per-second by default.
289//! 2. Calls elapsed timer handlers.
290//! 3. Calls elapsed animation handlers.
291//! * These handlers mostly just request var updates are applied in the updates loop.
292//! 4. Does a [view events loop](#view-events-loop).
293//! 4. Does an [updates loop](#updates-loop).
294//! 5. Does an [update events loop](#update-events-loop).
295//! 6. If the view-process is not busy does a [layout loop and render](#layout-loop-and-render).
296//! 7. If exit was requested and not cancelled breaks the loop.
297//! * Exit is requested automatically when the last open window closes, this is controlled by [`WINDOWS.exit_on_last_close`].
298//! * Exit can also be requested using [`APP.exit`].
299//!
300//! #### View Events Loop
301//!
302//! All pending events send by the view-process are coalesced and notify sequentially.
303//!
304//! 1. For each event in the received order (FIFO) that converts to a RAW event.
305//! 1. Calls [`AppExtension::event_preview`].
306//! 2. Calls [`Event::on_pre_event`] handlers.
307//! 3. Calls [`AppExtension::event_ui`].
308//! * Raw events don't target any widget, but widgets can subscribe, subscribers receive the event in parallel by default.
309//! 4. Calls [`AppExtension::event`].
310//! 5. Calls [`Event::on_event`] handlers.
311//! 6. Does an [updates loop](#updates-loop).
312//! 2. Frame rendered raw event.
313//! * Same notification sequence as other view-events, just delayed.
314//!
315//! #### Updates Loop
316//!
317//! The updates loop rebuilds info trees if needed , applies pending variable updates and hooks and collects event updates
318//! requested by the app.
319//!
320//! 1. Takes info rebuild request flag.
321//! * Calls [`AppExtension::info`] if needed.
322//! * Windows and widgets that requested info (re)build are called.
323//! * Info rebuild happens in parallel by default.
324//! 2. Takes events and updates requests.
325//! 1. Event hooks are called for new event requests.
326//! * Full event notification is delayed to after the updates loop.
327//! 2. [var updates loop](#var-updates-loop)
328//! 3. Calls [`AppExtension::update_preview`] if any update was requested.
329//! 4. Calls [`UPDATES.on_pre_update`] handlers if needed.
330//! 5. Calls [`AppExtension::update_ui`] if any update was requested.
331//! * Windows and widgets that requested update receive it here.
332//! * All the pending updates are processed in one pass, all targeted widgets are visited once, in parallel by default.
333//! 6. Calls [`AppExtension::update`] if any update was requested.
334//! 7. Calls [`UPDATES.on_update`] handlers if needed.
335//! 3. The loop repeats immediately if any info rebuild or update was requested by update callbacks.
336//! * The loops breaks if it repeats over 1000 times.
337//! * An error is logged with a trace the most frequent sources of update requests.
338//!
339//! #### Var Updates Loop
340//!
341//! The variable updates loop applies pending modifications, calls hooks to update variable and bindings.
342//!
343//! 1. Pending variable modifications are applied.
344//! 2. Var hooks are called.
345//! * The mapping and binding mechanism is implemented using hooks.
346//! 3. The loop repeats until hooks have stopped modifying variables.
347//! * The loop breaks if it repeats over 1000 times.
348//! * An error is logged if this happens.
349//!
350//! #### Update Events Loop
351//!
352//! The update events loop notifies each event raised by the app code during previous updates.
353//!
354//! 1. For each event in the request order (FIFO).
355//! 1. Calls [`AppExtension::event_preview`].
356//! 2. Calls [`Event::on_pre_event`] handlers.
357//! 3. Calls [`AppExtension::event_ui`].
358//! * Windows and widgets targeted by the event update receive it here.
359//! * If the event targets multiple widgets they receive it in parallel by default.
360//! 4. Calls [`AppExtension::event`].
361//! 5. Calls [`Event::on_event`] handlers.
362//! 6. Does an [updates loop](#updates-loop).
363//!
364//! #### Layout Loop and Render
365//!
366//! Layout and render requests are coalesced, multiple layout requests for the same widget update it once, multiple
367//! render requests become one frame, and if both render and render_update are requested for a window it will fully render.
368//!
369//! 1. Take layout and render requests.
370//! 2. Layout loop.
371//! 1. Calls [`AppExtension::layout`].
372//! * Windows and widgets that requested layout update in parallel by default.
373//! 2. Does an [updates loop](#updates-loop).
374//! 3. Take layout and render requests, the loop repeats immediately if layout was requested again.
375//! * The loop breaks if it repeats over 1000 times.
376//! * An error is logged with a trace the most frequent sources of update requests.
377//! 3. If render was requested, calls [`AppExtension::render`].
378//! * Windows and widgets that requested render (or render_update) do know in parallel by default.
379//! * The render pass updates widget transforms and hit-test, generates a display list and sends it to the view-process.
380//!
381//! [`APP.defaults()`]: crate::APP::defaults
382//! [`UPDATES.update`]: crate::update::UPDATES::update
383//! [`task`]: crate::task
384//! [`ResponseVar<R>`]: crate::var::ResponseVar
385//! [`TIMERS`]: crate::timer::TIMERS
386//! [`VARS`]: crate::var::VARS
387//! [`VARS.frame_duration`]: crate::var::VARS::frame_duration
388//! [`WINDOWS.parallel`]: crate::window::WINDOWS::parallel
389//! [`parallel`]: fn@crate::widget::parallel
390//! [`UPDATES.on_pre_update`]: crate::update::UPDATES::on_pre_update
391//! [`UPDATES.on_update`]: crate::update::UPDATES::on_update
392//! [`Event::on_pre_event`]: crate::event::Event::on_pre_event
393//! [`Event::on_event`]: crate::event::Event::on_event
394//! [`WINDOWS.exit_on_last_close`]: crate::window::WINDOWS::exit_on_last_close
395//! [`APP.exit`]: crate::APP#method.exit
396//!
397//! # Full API
398//!
399//! This module provides most of the app API needed to make and extend apps, some more advanced or experimental API
400//! may be available at the [`zng_app`], [`zng_app_context`] and [`zng_ext_single_instance`] base crates.
401
402pub use zng_app::{
403 AppControlFlow, AppEventObserver, AppExtended, AppExtension, AppExtensionBoxed, AppExtensionInfo, AppStartArgs, DInstant, Deadline,
404 EXIT_CMD, EXIT_REQUESTED_EVENT, ExitRequestedArgs, HeadlessApp, INSTANT, InstantMode, on_app_start, print_tracing,
405 print_tracing_filter,
406};
407
408#[cfg(feature = "test_util")]
409pub use zng_app::test_log;
410
411pub use zng_app_context::{
412 AppId, AppLocal, AppScope, CaptureFilter, ContextLocal, ContextValueSet, LocalContext, MappedRwLockReadGuardOwned,
413 MappedRwLockWriteGuardOwned, ReadOnlyRwLock, RunOnDrop, RwLockReadGuardOwned, RwLockWriteGuardOwned, app_local, context_local,
414};
415pub use zng_wgt_input::cmd::{
416 NEW_CMD, OPEN_CMD, SAVE_AS_CMD, SAVE_CMD, on_new, on_open, on_pre_new, on_pre_open, on_pre_save, on_pre_save_as, on_save, on_save_as,
417};
418
419pub use zng_app::view_process::raw_events::{LOW_MEMORY_EVENT, LowMemoryArgs};
420
421/// Input device hardware ID and events.
422///
423/// # Full API
424///
425/// See [`zng_app::view_process::raw_device_events`] for the full API.
426pub mod raw_device_events {
427 pub use zng_app::view_process::raw_device_events::{
428 DEVICE_ADDED_EVENT, DEVICE_REMOVED_EVENT, DeviceArgs, DeviceId, MOTION_EVENT, MotionArgs,
429 };
430}
431
432#[cfg(single_instance)]
433pub use zng_ext_single_instance::{APP_INSTANCE_EVENT, AppInstanceArgs};
434
435/// App-process crash handler.
436///
437/// In builds with `"crash_handler"` feature the crash handler takes over the first "app-process" turning it into
438/// the monitor-process, it spawns another process that is the monitored app-process. If the app-process crashes
439/// the monitor-process spawns a dialog-process that calls the dialog handler to show an error message, upload crash reports, etc.
440///
441/// The dialog handler can be set using [`crash_handler_config!`].
442///
443/// [`crash_handler_config!`]: crate::app::crash_handler::crash_handler_config
444///
445/// # Examples
446///
447/// The example below demonstrates an app setup to show a custom crash dialog.
448///
449/// ```no_run
450/// use zng::prelude::*;
451///
452/// fn main() {
453/// // tracing applied to all processes.
454/// zng::app::print_tracing(tracing::Level::INFO);
455///
456/// // monitor-process spawns app-process and if needed dialog-process here.
457/// zng::env::init!();
458///
459/// // app-process:
460/// app_main();
461/// }
462///
463/// fn app_main() {
464/// APP.defaults().run_window(async {
465/// Window! {
466/// child_align = Align::CENTER;
467/// child = Stack! {
468/// direction = StackDirection::top_to_bottom();
469/// spacing = 5;
470/// children = ui_vec![
471/// Button! {
472/// child = Text!("Crash (panic)");
473/// on_click = hn_once!(|_| {
474/// panic!("Test panic!");
475/// });
476/// },
477/// Button! {
478/// child = Text!("Crash (access violation)");
479/// on_click = hn_once!(|_| {
480/// // SAFETY: deliberate access violation
481/// #[expect(deref_nullptr)]
482/// unsafe {
483/// *std::ptr::null_mut() = true;
484/// }
485/// });
486/// }
487/// ]
488/// };
489/// }
490/// });
491/// }
492///
493/// zng::app::crash_handler::crash_handler_config!(|cfg| {
494/// // monitor-process and dialog-process
495///
496/// cfg.dialog(|args| {
497/// // dialog-process
498/// APP.defaults().run_window(async move {
499/// Window! {
500/// title = "App Crashed!";
501/// auto_size = true;
502/// min_size = (300, 100);
503/// start_position = window::StartPosition::CenterMonitor;
504/// on_load = hn_once!(|_| WINDOW.bring_to_top());
505/// padding = 10;
506/// child = Text!(args.latest().message());
507/// child_bottom = Stack! {
508/// direction = StackDirection::start_to_end();
509/// layout::align = Align::BOTTOM_END;
510/// spacing = 5;
511/// children = ui_vec![
512/// Button! {
513/// child = Text!("Restart App");
514/// on_click = hn_once!(args, |_| {
515/// args.restart();
516/// });
517/// },
518/// Button! {
519/// child = Text!("Exit App");
520/// on_click = hn_once!(|_| {
521/// args.exit(0);
522/// });
523/// },
524/// ]
525/// }, 10;
526/// }
527/// });
528/// });
529/// });
530///
531/// ```
532///
533/// # Debugger
534///
535/// Note that because the crash handler spawns a different process for the app debuggers will not
536/// stop at break points in the app code. You can configure your debugger to set the `NO_ZNG_CRASH_HANDLER` environment
537/// variable to not use a crash handler in debug runs.
538///
539/// On VS Code with the CodeLLDB extension you can add this workspace configuration:
540///
541/// ```json
542/// "lldb.launch.env": {
543/// "ZNG_NO_CRASH_HANDLER": ""
544/// }
545/// ```
546///
547/// # Full API
548///
549/// See [`zng_app::crash_handler`] and [`zng_wgt_inspector::crash_handler`] for the full API.
550#[cfg(crash_handler)]
551pub mod crash_handler {
552 pub use zng_app::crash_handler::{BacktraceFrame, CrashArgs, CrashConfig, CrashError, CrashPanic, crash_handler_config};
553
554 #[cfg(feature = "crash_handler_debug")]
555 pub use zng_wgt_inspector::crash_handler::debug_dialog;
556
557 crash_handler_config!(|cfg| {
558 cfg.default_dialog(|args| {
559 if let Some(c) = &args.dialog_crash {
560 eprintln!("DEBUG CRASH DIALOG ALSO CRASHED");
561 eprintln!(" {}", c.message());
562 eprintln!("ORIGINAL APP CRASH");
563 eprintln!(" {}", args.latest().message());
564 args.exit(0xBADC0DE)
565 } else {
566 #[cfg(feature = "crash_handler_debug")]
567 {
568 use crate::prelude::*;
569 APP.defaults()
570 .run_window(async_clmv!(args, { zng_wgt_inspector::crash_handler::debug_dialog(args) }));
571 }
572
573 #[cfg(not(feature = "crash_handler_debug"))]
574 {
575 eprintln!(
576 "app crashed {}\n\nbuild with feature = \"crash_handler_debug\" to se the debug crash dialog",
577 args.latest().message()
578 );
579 }
580 }
581 args.exit(0)
582 });
583 });
584}