261 lines
8.7 KiB
Rust
261 lines
8.7 KiB
Rust
//! Demonstrates app-process crash handler and view-process respawn.
|
|
|
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
use zng::{
|
|
color::{filter::opacity, gradient::stops},
|
|
layout::size,
|
|
markdown::Markdown,
|
|
prelude::*,
|
|
};
|
|
use zng_app::view_process::VIEW_PROCESS;
|
|
use zng_view::extensions::ViewExtensions;
|
|
|
|
fn main() {
|
|
examples_util::print_info();
|
|
|
|
// init crash-handler before view to use different view for the crash dialog app.
|
|
zng::app::crash_handler::init_debug();
|
|
// zng::app::crash_handler::init(zng::app::crash_handler::CrashConfig::new(app_crash_dialog));
|
|
|
|
// this is the normal app-process:
|
|
|
|
// init view with extensions used to cause a crash in the view-process.
|
|
zng_view::init_extended(test_extensions);
|
|
app_main();
|
|
}
|
|
|
|
fn app_main() {
|
|
APP.defaults().run_window(async {
|
|
Window! {
|
|
title = "Respawn Example";
|
|
icon = WindowIcon::render(icon);
|
|
start_position = window::StartPosition::CenterMonitor;
|
|
widget::foreground = window_status();
|
|
child_align = Align::TOP;
|
|
child = Stack! {
|
|
direction = StackDirection::top_to_bottom();
|
|
layout::margin = 10;
|
|
spacing = 10;
|
|
children_align = Align::TOP;
|
|
children = ui_vec![
|
|
Markdown! {
|
|
layout::margin = (20, 0, 0, 0);
|
|
txt = "The renderer and OS windows are created in separate process, the `view-process`. \
|
|
It automatically respawns in case of a graphics driver crash or other similar fatal error.";
|
|
},
|
|
Wrap! {
|
|
spacing = 5;
|
|
children = ui_vec![
|
|
view_respawn(),
|
|
view_crash(),
|
|
],
|
|
},
|
|
Markdown! {
|
|
layout::margin = (20, 0, 0, 0);
|
|
txt = "When the app is instantiated the crash handler takes over the process, becoming the `monitor-process`. \
|
|
It spawns the `app-process` that is the normal execution. If the `app-process` crashes it spawns the \
|
|
`dialog-process` that runs a different app that shows an error message.";
|
|
},
|
|
Wrap! {
|
|
spacing = 5;
|
|
children = ui_vec![
|
|
app_crash("panic"),
|
|
app_crash("access violation"),
|
|
app_crash("stack overflow"),
|
|
app_crash("custom exit"),
|
|
],
|
|
},
|
|
Markdown! {
|
|
layout::margin = (20, 0, 0, 0);
|
|
txt = "The states of these buttons is only preserved for `view-process` crashes. \
|
|
use `CONFIG` or some other state saving to better recover from `app-process` crashes.";
|
|
},
|
|
Wrap! {
|
|
spacing = 5;
|
|
children = ui_vec![
|
|
click_counter(),
|
|
image(),
|
|
click_counter(),
|
|
],
|
|
},
|
|
];
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
// Crash dialog app, runs in the dialog-process.
|
|
#[allow(unused)]
|
|
fn app_crash_dialog(args: zng::app::crash_handler::CrashArgs) -> ! {
|
|
zng::view_process::prebuilt::init();
|
|
APP.defaults().run_window(async move {
|
|
Window! {
|
|
title = "Respawn Example - App Crashed";
|
|
icon = WindowIcon::render(icon);
|
|
|
|
start_position = zng::window::StartPosition::CenterMonitor;
|
|
auto_size = window::AutoSize::CONTENT;
|
|
min_size = (300, 100);
|
|
enabled_buttons = !window::WindowButton::MAXIMIZE;
|
|
|
|
on_load = hn_once!(|_| {
|
|
// force to foreground
|
|
let _ = WINDOWS.focus(WINDOW.id());
|
|
});
|
|
|
|
padding = 5;
|
|
child = Markdown!(
|
|
"The Respawn Example app has crashed.\n\n{}\n\n",
|
|
args.latest().message(),
|
|
);
|
|
child_bottom = Stack! {
|
|
spacing = 5;
|
|
direction = StackDirection::start_to_end();
|
|
layout::align = Align::END;
|
|
children = ui_vec![
|
|
Button! {
|
|
child = Text!("Crash Dialog");
|
|
on_click = hn_once!(|_| {
|
|
panic!("Test dialog-process crash!");
|
|
});
|
|
},
|
|
zng::rule_line::vr::Vr!(),
|
|
Button! {
|
|
child = Text!("Restart App");
|
|
on_click = hn_once!(args, |_| {
|
|
args.restart();
|
|
});
|
|
},
|
|
Button! {
|
|
child = Text!("Exit App");
|
|
on_click = hn_once!(args, |_| {
|
|
args.exit(0);
|
|
});
|
|
}
|
|
];
|
|
}, 5;
|
|
}
|
|
});
|
|
std::process::exit(0)
|
|
}
|
|
|
|
fn view_respawn() -> impl UiNode {
|
|
Button! {
|
|
child = Text!("Respawn View-Process (F5)");
|
|
gesture::click_shortcut = shortcut!(F5);
|
|
on_click = hn!(|_| {
|
|
VIEW_PROCESS.respawn();
|
|
});
|
|
}
|
|
}
|
|
|
|
fn view_crash() -> impl UiNode {
|
|
Button! {
|
|
child = Text!("Crash View-Process");
|
|
on_click = hn!(|_| {
|
|
if let Ok(Some(ext)) = VIEW_PROCESS.extension_id("zng.examples.respawn.crash") {
|
|
let _ = VIEW_PROCESS.app_extension::<_, ()>(ext, &());
|
|
} else {
|
|
tracing::error!(r#"extension "zng-view.crash" unavailable"#)
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
fn app_crash(crash_name: &'static str) -> impl UiNode {
|
|
Button! {
|
|
child = Text!("Crash ({crash_name})");
|
|
on_click = hn!(|_| {
|
|
match crash_name {
|
|
"panic" => panic!("Test app-process crash!"),
|
|
"access violation" => {
|
|
// SAFETY: deliberate access violation
|
|
#[allow(deref_nullptr)]
|
|
unsafe {
|
|
*std::ptr::null_mut() = true;
|
|
}
|
|
}
|
|
"stack overflow" => {
|
|
fn overflow(c: u64) {
|
|
if c < u64::MAX {
|
|
overflow(c + 1)
|
|
}
|
|
}
|
|
overflow(0)
|
|
}
|
|
"custom exit" => {
|
|
eprintln!("custom error");
|
|
std::process::exit(0xBAD);
|
|
}
|
|
n => panic!("Unknown crash '{n}'"),
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
fn click_counter() -> impl UiNode {
|
|
let t = var_from("Click Me!");
|
|
let mut count = 0;
|
|
|
|
Button! {
|
|
on_click = hn!(t, |_| {
|
|
count += 1;
|
|
let new_txt = formatx!("Clicked {count} time{}!", if count > 1 {"s"} else {""});
|
|
t.set(new_txt);
|
|
});
|
|
child = Text!(t);
|
|
}
|
|
}
|
|
|
|
fn image() -> impl UiNode {
|
|
Image! {
|
|
source = include_bytes!("res/window/icon-bytes.png"); size = (32, 32);
|
|
tooltip = Tip!(Text!("Image reloads after respawn"));
|
|
}
|
|
}
|
|
|
|
fn window_status() -> impl UiNode {
|
|
let vars = WINDOW.vars();
|
|
|
|
macro_rules! status {
|
|
($name:ident) => {
|
|
Text!(vars.$name().map(|v| formatx!("{}: {v:?}", stringify!($name))))
|
|
};
|
|
}
|
|
|
|
Stack! {
|
|
direction = StackDirection::top_to_bottom();
|
|
spacing = 5;
|
|
layout::margin = 10;
|
|
layout::align = Align::BOTTOM_START;
|
|
widget::background_color = color::color_scheme_map(colors::WHITE.with_alpha(10.pct()), colors::BLACK.with_alpha(10.pct()));
|
|
text::font_family = "monospace";
|
|
opacity = 80.pct();
|
|
children = ui_vec![
|
|
status!(actual_position),
|
|
status!(actual_size),
|
|
status!(restore_state),
|
|
status!(restore_rect),
|
|
]
|
|
}
|
|
}
|
|
|
|
fn icon() -> impl UiNode {
|
|
Container! {
|
|
size = (36, 36);
|
|
widget::background_gradient = layout::Line::to_bottom_right(), stops![web_colors::ORANGE_RED, 70.pct(), web_colors::DARK_RED];
|
|
widget::corner_radius = 6;
|
|
text::font_size = 28;
|
|
text::font_weight = FontWeight::BOLD;
|
|
child_align = Align::CENTER;
|
|
child = Text!("R");
|
|
}
|
|
}
|
|
|
|
fn test_extensions() -> ViewExtensions {
|
|
let mut ext = ViewExtensions::new();
|
|
ext.command::<(), ()>("zng.examples.respawn.crash", |_, _| panic!("Test view-process crash!"));
|
|
ext
|
|
}
|