use std::{
collections::HashMap,
fmt, mem,
path::PathBuf,
sync::Arc,
task::Waker,
time::{Duration, Instant},
};
use crate::Deadline;
use parking_lot::Mutex;
use zng_app_context::{app_local, AppScope};
use zng_task::DEADLINE_APP;
use zng_time::{InstantMode, INSTANT_APP};
use zng_txt::Txt;
use zng_var::{response_var, ArcVar, ReadOnlyArcVar, ResponderVar, ResponseVar, Var as _, VARS, VARS_APP};
use crate::{
event::{
command, event, AnyEventArgs, AppDisconnected, CommandHandle, CommandInfoExt, CommandNameExt, EventPropagationHandle,
TimeoutOrAppDisconnected, EVENTS,
},
event_args,
shortcut::shortcut,
shortcut::CommandShortcutExt,
timer::TimersService,
update::{
ContextUpdates, EventUpdate, InfoUpdates, LayoutUpdates, RenderUpdates, UpdateOp, UpdateTrace, UpdatesTrace, WidgetUpdates, UPDATES,
},
view_process::{raw_device_events::DeviceId, *},
widget::WidgetId,
window::WindowId,
AppControlFlow, AppEventObserver, AppExtension, AppExtensionsInfo, DInstant, APP, INSTANT,
};
pub(crate) struct RunningApp<E: AppExtension> {
extensions: (AppIntrinsic, E),
receiver: flume::Receiver<AppEvent>,
loop_timer: LoopTimer,
loop_monitor: LoopMonitor,
pending_view_events: Vec<zng_view_api::Event>,
pending_view_frame_events: Vec<zng_view_api::window::EventFrameRendered>,
pending: ContextUpdates,
exited: bool,
_scope: AppScope,
}
impl<E: AppExtension> RunningApp<E> {
pub(crate) fn start(
scope: AppScope,
mut extensions: E,
is_headed: bool,
with_renderer: bool,
view_process_exe: Option<PathBuf>,
view_process_env: HashMap<Txt, Txt>,
) -> Self {
let _s = tracing::debug_span!("APP::start").entered();
let (sender, receiver) = AppEventSender::new();
UPDATES.init(sender);
fn app_waker() {
UPDATES.update(None);
}
VARS_APP.init_app_waker(app_waker);
VARS_APP.init_modify_trace(UpdatesTrace::log_var);
DEADLINE_APP.init_deadline_service(crate::timer::deadline_service);
zng_var::types::TRANSITIONABLE_APP.init_rgba_lerp(zng_color::lerp_rgba);
let mut info = AppExtensionsInfo::start();
{
let _t = INSTANT_APP.pause_for_update();
extensions.register(&mut info);
}
let device_events = extensions.enable_device_events();
{
let mut sv = APP_PROCESS_SV.write();
sv.set_extensions(info, device_events);
}
if with_renderer && view_process_exe.is_none() {
zng_env::assert_inited();
}
#[cfg(not(target_arch = "wasm32"))]
let view_process_exe = view_process_exe.unwrap_or_else(|| std::env::current_exe().expect("current_exe"));
#[cfg(target_arch = "wasm32")]
let view_process_exe = std::path::PathBuf::from("<wasm>");
let process = AppIntrinsic::pre_init(is_headed, with_renderer, view_process_exe, view_process_env, device_events);
{
let _s = tracing::debug_span!("extensions.init").entered();
extensions.init();
}
let args = AppStartArgs { _private: () };
for h in zng_unique_id::hot_static_ref!(ON_APP_START).lock().iter_mut() {
h(&args)
}
RunningApp {
extensions: (process, extensions),
receiver,
loop_timer: LoopTimer::default(),
loop_monitor: LoopMonitor::default(),
pending_view_events: Vec::with_capacity(100),
pending_view_frame_events: Vec::with_capacity(5),
pending: ContextUpdates {
events: Vec::with_capacity(100),
update: false,
info: false,
layout: false,
render: false,
update_widgets: WidgetUpdates::default(),
info_widgets: InfoUpdates::default(),
layout_widgets: LayoutUpdates::default(),
render_widgets: RenderUpdates::default(),
render_update_widgets: RenderUpdates::default(),
},
exited: false,
_scope: scope,
}
}
pub fn has_exited(&self) -> bool {
self.exited
}
pub fn notify_event<O: AppEventObserver>(&mut self, mut update: EventUpdate, observer: &mut O) {
let _scope = tracing::trace_span!("notify_event", event = update.event().name()).entered();
let _t = INSTANT_APP.pause_for_update();
update.event().on_update(&mut update);
self.extensions.event_preview(&mut update);
observer.event_preview(&mut update);
update.call_pre_actions();
self.extensions.event_ui(&mut update);
observer.event_ui(&mut update);
self.extensions.event(&mut update);
observer.event(&mut update);
update.call_pos_actions();
}
fn device_id(&mut self, id: zng_view_api::DeviceId) -> DeviceId {
VIEW_PROCESS.device_id(id)
}
fn on_view_event<O: AppEventObserver>(&mut self, ev: zng_view_api::Event, observer: &mut O) {
use crate::view_process::raw_device_events::*;
use crate::view_process::raw_events::*;
use zng_view_api::Event;
fn window_id(id: zng_view_api::window::WindowId) -> WindowId {
WindowId::from_raw(id.get())
}
match ev {
Event::MouseMoved {
window: w_id,
device: d_id,
coalesced_pos,
position,
} => {
let args = RawMouseMovedArgs::now(window_id(w_id), self.device_id(d_id), coalesced_pos, position);
self.notify_event(RAW_MOUSE_MOVED_EVENT.new_update(args), observer);
}
Event::MouseEntered {
window: w_id,
device: d_id,
} => {
let args = RawMouseArgs::now(window_id(w_id), self.device_id(d_id));
self.notify_event(RAW_MOUSE_ENTERED_EVENT.new_update(args), observer);
}
Event::MouseLeft {
window: w_id,
device: d_id,
} => {
let args = RawMouseArgs::now(window_id(w_id), self.device_id(d_id));
self.notify_event(RAW_MOUSE_LEFT_EVENT.new_update(args), observer);
}
Event::WindowChanged(c) => {
let monitor_id = c.monitor.map(|id| VIEW_PROCESS.monitor_id(id));
let args = RawWindowChangedArgs::now(
window_id(c.window),
c.state,
c.position,
monitor_id,
c.size,
c.safe_padding,
c.cause,
c.frame_wait_id,
);
self.notify_event(RAW_WINDOW_CHANGED_EVENT.new_update(args), observer);
}
Event::DroppedFile { window: w_id, file } => {
let args = RawDroppedFileArgs::now(window_id(w_id), file);
self.notify_event(RAW_DROPPED_FILE_EVENT.new_update(args), observer);
}
Event::HoveredFile { window: w_id, file } => {
let args = RawHoveredFileArgs::now(window_id(w_id), file);
self.notify_event(RAW_HOVERED_FILE_EVENT.new_update(args), observer);
}
Event::HoveredFileCancelled(w_id) => {
let args = RawHoveredFileCancelledArgs::now(window_id(w_id));
self.notify_event(RAW_HOVERED_FILE_CANCELLED_EVENT.new_update(args), observer);
}
Event::FocusChanged { prev, new } => {
let args = RawWindowFocusArgs::now(prev.map(window_id), new.map(window_id));
self.notify_event(RAW_WINDOW_FOCUS_EVENT.new_update(args), observer);
}
Event::KeyboardInput {
window: w_id,
device: d_id,
key_code,
state,
key,
key_location,
key_modified,
text,
} => {
let args = RawKeyInputArgs::now(
window_id(w_id),
self.device_id(d_id),
key_code,
key_location,
state,
key,
key_modified,
text,
);
self.notify_event(RAW_KEY_INPUT_EVENT.new_update(args), observer);
}
Event::Ime { window: w_id, ime } => {
let args = RawImeArgs::now(window_id(w_id), ime);
self.notify_event(RAW_IME_EVENT.new_update(args), observer);
}
Event::MouseWheel {
window: w_id,
device: d_id,
delta,
phase,
} => {
let args = RawMouseWheelArgs::now(window_id(w_id), self.device_id(d_id), delta, phase);
self.notify_event(RAW_MOUSE_WHEEL_EVENT.new_update(args), observer);
}
Event::MouseInput {
window: w_id,
device: d_id,
state,
button,
} => {
let args = RawMouseInputArgs::now(window_id(w_id), self.device_id(d_id), state, button);
self.notify_event(RAW_MOUSE_INPUT_EVENT.new_update(args), observer);
}
Event::TouchpadPressure {
window: w_id,
device: d_id,
pressure,
stage,
} => {
let args = RawTouchpadPressureArgs::now(window_id(w_id), self.device_id(d_id), pressure, stage);
self.notify_event(RAW_TOUCHPAD_PRESSURE_EVENT.new_update(args), observer);
}
Event::AxisMotion {
window: w_id,
device: d_id,
axis,
value,
} => {
let args = RawAxisMotionArgs::now(window_id(w_id), self.device_id(d_id), axis, value);
self.notify_event(RAW_AXIS_MOTION_EVENT.new_update(args), observer);
}
Event::Touch {
window: w_id,
device: d_id,
touches,
} => {
let args = RawTouchArgs::now(window_id(w_id), self.device_id(d_id), touches);
self.notify_event(RAW_TOUCH_EVENT.new_update(args), observer);
}
Event::ScaleFactorChanged {
monitor: id,
windows,
scale_factor,
} => {
let monitor_id = VIEW_PROCESS.monitor_id(id);
let windows: Vec<_> = windows.into_iter().map(window_id).collect();
let args = RawScaleFactorChangedArgs::now(monitor_id, windows, scale_factor);
self.notify_event(RAW_SCALE_FACTOR_CHANGED_EVENT.new_update(args), observer);
}
Event::MonitorsChanged(monitors) => {
let monitors: Vec<_> = monitors.into_iter().map(|(id, info)| (VIEW_PROCESS.monitor_id(id), info)).collect();
let args = RawMonitorsChangedArgs::now(monitors);
self.notify_event(RAW_MONITORS_CHANGED_EVENT.new_update(args), observer);
}
Event::WindowCloseRequested(w_id) => {
let args = RawWindowCloseRequestedArgs::now(window_id(w_id));
self.notify_event(RAW_WINDOW_CLOSE_REQUESTED_EVENT.new_update(args), observer);
}
Event::WindowOpened(w_id, data) => {
let w_id = window_id(w_id);
let (window, data) = VIEW_PROCESS.on_window_opened(w_id, data);
let args = RawWindowOpenArgs::now(w_id, window, data);
self.notify_event(RAW_WINDOW_OPEN_EVENT.new_update(args), observer);
}
Event::HeadlessOpened(w_id, data) => {
let w_id = window_id(w_id);
let (surface, data) = VIEW_PROCESS.on_headless_opened(w_id, data);
let args = RawHeadlessOpenArgs::now(w_id, surface, data);
self.notify_event(RAW_HEADLESS_OPEN_EVENT.new_update(args), observer);
}
Event::WindowOrHeadlessOpenError { id: w_id, error } => {
let w_id = window_id(w_id);
let args = RawWindowOrHeadlessOpenErrorArgs::now(w_id, error);
self.notify_event(RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT.new_update(args), observer);
}
Event::WindowClosed(w_id) => {
let args = RawWindowCloseArgs::now(window_id(w_id));
self.notify_event(RAW_WINDOW_CLOSE_EVENT.new_update(args), observer);
}
Event::ImageMetadataLoaded {
image: id,
size,
ppi,
is_mask,
} => {
if let Some(img) = VIEW_PROCESS.on_image_metadata_loaded(id, size, ppi, is_mask) {
let args = RawImageArgs::now(img);
self.notify_event(RAW_IMAGE_METADATA_LOADED_EVENT.new_update(args), observer);
}
}
Event::ImagePartiallyLoaded {
image: id,
partial_size,
ppi,
is_opaque,
is_mask,
partial_pixels: partial_bgra8,
} => {
if let Some(img) = VIEW_PROCESS.on_image_partially_loaded(id, partial_size, ppi, is_opaque, is_mask, partial_bgra8) {
let args = RawImageArgs::now(img);
self.notify_event(RAW_IMAGE_PARTIALLY_LOADED_EVENT.new_update(args), observer);
}
}
Event::ImageLoaded(image) => {
if let Some(img) = VIEW_PROCESS.on_image_loaded(image) {
let args = RawImageArgs::now(img);
self.notify_event(RAW_IMAGE_LOADED_EVENT.new_update(args), observer);
}
}
Event::ImageLoadError { image: id, error } => {
if let Some(img) = VIEW_PROCESS.on_image_error(id, error) {
let args = RawImageArgs::now(img);
self.notify_event(RAW_IMAGE_LOAD_ERROR_EVENT.new_update(args), observer);
}
}
Event::ImageEncoded { image: id, format, data } => VIEW_PROCESS.on_image_encoded(id, format, data),
Event::ImageEncodeError { image: id, format, error } => {
VIEW_PROCESS.on_image_encode_error(id, format, error);
}
Event::FrameImageReady {
window: w_id,
frame: frame_id,
image: image_id,
selection,
} => {
if let Some(img) = VIEW_PROCESS.on_frame_image_ready(image_id) {
let args = RawFrameImageReadyArgs::now(img, window_id(w_id), frame_id, selection);
self.notify_event(RAW_FRAME_IMAGE_READY_EVENT.new_update(args), observer);
}
}
Event::AccessInit { window: w_id } => {
self.notify_event(crate::access::on_access_init(window_id(w_id)), observer);
}
Event::AccessCommand {
window: win_id,
target: wgt_id,
command,
} => {
if let Some(update) = crate::access::on_access_command(window_id(win_id), WidgetId::from_raw(wgt_id.0), command) {
self.notify_event(update, observer);
}
}
Event::AccessDeinit { window: w_id } => {
self.notify_event(crate::access::on_access_deinit(window_id(w_id)), observer);
}
Event::MsgDialogResponse(id, response) => {
VIEW_PROCESS.on_message_dlg_response(id, response);
}
Event::FileDialogResponse(id, response) => {
VIEW_PROCESS.on_file_dlg_response(id, response);
}
Event::ExtensionEvent(id, payload) => {
let args = RawExtensionEventArgs::now(id, payload);
self.notify_event(RAW_EXTENSION_EVENT.new_update(args), observer);
}
Event::FontsChanged => {
let args = RawFontChangedArgs::now();
self.notify_event(RAW_FONT_CHANGED_EVENT.new_update(args), observer);
}
Event::FontAaChanged(aa) => {
let args = RawFontAaChangedArgs::now(aa);
self.notify_event(RAW_FONT_AA_CHANGED_EVENT.new_update(args), observer);
}
Event::MultiClickConfigChanged(cfg) => {
let args = RawMultiClickConfigChangedArgs::now(cfg);
self.notify_event(RAW_MULTI_CLICK_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::AnimationsConfigChanged(cfg) => {
VARS_APP.set_sys_animations_enabled(cfg.enabled);
let args = RawAnimationsConfigChangedArgs::now(cfg);
self.notify_event(RAW_ANIMATIONS_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::KeyRepeatConfigChanged(cfg) => {
let args = RawKeyRepeatConfigChangedArgs::now(cfg);
self.notify_event(RAW_KEY_REPEAT_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::TouchConfigChanged(cfg) => {
let args = RawTouchConfigChangedArgs::now(cfg);
self.notify_event(RAW_TOUCH_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::LocaleChanged(cfg) => {
let args = RawLocaleChangedArgs::now(cfg);
self.notify_event(RAW_LOCALE_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::ColorsConfigChanged(cfg) => {
let args = RawColorsConfigChangedArgs::now(cfg);
self.notify_event(RAW_COLORS_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::ChromeConfigChanged(cfg) => {
let args = RawChromeConfigChangedArgs::now(cfg);
self.notify_event(RAW_CHROME_CONFIG_CHANGED_EVENT.new_update(args), observer);
}
Event::DeviceAdded(d_id) => {
let args = DeviceArgs::now(self.device_id(d_id));
self.notify_event(DEVICE_ADDED_EVENT.new_update(args), observer);
}
Event::DeviceRemoved(d_id) => {
let args = DeviceArgs::now(self.device_id(d_id));
self.notify_event(DEVICE_REMOVED_EVENT.new_update(args), observer);
}
Event::DeviceMouseMotion { device: d_id, delta } => {
let args = MouseMotionArgs::now(self.device_id(d_id), delta);
self.notify_event(MOUSE_MOTION_EVENT.new_update(args), observer);
}
Event::DeviceMouseWheel { device: d_id, delta } => {
let args = MouseWheelArgs::now(self.device_id(d_id), delta);
self.notify_event(MOUSE_WHEEL_EVENT.new_update(args), observer);
}
Event::DeviceMotion { device: d_id, axis, value } => {
let args = MotionArgs::now(self.device_id(d_id), axis, value);
self.notify_event(MOTION_EVENT.new_update(args), observer);
}
Event::DeviceButton {
device: d_id,
button,
state,
} => {
let args = ButtonArgs::now(self.device_id(d_id), button, state);
self.notify_event(BUTTON_EVENT.new_update(args), observer);
}
Event::DeviceKey {
device: d_id,
key_code,
state,
} => {
let args = KeyArgs::now(self.device_id(d_id), key_code, state);
self.notify_event(KEY_EVENT.new_update(args), observer);
}
Event::LowMemory => {
LOW_MEMORY_EVENT.notify(LowMemoryArgs::now());
}
Event::RecoveredFromComponentPanic { component, recover, panic } => {
tracing::error!("view-process recovered from internal component panic\n component: {component}\n recover: {recover}\n```panic\n{panic}\n```");
}
Event::Inited(zng_view_api::Inited { .. }) | Event::Suspended | Event::Disconnected(_) | Event::FrameRendered(_) => {
unreachable!()
} }
}
fn on_view_rendered_event<O: AppEventObserver>(&mut self, ev: zng_view_api::window::EventFrameRendered, observer: &mut O) {
debug_assert!(ev.window != zng_view_api::window::WindowId::INVALID);
let window_id = WindowId::from_raw(ev.window.get());
let image = ev.frame_image.map(|img| VIEW_PROCESS.on_frame_image(img));
let args = crate::view_process::raw_events::RawFrameRenderedArgs::now(window_id, ev.frame, image);
self.notify_event(crate::view_process::raw_events::RAW_FRAME_RENDERED_EVENT.new_update(args), observer);
}
pub(crate) fn run_headed(mut self) {
let mut observer = ();
#[cfg(feature = "dyn_app_extension")]
let mut observer = observer.as_dyn();
self.apply_updates(&mut observer);
self.apply_update_events(&mut observer);
let mut wait = false;
loop {
wait = match self.poll_impl(wait, &mut observer) {
AppControlFlow::Poll => false,
AppControlFlow::Wait => true,
AppControlFlow::Exit => break,
};
}
}
fn push_coalesce<O: AppEventObserver>(&mut self, ev: AppEvent, observer: &mut O) {
match ev {
AppEvent::ViewEvent(ev) => match ev {
zng_view_api::Event::FrameRendered(ev) => {
if ev.window == zng_view_api::window::WindowId::INVALID {
tracing::error!("ignored rendered event for invalid window id, {ev:?}");
return;
}
let window = WindowId::from_raw(ev.window.get());
{
if VIEW_PROCESS.is_available() {
VIEW_PROCESS.on_frame_rendered(window);
}
}
#[cfg(debug_assertions)]
if self.pending_view_frame_events.iter().any(|e| e.window == ev.window) {
tracing::warn!("window `{window:?}` probably sent a frame request without awaiting renderer idle");
}
self.pending_view_frame_events.push(ev);
}
zng_view_api::Event::Inited(zng_view_api::Inited {
generation,
is_respawn,
available_monitors,
multi_click_config,
key_repeat_config,
touch_config,
font_aa,
animations_config,
locale_config,
colors_config,
chrome_config,
extensions,
}) => {
if is_respawn {
VIEW_PROCESS.on_respawned(generation);
APP_PROCESS_SV.read().is_suspended.set(false);
}
VIEW_PROCESS.handle_inited(generation, extensions.clone());
let monitors: Vec<_> = available_monitors
.into_iter()
.map(|(id, info)| (VIEW_PROCESS.monitor_id(id), info))
.collect();
VARS.animations_enabled().set(animations_config.enabled);
let args = crate::view_process::ViewProcessInitedArgs::now(
generation,
is_respawn,
monitors,
multi_click_config,
key_repeat_config,
touch_config,
font_aa,
animations_config,
locale_config,
colors_config,
chrome_config,
extensions,
);
self.notify_event(VIEW_PROCESS_INITED_EVENT.new_update(args), observer);
}
zng_view_api::Event::Suspended => {
VIEW_PROCESS.handle_suspended();
let args = crate::view_process::ViewProcessSuspendedArgs::now();
self.notify_event(VIEW_PROCESS_SUSPENDED_EVENT.new_update(args), observer);
APP_PROCESS_SV.read().is_suspended.set(true);
}
zng_view_api::Event::Disconnected(gen) => {
VIEW_PROCESS.handle_disconnect(gen);
}
ev => {
if let Some(last) = self.pending_view_events.last_mut() {
match last.coalesce(ev) {
Ok(()) => {}
Err(ev) => self.pending_view_events.push(ev),
}
} else {
self.pending_view_events.push(ev);
}
}
},
AppEvent::Event(ev) => EVENTS.notify(ev.get()),
AppEvent::Update(op, target) => {
UPDATES.update_op(op, target);
}
AppEvent::CheckUpdate => {}
AppEvent::ResumeUnwind(p) => std::panic::resume_unwind(p),
}
}
fn has_pending_updates(&mut self) -> bool {
!self.pending_view_events.is_empty() || self.pending.has_updates() || UPDATES.has_pending_updates() || !self.receiver.is_empty()
}
pub(crate) fn poll<O: AppEventObserver>(&mut self, wait_app_event: bool, observer: &mut O) -> AppControlFlow {
#[cfg(feature = "dyn_app_extension")]
let mut observer = observer.as_dyn();
#[cfg(feature = "dyn_app_extension")]
let observer = &mut observer;
self.poll_impl(wait_app_event, observer)
}
fn poll_impl<O: AppEventObserver>(&mut self, wait_app_event: bool, observer: &mut O) -> AppControlFlow {
let mut disconnected = false;
if self.exited {
return AppControlFlow::Exit;
}
if wait_app_event {
let idle = tracing::debug_span!("<idle>", ended_by = tracing::field::Empty).entered();
let timer = if self.view_is_busy() { None } else { self.loop_timer.poll() };
if let Some(time) = timer {
match self.receiver.recv_deadline_sp(time) {
Ok(ev) => {
idle.record("ended_by", "event");
drop(idle);
self.push_coalesce(ev, observer)
}
Err(e) => match e {
flume::RecvTimeoutError::Timeout => {
idle.record("ended_by", "timeout");
}
flume::RecvTimeoutError::Disconnected => {
idle.record("ended_by", "disconnected");
disconnected = true
}
},
}
} else {
match self.receiver.recv() {
Ok(ev) => {
idle.record("ended_by", "event");
drop(idle);
self.push_coalesce(ev, observer)
}
Err(e) => match e {
flume::RecvError::Disconnected => {
idle.record("ended_by", "disconnected");
disconnected = true
}
},
}
}
}
loop {
match self.receiver.try_recv() {
Ok(ev) => self.push_coalesce(ev, observer),
Err(e) => match e {
flume::TryRecvError::Empty => break,
flume::TryRecvError::Disconnected => {
disconnected = true;
break;
}
},
}
}
if disconnected {
panic!("app events channel disconnected");
}
if self.view_is_busy() {
return AppControlFlow::Wait;
}
UPDATES.on_app_awake();
let updated_timers = self.loop_timer.awake();
if updated_timers {
UPDATES.update_timers(&mut self.loop_timer);
self.apply_updates(observer);
}
let mut events = mem::take(&mut self.pending_view_events);
for ev in events.drain(..) {
self.on_view_event(ev, observer);
self.apply_updates(observer);
}
debug_assert!(self.pending_view_events.is_empty());
self.pending_view_events = events; let mut events = mem::take(&mut self.pending_view_frame_events);
for ev in events.drain(..) {
self.on_view_rendered_event(ev, observer);
}
self.pending_view_frame_events = events;
if self.has_pending_updates() {
self.apply_updates(observer);
self.apply_update_events(observer);
}
if self.view_is_busy() {
return AppControlFlow::Wait;
}
self.finish_frame(observer);
UPDATES.next_deadline(&mut self.loop_timer);
if self.extensions.0.exit() {
UPDATES.on_app_sleep();
self.exited = true;
AppControlFlow::Exit
} else if self.has_pending_updates() || UPDATES.has_pending_layout_or_render() {
AppControlFlow::Poll
} else {
UPDATES.on_app_sleep();
AppControlFlow::Wait
}
}
fn apply_updates<O: AppEventObserver>(&mut self, observer: &mut O) {
let _s = tracing::debug_span!("apply_updates").entered();
let mut run = true;
while run {
run = self.loop_monitor.update(|| {
let mut any = false;
self.pending |= UPDATES.apply_info();
if mem::take(&mut self.pending.info) {
any = true;
let _s = tracing::debug_span!("info").entered();
let mut info_widgets = mem::take(&mut self.pending.info_widgets);
let _t = INSTANT_APP.pause_for_update();
{
let _s = tracing::debug_span!("ext.info").entered();
self.extensions.info(&mut info_widgets);
}
{
let _s = tracing::debug_span!("obs.info").entered();
observer.info(&mut info_widgets);
}
}
self.pending |= UPDATES.apply_updates();
TimersService::notify();
if mem::take(&mut self.pending.update) {
any = true;
let _s = tracing::debug_span!("update").entered();
let mut update_widgets = mem::take(&mut self.pending.update_widgets);
let _t = INSTANT_APP.pause_for_update();
{
let _s = tracing::debug_span!("ext.update_preview").entered();
self.extensions.update_preview();
}
{
let _s = tracing::debug_span!("obs.update_preview").entered();
observer.update_preview();
}
UPDATES.on_pre_updates();
{
let _s = tracing::debug_span!("ext.update_ui").entered();
self.extensions.update_ui(&mut update_widgets);
}
{
let _s = tracing::debug_span!("obs.update_ui").entered();
observer.update_ui(&mut update_widgets);
}
{
let _s = tracing::debug_span!("ext.update").entered();
self.extensions.update();
}
{
let _s = tracing::debug_span!("obs.update").entered();
observer.update();
}
UPDATES.on_updates();
}
any
});
}
}
fn apply_update_events<O: AppEventObserver>(&mut self, observer: &mut O) {
let _s = tracing::debug_span!("apply_update_events").entered();
loop {
let events: Vec<_> = self.pending.events.drain(..).collect();
if events.is_empty() {
break;
}
for mut update in events {
let _s = tracing::debug_span!("update_event", ?update).entered();
self.loop_monitor.maybe_trace(|| {
let _t = INSTANT_APP.pause_for_update();
{
let _s = tracing::debug_span!("ext.event_preview").entered();
self.extensions.event_preview(&mut update);
}
{
let _s = tracing::debug_span!("obs.event_preview").entered();
observer.event_preview(&mut update);
}
update.call_pre_actions();
{
let _s = tracing::debug_span!("ext.event_ui").entered();
self.extensions.event_ui(&mut update);
}
{
let _s = tracing::debug_span!("obs.event_ui").entered();
observer.event_ui(&mut update);
}
{
let _s = tracing::debug_span!("ext.event").entered();
self.extensions.event(&mut update);
}
{
let _s = tracing::debug_span!("obs.event").entered();
observer.event(&mut update);
}
update.call_pos_actions();
});
self.apply_updates(observer);
}
}
}
fn view_is_busy(&mut self) -> bool {
VIEW_PROCESS.is_available() && VIEW_PROCESS.pending_frames() > 0
}
fn finish_frame<O: AppEventObserver>(&mut self, observer: &mut O) {
debug_assert!(!self.view_is_busy());
self.pending |= UPDATES.apply_layout_render();
while mem::take(&mut self.pending.layout) {
let _s = tracing::debug_span!("apply_layout").entered();
let mut layout_widgets = mem::take(&mut self.pending.layout_widgets);
self.loop_monitor.maybe_trace(|| {
let _t = INSTANT_APP.pause_for_update();
{
let _s = tracing::debug_span!("ext.layout").entered();
self.extensions.layout(&mut layout_widgets);
}
{
let _s = tracing::debug_span!("obs.layout").entered();
observer.layout(&mut layout_widgets);
}
});
self.apply_updates(observer);
self.pending |= UPDATES.apply_layout_render();
}
if mem::take(&mut self.pending.render) {
let _s = tracing::debug_span!("apply_render").entered();
let mut render_widgets = mem::take(&mut self.pending.render_widgets);
let mut render_update_widgets = mem::take(&mut self.pending.render_update_widgets);
let _t = INSTANT_APP.pause_for_update();
{
let _s = tracing::debug_span!("ext.render").entered();
self.extensions.render(&mut render_widgets, &mut render_update_widgets);
}
{
let _s = tracing::debug_span!("obs.render").entered();
observer.render(&mut render_widgets, &mut render_update_widgets);
}
}
self.loop_monitor.finish_frame();
}
}
impl<E: AppExtension> Drop for RunningApp<E> {
fn drop(&mut self) {
let _s = tracing::debug_span!("ext.deinit").entered();
self.extensions.deinit();
VIEW_PROCESS.exit();
}
}
pub struct AppStartArgs {
_private: (),
}
pub fn on_app_start(handler: impl FnMut(&AppStartArgs) + Send + 'static) {
zng_unique_id::hot_static_ref!(ON_APP_START).lock().push(Box::new(handler))
}
zng_unique_id::hot_static! {
static ON_APP_START: Mutex<Vec<AppStartHandler>> = Mutex::new(vec![]);
}
type AppStartHandler = Box<dyn FnMut(&AppStartArgs) + Send + 'static>;
#[derive(Debug)]
pub(crate) struct LoopTimer {
now: DInstant,
deadline: Option<Deadline>,
}
impl Default for LoopTimer {
fn default() -> Self {
Self {
now: INSTANT.now(),
deadline: None,
}
}
}
impl LoopTimer {
pub fn elapsed(&mut self, deadline: Deadline) -> bool {
if deadline.0 <= self.now {
true
} else {
self.register(deadline);
false
}
}
pub fn register(&mut self, deadline: Deadline) {
if let Some(d) = &mut self.deadline {
if deadline < *d {
*d = deadline;
}
} else {
self.deadline = Some(deadline)
}
}
pub(crate) fn poll(&mut self) -> Option<Deadline> {
self.deadline
}
pub(crate) fn awake(&mut self) -> bool {
self.now = INSTANT.now();
if let Some(d) = self.deadline {
if d.0 <= self.now {
self.deadline = None;
return true;
}
}
false
}
pub fn now(&self) -> DInstant {
self.now
}
}
impl zng_var::animation::AnimationTimer for LoopTimer {
fn elapsed(&mut self, deadline: Deadline) -> bool {
self.elapsed(deadline)
}
fn register(&mut self, deadline: Deadline) {
self.register(deadline)
}
fn now(&self) -> DInstant {
self.now()
}
}
#[derive(Default)]
struct LoopMonitor {
update_count: u16,
skipped: bool,
trace: Vec<UpdateTrace>,
}
impl LoopMonitor {
pub fn update(&mut self, update_once: impl FnOnce() -> bool) -> bool {
self.update_count += 1;
if self.update_count < 500 {
update_once()
} else if self.update_count < 1000 {
UpdatesTrace::collect_trace(&mut self.trace, update_once)
} else if self.update_count == 1000 {
self.skipped = true;
let trace = UpdatesTrace::format_trace(mem::take(&mut self.trace));
tracing::error!(
"updated 1000 times without rendering, probably stuck in an infinite loop\n\
will start skipping updates to render and poll system events\n\
top 20 most frequent update requests (in 500 cycles):\n\
{trace}\n\
you can use `UpdatesTraceUiNodeExt` and `updates_trace_event` to refine the trace"
);
false
} else if self.update_count == 1500 {
self.update_count = 1001;
false
} else {
update_once()
}
}
pub fn maybe_trace(&mut self, notify_once: impl FnOnce()) {
if (500..1000).contains(&self.update_count) {
UpdatesTrace::collect_trace(&mut self.trace, notify_once);
} else {
notify_once();
}
}
pub fn finish_frame(&mut self) {
if !self.skipped {
self.skipped = false;
self.update_count = 0;
self.trace = vec![];
}
}
}
impl APP {
pub fn exit(&self) -> ResponseVar<ExitCancelled> {
APP_PROCESS_SV.write().exit()
}
pub fn is_suspended(&self) -> ReadOnlyArcVar<bool> {
APP_PROCESS_SV.read().is_suspended.read_only()
}
}
impl APP {
pub fn pause_time_for_update(&self) -> ArcVar<bool> {
APP_PROCESS_SV.read().pause_time_for_updates.clone()
}
pub fn start_manual_time(&self) {
INSTANT_APP.set_mode(InstantMode::Manual);
INSTANT_APP.set_now(INSTANT.now());
UPDATES.update(None);
}
pub fn advance_manual_time(&self, advance: Duration) {
INSTANT_APP.advance_now(advance);
UPDATES.update(None);
}
pub fn set_manual_time(&self, now: DInstant) {
INSTANT_APP.set_now(now);
UPDATES.update(None);
}
pub fn end_manual_time(&self) {
INSTANT_APP.set_mode(match APP.pause_time_for_update().get() {
true => InstantMode::UpdatePaused,
false => InstantMode::Now,
});
UPDATES.update(None);
}
}
command! {
pub static EXIT_CMD = {
l10n!: true,
name: "Exit",
info: "Close all windows and exit",
shortcut: shortcut!(Exit),
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExitCancelled;
impl fmt::Display for ExitCancelled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "exit request cancelled")
}
}
struct AppIntrinsic {
exit_handle: CommandHandle,
pending_exit: Option<PendingExit>,
}
struct PendingExit {
handle: EventPropagationHandle,
response: ResponderVar<ExitCancelled>,
}
impl AppIntrinsic {
pub(super) fn pre_init(
is_headed: bool,
with_renderer: bool,
view_process_exe: PathBuf,
view_process_env: HashMap<Txt, Txt>,
device_events: bool,
) -> Self {
APP_PROCESS_SV
.read()
.pause_time_for_updates
.hook(|a| {
if !matches!(INSTANT.mode(), zng_time::InstantMode::Manual) {
if *a.value() {
INSTANT_APP.set_mode(InstantMode::UpdatePaused);
} else {
INSTANT_APP.set_mode(InstantMode::Now);
}
}
true
})
.perm();
if is_headed {
debug_assert!(with_renderer);
let view_evs_sender = UPDATES.sender();
VIEW_PROCESS.start(view_process_exe, view_process_env, device_events, false, move |ev| {
let _ = view_evs_sender.send_view_event(ev);
});
} else if with_renderer {
let view_evs_sender = UPDATES.sender();
VIEW_PROCESS.start(view_process_exe, view_process_env, false, true, move |ev| {
let _ = view_evs_sender.send_view_event(ev);
});
}
AppIntrinsic {
exit_handle: EXIT_CMD.subscribe(true),
pending_exit: None,
}
}
pub(super) fn exit(&mut self) -> bool {
if let Some(pending) = self.pending_exit.take() {
if pending.handle.is_stopped() {
pending.response.respond(ExitCancelled);
false
} else {
true
}
} else {
false
}
}
}
impl AppExtension for AppIntrinsic {
fn event_preview(&mut self, update: &mut EventUpdate) {
if let Some(args) = EXIT_CMD.on(update) {
args.handle_enabled(&self.exit_handle, |_| {
APP.exit();
});
}
}
fn update(&mut self) {
if let Some(response) = APP_PROCESS_SV.write().take_requests() {
let args = ExitRequestedArgs::now();
self.pending_exit = Some(PendingExit {
handle: args.propagation().clone(),
response,
});
EXIT_REQUESTED_EVENT.notify(args);
}
}
}
pub(crate) fn assert_not_view_process() {
if zng_view_api::ViewConfig::from_env().is_some() {
panic!("cannot start App in view-process");
}
}
#[cfg(feature = "deadlock_detection")]
pub(crate) fn check_deadlock() {
use parking_lot::deadlock;
use std::{
sync::atomic::{self, AtomicBool},
thread,
time::*,
};
static CHECK_RUNNING: AtomicBool = AtomicBool::new(false);
if CHECK_RUNNING.swap(true, atomic::Ordering::SeqCst) {
return;
}
thread::spawn(|| loop {
thread::sleep(Duration::from_secs(10));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
use std::fmt::Write;
let mut msg = String::new();
let _ = writeln!(&mut msg, "{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
let _ = writeln!(&mut msg, "Deadlock #{}, {} threads", i, threads.len());
for t in threads {
let _ = writeln!(&mut msg, "Thread Id {:#?}", t.thread_id());
let _ = writeln!(&mut msg, "{:#?}", t.backtrace());
}
}
#[cfg(not(feature = "test_util"))]
eprint!("{msg}");
#[cfg(feature = "test_util")]
{
use std::io::Write;
let _ = write!(&mut std::io::stderr(), "{msg}");
zng_env::exit(-1);
}
});
}
#[cfg(not(feature = "deadlock_detection"))]
pub(crate) fn check_deadlock() {}
app_local! {
pub(super) static APP_PROCESS_SV: AppProcessService = AppProcessService {
exit_requests: None,
extensions: None,
device_events: false,
pause_time_for_updates: zng_var::var(true),
is_suspended: zng_var::var(false),
};
}
pub(super) struct AppProcessService {
exit_requests: Option<ResponderVar<ExitCancelled>>,
extensions: Option<Arc<AppExtensionsInfo>>,
pub(super) device_events: bool,
pause_time_for_updates: ArcVar<bool>,
is_suspended: ArcVar<bool>,
}
impl AppProcessService {
pub(super) fn take_requests(&mut self) -> Option<ResponderVar<ExitCancelled>> {
self.exit_requests.take()
}
fn exit(&mut self) -> ResponseVar<ExitCancelled> {
if let Some(r) = &self.exit_requests {
r.response_var()
} else {
let (responder, response) = response_var();
self.exit_requests = Some(responder);
UPDATES.update(None);
response
}
}
pub(super) fn extensions(&self) -> Arc<AppExtensionsInfo> {
self.extensions
.clone()
.unwrap_or_else(|| Arc::new(AppExtensionsInfo { infos: vec![] }))
}
pub(super) fn set_extensions(&mut self, info: AppExtensionsInfo, device_events: bool) {
self.extensions = Some(Arc::new(info));
self.device_events = device_events;
}
}
#[derive(Debug)]
#[expect(clippy::large_enum_variant)]
pub(crate) enum AppEvent {
ViewEvent(zng_view_api::Event),
Event(crate::event::EventUpdateMsg),
Update(UpdateOp, Option<WidgetId>),
ResumeUnwind(PanicPayload),
CheckUpdate,
}
#[derive(Clone)]
pub struct AppEventSender(flume::Sender<AppEvent>);
impl AppEventSender {
pub(crate) fn new() -> (Self, flume::Receiver<AppEvent>) {
let (sender, receiver) = flume::unbounded();
(Self(sender), receiver)
}
#[allow(clippy::result_large_err)] fn send_app_event(&self, event: AppEvent) -> Result<(), AppDisconnected<AppEvent>> {
self.0.send(event)?;
Ok(())
}
#[allow(clippy::result_large_err)]
fn send_view_event(&self, event: zng_view_api::Event) -> Result<(), AppDisconnected<AppEvent>> {
self.0.send(AppEvent::ViewEvent(event))?;
Ok(())
}
pub fn send_update(&self, op: UpdateOp, target: impl Into<Option<WidgetId>>) -> Result<(), AppDisconnected<()>> {
UpdatesTrace::log_update();
self.send_app_event(AppEvent::Update(op, target.into()))
.map_err(|_| AppDisconnected(()))
}
pub(crate) fn send_event(&self, event: crate::event::EventUpdateMsg) -> Result<(), AppDisconnected<crate::event::EventUpdateMsg>> {
self.send_app_event(AppEvent::Event(event)).map_err(|e| match e.0 {
AppEvent::Event(ev) => AppDisconnected(ev),
_ => unreachable!(),
})
}
pub fn send_resume_unwind(&self, payload: PanicPayload) -> Result<(), AppDisconnected<PanicPayload>> {
self.send_app_event(AppEvent::ResumeUnwind(payload)).map_err(|e| match e.0 {
AppEvent::ResumeUnwind(p) => AppDisconnected(p),
_ => unreachable!(),
})
}
pub(crate) fn send_check_update(&self) -> Result<(), AppDisconnected<()>> {
self.send_app_event(AppEvent::CheckUpdate).map_err(|_| AppDisconnected(()))
}
pub fn waker(&self, target: impl Into<Option<WidgetId>>) -> Waker {
Arc::new(AppWaker(self.0.clone(), target.into())).into()
}
pub fn ext_channel<T>(&self) -> (AppExtSender<T>, AppExtReceiver<T>) {
let (sender, receiver) = flume::unbounded();
(
AppExtSender {
update: self.clone(),
sender,
},
AppExtReceiver { receiver },
)
}
pub fn ext_channel_bounded<T>(&self, cap: usize) -> (AppExtSender<T>, AppExtReceiver<T>) {
let (sender, receiver) = flume::bounded(cap);
(
AppExtSender {
update: self.clone(),
sender,
},
AppExtReceiver { receiver },
)
}
}
struct AppWaker(flume::Sender<AppEvent>, Option<WidgetId>);
impl std::task::Wake for AppWaker {
fn wake(self: std::sync::Arc<Self>) {
self.wake_by_ref()
}
fn wake_by_ref(self: &Arc<Self>) {
let _ = self.0.send(AppEvent::Update(UpdateOp::Update, self.1));
}
}
type PanicPayload = Box<dyn std::any::Any + Send + 'static>;
pub struct AppExtSender<T> {
update: AppEventSender,
sender: flume::Sender<T>,
}
impl<T> Clone for AppExtSender<T> {
fn clone(&self) -> Self {
Self {
update: self.update.clone(),
sender: self.sender.clone(),
}
}
}
impl<T: Send> AppExtSender<T> {
pub fn send(&self, msg: T) -> Result<(), AppDisconnected<T>> {
match self.update.send_update(UpdateOp::Update, None) {
Ok(()) => self.sender.send(msg).map_err(|e| AppDisconnected(e.0)),
Err(_) => Err(AppDisconnected(msg)),
}
}
pub fn send_timeout(&self, msg: T, dur: Duration) -> Result<(), TimeoutOrAppDisconnected> {
match self.update.send_update(UpdateOp::Update, None) {
Ok(()) => self.sender.send_timeout(msg, dur).map_err(|e| match e {
flume::SendTimeoutError::Timeout(_) => TimeoutOrAppDisconnected::Timeout,
flume::SendTimeoutError::Disconnected(_) => TimeoutOrAppDisconnected::AppDisconnected,
}),
Err(_) => Err(TimeoutOrAppDisconnected::AppDisconnected),
}
}
pub fn send_deadline(&self, msg: T, deadline: Instant) -> Result<(), TimeoutOrAppDisconnected> {
match self.update.send_update(UpdateOp::Update, None) {
Ok(()) => self.sender.send_deadline(msg, deadline).map_err(|e| match e {
flume::SendTimeoutError::Timeout(_) => TimeoutOrAppDisconnected::Timeout,
flume::SendTimeoutError::Disconnected(_) => TimeoutOrAppDisconnected::AppDisconnected,
}),
Err(_) => Err(TimeoutOrAppDisconnected::AppDisconnected),
}
}
}
pub struct AppExtReceiver<T> {
receiver: flume::Receiver<T>,
}
impl<T> Clone for AppExtReceiver<T> {
fn clone(&self) -> Self {
Self {
receiver: self.receiver.clone(),
}
}
}
impl<T> AppExtReceiver<T> {
pub fn try_recv(&self) -> Result<T, Option<AppExtSenderDisconnected>> {
self.receiver.try_recv().map_err(|e| match e {
flume::TryRecvError::Empty => None,
flume::TryRecvError::Disconnected => Some(AppExtSenderDisconnected),
})
}
}
#[derive(Debug)]
pub struct AppExtSenderDisconnected;
impl fmt::Display for AppExtSenderDisconnected {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cannot receive because the sender disconnected")
}
}
impl std::error::Error for AppExtSenderDisconnected {}
event_args! {
pub struct ExitRequestedArgs {
..
fn delivery_list(&self, list: &mut UpdateDeliveryList) {
list.search_all()
}
}
}
event! {
pub static EXIT_REQUESTED_EVENT: ExitRequestedArgs;
}
trait ReceiverExt<T> {
fn recv_deadline_sp(&self, deadline: Deadline) -> Result<T, flume::RecvTimeoutError>;
}
const WORST_SLEEP_ERR: Duration = Duration::from_millis(if cfg!(windows) { 20 } else { 10 });
const WORST_SPIN_ERR: Duration = Duration::from_millis(if cfg!(windows) { 2 } else { 1 });
impl<T> ReceiverExt<T> for flume::Receiver<T> {
fn recv_deadline_sp(&self, deadline: Deadline) -> Result<T, flume::RecvTimeoutError> {
loop {
if let Some(d) = deadline.0.checked_duration_since(INSTANT.now()) {
if matches!(INSTANT.mode(), zng_time::InstantMode::Manual) {
match self.recv_timeout(d.checked_sub(WORST_SLEEP_ERR).unwrap_or_default()) {
Err(flume::RecvTimeoutError::Timeout) => continue, interrupt => return interrupt,
}
} else if d > WORST_SLEEP_ERR {
#[cfg(not(target_arch = "wasm32"))]
match self.recv_deadline(deadline.0.checked_sub(WORST_SLEEP_ERR).unwrap().into()) {
Err(flume::RecvTimeoutError::Timeout) => continue, interrupt => return interrupt,
}
#[cfg(target_arch = "wasm32")] match self.recv_timeout(d.checked_sub(WORST_SLEEP_ERR).unwrap_or_default()) {
Err(flume::RecvTimeoutError::Timeout) => continue, interrupt => return interrupt,
}
} else if d > WORST_SPIN_ERR {
let spin_deadline = Deadline(deadline.0.checked_sub(WORST_SPIN_ERR).unwrap());
while !spin_deadline.has_elapsed() {
match self.try_recv() {
Err(flume::TryRecvError::Empty) => std::thread::yield_now(),
Err(flume::TryRecvError::Disconnected) => return Err(flume::RecvTimeoutError::Disconnected),
Ok(msg) => return Ok(msg),
}
}
continue; } else {
while !deadline.has_elapsed() {
std::thread::yield_now();
}
return Err(flume::RecvTimeoutError::Timeout);
}
} else {
return Err(flume::RecvTimeoutError::Timeout);
}
}
}
}