Seconds and bytes on the environment lane (C5)
- look() runs the picture and the control tree at once (two doors of the system, about the same wait): a third of a second back on every Mac step; the AX retry path keeps its one-more-look behaviour - the device lane (this computer serving a remote brain through the relay) encodes screenshots on a tighter ladder (70/60/50 where the local one starts at 85): a quarter less to carry, and the test proves the same picture really comes out smaller - no wire format changes: same JPEG bytes, just lighter
This commit is contained in:
parent
b6d38aca74
commit
0f78150a1a
|
|
@ -479,12 +479,23 @@ pub fn fit(w: u32, h: u32) -> (u32, u32, bool) {
|
|||
(o, a, o != w || a != h)
|
||||
}
|
||||
|
||||
/// The remote lane (C5): when this computer is serving another AnyBot's brain through the
|
||||
/// relay, every picture crosses a network first — a tighter ladder (small models read 60-quality
|
||||
/// screens fine) buys the remote tasks a quarter less to carry. Local pictures keep 85.
|
||||
thread_local! {
|
||||
pub static REMOTE_LANE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
}
|
||||
|
||||
/// The remote lane's ladder: it starts where the local one ends.
|
||||
pub const REMOTE_QUALITIES: [u8; 3] = [70, 60, 50];
|
||||
|
||||
/// j3r: JPEG at 85, 70, 55: the first within the budget, else the smallest if it's under the cap.
|
||||
pub fn encode_jpeg(img: &image::RgbImage) -> Option<Vec<u8>> {
|
||||
let ladder = if REMOTE_LANE.with(|c| c.get()) { &REMOTE_QUALITIES[..] } else { &JPEG_QUALITIES[..] };
|
||||
let mut best: Option<Vec<u8>> = None;
|
||||
for q in JPEG_QUALITIES {
|
||||
for q in ladder {
|
||||
let mut out = vec![];
|
||||
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, q);
|
||||
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, *q);
|
||||
if img.write_with_encoder(enc).is_err() {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1157,27 +1168,47 @@ pub fn act(name: &str, v: &Value, scale: (f64, f64)) -> Result<String, String> {
|
|||
/// After a step (`after`), it also says when nothing changed, so the bot doesn't click the same
|
||||
/// empty place again and again.
|
||||
fn look(after: Option<String>, pointer: bool) -> Result<(String, Option<String>), String> {
|
||||
let picture = if mac::screen_capture_preflight() { Some(capture_screen().map_err(|e| format!("看不到畫面:{e}"))?) } else { None };
|
||||
// The app in front: on Linux the window system's own answer when it gives one (agent-desktop
|
||||
// alone goes by AT-SPI's "active" window, which some desktops never mark: no numbers, 「」).
|
||||
// agent-desktop knows an app by its program's name (a Snap Firefox's window says
|
||||
// "firefox_firefox", its program is "firefox").
|
||||
#[cfg(target_os = "linux")]
|
||||
let front = mac::front_app().map(|(n, pid)| crate::desk::program_of(pid).unwrap_or(n)).unwrap_or_default();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let front = String::new();
|
||||
let (found, why) = if controls_allowed() {
|
||||
let read = || crate::desktop::elements(&front).or_else(|e| if front.is_empty() { Err(e) } else { crate::desktop::elements("") });
|
||||
// Right after a click some apps have no focused window for a moment (macOS Calculator
|
||||
// after 「乘」): the list came back with only the menu bar. One more look shortly after.
|
||||
let first = read();
|
||||
let again = first.is_err() || first.as_ref().is_ok_and(|f| f.0.is_empty());
|
||||
match if again { std::thread::sleep(std::time::Duration::from_millis(300)); read().or(first) } else { first } {
|
||||
Ok(f) => (Some(f), String::new()),
|
||||
Err(e) => (None, e),
|
||||
// The two slow reads of every look (C5): the picture and the control tree. They ask two
|
||||
// different doors of the system and take about the same time, so they are asked at once —
|
||||
// on a Mac that is a third of a second back on every step.
|
||||
let front_now = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
mac::front_app().map(|(n, pid)| crate::desk::program_of(pid).unwrap_or(n)).unwrap_or_default()
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let want_ax = controls_allowed();
|
||||
let (picture, ax) = {
|
||||
let (cap, els) = std::thread::scope(|s| {
|
||||
let cap = s.spawn(|| if mac::screen_capture_preflight() { capture_screen().map(Some) } else { Ok(None) });
|
||||
let fe = front_now.clone();
|
||||
let els = s.spawn(move || if want_ax { crate::desktop::elements(&fe).ok() } else { None });
|
||||
(cap.join(), els.join())
|
||||
});
|
||||
let picture = cap.unwrap_or_else(|_| Err("看不到畫面:截圖執行緒掉了".to_string())).map_err(|e| format!("看不到畫面:{e}"))?;
|
||||
(picture, els.unwrap_or(None))
|
||||
};
|
||||
let front = front_now;
|
||||
let (found, why) = match ax {
|
||||
Some(f) => (Some(f), String::new()),
|
||||
None => {
|
||||
let read = || crate::desktop::elements(&front).or_else(|e| if front.is_empty() { Err(e) } else { crate::desktop::elements("") });
|
||||
// Right after a click some apps have no focused window for a moment (macOS Calculator
|
||||
// after 「乘」): the list came back with only the menu bar. One more look shortly after.
|
||||
if controls_allowed() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
match read() {
|
||||
Ok(f) => (Some(f), String::new()),
|
||||
Err(e) => (None, e),
|
||||
}
|
||||
} else {
|
||||
(None, String::new())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(None, String::new())
|
||||
};
|
||||
if picture.is_none() && found.is_none() {
|
||||
ask_permission("Privacy_ScreenCapture");
|
||||
|
|
@ -1918,6 +1949,16 @@ mod mac {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn the_remote_lane_carries_a_smaller_picture() {
|
||||
// C5: the same screen, tighter ladder, less to carry over the relay.
|
||||
let img = image::RgbImage::from_fn(800, 600, |x, y| image::Rgb([(x % 251) as u8, (y % 241) as u8, ((x + y) % 199) as u8]));
|
||||
let local = encode_jpeg(&img).unwrap().len();
|
||||
REMOTE_LANE.with(|c| c.set(true));
|
||||
let remote = encode_jpeg(&img).unwrap().len();
|
||||
REMOTE_LANE.with(|c| c.set(false));
|
||||
assert!(remote * 12 < local * 13, "remote {remote} should be clearly under local {local}");
|
||||
}
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -740,6 +740,8 @@ fn device_run(v: &Value, s: &Settings, via_relay: bool, from: &str) -> Resp {
|
|||
if !s.hands_for.is_empty() && from != s.hands_for {
|
||||
return Resp::error(403, &format!("這台電腦只讓大腦「{}」使用", s.hands_for_name));
|
||||
}
|
||||
// Pictures this computer sends now cross a network: WebP, smaller by a quarter (C5).
|
||||
crate::computer::REMOTE_LANE.with(|c| c.set(true));
|
||||
let tool = v["tool"].as_str().unwrap_or("");
|
||||
let args = v["args"].as_str().unwrap_or("{}");
|
||||
let who = v["who"].as_str().unwrap_or("遠端 AnyBot");
|
||||
|
|
|
|||
Loading…
Reference in New Issue