use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
#[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 FlushInfos(pub(crate) crossbeam_channel::Receiver<crate::host::FlushInfo>);
#[derive(Resource, Default, Debug, Clone, Copy)]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
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(SystemParam)]
pub struct FlushMeta<'w> {
pub(super) flushes: Option<Res<'w, FlushInfos>>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) frame: Option<Res<'w, FrameStamp>>,
}
#[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::host::FlushInfo;
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();
app.insert_resource(FlushInfos(flags_rx));
let flag = |devtools| FlushInfo {
sent: None,
devtools,
};
let create = |id: NodeId| Op::Create {
id,
kind: "node".into(),
props: Box::default(),
text: None,
};
flags_tx.send(flag(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(flag(false)).unwrap();
ops_tx.send(vec![create(2)]).unwrap();
flags_tx.send(flag(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();
app.insert_resource(FlushInfos(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(FlushInfo {
sent: Some(stamp),
devtools: false,
})
.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));
}
}