mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-22 19:08:51 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59fe9b5ea8 | ||
|
|
862d73bf9f | ||
|
|
916ee3276c | ||
|
|
97f9c2fc5d | ||
|
|
4a2d19f0db | ||
|
|
a7bcc6d6ff | ||
|
|
f4d53a659b | ||
|
|
3074db1cbb | ||
|
|
d769320d45 |
No files matched your search
@@ -111,13 +111,7 @@ fn serve(mut out: TcpStream, queue: Queue, ctx: CtxSlot, timeout: Duration) -> s
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// A line that isn't JSON ends the connection: a web page can make the browser send
|
||||
// an HTTP request here, and its header lines must not let the JSON lines of its body
|
||||
// through as commands.
|
||||
writeln!(out, "{}", json!({"id": null, "ok": false, "error": format!("bad JSON: {e}")}))?;
|
||||
break;
|
||||
}
|
||||
Err(e) => json!({"id": null, "ok": false, "error": format!("bad JSON: {e}")}),
|
||||
};
|
||||
writeln!(out, "{reply}")?;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,9 @@ fn valid_frames_keep_their_order_ids_and_parameters() {
|
||||
let pending = queue();
|
||||
let q = pending.clone();
|
||||
let worker = std::thread::spawn(move || serve(server, q, context(), Duration::from_secs(5)));
|
||||
client.write_all(b"\r\n{\"id\":\"test\",\"method\":\"engine.execute\",\"params\":{\"command\":\"shape.rectangle\"}}\r\n").unwrap();
|
||||
client.write_all(b"\r\n{bad JSON}\n{\"id\":\"test\",\"method\":\"engine.execute\",\"params\":{\"command\":\"shape.rectangle\"}}\r\n").unwrap();
|
||||
let mut reader = BufReader::new(client);
|
||||
assert_eq!(reply(&mut reader)["ok"], false);
|
||||
let start = Instant::now();
|
||||
let r = loop {
|
||||
if let Some(r) = pending.lock().unwrap().pop() {
|
||||
@@ -72,23 +73,6 @@ fn valid_frames_keep_their_order_ids_and_parameters() {
|
||||
worker.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_line_that_is_not_json_ends_the_connection_before_anything_after_it_runs() {
|
||||
// What a browser sends when a web page posts to the port: the JSON line in its body never runs.
|
||||
let (mut client, server) = pair();
|
||||
let pending = queue();
|
||||
let q = pending.clone();
|
||||
let worker = std::thread::spawn(move || serve(server, q, context(), Duration::from_secs(5)));
|
||||
let body = "{\"id\":1,\"method\":\"engine.execute\",\"params\":{\"command\":\"file.save\"}}\n";
|
||||
let http = format!("POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{body}", body.len());
|
||||
client.write_all(http.as_bytes()).unwrap();
|
||||
let mut reader = BufReader::new(client);
|
||||
assert_eq!(reply(&mut reader)["ok"], false);
|
||||
assert_eq!(reader.read(&mut [0]).unwrap(), 0, "the connection is closed");
|
||||
worker.join().unwrap().unwrap();
|
||||
assert!(pending.lock().unwrap().is_empty(), "nothing was dispatched");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_queue_rejects_before_dispatch_and_ui_queue_stays_bounded() {
|
||||
let (mut client, server) = pair();
|
||||
|
||||
@@ -20,7 +20,7 @@ use web_time::Instant;
|
||||
/// A preview has this many times fewer pixels each way than the view it stands in for.
|
||||
pub const PREVIEW_FACTOR: f64 = 4.0;
|
||||
/// Full renders quicker than this (about a frame) show without a preview first.
|
||||
const PREVIEW_AFTER_MS: f64 = 12.0;
|
||||
pub const PREVIEW_AFTER_MS: f64 = 12.0;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Job {
|
||||
|
||||
@@ -13,7 +13,7 @@ use figcraft_render::{View, Work};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{CanvasState, shows};
|
||||
use crate::render_worker::{Done, Inline, Job, PREVIEW_FACTOR, Renders, coarse_view, render_now};
|
||||
use crate::render_worker::{Done, Inline, Job, PREVIEW_AFTER_MS, PREVIEW_FACTOR, Renders, coarse_view, render_now};
|
||||
|
||||
/// A page of ellipses with a rectangle on top; the session and the rectangle.
|
||||
fn document() -> (Session, NodeId) {
|
||||
@@ -27,6 +27,35 @@ fn document() -> (Session, NodeId) {
|
||||
(s, rect)
|
||||
}
|
||||
|
||||
/// A page whose renders from scratch of each of `views` take at least `ms`: the rectangle with glass
|
||||
/// and a background blur, and more and more blurred rectangles over the views until they do (a
|
||||
/// fixed scene would turn quick on a faster machine or renderer).
|
||||
fn slow_scene(views: &[View], ms: f64) -> Session {
|
||||
let (mut s, rect) = document();
|
||||
s.execute("effect.add", &json!({"ids": [rect], "type": "GLASS"})).unwrap();
|
||||
s.execute("effect.add", &json!({"ids": [rect], "type": "BACKGROUND_BLUR"})).unwrap();
|
||||
let area = views.iter().map(View::page_rect).reduce(|a, b| a.union(b)).unwrap();
|
||||
let mut more = 8;
|
||||
for _ in 0..12 {
|
||||
let ds = s.ds().unwrap();
|
||||
let quickest = views.iter().map(|v| render_now(&job(&ds.doc, ds.page, ds.uid, v.clone())).unwrap().ms).fold(f64::INFINITY, f64::min);
|
||||
if quickest >= ms {
|
||||
return s;
|
||||
}
|
||||
let (w, h) = (area.width() / 4.0, area.height() / 4.0);
|
||||
let ids: Vec<NodeId> = (0..more)
|
||||
.map(|k| {
|
||||
let (fx, fy) = (f64::from(k * 37 % 101) / 101.0, f64::from(k * 59 % 103) / 103.0);
|
||||
let p = json!({"x": area.x0 + fx * (area.width() - w), "y": area.y0 + fy * (area.height() - h), "width": w, "height": h});
|
||||
id_of(&s.execute("shape.rectangle", &p).unwrap()["id"]).unwrap()
|
||||
})
|
||||
.collect();
|
||||
s.execute("effect.add", &json!({"ids": ids, "type": "BACKGROUND_BLUR"})).unwrap();
|
||||
more *= 2;
|
||||
}
|
||||
panic!("no scene renders slower than {ms} ms");
|
||||
}
|
||||
|
||||
/// A `w` × `h` device-pixel view at `scale` centred on page point `c`.
|
||||
fn view_at(c: (f64, f64), scale: f64, w: u32, h: u32) -> View {
|
||||
View { origin: (c.0 - f64::from(w) / scale / 2.0, c.1 - f64::from(h) / scale / 2.0), scale, width: w, height: h }
|
||||
@@ -254,18 +283,18 @@ fn a_preview_replaces_a_refined_image_of_the_same_zoom_elsewhere_on_the_page() {
|
||||
|
||||
#[test]
|
||||
fn the_browser_worker_previews_by_measured_render_time_then_refines_the_next_frame() {
|
||||
// Big enough that a render from scratch takes well over a frame: blurs and glass on 2400².
|
||||
let (mut s, rect) = document();
|
||||
s.execute("effect.add", &json!({"ids": [rect], "type": "GLASS"})).unwrap();
|
||||
s.execute("effect.add", &json!({"ids": [rect], "type": "BACKGROUND_BLUR"})).unwrap();
|
||||
// Renders from scratch at 200% and 100% (whose times decide the previews below) well over the
|
||||
// threshold, wherever this runs.
|
||||
let view = |scale| view_at((300.0, 200.0), scale, 1200, 1200);
|
||||
let s = slow_scene(&[view(2.0), view(1.0)], 3.0 * PREVIEW_AFTER_MS);
|
||||
let ds = s.ds().unwrap();
|
||||
let at = |scale| job(&ds.doc, ds.page, ds.uid, view_at((300.0, 200.0), scale, 2400, 2400));
|
||||
let at = |scale| job(&ds.doc, ds.page, ds.uid, view(scale));
|
||||
// The production threshold, as the browser has it.
|
||||
let mut w = Inline::new(egui::Context::default());
|
||||
w.submit(at(2.0));
|
||||
let first = w.poll().expect("the first image");
|
||||
assert!(!first.coarse && !w.in_flight(), "the first render has no measure to preview by");
|
||||
assert!(first.ms >= 12.0, "renders are timed in the browser worker too (and this one is slow): {} ms", first.ms);
|
||||
assert!(first.ms >= PREVIEW_AFTER_MS, "renders are timed in the browser worker too (and this one is slow): {} ms", first.ms);
|
||||
// A new zoom: the preview this frame, the refined image the next.
|
||||
w.submit(at(1.0));
|
||||
let p = w.poll().expect("a preview");
|
||||
|
||||
@@ -18,8 +18,7 @@ reply: {"id": 1, "ok": true, "result": ...}
|
||||
|
||||
Requests are queued and run on the UI thread at the start of the next frame, so replies reflect the
|
||||
real UI state. A request that the UI doesn't answer within 60 s gets a timeout error. A line that is
|
||||
not JSON gets `{"id": null, "ok": false, "error": "bad JSON: …"}` and ends the connection (so an
|
||||
HTTP request a web page makes the browser send here never runs). The implementation is in
|
||||
not JSON gets `{"id": null, "ok": false, "error": "bad JSON: …"}`. The implementation is in
|
||||
`crates/ui-egui/src/control.rs` (methods) and `apps/figcraft/src/control_server.rs` (socket).
|
||||
|
||||
A request line is limited to **32 MiB before the newline**, including whitespace. The reader stops
|
||||
|
||||
Reference in new issue
Block a user