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    /// 可见性/门控快速轮询间隔(desktop/display_gate/window_ready 探测拍)
32    pub fast_poll_ms: u64,
33    /// window_ready 主轮询间隔
34    pub ready_poll_ms: u64,
35    /// window_ready 首段可见性探测窗(等首帧可见再进主轮询)
36    pub vis_probe_ms: u64,
37    /// 桌面交接 pending 态轮询间隔
38    pub handoff_poll_ms: u64,
39}
40
41/// CLI 协议默认值(help 四要素承诺的值)。**刻意不接 env 覆盖**:
42/// 它们是协议契约的一部分,覆盖通道是命令行 `--timeout`;环境变量静默
43/// 改协议默认值会让 help 文本变成谎言(agent-desktop 的 dead env knobs
44/// 前车之鉴)。调内部旋钮用上面的 Timing 字段。
45pub const EXPECT_MS: u64 = 2000;
46pub const WAIT_MS: u64 = 5000;
47pub const WAIT_WINDOW_MS: u64 = 5000;
48/// 显示端握手窗口(display-start help 承诺"up to 4s";预告确认 3s)。
49pub const DISPLAY_READY_MS: u64 = 4000;
50pub const DISPLAY_ACK_MS: u64 = 3000;
51
52impl Default for Timing {
53    fn default() -> Self {
54        Self {
55            poll_ms: 100,
56            wake_bound_ms: 1600,
57            probe_ms: 60,
58            focus_retries: 3,
59            press_focus_ms: 120,
60            type_focus_ms: 150,
61            segment_chars: 32,
62            modifier_wait_ms: 1500,
63            lock_wait_ms: 3000,
64            settle_ms: 250,
65            fast_poll_ms: 25,
66            ready_poll_ms: 50,
67            vis_probe_ms: 1000,
68            handoff_poll_ms: 100,
69        }
70    }
71}
72
73fn env_num<T: std::str::FromStr>(name: &str, default: T) -> T {
74    std::env::var(name)
75        .ok()
76        .and_then(|v| v.trim().parse().ok())
77        .unwrap_or(default)
78}
79
80impl Timing {
81    /// 进程内首次调用读 env 并缓存;之后零开销。
82    pub fn load() -> &'static Timing {
83        static T: OnceLock<Timing> = OnceLock::new();
84        T.get_or_init(|| {
85            let d = Timing::default();
86            Timing {
87                poll_ms: env_num("ACTL_POLL_MS", d.poll_ms),
88                wake_bound_ms: env_num("ACTL_WAKE_BOUND_MS", d.wake_bound_ms),
89                probe_ms: env_num("ACTL_PROBE_MS", d.probe_ms),
90                focus_retries: env_num("ACTL_FOCUS_RETRIES", d.focus_retries),
91                press_focus_ms: env_num("ACTL_PRESS_FOCUS_MS", d.press_focus_ms),
92                type_focus_ms: env_num("ACTL_TYPE_FOCUS_MS", d.type_focus_ms),
93                segment_chars: env_num("ACTL_SEGMENT_CHARS", d.segment_chars),
94                modifier_wait_ms: env_num("ACTL_MODIFIER_WAIT_MS", d.modifier_wait_ms),
95                lock_wait_ms: env_num("ACTL_LOCK_WAIT_MS", d.lock_wait_ms),
96                settle_ms: env_num("ACTL_SETTLE_MS", d.settle_ms),
97                fast_poll_ms: env_num("ACTL_FAST_POLL_MS", d.fast_poll_ms),
98                ready_poll_ms: env_num("ACTL_READY_POLL_MS", d.ready_poll_ms),
99                vis_probe_ms: env_num("ACTL_VIS_PROBE_MS", d.vis_probe_ms),
100                handoff_poll_ms: env_num("ACTL_HANDOFF_POLL_MS", d.handoff_poll_ms),
101            }
102        })
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn defaults_match_production_values() {
112        let d = Timing::default();
113        assert_eq!(d.poll_ms, 100); // 生态共识
114        assert_eq!(d.wake_bound_ms, 1600); // 挂起 stall 实测 ~2s
115        assert_eq!(d.segment_chars, 32);
116        assert_eq!(d.lock_wait_ms, 3000);
117        assert_eq!(d.modifier_wait_ms, 1500);
118        assert_eq!(d.focus_retries, 3);
119        assert_eq!(d.settle_ms, 250); // 稳定窗 = 2×250ms
120        assert_eq!(d.fast_poll_ms, 25);
121        assert_eq!(d.ready_poll_ms, 50);
122        assert_eq!(d.vis_probe_ms, 1000);
123        assert_eq!(d.handoff_poll_ms, 100);
124    }
125
126    #[test]
127    fn protocol_defaults_are_pinned() {
128        // 协议默认值是 help 契约,锁死后随 spec 变更(硬规则 8)
129        assert_eq!(EXPECT_MS, 2000);
130        assert_eq!(WAIT_MS, 5000);
131        assert_eq!(WAIT_WINDOW_MS, 5000);
132    }
133
134    #[test]
135    fn env_num_parses_and_falls_back() {
136        assert_eq!(env_num::<u64>("_ACTL_TEST_ABSENT_", 42), 42);
137        assert_eq!(env_num::<u64>("PATH", 42), 42); // 非数字 → 默认
138        // 不测 set_var(edition 2024 unsafe):解析正确性由上两行覆盖
139    }
140}