Skip to main content

actl_core/
timing.rs

1//! 时序参数单源:默认值 = 生产路径;环境变量覆盖 = 测试/调优路径
2//! (timing-profile/timing-sweep fixtures 用,免重编译扫描;也是未来
3//! "按应用自适应时序"的种子)。env 只在进程内读一次(OnceLock),
4//! 非法值静默回退默认——时序旋钮绝不能因配置错误让命令失败。
5
6use std::sync::OnceLock;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Timing {
10    /// wait 族轮询间隔(docs/13 §4.2 生态共识:pywinauto 90 / terminator 100)
11    pub poll_ms: u64,
12    /// 挂起唤醒的有界等待上界(实测挂起 stall ~2s,取略低)
13    pub wake_bound_ms: u64,
14    /// WM_NULL 探针超时(快路径零误伤)
15    pub probe_ms: u32,
16    /// 焦点建立重试轮数
17    pub focus_retries: u32,
18    /// press 链 SetFocus 后的稳定等待
19    pub press_focus_ms: u64,
20    /// type 链 SetFocus 后的稳定等待
21    pub type_focus_ms: u64,
22    /// type 键盘通道分批大小(停止响应粒度 = 本值)
23    pub segment_chars: usize,
24    /// 修饰键空置等待上限
25    pub modifier_wait_ms: u64,
26    /// 输入占用锁等待上限
27    pub lock_wait_ms: u32,
28    /// wait --stable 的采样间隔(settle 判定:连续两次样本相等即稳定;
29    /// 与 poll_ms 分开——内容加载的稳定窗需要比存在性轮询更长的间隔)
30    pub settle_ms: u64,
31}
32
33impl Default for Timing {
34    fn default() -> Self {
35        Self {
36            poll_ms: 100,
37            wake_bound_ms: 1600,
38            probe_ms: 60,
39            focus_retries: 3,
40            press_focus_ms: 120,
41            type_focus_ms: 150,
42            segment_chars: 32,
43            modifier_wait_ms: 1500,
44            lock_wait_ms: 3000,
45            settle_ms: 250,
46        }
47    }
48}
49
50fn env_num<T: std::str::FromStr>(name: &str, default: T) -> T {
51    std::env::var(name)
52        .ok()
53        .and_then(|v| v.trim().parse().ok())
54        .unwrap_or(default)
55}
56
57impl Timing {
58    /// 进程内首次调用读 env 并缓存;之后零开销。
59    pub fn load() -> &'static Timing {
60        static T: OnceLock<Timing> = OnceLock::new();
61        T.get_or_init(|| {
62            let d = Timing::default();
63            Timing {
64                poll_ms: env_num("ACTL_POLL_MS", d.poll_ms),
65                wake_bound_ms: env_num("ACTL_WAKE_BOUND_MS", d.wake_bound_ms),
66                probe_ms: env_num("ACTL_PROBE_MS", d.probe_ms),
67                focus_retries: env_num("ACTL_FOCUS_RETRIES", d.focus_retries),
68                press_focus_ms: env_num("ACTL_PRESS_FOCUS_MS", d.press_focus_ms),
69                type_focus_ms: env_num("ACTL_TYPE_FOCUS_MS", d.type_focus_ms),
70                segment_chars: env_num("ACTL_SEGMENT_CHARS", d.segment_chars),
71                modifier_wait_ms: env_num("ACTL_MODIFIER_WAIT_MS", d.modifier_wait_ms),
72                lock_wait_ms: env_num("ACTL_LOCK_WAIT_MS", d.lock_wait_ms),
73                settle_ms: env_num("ACTL_SETTLE_MS", d.settle_ms),
74            }
75        })
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn defaults_match_production_values() {
85        let d = Timing::default();
86        assert_eq!(d.poll_ms, 100); // 生态共识
87        assert_eq!(d.wake_bound_ms, 1600); // 挂起 stall 实测 ~2s
88        assert_eq!(d.segment_chars, 32);
89        assert_eq!(d.lock_wait_ms, 3000);
90        assert_eq!(d.modifier_wait_ms, 1500);
91        assert_eq!(d.focus_retries, 3);
92        assert_eq!(d.settle_ms, 250); // 稳定窗 = 2×250ms
93    }
94
95    #[test]
96    fn env_num_parses_and_falls_back() {
97        assert_eq!(env_num::<u64>("_ACTL_TEST_ABSENT_", 42), 42);
98        assert_eq!(env_num::<u64>("PATH", 42), 42); // 非数字 → 默认
99        // 不测 set_var(edition 2024 unsafe):解析正确性由上两行覆盖
100    }
101}