Started integrating INSTANT with APP.

This commit is contained in:
Samuel Guerra 2024-02-02 16:11:11 -03:00
parent b707b40bec
commit 8561d2d1c8
7 changed files with 104 additions and 18 deletions

View File

@ -1,4 +0,0 @@
# Timers TODO
* Configurable `Instant::now` source, to advance time manually in tests.
* Time scale, for recording?

View File

@ -1,4 +1,5 @@
use zero_ui_app_context::app_local;
use zero_ui_time::INSTANT_APP;
use crate::update::{UpdatesTrace, UPDATES};
@ -87,9 +88,14 @@ impl EVENTS {
let mut updates: Vec<_> = ev.updates.get_mut().drain(..).collect();
drop(ev);
for u in &mut updates {
let ev = u.event;
ev.on_update(u);
if !updates.is_empty() {
let _t = INSTANT_APP.pause_for_update();
for u in &mut updates {
let ev = u.event;
ev.on_update(u);
}
}
updates
}

View File

@ -1206,6 +1206,7 @@ impl APP {
assert_not_view_process();
Self::assert_can_run();
check_deadlock();
let _ = INSTANT.now();
let scope = LocalContext::start_app(AppId::new_unique());
AppExtended {
extensions: vec![],

View File

@ -8,7 +8,8 @@ use std::{
use crate::Deadline;
use zero_ui_app_context::{app_local, AppScope};
use zero_ui_var::{response_var, ResponderVar, ResponseVar, VARS, VARS_APP};
use zero_ui_time::INSTANT_APP;
use zero_ui_var::{response_var, ArcVar, ResponderVar, ResponseVar, Var as _, VARS, VARS_APP};
use crate::{
event::{
@ -66,7 +67,10 @@ impl<E: AppExtension> RunningApp<E> {
VARS_APP.init_modify_trace(UpdatesTrace::log_var);
let mut info = AppExtensionsInfo::start();
extensions.register(&mut info);
{
let _t = INSTANT_APP.pause_for_update();
extensions.register(&mut info);
}
APP_PROCESS_SV.write().set_extensions(info);
let device_events = extensions.enable_device_events();
@ -114,6 +118,8 @@ impl<E: AppExtension> RunningApp<E> {
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);
@ -712,6 +718,9 @@ impl<E: AppExtension> RunningApp<E> {
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);
@ -730,6 +739,8 @@ impl<E: AppExtension> RunningApp<E> {
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();
@ -778,6 +789,8 @@ impl<E: AppExtension> RunningApp<E> {
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);
@ -828,6 +841,8 @@ impl<E: AppExtension> RunningApp<E> {
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);
@ -848,6 +863,8 @@ impl<E: AppExtension> RunningApp<E> {
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);
@ -1005,6 +1022,18 @@ impl APP {
pub fn exit(&self) -> ResponseVar<ExitCancelled> {
APP_PROCESS_SV.write().exit()
}
/// Gets a variable that sets if [`INSTANT.now`] is the same exact value during each update pass.
///
/// Time is paused for each update pass, event notification, layout and render so that all widgets observe
/// the same time in the same update.
///
/// This is enabled by default.
///
/// [`INSTANT.now`]: crate::INSTANT::now
pub fn pause_time_for_update(&self) -> ArcVar<bool> {
APP_PROCESS_SV.read().pause_time_for_updates.clone()
}
}
command! {
@ -1041,6 +1070,21 @@ struct PendingExit {
impl AppIntrinsic {
/// Pre-init intrinsic services and commands, must be called before extensions init.
pub(super) fn pre_init(is_headed: bool, with_renderer: bool, view_process_exe: Option<PathBuf>, device_events: bool) -> Self {
APP_PROCESS_SV
.read()
.pause_time_for_updates
.hook(|a| {
if !matches!(INSTANT.mode(), zero_ui_time::InstantMode::Manual) {
if *a.value() {
INSTANT_APP.set_mode(zero_ui_time::InstantMode::UpdatePaused);
} else {
INSTANT_APP.set_mode(zero_ui_time::InstantMode::Now);
}
}
true
})
.perm();
if is_headed {
debug_assert!(with_renderer);
@ -1154,17 +1198,17 @@ pub(crate) fn check_deadlock() {
pub(crate) fn check_deadlock() {}
app_local! {
pub(super) static APP_PROCESS_SV: AppProcessService = const {
AppProcessService {
exit_requests: None,
extensions: None,
}
pub(super) static APP_PROCESS_SV: AppProcessService =AppProcessService {
exit_requests: None,
extensions: None,
pause_time_for_updates: zero_ui_var::var(true),
};
}
pub(super) struct AppProcessService {
exit_requests: Option<ResponderVar<ExitCancelled>>,
extensions: Option<Arc<AppExtensionsInfo>>,
pause_time_for_updates: ArcVar<bool>,
}
impl AppProcessService {
pub(super) fn take_requests(&mut self) -> Option<ResponderVar<ExitCancelled>> {

View File

@ -13,7 +13,7 @@ use std::{
};
use zero_ui_app_context::app_local;
use zero_ui_handle::{Handle, HandleOwner, WeakHandle};
use zero_ui_time::{DInstant, INSTANT};
use zero_ui_time::{DInstant, INSTANT, INSTANT_APP};
use zero_ui_var::{types::WeakArcVar, var, ReadOnlyArcVar, Var, WeakVar};
use crate::{
@ -243,6 +243,8 @@ impl TimersService {
pub(crate) fn notify() {
let _s = tracing::trace_span!("TIMERS").entered();
let _t = INSTANT_APP.pause_for_update();
// we need to detach the handlers, so we can pass the context for then
// so we `mem::take` for the duration of the call. But new timers can be registered inside
// the handlers, so we add those handlers using `extend`.

View File

@ -13,7 +13,10 @@ pub struct INSTANT;
impl INSTANT {
/// Returns an instant corresponding to "now" or an instant configured by the app.
///
/// This method can be called in non-app threads.
/// This method can be called in non-app threads. Apps can override this time in app threads,
/// by default the time is *paused* for each widget OP pass so that all widgets observe the same
/// time on the same pass, you can use [`mode`](Self::mode) to check how `now` updates and you
/// can use the `APP.pause_time_for_update` variable to disable pausing.
pub fn now(&self) -> DInstant {
if zero_ui_app_context::LocalContext::current_app().is_some() {
if let Some(now) = INSTANT_SV.read().now {
@ -88,6 +91,36 @@ impl INSTANT_APP {
pub fn custom_now(&self) -> Option<DInstant> {
INSTANT_SV.read().now
}
/// If mode is [`InstantMode::UpdatePaused`] sets the app custom_now to the current time and returns
/// an object that unsets the custom now on drop.
pub fn pause_for_update(&self) -> Option<InstantUpdatePause> {
let mut sv = INSTANT_SV.write();
match sv.mode {
InstantMode::UpdatePaused => {
let now = DInstant(INSTANT.epoch().elapsed());
sv.now = Some(now);
Some(InstantUpdatePause { now })
}
_ => None,
}
}
}
/// Unset now on drop.
///
/// The time is only unset if it is still set to the same pause time.
#[must_use = "unset_now on drop"]
pub struct InstantUpdatePause {
now: DInstant,
}
impl Drop for InstantUpdatePause {
fn drop(&mut self) {
let mut sv = INSTANT_SV.write();
if sv.now == Some(self.now) {
sv.now = None;
}
}
}
/// Duration elapsed since an epoch.
@ -175,7 +208,7 @@ impl From<DInstant> for Instant {
pub enum InstantMode {
/// Calls during an widget update (or layout, render) pass read the same time.
/// Other calls to `now` resamples the time.
UpdateLocked,
UpdatePaused,
/// Every call to `now` resamples the time.
Now,
/// Time is controlled by the app.
@ -187,7 +220,7 @@ static EPOCH: RwLock<Option<Instant>> = RwLock::new(None);
app_local! {
static INSTANT_SV: InstantService = const {
InstantService {
mode: InstantMode::UpdateLocked,
mode: InstantMode::UpdatePaused,
now: None,
}
};
@ -208,6 +241,8 @@ struct InstantService {
///
/// ```
/// # use zero_ui_time::*;
/// # trait TimeUnits { fn secs(self) -> std::time::Duration where Self: Sized { std::time::Duration::ZERO } }
/// # impl TimeUnits for i32 { }
/// fn timer(deadline: impl Into<Deadline>) { }
///
/// timer(5.secs());

View File

@ -1,6 +1,7 @@
use std::{mem, thread::ThreadId, time::Duration};
use zero_ui_app_context::{app_local, context_local};
use zero_ui_time::INSTANT_APP;
use crate::animation::AnimationTimer;
@ -318,6 +319,7 @@ impl VARS_APP {
/// This must be called by app framework implementers only.
pub fn apply_updates(&self) {
let _s = tracing::trace_span!("VARS").entered();
let _t = INSTANT_APP.pause_for_update();
Self::apply_updates_and_after(0)
}
fn apply_updates_and_after(depth: u8) {