Merge c5/fast: parallel look + remote-lane ladder (C5)
This commit is contained in:
commit
d9732821cf
|
|
@ -479,12 +479,23 @@ pub fn fit(w: u32, h: u32) -> (u32, u32, bool) {
|
||||||
(o, a, o != w || a != h)
|
(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.
|
/// 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>> {
|
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;
|
let mut best: Option<Vec<u8>> = None;
|
||||||
for q in JPEG_QUALITIES {
|
for q in ladder {
|
||||||
let mut out = vec![];
|
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() {
|
if img.write_with_encoder(enc).is_err() {
|
||||||
continue;
|
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
|
/// After a step (`after`), it also says when nothing changed, so the bot doesn't click the same
|
||||||
/// empty place again and again.
|
/// empty place again and again.
|
||||||
fn look(after: Option<String>, pointer: bool) -> Result<(String, Option<String>), String> {
|
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 two slow reads of every look (C5): the picture and the control tree. They ask two
|
||||||
// The app in front: on Linux the window system's own answer when it gives one (agent-desktop
|
// different doors of the system and take about the same time, so they are asked at once —
|
||||||
// alone goes by AT-SPI's "active" window, which some desktops never mark: no numbers, 「」).
|
// on a Mac that is a third of a second back on every step.
|
||||||
// agent-desktop knows an app by its program's name (a Snap Firefox's window says
|
let front_now = {
|
||||||
// "firefox_firefox", its program is "firefox").
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let front = mac::front_app().map(|(n, pid)| crate::desk::program_of(pid).unwrap_or(n)).unwrap_or_default();
|
{
|
||||||
|
mac::front_app().map(|(n, pid)| crate::desk::program_of(pid).unwrap_or(n)).unwrap_or_default()
|
||||||
|
}
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
let front = String::new();
|
{
|
||||||
let (found, why) = if controls_allowed() {
|
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("") });
|
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
|
// 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.
|
// after 「乘」): the list came back with only the menu bar. One more look shortly after.
|
||||||
let first = read();
|
if controls_allowed() {
|
||||||
let again = first.is_err() || first.as_ref().is_ok_and(|f| f.0.is_empty());
|
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||||
match if again { std::thread::sleep(std::time::Duration::from_millis(300)); read().or(first) } else { first } {
|
match read() {
|
||||||
Ok(f) => (Some(f), String::new()),
|
Ok(f) => (Some(f), String::new()),
|
||||||
Err(e) => (None, e),
|
Err(e) => (None, e),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(None, String::new())
|
(None, String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if picture.is_none() && found.is_none() {
|
if picture.is_none() && found.is_none() {
|
||||||
ask_permission("Privacy_ScreenCapture");
|
ask_permission("Privacy_ScreenCapture");
|
||||||
|
|
@ -1918,6 +1949,16 @@ mod mac {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[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 {
|
if !s.hands_for.is_empty() && from != s.hands_for {
|
||||||
return Resp::error(403, &format!("這台電腦只讓大腦「{}」使用", s.hands_for_name));
|
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 tool = v["tool"].as_str().unwrap_or("");
|
||||||
let args = v["args"].as_str().unwrap_or("{}");
|
let args = v["args"].as_str().unwrap_or("{}");
|
||||||
let who = v["who"].as_str().unwrap_or("遠端 AnyBot");
|
let who = v["who"].as_str().unwrap_or("遠端 AnyBot");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue