use std::time::{Duration, Instant};
use accesskit::{
ActionRequest as AccessibilityActionRequest, TreeUpdate as AccessibilityTreeUpdate,
};
use hydrolysis::{
FrameProfile, HeadlessRuntime, InputEvent, KeyCode, KeyState, Modifiers, PointerButton,
PointerKind, SemanticRuntime, TouchPhase,
};
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, get_current_pid};
use crate::semantics::NodeId;
use crate::snapshot::Snapshot;
const TEST_POINTER_ID: u64 = 0;
pub const VIRTUAL_FRAME: Duration = Duration::from_millis(16);
pub trait RuntimeDriver {
fn pump_at(&mut self, at: Instant, capture_snapshot: bool) -> DriverPumpResult;
fn is_settled(&self) -> bool;
fn has_pending_semantic_update(&self) -> bool;
fn perform_accessibility_action(&mut self, request: AccessibilityActionRequest) -> bool;
fn push_input_event(&mut self, event: InputEvent);
fn request_redraw(&mut self);
fn clear_ui_focus(&mut self) -> bool;
}
#[derive(Debug)]
pub struct DriverPumpResult {
pub(crate) rebuilt: bool,
pub(crate) profile: FrameProfile,
pub(crate) tree_update: Option<AccessibilityTreeUpdate>,
pub(crate) snapshot: Option<Snapshot>,
pub(crate) ui_focus: Option<NodeId>,
}
impl RuntimeDriver for SemanticRuntime {
fn pump_at(&mut self, at: Instant, _capture_snapshot: bool) -> DriverPumpResult {
let result = Self::pump_at(self, at);
DriverPumpResult {
rebuilt: result.rebuilt,
profile: result.profile,
tree_update: result.tree_update,
snapshot: None,
ui_focus: result.ui_focus.map(NodeId::from),
}
}
fn is_settled(&self) -> bool {
Self::is_settled(self)
}
fn has_pending_semantic_update(&self) -> bool {
Self::has_pending_semantic_update(self)
}
fn perform_accessibility_action(&mut self, request: AccessibilityActionRequest) -> bool {
Self::perform_accessibility_action(self, request)
}
fn push_input_event(&mut self, event: InputEvent) {
Self::push_input_event(self, event);
}
fn request_redraw(&mut self) {
Self::request_redraw(self);
}
fn clear_ui_focus(&mut self) -> bool {
Self::clear_ui_focus(self)
}
}
impl RuntimeDriver for HeadlessRuntime {
fn pump_at(&mut self, at: Instant, capture_snapshot: bool) -> DriverPumpResult {
let result = Self::pump_at(self, capture_snapshot, at);
DriverPumpResult {
rebuilt: result.rebuilt,
profile: result.profile,
tree_update: result.tree_update,
snapshot: result.snapshot.map(|snapshot| Snapshot {
width: snapshot.width,
height: snapshot.height,
rgba8: snapshot.rgba8,
}),
ui_focus: result.ui_focus.map(NodeId::from),
}
}
fn is_settled(&self) -> bool {
Self::is_settled(self)
}
fn has_pending_semantic_update(&self) -> bool {
Self::has_pending_semantic_update(self)
}
fn perform_accessibility_action(&mut self, request: AccessibilityActionRequest) -> bool {
Self::perform_accessibility_action(self, request)
}
fn push_input_event(&mut self, event: InputEvent) {
Self::push_input_event(self, event);
}
fn request_redraw(&mut self) {
Self::request_redraw(self);
}
fn clear_ui_focus(&mut self) -> bool {
Self::clear_ui_focus(self)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FrameTiming {
pub total: std::time::Duration,
pub rebuilt: bool,
pub profile: FrameProfile,
pub resources: ResourceSample,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ResourceSample {
pub cpu_percent: f32,
pub memory_bytes: u64,
}
pub struct ResourceSampler {
system: Option<System>,
pid: Option<sysinfo::Pid>,
}
impl ResourceSampler {
pub const fn new() -> Self {
Self {
system: None,
pid: None,
}
}
pub fn sample(&mut self) -> ResourceSample {
let pid = *self.pid.get_or_insert_with(|| {
get_current_pid().expect("waterui-testing perf: failed to resolve current process id")
});
let system = self.system.get_or_insert_with(|| {
let mut system = System::new();
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
true,
ProcessRefreshKind::nothing().with_cpu().with_memory(),
);
system
});
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
true,
ProcessRefreshKind::nothing().with_cpu().with_memory(),
);
let process = system
.process(pid)
.expect("waterui-testing perf: current process disappeared during sampling");
ResourceSample {
cpu_percent: process.cpu_usage(),
memory_bytes: process.memory(),
}
}
}
pub const fn pointer_move_event(x: f32, y: f32) -> InputEvent {
InputEvent::PointerMove {
id: TEST_POINTER_ID,
kind: PointerKind::Mouse,
x,
y,
}
}
pub const fn pointer_down_event(x: f32, y: f32) -> InputEvent {
InputEvent::PointerDown {
id: TEST_POINTER_ID,
kind: PointerKind::Mouse,
x,
y,
button: PointerButton::Primary,
}
}
pub const fn pointer_up_event(x: f32, y: f32) -> InputEvent {
InputEvent::PointerUp {
id: TEST_POINTER_ID,
kind: PointerKind::Mouse,
x,
y,
button: PointerButton::Primary,
}
}
pub const fn secondary_click_events(x: f32, y: f32) -> [InputEvent; 2] {
[
InputEvent::PointerDown {
id: TEST_POINTER_ID,
kind: PointerKind::Mouse,
x,
y,
button: PointerButton::Secondary,
},
InputEvent::PointerUp {
id: TEST_POINTER_ID,
kind: PointerKind::Mouse,
x,
y,
button: PointerButton::Secondary,
},
]
}
pub const fn scroll_event(x: f32, y: f32, dx: f32, dy: f32, is_line_delta: bool) -> InputEvent {
InputEvent::Scroll {
x,
y,
dx,
dy,
is_line_delta,
}
}
pub const fn text_input_event(text: String) -> InputEvent {
InputEvent::TextInput { text }
}
pub fn key_press_event(key: KeyCode, modifiers: Modifiers) -> InputEvent {
InputEvent::Key {
logical_key: key.to_w3c_key(),
physical_code: hydrolysis::keyboard_types::Code::Unidentified,
repeat: false,
key,
state: KeyState::Pressed,
modifiers,
}
}
pub fn magnification_events(x: f32, y: f32, factor: f32) -> [InputEvent; 3] {
[
InputEvent::Magnification {
x,
y,
delta: 0.0,
phase: TouchPhase::Started,
},
InputEvent::Magnification {
x,
y,
delta: factor - 1.0,
phase: TouchPhase::Moved,
},
InputEvent::Magnification {
x,
y,
delta: 0.0,
phase: TouchPhase::Ended,
},
]
}