#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/master/examples/res/image/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/master/examples/res/image/zng-logo.png")]
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
use std::{
fmt, ops,
time::{Duration, Instant},
};
use parking_lot::RwLock;
use zng_app_context::app_local;
pub struct INSTANT;
impl INSTANT {
pub fn now(&self) -> DInstant {
if zng_app_context::LocalContext::current_app().is_some() {
if let Some(now) = INSTANT_SV.read().now {
return now;
}
}
DInstant(self.epoch().elapsed())
}
pub fn epoch(&self) -> Instant {
if let Some(t) = *EPOCH.read() {
return t;
}
*EPOCH.write().get_or_insert_with(|| {
let mut now = Instant::now();
for t in [60 * 60 * 24, 60 * 60, 60 * 30, 60 * 15, 60 * 10, 60] {
if let Some(t) = now.checked_sub(Duration::from_secs(t)) {
now = t;
break;
}
}
now
})
}
pub fn mode(&self) -> InstantMode {
INSTANT_SV.read().mode
}
}
#[allow(non_camel_case_types)]
pub struct INSTANT_APP;
impl INSTANT_APP {
pub fn set_mode(&self, mode: InstantMode) {
let mut sv = INSTANT_SV.write();
sv.mode = mode;
if let InstantMode::Now = mode {
sv.now = None;
}
}
pub fn set_now(&self, now: DInstant) {
let mut sv = INSTANT_SV.write();
if let InstantMode::Now = sv.mode {
panic!("cannot set now with `TimeMode::Now`");
}
sv.now = Some(now);
}
pub fn advance_now(&self, advance: Duration) {
let mut sv = INSTANT_SV.write();
if let InstantMode::Manual = sv.mode {
*sv.now.get_or_insert_with(|| DInstant(INSTANT.epoch().elapsed())) += advance;
} else {
panic!("cannot advance now, not `InstantMode::Manual`");
}
}
pub fn unset_now(&self) {
INSTANT_SV.write().now = None;
}
pub fn custom_now(&self) -> Option<DInstant> {
INSTANT_SV.read().now
}
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,
}
}
}
#[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;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DInstant(Duration);
impl DInstant {
pub fn elapsed(self) -> Duration {
INSTANT.now().0 - self.0
}
pub fn duration_since(self, earlier: DInstant) -> Duration {
self.0 - earlier.0
}
pub fn checked_add(&self, duration: Duration) -> Option<DInstant> {
self.0.checked_add(duration).map(Self)
}
pub fn checked_sub(self, duration: Duration) -> Option<DInstant> {
self.0.checked_sub(duration).map(Self)
}
pub fn checked_duration_since(&self, earlier: DInstant) -> Option<Duration> {
self.0.checked_sub(earlier.0)
}
pub fn saturating_duration_since(&self, earlier: DInstant) -> Duration {
self.0.saturating_sub(earlier.0)
}
pub const EPOCH: DInstant = DInstant(Duration::ZERO);
pub const MAX: DInstant = DInstant(Duration::MAX);
}
impl ops::Add<Duration> for DInstant {
type Output = Self;
fn add(self, rhs: Duration) -> Self {
Self(self.0 + rhs)
}
}
impl ops::AddAssign<Duration> for DInstant {
fn add_assign(&mut self, rhs: Duration) {
self.0 += rhs;
}
}
impl ops::Sub<Duration> for DInstant {
type Output = Self;
fn sub(self, rhs: Duration) -> Self {
Self(self.0 - rhs)
}
}
impl ops::SubAssign<Duration> for DInstant {
fn sub_assign(&mut self, rhs: Duration) {
self.0 -= rhs;
}
}
impl ops::Sub for DInstant {
type Output = Duration;
fn sub(self, rhs: Self) -> Self::Output {
self.0.saturating_sub(rhs.0)
}
}
impl From<DInstant> for Instant {
fn from(t: DInstant) -> Self {
INSTANT.epoch() + t.0
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InstantMode {
UpdatePaused,
Now,
Manual,
}
static EPOCH: RwLock<Option<Instant>> = RwLock::new(None);
app_local! {
static INSTANT_SV: InstantService = const {
InstantService {
mode: InstantMode::UpdatePaused,
now: None,
}
};
}
struct InstantService {
mode: InstantMode,
now: Option<DInstant>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Deadline(pub DInstant);
impl Deadline {
pub fn timeout(dur: Duration) -> Self {
Deadline(INSTANT.now() + dur)
}
pub fn has_elapsed(self) -> bool {
self.0 <= INSTANT.now()
}
pub fn time_left(self) -> Option<Duration> {
self.0.checked_duration_since(INSTANT.now())
}
pub fn min(self, other: Deadline) -> Deadline {
Deadline(self.0.min(other.0))
}
pub fn max(self, other: Deadline) -> Deadline {
Deadline(self.0.max(other.0))
}
pub const ELAPSED: Deadline = Deadline(DInstant::EPOCH);
pub const MAX: Deadline = Deadline(DInstant::MAX);
}
impl fmt::Display for Deadline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dur = self.0 - INSTANT.now();
write!(f, "{dur:?} left")
}
}
impl fmt::Debug for Deadline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Deadline({self})")
}
}
impl From<DInstant> for Deadline {
fn from(value: DInstant) -> Self {
Deadline(value)
}
}
impl From<Duration> for Deadline {
fn from(value: Duration) -> Self {
Deadline::timeout(value)
}
}
impl ops::Add<Duration> for Deadline {
type Output = Self;
fn add(mut self, rhs: Duration) -> Self {
self.0 += rhs;
self
}
}
impl ops::AddAssign<Duration> for Deadline {
fn add_assign(&mut self, rhs: Duration) {
self.0 += rhs;
}
}
impl ops::Sub<Duration> for Deadline {
type Output = Self;
fn sub(mut self, rhs: Duration) -> Self {
self.0 -= rhs;
self
}
}
impl ops::SubAssign<Duration> for Deadline {
fn sub_assign(&mut self, rhs: Duration) {
self.0 -= rhs;
}
}