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.21.8", 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.doc_test_deadline();
85//! app.run_window("id", async {
86//!     Window! {
87//!         child = Text!("Some text");
88//!         auto_size = true;
89//!
90//!         render_mode = window::RenderMode::Software;
91//!         frame_capture_mode = window::FrameCaptureMode::Next;
92//!
93//!         on_frame_image_ready = async_hn!(|args| {
94//!             if let Some(img) = args.frame_image.upgrade() {
95//!                 // if the app runs with `run_headless(/* with_renderer: */ true)` an image is captured
96//!                 // and saved here.
97//!                 img.get().save("screenshot.png").await.ok();
98//!             }
99//!
100//!             // close the window, causing the app to exit.
101//!             WINDOW.close();
102//!         });
103//!     }
104//! });
105//! ```
106//!
107//! You can also run multiple headless apps in the same process, one per thread, if the crate is build using the `"multi_app"` feature.
108//!
109//! # App Extensions
110//!
111//! Services and events bundles are named *app extensions*. They are usually implemented in a crate with `zng-ext-` prefix and a
112//! selection of the API is reexported in the `zng` crate in a module. Custom services and events can be declared using the same
113//! API the built-in services use, these custom extensions have the same level of access and performance as the built-in extensions.
114//!
115//! ## Services
116//!
117//! App services are defined by convention, there is no service trait or struct. Proper service implementations follow
118//! these rules:
119//!
120//! #### App services are an unit struct named like a static
121//!
122//! This is because services are a kind of *singleton*. The service API is implemented as methods on the service struct.
123//!
124//! ```
125//! # use zng::var::*;
126//! #[expect(non_camel_case_types)]
127//! pub struct SCREAMING_CASE;
128//! impl SCREAMING_CASE {
129//!     pub fn state(&self) -> Var<bool> {
130//!         # var(true)
131//!     }
132//! }
133//! ```
134//!
135//! Note that you need to suppress a lint if the service name has more then one word.
136//!
137//! Service state and config methods should prefer variables over direct values. The use of variables allows the service state
138//! to be plugged directly into the UI. Async operations should prefer using [`ResponseVar<R>`] over `async` methods for
139//! the same reason.
140//!
141//! #### App services lifetime is the current app lifetime
142//!
143//! Unlike a simple singleton app services must only live for the duration of the app and must support
144//! multiple parallel instances if built with the `"multi_app"` feature. You can use private
145//! [`app_local!`] static variables as backing storage to fulfill this requirement.
146//!
147//! A common pattern in the zng services is to name the app locals with a `_SV` suffix.
148//!
149//! Services do not expose the app local locking, all state output is cloned the state is only locked
150//! for the duration of the service method call.
151//!
152//! #### App services don't change public state mid update
153//!
154//! All widgets using the service during the same update see the same state. State change requests are scheduled
155//! for the next update, just like variable updates or event notifications. Services can use the [`UPDATES.once_update`]
156//! method to delegate requests to after the current update pass ends.
157//!
158//! This is even true for the [`INSTANT`] service, although this can be configured for this service using [`APP.pause_time_for_update`].
159//!
160//! [`APP.pause_time_for_update`]: zng_app::APP::pause_time_for_update
161//!
162//! ### Examples
163//!
164//! The example below demonstrates a service.
165//!
166//! ```
167//! use zng::prelude_wgt::*;
168//! # fn main() {}
169//!
170//! /// Foo service.
171//! pub struct FOO;
172//!
173//! impl FOO {
174//!     /// Foo read-write var.
175//!     pub fn config(&self) -> Var<bool> {
176//!         FOO_SV.read().config.clone()
177//!     }
178//!
179//!     /// Foo request.
180//!     pub fn request(&self, request: char) -> ResponseVar<char> {
181//!         let (responder, response) = response_var();
182//!         UPDATES.once_update("FOO.request", move || {
183//!             let mut s = FOO_SV.write();
184//!             if request == '\n' {
185//!                 s.state = true;
186//!             }
187//!             let r = if s.config.get() {
188//!                 request.to_ascii_uppercase()
189//!             } else {
190//!                 request.to_ascii_lowercase()
191//!             };
192//!             responder.respond(r);
193//!         });
194//!         response
195//!     }
196//! }
197//!
198//! struct FooService {
199//!     config: Var<bool>,
200//!     state: bool,
201//! }
202//!
203//! app_local! {
204//!     static FOO_SV: FooService = {
205//!         foo_hooks();
206//!         FooService {
207//!             config: var(false),
208//!             state: false,
209//!         }
210//!     };
211//! }
212//! fn foo_hooks() {
213//!     // Event hooks can be setup here
214//! }
215//! ```
216//!
217//! Note that in the example requests are processed in the [`UPDATES.once_update`] update that is called
218//! after all widgets have had a chance to make requests. Requests can also be made from parallel [`task`] threads,
219//! that causes the app main loop to wake and immediately process the request.
220//!
221//! # Init & Main Loop
222//!
223//! A headed app initializes in this sequence once run starts:
224//!
225//! 1. View-process spawns asynchronously.
226//! 2. [`APP.on_init`] handlers are called.
227//! 4. Schedule the app run future to run in the first preview update.
228//! 5. Does [updates loop](#updates-loop).
229//! 7. Does [main loop](#main-loop).
230//!
231//! #### Main Loop
232//!
233//! The main loop coordinates view-process events, timers, app events and updates. There is no scheduler, update and event requests
234//! are captured and coalesced to various buffers that are drained in known sequential order. App level handlers update in the
235//! register order, windows and widgets update in parallel by default, this is controlled by [`WINDOWS.parallel`] and [`parallel`].
236//!
237//! 1. Sleep if there are not pending events or updates.
238//!    * If the view-process is busy blocks until it sends a message, this is a mechanism to stop the app-process
239//!      from overwhelming the view-process.
240//!    * Block until a message is received, from the view-process or from other app threads.
241//!    * If there are [`TIMERS`] or [`VARS`] animations the message block has a deadline to the nearest timer or animation frame.
242//!        * Animations have a fixed frame-rate defined in [`VARS.frame_duration`], by default it is set to the monitor refresh
243//!          rate by the [`WINDOWS`] service.
244//! 2. Calls elapsed timer handlers.
245//! 3. Calls elapsed animation handlers.
246//!     * These handlers mostly just request var updates that are applied in the updates loop.
247//! 4. Does an [updates loop](#updates-loop).
248//! 5. If the view-process is not busy does a [layout loop and render](#layout-loop-and-render).
249//! 6. If exit was requested and not cancelled breaks the loop.
250//!     * Exit is requested automatically when the last open window closes, this is controlled by [`WINDOWS.exit_on_last_close`].
251//!     * Exit can also be requested using [`APP.exit`].
252//!
253//! #### Updates Loop
254//!
255//! The updates loop rebuilds info trees if needed, applies pending variable updates and hooks and collects event updates
256//! requested by the app.
257//!
258//! 1. Takes info rebuild request flag.
259//!     * Windows and widgets that requested info (re)build are called.
260//!     * Info rebuild happens in parallel by default (between windows and widgets).
261//! 2. Takes events, vars and other updates requests.
262//!     1. [var updates loop](#var-updates-loop), note that includes events that are also vars.
263//!     2. Calls [`UPDATES.on_pre_update`] handlers if needed.
264//!         * Both [`Event::on_pre_event`] and [`Var::on_pre_new`] are implemented as pre updates too.
265//!     3. Updates windows and widgets, in parallel by default.
266//!         * Windows and widgets that requested update receive it here.
267//!         * All the pending updates are processed in one pass, all targeted widgets are visited once, in parallel by default.
268//!     4. Calls [`UPDATES.on_update`] handlers if needed.
269//!         * Both [`Event::on_event`] and [`Var::on_new`] are implemented as updates too.
270//! 3. The loop repeats immediately if any info rebuild or update was requested by update callbacks.
271//!     * The loops breaks if it repeats over 1000 times.
272//!     * An error is logged with a trace of the most frequent sources of update requests.
273//!
274//! #### Var Updates Loop
275//!
276//! The variable updates loop applies pending modifications, calls hooks to update variable and bindings.
277//!
278//! 1. Pending variable modifications are applied.
279//! 2. Var hooks are called.
280//!     * The mapping and binding mechanism is implemented using hooks.
281//! 3. The loop repeats until hooks have stopped modifying variables.
282//!     * The loop breaks if it repeats over 1000 times.
283//!     * An error is logged if this happens.
284//!
285//! Note that events are just specialized variables, they update (notify) at the same time as variables modify,
286//! the [`UPDATES.once_update`] closure is also called here.
287//!
288//! Think of this loop as a *staging loop* for the main update notifications, it should quickly prepare data that will
289//! be immutable during the [updates loop](#updates-loop), affected hooks all run sequentially and **must not block**,
290//! UI node methods also should never be called inside hooks.
291//!
292//! #### Layout Loop and Render
293//!
294//! Layout and render requests are coalesced, multiple layout requests for the same widget update it once, multiple
295//! render requests become one frame, and if both `render` and `render_update` are requested for a window it will just fully `render`.
296//!
297//! 1. Take layout and render requests.
298//! 2. Layout loop.
299//!     1. Windows and widgets that requested layout update, in parallel by default.
300//!     2. Does an [updates loop](#updates-loop).
301//!     3. Take layout and render requests, the loop repeats immediately if layout was requested again.
302//!         * The loop breaks if it repeats over 1000 times.
303//!         * An error is logged with a trace the most frequent sources of update requests.
304//! 3. Windows and widgets that requested render (or render_update) are rendered, in parallel by default.
305//!     * The render pass updates widget transforms and hit-test, generates a display list and sends it to the view-process.
306//!
307//! [`APP.defaults()`]: crate::app::APP::defaults
308//! [`APP.on_init`]: crate::app::APP::on_init
309//! [`UPDATES.update`]: crate::update::UPDATES::update
310//! [`task`]: crate::task
311//! [`ResponseVar<R>`]: crate::var::ResponseVar
312//! [`TIMERS`]: crate::timer::TIMERS
313//! [`VARS`]: crate::var::VARS
314//! [`VARS.frame_duration`]: crate::var::VARS::frame_duration
315//! [`WINDOWS`]: crate::window::WINDOWS
316//! [`WINDOWS.parallel`]: crate::window::WINDOWS::parallel
317//! [`parallel`]: fn@crate::widget::parallel
318//! [`UPDATES.on_pre_update`]: crate::update::UPDATES::on_pre_update
319//! [`UPDATES.on_update`]: crate::update::UPDATES::on_update
320//! [`Var::on_pre_new`]: crate::var::VarSubscribe::on_pre_new
321//! [`Var::on_new`]: crate::var::VarSubscribe::on_new
322//! [`Event::on_pre_event`]: crate::event::Event::on_pre_event
323//! [`Event::on_event`]: crate::event::Event::on_event
324//! [`WINDOWS.exit_on_last_close`]: crate::window::WINDOWS::exit_on_last_close
325//! [`APP.exit`]: crate::app::APP#method.exit
326//! [`UPDATES.once_update`]: zng::update::UPDATES::once_update
327//!
328//! # Full API
329//!
330//! This module provides most of the app API needed to make and extend apps, some more advanced or experimental API
331//! may be available at the [`zng_app`], [`zng_app_context`] and [`zng_ext_single_instance`] base crates.
332
333pub use zng_app::{
334    APP, AppBuilder, AppControlFlow, DInstant, Deadline, EXIT_CMD, EXIT_REQUESTED_EVENT, ExitRequestedArgs, HeadlessApp, INSTANT,
335    InstantMode, print_tracing, print_tracing_filter, spawn_deadlock_detection,
336};
337
338#[cfg(feature = "test_util")]
339pub use zng_app::test_log;
340
341pub use zng_app_context::{
342    AppId, AppLocal, AppScope, CaptureFilter, ContextLocal, ContextValueSet, LocalContext, MappedRwLockReadGuardOwned,
343    MappedRwLockWriteGuardOwned, ReadOnlyRwLock, RunOnDrop, RwLockReadGuardOwned, RwLockWriteGuardOwned, app_local, context_local,
344};
345pub use zng_wgt_input::cmd::{
346    NEW_CMD, OPEN_CMD, SAVE_AS_CMD, SAVE_CMD, can_new, can_open, can_save, can_save_as, on_new, on_open, on_pre_new, on_pre_open,
347    on_pre_save, on_pre_save_as, on_save, on_save_as,
348};
349
350pub use zng_app::view_process::raw_events::{LOW_MEMORY_EVENT, LowMemoryArgs};
351
352/// Input device hardware ID and events.
353///
354/// # Full API
355///
356/// See [`zng_app::view_process::raw_device_events`] for the full API.
357pub mod raw_device_events {
358    pub use zng_app::view_process::raw_device_events::{
359        AXIS_MOTION_EVENT, AxisId, AxisMotionArgs, INPUT_DEVICES, INPUT_DEVICES_CHANGED_EVENT, InputDeviceCapability, InputDeviceId,
360        InputDeviceInfo, InputDevicesChangedArgs,
361    };
362}
363
364#[cfg(single_instance)]
365pub use zng_ext_single_instance::{APP_INSTANCE_EVENT, AppInstanceArgs};
366
367/// App-process crash handler.
368///
369/// In builds with `"crash_handler"` feature the crash handler takes over the first "app-process" turning it into
370/// the monitor-process, it spawns another process that is the monitored app-process. If the app-process crashes
371/// the monitor-process spawns a dialog-process that calls the dialog handler to show an error message, upload crash reports, etc.
372///
373/// The dialog handler can be set using [`crash_handler_config!`].
374///
375/// [`crash_handler_config!`]: crate::app::crash_handler::crash_handler_config
376///
377/// # Examples
378///
379/// The example below demonstrates an app setup to show a custom crash dialog.
380///
381/// ```no_run
382/// use zng::prelude::*;
383///
384/// fn main() {
385///     // tracing applied to all processes.
386///     zng::app::print_tracing(tracing::Level::INFO, false, |_| true);
387///
388///     // monitor-process spawns app-process and if needed dialog-process here.
389///     zng::env::init!();
390///
391///     // app-process:
392///     app_main();
393/// }
394///
395/// fn app_main() {
396///     APP.defaults().run_window("main", async {
397///         Window! {
398///             child_align = Align::CENTER;
399///             child = Stack! {
400///                 direction = StackDirection::top_to_bottom();
401///                 spacing = 5;
402///                 children = ui_vec![
403///                     Button! {
404///                         child = Text!("Crash (panic)");
405///                         on_click = hn_once!(|_| {
406///                             panic!("Test panic!");
407///                         });
408///                     },
409///                     Button! {
410///                         child = Text!("Crash (access violation)");
411///                         on_click = hn_once!(|_| {
412///                             // SAFETY: deliberate access violation
413///                             #[expect(deref_nullptr)]
414///                             unsafe {
415///                                 *std::ptr::null_mut() = true;
416///                             }
417///                         });
418///                     }
419///                 ];
420///             };
421///         }
422///     });
423/// }
424///
425/// zng::app::crash_handler::crash_handler_config!(|cfg| {
426///     // monitor-process and dialog-process
427///
428///     cfg.dialog(|args| {
429///         // dialog-process
430///         APP.defaults().run_window("crash-dialog", async move {
431///             Window! {
432///                 title = "App Crashed!";
433///                 auto_size = true;
434///                 min_size = (300, 100);
435///                 start_position = window::StartPosition::CenterMonitor;
436///                 on_load = hn_once!(|_| WINDOW.bring_to_top());
437///                 padding = 10;
438///                 child_spacing = 10;
439///                 child = Text!(args.latest().message());
440///                 child_bottom = Stack! {
441///                     direction = StackDirection::start_to_end();
442///                     layout::align = Align::BOTTOM_END;
443///                     spacing = 5;
444///                     children = ui_vec![
445///                         Button! {
446///                             child = Text!("Restart App");
447///                             on_click = hn_once!(args, |_| {
448///                                 args.restart();
449///                             });
450///                         },
451///                         Button! {
452///                             child = Text!("Exit App");
453///                             on_click = hn_once!(|_| {
454///                                 args.exit(0);
455///                             });
456///                         },
457///                     ];
458///                 };
459///             }
460///         });
461///     });
462/// });
463/// ```
464///
465/// # Debugger
466///
467/// Note that because the crash handler spawns a different process for the app debuggers will not
468/// stop at break points in the app code. You can configure your debugger to set the `NO_ZNG_CRASH_HANDLER` environment
469/// variable to not use a crash handler in debug runs.
470///
471/// On VS Code with the CodeLLDB extension you can add this workspace configuration:
472///
473/// ```json
474/// "lldb.launch.env": {
475///    "ZNG_NO_CRASH_HANDLER": ""
476/// }
477/// ```
478///
479/// # Full API
480///
481/// See [`zng_app::crash_handler`] and [`zng_wgt_inspector::crash_handler`] for the full API.
482#[cfg(crash_handler)]
483pub mod crash_handler {
484    pub use zng_app::crash_handler::{BacktraceFrame, CrashArgs, CrashConfig, CrashError, CrashPanic, crash_handler_config};
485
486    #[cfg(feature = "crash_handler_debug")]
487    pub use zng_wgt_inspector::crash_handler::debug_dialog;
488
489    crash_handler_config!(|cfg| {
490        cfg.default_dialog(|args| {
491            if let Some(c) = &args.dialog_crash {
492                eprintln!("DEBUG CRASH DIALOG ALSO CRASHED");
493                eprintln!("   {}", c.message());
494                eprintln!("ORIGINAL APP CRASH");
495                eprintln!("   {}", args.latest().message());
496                args.exit(0xBADC0DE)
497            } else {
498                #[cfg(feature = "crash_handler_debug")]
499                {
500                    use crate::prelude::*;
501                    APP.defaults().run_window(
502                        "debug-crash-dialog",
503                        async_clmv!(args, { zng_wgt_inspector::crash_handler::debug_dialog(args) }),
504                    );
505                }
506
507                #[cfg(not(feature = "crash_handler_debug"))]
508                {
509                    eprintln!(
510                        "app crashed {}\n\nbuild with feature = \"crash_handler_debug\" to se the debug crash dialog",
511                        args.latest().message()
512                    );
513                }
514            }
515            args.exit(0)
516        });
517    });
518}
519
520/// Trace recording and data model.
521///
522/// All tracing instrumentation in Zng projects is done using the `tracing` crate, trace recording is done using the `tracing-chrome` crate.
523/// The recorded traces can be viewed in `chrome://tracing` or `ui.perfetto.dev` and can be parsed by the [`Trace`] data model.
524///
525/// Build the app with `"trace_recorder"` and run it with the `"ZNG_RECORD_TRACE"` env var to record all processes spawned by the app.
526///
527/// ```no_run
528/// use zng::prelude::*;
529///
530/// fn main() {
531///     unsafe {
532///         std::env::set_var("ZNG_RECORD_TRACE", "");
533///     }
534///     unsafe {
535///         std::env::set_var("ZNG_RECORD_TRACE_FILTER", "debug");
536///     }
537///
538///     // recording start here for all app processes when ZNG_RECORD_TRACE is set.
539///     zng::env::init!();
540///
541///     // .. app
542/// }
543/// ```
544///
545/// The example above hardcodes trace recording for all app processes by setting the `"ZNG_RECORD_TRACE"` environment
546/// variable before the `init!()` call. It also sets `"ZNG_RECORD_TRACE_FILTER"` to a slightly less verbose level.
547///
548/// # Config
549///
550/// The `"ZNG_RECORD_TRACE_DIR"` variable can be set to define a custom output directory path, relative to the current dir.
551/// The default dir is `"./zng-trace/"`.
552///
553/// The `"ZNG_RECORD_TRACE_FILTER"` or `"RUST_LOG"` variables can be used to set custom tracing filters, see the [filter syntax] for details.
554/// The default filter is `"trace"` that records all spans and events.
555///
556/// # Output
557///
558/// Raw trace files are saved to `"{ZNG_RECORD_TRACE_DIR}/{timestamp}/{pid}.json"`.
559///
560/// The timestamp is in microseconds from Unix epoch and is defined by the first process that runs. All processes are
561/// recorded to the same *timestamp* folder.
562///
563/// The process name is defined by an event INFO message that reads `"pid: {pid}, name: {name}"`. See [`zng::env::process_name`] for more details.
564///
565/// The process record start timestamp is defined by an event INFO message that reads `"zng-record-start: {timestamp}"`. This timestamp is also
566/// in microseconds from Unix epoch.
567///
568/// Fake *threads* can be defined to better track a *task span* by setting a `thread = "<name>"` argument in the span. Both fake threads and process
569/// names are converted to real entries by `cargo zng trace`.
570///
571/// # Cargo Zng
572///
573/// You can also use the `cargo zng trace` subcommand to record traces, it handles setting the env variables, merges the multi
574/// process traces into a single file and properly names the processes for better compatibility with trace viewers.
575///
576/// ```console
577/// cargo zng trace --filter debug "path/my-exe"
578/// ```
579///
580/// You can also run using custom commands after `--`:
581///
582/// ```console
583/// cargo zng trace -- cargo run my-exe
584/// ```
585///
586/// Call `cargo zng trace --help` for more details.
587///
588/// # Full API
589///
590/// See [`zng_app::trace_recorder`] for the full API.
591///
592/// [`Trace`]: zng::app::trace_recorder::Trace
593/// [filter syntax]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/index.html#filtering-events-with-environment-variables
594#[cfg(trace_recorder)]
595pub mod trace_recorder {
596    pub use zng_app::trace_recorder::{EventTrace, ProcessTrace, ThreadTrace, Trace, stop_recording};
597}
598
599/// Heap memory usage profiling.
600///
601/// Build with debug symbols and `"memory_profiler"` feature to record heap allocations.
602///
603/// Instrumentation and recording is done with the `dhat` crate. Recorded profiles can be visualized using the
604/// [online DHAT Viewer](https://nnethercote.github.io/dh_view/dh_view.html). Stack traces are captured for each significant allocation.
605///
606/// # Config
607///
608/// The `"ZNG_MEMORY_PROFILER_DIR"` variable can be set to define a custom output directory path, relative to the current dir.
609/// The default dir is `"./zng-dhat/"`.
610///
611/// # Output
612///
613/// The recorded data is saved to `"{ZNG_MEMORY_PROFILER_DIR}/{timestamp}/{pname}-{pid}.json"`.
614///
615/// The timestamp is in microseconds from Unix epoch and is defined by the first process that runs. All processes are recorded
616/// to the same *timestamp* folder, even worker processes started later.
617///
618/// The primary process is named `"app-process"`. See [`zng::env::process_name`] for more details about the default processes.
619///
620/// # Limitations
621///
622/// Only heap allocations using the `#[global_allocator]` are captured, some dependencies can skip the allocator, for example, the view-process
623/// only traces a fraction of allocations because most of its heap usage comes from the graphics driver.
624///
625/// Compiling with `"memory_profiler"` feature replaces the global allocator, so if you use a custom allocator you need to setup
626/// a feature that disables it, otherwise it will not compile. The instrumented allocator also has an impact in performance so
627/// it is only recommended for test builds.
628///
629/// As an alternative on Unix you can use the external [Valgrind DHAT tool](https://valgrind.org/docs/manual/dh-manual.html).
630/// On Windows you can [Record a Heap Snapshot](https://learn.microsoft.com/en-us/windows-hardware/test/wpt/record-heap-snapshot).
631///
632/// # Full API
633///
634/// See [`zng_app::memory_profiler`] for the full API.
635#[cfg(memory_profiler)]
636pub mod memory_profiler {
637    pub use zng_app::memory_profiler::stop_recording;
638}