use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use crate::ui_map::AtlasLayoutCache;
#[derive(Resource, Default, Debug, Clone, Copy)]
pub struct OpApplyStats {
pub applied_count: u64,
pub reset_count: u64,
pub app_applied_count: u64,
pub last_ops: usize,
pub last_frame_wait: std::time::Duration,
pub last_pre_apply: std::time::Duration,
pub last_translate: std::time::Duration,
pub last_apply_end: Option<std::time::Instant>,
}
#[derive(Resource)]
pub struct FlushStamps(pub(crate) crossbeam_channel::Receiver<std::time::Instant>);
#[derive(Resource, Default, Debug, Clone, Copy)]
pub struct FrameStamp(pub Option<std::time::Instant>);
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn mark_frame_start(mut stamp: ResMut<FrameStamp>) {
stamp.0 = Some(std::time::Instant::now());
}
#[derive(Resource)]
pub struct FlushFlags(pub(crate) crossbeam_channel::Receiver<bool>);
#[derive(SystemParam)]
pub struct FlushMeta<'w> {
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) stamps: Option<Res<'w, FlushStamps>>,
pub(super) flags: Option<Res<'w, FlushFlags>>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) frame: Option<Res<'w, FrameStamp>>,
}
#[derive(SystemParam)]
pub struct UiAssets<'w> {
pub(super) layouts: ResMut<'w, Assets<TextureAtlasLayout>>,
pub(super) atlas_cache: ResMut<'w, AtlasLayoutCache>,
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn split_pre_apply(
stamp: std::time::Instant,
frame_start: Option<std::time::Instant>,
apply_start: std::time::Instant,
) -> (std::time::Duration, std::time::Duration) {
let boundary = frame_start.map_or(stamp, |fs| fs.max(stamp));
(
boundary.saturating_duration_since(stamp),
apply_start.saturating_duration_since(boundary),
)
}
#[cfg(test)]
mod tests {
use super::super::test_util::op_app;
use super::*;
use crate::protocol::{NodeId, op::Op};
#[test]
fn devtools_flagged_batches_skip_app_applied_count() {
let (mut app, ops_tx) = op_app();
let (flags_tx, flags_rx) = crossbeam_channel::unbounded::<bool>();
app.insert_resource(FlushFlags(flags_rx));
let create = |id: NodeId| Op::Create {
id,
kind: "node".into(),
props: Box::default(),
text: None,
};
flags_tx.send(true).unwrap();
ops_tx.send(vec![create(1)]).unwrap();
app.update();
let stats = *app.world().resource::<OpApplyStats>();
assert_eq!((stats.applied_count, stats.app_applied_count), (1, 0));
flags_tx.send(false).unwrap();
ops_tx.send(vec![create(2)]).unwrap();
flags_tx.send(true).unwrap();
ops_tx.send(vec![create(3)]).unwrap();
app.update();
let stats = *app.world().resource::<OpApplyStats>();
assert_eq!((stats.applied_count, stats.app_applied_count), (2, 1));
}
#[test]
fn split_pre_apply_splits_wait_and_in_frame() {
use std::time::Duration;
let t0 = std::time::Instant::now();
let t1 = t0 + Duration::from_millis(12);
let t2 = t1 + Duration::from_millis(3);
assert_eq!(
split_pre_apply(t0, Some(t1), t2),
(Duration::from_millis(12), Duration::from_millis(3))
);
assert_eq!(split_pre_apply(t1, Some(t0), t2), (Duration::ZERO, t2 - t1));
assert_eq!(split_pre_apply(t0, None, t2), (Duration::ZERO, t2 - t0));
}
#[test]
fn flush_stamp_splits_frame_wait_from_pre_apply() {
use std::time::{Duration, Instant};
let (mut app, ops_tx) = op_app();
let (stamps_tx, stamps_rx) = crossbeam_channel::unbounded::<Instant>();
app.insert_resource(FlushStamps(stamps_rx));
let now = Instant::now();
let stamp = now - Duration::from_millis(30);
let frame_start = now - Duration::from_millis(10);
app.insert_resource(FrameStamp(Some(frame_start)));
stamps_tx.send(stamp).unwrap();
ops_tx
.send(vec![Op::Create {
id: 1,
kind: "node".into(),
props: Box::default(),
text: None,
}])
.unwrap();
app.update();
let stats = *app.world().resource::<OpApplyStats>();
assert_eq!(stats.last_frame_wait, Duration::from_millis(20));
assert!(stats.last_pre_apply >= Duration::from_millis(10));
}
}