iris-android-app: the bench's fixture half comes from transcript-fixture
The fixture bytes, the backlog/tail split and the fold into a screen were `bench_client.rs`'s alone; they are `transcript-fixture`'s now, so the Android bench, the headless harness and the phone-shaped desktop window open one screen from one copy (AGENTS.md: nothing UI-shaped in a platform crate). What stays here is the JNI half -- clipboard, battery, IME, the report and the four phases. Built with `cargo ndk -t arm64-v8a -P 29 build --features "transcript-screen bench"`; the two warnings it prints (bench_jni's unused overlay methods, the unused `tabs-ui` dependency under this feature set) predate this change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
333220196e
commit
6840edf61e
5 files changed
+70
-66
No files matched your search
Generated
+12
@@ -1774,6 +1774,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tabs-ui",
|
"tabs-ui",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"transcript-fixture",
|
||||||
"transcript-ui",
|
"transcript-ui",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3864,6 +3865,17 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "transcript-fixture"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"client-core",
|
||||||
|
"event-model",
|
||||||
|
"iris",
|
||||||
|
"serde_json",
|
||||||
|
"transcript-ui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "transcript-ui"
|
name = "transcript-ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ log = "0.4.28"
|
|||||||
# which Cargo's `unused_dependencies` lint (on by default) correctly flags.
|
# which Cargo's `unused_dependencies` lint (on by default) correctly flags.
|
||||||
tabs-ui = { path = "../tabs-ui", optional = true }
|
tabs-ui = { path = "../tabs-ui", optional = true }
|
||||||
transcript-ui = { path = "../transcript-ui", optional = true }
|
transcript-ui = { path = "../transcript-ui", optional = true }
|
||||||
|
# P0's bench build only: the fixture and the folded screen both bench
|
||||||
|
# clients open, shared with the headless harness and the desktop window
|
||||||
|
# (docs/RUST.md's "Three test layers").
|
||||||
|
transcript-fixture = { path = "../transcript-fixture", optional = true }
|
||||||
client-core = { path = "../../client-core", optional = true }
|
client-core = { path = "../../client-core", optional = true }
|
||||||
event-model = { path = "../../event-model", optional = true }
|
event-model = { path = "../../event-model", optional = true }
|
||||||
serde_json = { version = "1", features = ["float_roundtrip"], optional = true }
|
serde_json = { version = "1", features = ["float_roundtrip"], optional = true }
|
||||||
@@ -65,7 +69,7 @@ force-gles = ["iris/force-gles"]
|
|||||||
# `event-model` -- `lib.rs`'s `ActiveClient` selection gives this feature
|
# `event-model` -- `lib.rs`'s `ActiveClient` selection gives this feature
|
||||||
# priority over `transcript-screen`'s own `TranscriptClient` when both are
|
# priority over `transcript-screen`'s own `TranscriptClient` when both are
|
||||||
# listed, which is how this crate's build command names both explicitly.
|
# listed, which is how this crate's build command names both explicitly.
|
||||||
bench = ["transcript-screen", "dep:libc", "dep:tokio"]
|
bench = ["transcript-screen", "dep:transcript-fixture", "dep:libc", "dep:tokio"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|||||||
@@ -6,14 +6,11 @@
|
|||||||
//!
|
//!
|
||||||
//! **Reuses `transcript_client.rs`'s shape** (folded items, the same
|
//! **Reuses `transcript_client.rs`'s shape** (folded items, the same
|
||||||
//! `TranscriptScreen::apply` incremental update on every event) with the
|
//! `TranscriptScreen::apply` incremental update on every event) with the
|
||||||
//! network half replaced by the checked-in fixture, embedded with
|
//! network half replaced by the checked-in fixture. Reading that fixture
|
||||||
//! `include_str!` -- `app/bench-fixture/assets/transcript.jsonl`,
|
//! and folding it into a screen is **`transcript-fixture`'s** job, not
|
||||||
//! 1,915,760 bytes, generated by `app/bench-fixture/generate.py` and never
|
//! this file's -- the same crate the headless harness and the
|
||||||
//! a real transcript (that file's own README). The first 3,200 lines are
|
//! phone-shaped desktop window open, so all three measure one screen
|
||||||
//! the opening backlog, folded once through
|
//! (AGENTS.md's sharing rule; moved out of here 2026-09-07). The tail is
|
||||||
//! `client_core::transcript_fold::fold_page` exactly as a real
|
|
||||||
//! `/transcript` page would be (then a full `transcript_ui::build_tree`,
|
|
||||||
//! same as any first load); the remaining ~400 are the streaming tail,
|
|
||||||
//! replayed one at a time through `fold_event` -- the same fold path a
|
//! replayed one at a time through `fold_event` -- the same fold path a
|
||||||
//! live SSE reply arrives on -- by the "Run benchmark" control below.
|
//! live SSE reply arrives on -- by the "Run benchmark" control below.
|
||||||
//! Streaming through `apply` rather than a full rebuild per event is what
|
//! Streaming through `apply` rather than a full rebuild per event is what
|
||||||
@@ -22,7 +19,7 @@
|
|||||||
|
|
||||||
use crate::bench_jni::PlatformHandle;
|
use crate::bench_jni::PlatformHandle;
|
||||||
use android_view::jni::{JavaVM, objects::GlobalRef};
|
use android_view::jni::{JavaVM, objects::GlobalRef};
|
||||||
use client_core::transcript_fold::{TranscriptItem, fold_event, fold_page, group_tool_runs};
|
use client_core::transcript_fold::{TranscriptItem, fold_event};
|
||||||
use event_model::SeqEvent;
|
use event_model::SeqEvent;
|
||||||
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
@@ -30,13 +27,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
/// bench-fixture/README.md: the first `BACKLOG_COUNT` non-blank lines are
|
|
||||||
/// the opening window; the rest are the streaming tail. Kept in sync with
|
|
||||||
/// `BenchFixture.kt`'s identical constant by hand -- both read the same
|
|
||||||
/// checked-in file, so a mismatch would only mean the two apps' bench
|
|
||||||
/// builds open a different split of it, not a wrong-vs-right answer.
|
|
||||||
const BACKLOG_COUNT: usize = 3200;
|
|
||||||
|
|
||||||
/// RUST.md's "Benchmark v2" spec, written once so both apps' bench clients
|
/// RUST.md's "Benchmark v2" spec, written once so both apps' bench clients
|
||||||
/// implement the identical four phases -- see that box before changing any
|
/// implement the identical four phases -- see that box before changing any
|
||||||
/// constant here, since a mismatch would make the two reports stop
|
/// constant here, since a mismatch would make the two reports stop
|
||||||
@@ -90,8 +80,6 @@ const KEYBOARD_WAIT_MS: u64 = 1_000;
|
|||||||
/// when a later step in the same phase needs to read state back.
|
/// when a later step in the same phase needs to read state back.
|
||||||
const ANIM_STEP_MS: u64 = 16;
|
const ANIM_STEP_MS: u64 = 16;
|
||||||
|
|
||||||
const FIXTURE_JSONL: &str = include_str!("../../../app/bench-fixture/assets/transcript.jsonl");
|
|
||||||
|
|
||||||
/// How much of the screen a *filled* benchmark report may take before it
|
/// How much of the screen a *filled* benchmark report may take before it
|
||||||
/// scrolls instead of growing -- roughly a third of a phone screen, the
|
/// scrolls instead of growing -- roughly a third of a phone screen, the
|
||||||
/// share the pane used to reserve unconditionally. An empty report takes
|
/// share the pane used to reserve unconditionally. An empty report takes
|
||||||
@@ -158,31 +146,6 @@ impl HasAndroidUiState for BenchClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the fixture once: `serde_json::Value`s for the backlog
|
|
||||||
/// (`fold_page` takes a page of raw wire JSON, same as a real
|
|
||||||
/// `/transcript` response) and folded `SeqEvent`s for the tail (`fold_event`
|
|
||||||
/// takes one live wire event at a time, same as a real SSE frame).
|
|
||||||
fn parse_fixture() -> (Vec<serde_json::Value>, Vec<SeqEvent>) {
|
|
||||||
let lines: Vec<&str> = FIXTURE_JSONL
|
|
||||||
.lines()
|
|
||||||
.filter(|line| !line.trim().is_empty())
|
|
||||||
.collect();
|
|
||||||
let mut backlog = Vec::with_capacity(BACKLOG_COUNT.min(lines.len()));
|
|
||||||
let mut stream_tail = Vec::new();
|
|
||||||
for (i, line) in lines.iter().enumerate() {
|
|
||||||
let value: serde_json::Value =
|
|
||||||
serde_json::from_str(line).expect("bench fixture is generated JSON, always valid");
|
|
||||||
if i < BACKLOG_COUNT {
|
|
||||||
backlog.push(value);
|
|
||||||
} else {
|
|
||||||
let event: SeqEvent = serde_json::from_value(value)
|
|
||||||
.expect("bench fixture event matches event-model's SeqEvent");
|
|
||||||
stream_tail.push(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(backlog, stream_tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn placeholder<Rsc: HasEvents>(rsc: &mut Rsc, message: &str) -> StrongWidget {
|
fn placeholder<Rsc: HasEvents>(rsc: &mut Rsc, message: &str) -> StrongWidget {
|
||||||
wtext(message.to_string())
|
wtext(message.to_string())
|
||||||
.color(Color::WHITE)
|
.color(Color::WHITE)
|
||||||
@@ -322,12 +285,12 @@ impl AndroidAppState for BenchClient {
|
|||||||
last_top_pad: 0.0,
|
last_top_pad: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (backlog, stream_tail) = parse_fixture();
|
match transcript_fixture::build_screen(rsc) {
|
||||||
client.stream_tail = stream_tail;
|
Ok((opened, tree)) => {
|
||||||
match fold_page(&backlog) {
|
client.items = opened.items;
|
||||||
Ok(items) => {
|
client.stream_tail = opened.stream_tail;
|
||||||
client.items = items;
|
(client.content)(rsc).set(tree);
|
||||||
client.rebuild_transcript(rsc);
|
client.screen = Some(opened.screen);
|
||||||
}
|
}
|
||||||
Err(message) => {
|
Err(message) => {
|
||||||
client.show_message(rsc, &format!("Couldn't fold the bench fixture: {message}"))
|
client.show_message(rsc, &format!("Couldn't fold the bench fixture: {message}"))
|
||||||
@@ -546,8 +509,7 @@ impl BenchClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild_transcript(&mut self, rsc: &mut Rsc) {
|
fn rebuild_transcript(&mut self, rsc: &mut Rsc) {
|
||||||
let rows = group_tool_runs(&self.items);
|
let (screen, tree) = transcript_ui::build_tree(rsc, transcript_fixture::rows(&self.items));
|
||||||
let (screen, tree) = transcript_ui::build_tree(rsc, rows);
|
|
||||||
(self.content)(rsc).set(tree);
|
(self.content)(rsc).set(tree);
|
||||||
self.screen = Some(screen);
|
self.screen = Some(screen);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,22 +100,49 @@ pub fn rows(items: &[TranscriptItem]) -> Vec<TranscriptRow> {
|
|||||||
group_tool_runs(items)
|
group_tool_runs(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the transcript screen over the fixture's opening page and make
|
/// Everything a caller needs to run the fixture as an app screen would:
|
||||||
/// it the root -- what every layer of the rig opens. Returns the screen
|
/// the screen, the folded items behind it, and the events not yet
|
||||||
/// and the items behind it, so a caller can go on streaming the tail
|
/// streamed. The tree itself comes back separately from
|
||||||
/// through `fold_event`/`TranscriptScreen::apply` as the Android bench
|
/// [`build_screen`], since whoever takes it owns it.
|
||||||
/// does.
|
pub struct Opened {
|
||||||
pub fn open<Rsc: HasEvents>(
|
pub screen: transcript_ui::TranscriptScreen,
|
||||||
rsc: &mut Rsc,
|
pub items: Vec<TranscriptItem>,
|
||||||
ui_state: &mut impl HasRoot,
|
/// The tail, for a caller that goes on replaying it one event at a
|
||||||
) -> Result<(transcript_ui::TranscriptScreen, Vec<TranscriptItem>), String>
|
/// time through `fold_event`/`TranscriptScreen::apply` -- the
|
||||||
|
/// streaming phase of either app's benchmark.
|
||||||
|
pub stream_tail: Vec<SeqEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the transcript screen over the fixture's opening page, without
|
||||||
|
/// claiming the window's root -- `transcript_ui::build_tree`'s own split,
|
||||||
|
/// for a caller (the Android bench) that puts the screen inside a shell
|
||||||
|
/// of its own.
|
||||||
|
pub fn build_screen<Rsc: HasEvents>(rsc: &mut Rsc) -> Result<(Opened, StrongWidget), String>
|
||||||
where
|
where
|
||||||
Rsc::State: FocusHost + OpenUrl,
|
Rsc::State: FocusHost + OpenUrl,
|
||||||
{
|
{
|
||||||
let fixture = Fixture::parse();
|
let fixture = Fixture::parse();
|
||||||
let items = fixture.backlog_items()?;
|
let items = fixture.backlog_items()?;
|
||||||
let screen = transcript_ui::build(rsc, ui_state, rows(&items));
|
let (screen, tree) = transcript_ui::build_tree(rsc, rows(&items));
|
||||||
Ok((screen, items))
|
Ok((
|
||||||
|
Opened {
|
||||||
|
screen,
|
||||||
|
items,
|
||||||
|
stream_tail: fixture.stream_tail,
|
||||||
|
},
|
||||||
|
tree,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`build_screen`] with the screen as the window's root -- what the
|
||||||
|
/// headless harness and the desktop window open.
|
||||||
|
pub fn open<Rsc: HasEvents>(rsc: &mut Rsc, ui_state: &mut impl HasRoot) -> Result<Opened, String>
|
||||||
|
where
|
||||||
|
Rsc::State: FocusHost + OpenUrl,
|
||||||
|
{
|
||||||
|
let (opened, tree) = build_screen(rsc)?;
|
||||||
|
ui_state.set_root(tree);
|
||||||
|
Ok(opened)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
|||||||
/// anchor, which is what every assertion about scroll position reads.
|
/// anchor, which is what every assertion about scroll position reads.
|
||||||
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||||
let (screen, _items) =
|
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||||
transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
|
||||||
h.frame(0);
|
h.frame(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
h.frame(PHONE_FRAME_MS);
|
||||||
(h, screen)
|
(h, opened.screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn script(name: &str, text: &str) -> TouchScript {
|
fn script(name: &str, text: &str) -> TouchScript {
|
||||||
|
|||||||
Reference in new issue
Block a user