docs: P0's iris half done -- bench feature, emulator smoke run, APK
RUST.md's P0 box gets the iris-half account: the fixture, the scroll/stream mechanism, the report fields, build commands (all clean), packaging (no cargo xtask apk yet, so a new Gradle release build type on top of cargo ndk), and the emulator smoke run's report next to Compose's own. Used a second, differently-named AVD rather than contend with the session already on this checkout's own emulator. DECISIONS.md's P0 entry gets a matching summary bullet. IRIS.md records AndroidAppState::platform_ready. IRIS_TODO.md notes the one gap found: no read-only selectable text primitive, so the bench report's TextEdit picks up a keyboard on tap it has nothing to type into. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
683db4908a
commit
00767eed4d
4 files changed
+228
No files matched your search
@@ -18,6 +18,25 @@ marked **DEFERRED** are ones the agent chose not to decide alone.
|
||||
flagged here because it is the first half of something Iris explicitly
|
||||
asked to see before P1.
|
||||
|
||||
- **P0's iris half is also built and smoke-tested on the emulator,
|
||||
2026-09-05.** A new `bench` Cargo feature on `iris-android-app`, on top
|
||||
of `transcript-screen`: the same checked-in fixture (`include_str!`, no
|
||||
asset pipeline needed), the same 24-swipe scroll loop animated through
|
||||
`List::scroll` and the same 400-event/20s streaming phase through
|
||||
`fold_event`, "Run benchmark"/"Copy report" as named accessible
|
||||
controls, and the same three added report fields (process CPU time,
|
||||
peak RSS, battery current) via direct JNI calls
|
||||
(`bench_jni.rs::PlatformHandle`) since `android_view` has no
|
||||
`BatteryManager`/`ClipboardManager` wrapper of its own. One small public
|
||||
API addition to get there: `AndroidAppState::platform_ready` (`IRIS.md`),
|
||||
a default-no-op lifecycle hook handing an implementor a `JavaVM` +
|
||||
`GlobalRef` it can call Java through from any thread. Packaged with a
|
||||
new `release` build type on `iris-android-app`'s own Gradle project
|
||||
(there was previously only `debug`), signed with the same key
|
||||
`app/build-apk.sh` generates. Smoke run and the full report are in
|
||||
RUST.md's P0 box; not attempted this pass: the real on-phone runs and
|
||||
Iris's pass/fail call, which is the actual gate.
|
||||
|
||||
- **The intermittent touch-scroll dropout is root-caused and fixed: a
|
||||
missed `ACTION_DOWN` hit-test, not the previously-suspected coalesced
|
||||
first `ACTION_MOVE`.** Diagnosed by temporary logcat tracing of every
|
||||
|
||||
@@ -8,6 +8,29 @@ capability that moved. Small and trivial changes do not go here.
|
||||
An entry gives the date, what changed, why, and a short before/after where
|
||||
it helps judge the change without the session that made it. Newest first.
|
||||
|
||||
## 2026-09-05: `AndroidAppState::platform_ready` (RUST.md's P0 box, iris half)
|
||||
|
||||
Added a second, optional lifecycle method to `iris::android::AndroidAppState`
|
||||
(`iris/src/android/view.rs`), called once from `new_peer` right after `new`:
|
||||
|
||||
```rust
|
||||
fn platform_ready(&mut self, rsc: &mut AndroidRsc<Self>, vm: JavaVM, view: GlobalRef) {}
|
||||
```
|
||||
|
||||
Default does nothing, so every existing implementor (`Client`,
|
||||
`TranscriptClient`) is unaffected. It exists for a caller that needs to call
|
||||
into Java itself beyond what a `RequestRedraw` handle already covers --
|
||||
P0's bench build (`iris-android-app`'s new `bench` feature,
|
||||
`bench_client.rs`/`bench_jni.rs`) uses it to hold a `JavaVM` + `GlobalRef`
|
||||
to the view so its "Copy report" control and once-a-second battery sampler
|
||||
can call `BatteryManager`/`ClipboardManager` through the view's own
|
||||
`Context`, from a background tokio task as well as the UI thread. `new`
|
||||
itself was not extended with these two parameters: most implementors need
|
||||
nothing here, and `new`'s job is building the widget tree, not holding a
|
||||
platform handle. `vm`/`view` are independent handles from the ones
|
||||
`new_peer` keeps for its own `RequestRedraw` (a fresh `get_java_vm`/
|
||||
`new_global_ref` each), so storing them has no effect on that mechanism.
|
||||
|
||||
## 2026-09-05 (later the same day): `iris_core::FrameReport` (RUST.md's I5 box)
|
||||
|
||||
New public type, `iris_core::FrameReport` (re-exported from `iris_core`'s
|
||||
|
||||
@@ -82,6 +82,18 @@ order and what "done" looks like. Tick and date them in place.
|
||||
were exactly the same root cause measured two different ways. Frame 2
|
||||
now reports 0 (see the numbers above); not a separate fix.
|
||||
|
||||
- [ ] **A read-only text display has no widget of its own — P0's bench
|
||||
report area is a `TextEdit` standing in for one (2026-09-05).** The only
|
||||
way to get selectable text on screen today is `.editable(...)` plus
|
||||
`.attr::<Selectable>(())` (`Selectable` is only implemented for
|
||||
`TextEdit`, `iris/src/attr.rs`), which also makes the field focusable —
|
||||
tapping the bench report opens the soft keyboard over text nothing lets
|
||||
you type into. Harmless for a bench-only debug screen (not fixed this
|
||||
pass), but a real "selectable, not editable" text primitive would
|
||||
remove the keyboard side effect and is worth having before another
|
||||
screen wants the same thing (P1's own transcript rows already read
|
||||
their content from a `TextEdit` for the same reason).
|
||||
|
||||
## Build
|
||||
|
||||
- [x] **Benchmarks**, not unit tests, run on demand (2026-09-05; a
|
||||
|
||||
+174
@@ -3487,6 +3487,180 @@ device.
|
||||
this session was told not to touch `iris/`), and anything past the
|
||||
emulator — the actual on-phone runs and Iris's pass/fail call.
|
||||
|
||||
**iris half: done, 2026-09-05.** A `bench` Cargo feature on
|
||||
`iris-android-app`, built on top of `transcript-screen`
|
||||
(`bench = ["transcript-screen", "dep:libc", "dep:tokio"]`,
|
||||
`iris/android-app/Cargo.toml`), gives `lib.rs`'s `ActiveClient`
|
||||
priority a third `AndroidAppState` (`bench_client::BenchClient`)
|
||||
over `TranscriptClient` when both features are listed together --
|
||||
matching the exact build command below, which lists both.
|
||||
|
||||
**Fixture.** `include_str!("../../../app/bench-fixture/assets/
|
||||
transcript.jsonl")` (1,915,760 bytes) at compile time -- no asset
|
||||
pipeline needed the way the Compose half's Gradle source set does.
|
||||
`bench_client::parse_fixture` splits the same way `BenchFixture.kt`
|
||||
does: the first 3,200 non-blank lines parsed as `serde_json::Value`s
|
||||
and folded once through `client_core::transcript_fold::fold_page`
|
||||
(the real fold a `/transcript` page goes through), the rest parsed
|
||||
as `event_model::SeqEvent`s and held back as the streaming tail.
|
||||
`build.rs` (transcript-screen's own) now exits early under `bench`
|
||||
before requiring a live server's host/port/token/CA -- `BenchClient`
|
||||
never calls `build_transport()`, so that requirement made no sense
|
||||
for a build that talks to nothing.
|
||||
|
||||
**"Run benchmark" (`.label("Run benchmark")`) and "Copy report"
|
||||
(`.label("Copy report")`)** sit in a fixed bar above the transcript;
|
||||
a selectable `TextEdit` (`.attr::<Selectable>(())`, the same
|
||||
attribute the composer field uses) below it shows the report text.
|
||||
Pressing "Run benchmark" resets `FrameReport`, then drives
|
||||
`List::scroll` in ~60Hz steps (`ANIM_STEP_MS = 16`) to animate each
|
||||
900px/200ms swipe rather than jumping it -- iris's `List` has no
|
||||
built-in tween the way `animateScrollBy(tween(...))` gives Compose,
|
||||
so this is the one place the two backends' bench code has to differ
|
||||
in shape rather than only in numbers -- through the same
|
||||
`rsc.tasks.redraw_handle()` + manual `request_redraw()` per step
|
||||
`transcript_client.rs` already established (a `Tasks::spawn`d
|
||||
future's *automatic* redraw fires once, after the whole future
|
||||
completes, which would show nothing moving until the run ends).
|
||||
After the scroll loop, `List::jump_to_end()` pins to the newest
|
||||
content (matching `stream-bench.sh`'s "Jump to latest" tap), then
|
||||
400 fixture events replay at 20/s through `fold_event` -- the same
|
||||
fold path a live SSE frame takes in `transcript_client.rs`'s own
|
||||
`apply_event` -- each one triggering `rebuild_transcript`'s full
|
||||
`transcript_ui::build_tree` rebuild, same tradeoff as
|
||||
`TranscriptClient`/`desktop-app`. A battery sampler runs
|
||||
concurrently on its own `tokio::spawn`d task (not through
|
||||
`ctx.update`, since a JNI battery read needs no widget-tree access),
|
||||
attaching whichever thread it runs on via a stored `JavaVM` --
|
||||
`AndroidAppState::platform_ready` (new, `IRIS.md`) is what hands
|
||||
`bench_client.rs` that `JavaVM` + a `GlobalRef` to the view, since
|
||||
neither was reachable from `AndroidAppState::new` before this box.
|
||||
|
||||
**Report fields.** `FrameStats`'s existing `Display` (frames, janky
|
||||
%, p50/p90/p99, worst, and I5's own `cpu_p50`/`gpu_wait_p50` CPU/GPU
|
||||
split) plus a `bench:`-shaped tail this box added: process CPU time
|
||||
via `libc::getrusage(RUSAGE_SELF)` (user+system time; chosen over
|
||||
parsing `/proc/self/stat` by hand to avoid assuming `USER_HZ`), peak
|
||||
RSS from `/proc/self/status`'s `VmHWM` (same source `BenchRun.kt`
|
||||
reads), and battery current sampled once a second via
|
||||
`BatteryManager.getIntProperty(BATTERY_PROPERTY_CURRENT_NOW)`
|
||||
through direct JNI calls (`bench_jni.rs`'s `PlatformHandle` --
|
||||
`android_view::context`'s own `Context`/`Resources` wrappers have no
|
||||
`getSystemService`, so this calls it directly rather than growing
|
||||
that crate's wrapper for two one-off calls). `0`/`Integer.MIN_VALUE`
|
||||
read as "unavailable" rather than folded into the average, matching
|
||||
`BatterySampler`'s own rule and UI_RULES.md's "never present an
|
||||
inferred value as a measured one." The report is logged under the
|
||||
existing `iris-android-app` logcat tag on a line starting `iris
|
||||
bench report:` (grep-able the same way `transcript_client.rs`'s
|
||||
"Frame report" control already is), shown in the on-screen
|
||||
`TextEdit`, and copied to the system clipboard by "Copy report"
|
||||
through `ClipboardManager.setPrimaryClip` (`bench_jni.rs`, same
|
||||
`PlatformHandle`).
|
||||
|
||||
**Build commands, all clean this pass:**
|
||||
- `cargo fmt --all -- --check` (iris workspace) and
|
||||
`cd iris/android-app && cargo fmt --all -- --check`: clean.
|
||||
- `cargo clippy --workspace --all-targets` (iris workspace): clean
|
||||
(only the pre-existing `wgpu`/`winit`/`naga` future-incompat
|
||||
notice).
|
||||
- `cargo test --workspace` (iris workspace): 39 + 8 + 10 = the same
|
||||
pre-existing counts, all passing, unaffected by this box (it
|
||||
touched no logic under test there beyond `AndroidAppState`'s new
|
||||
default no-op method).
|
||||
- `cargo ndk -t x86_64 -P 26 clippy --features "transcript-screen
|
||||
force-gles bench" --lib -- -D warnings` (`iris/android-app`):
|
||||
clean.
|
||||
- `cargo ndk -t arm64-v8a -P 26 -o app/src/main/jniLibs/ build
|
||||
--release --features "transcript-screen force-gles bench"`:
|
||||
clean, `arm64-v8a/libmain.so` produced. The pre-existing "unused
|
||||
dependency `tabs-ui`" Cargo advisory also appears on a plain
|
||||
`--features transcript-screen` build with no `bench` (confirmed
|
||||
by building that combination alone with fake env vars) -- not
|
||||
something this box introduced, and not a clippy/rustc warning
|
||||
(AGENTS.md's "keep the build clean" gate is `cargo clippy`, which
|
||||
stays silent on it).
|
||||
|
||||
**Packaging.** No `cargo xtask apk` exists for `iris/android-app`
|
||||
yet (I2's own Gradle project is the only pipeline), so this reused
|
||||
that split rather than inventing one: `cargo ndk --release` above
|
||||
builds the cdylib straight into `app/src/main/jniLibs/`, then a new
|
||||
`release` build type in `app/build.gradle` (there was previously
|
||||
only `debug`) packages and signs it --
|
||||
`AI_APP_KEYSTORE=~/.config/ai-app/release.jks` +
|
||||
`AI_APP_KEYSTORE_PASSWORD` (the same key `app/build-apk.sh`
|
||||
generates for the Compose app) via `gradle :app:assembleRelease`,
|
||||
with `applicationIdSuffix ".bench"` so it installs beside the plain
|
||||
tabs demo rather than replacing it. `aapt2 dump badging` on the
|
||||
result: `package: name='dev.iris.android.demo.bench'`, one native
|
||||
library, `lib/arm64-v8a/libmain.so`. `apksigner verify
|
||||
--print-certs` shows the same `CN=ai-app` certificate
|
||||
`compose-bench-arm64.apk` is signed with.
|
||||
|
||||
**Emulator smoke run, 2026-09-05.** This checkout's own AVD
|
||||
(`ai-app-2`) was in use by the session recording I5's clean-scroll
|
||||
comparison in this same file (its Compose app was in the
|
||||
foreground, confirmed via `dumpsys window`/`dumpsys activity
|
||||
processes` before touching anything) -- rather than contend for it
|
||||
(AGENTS.md's "coordinate with peer agents"), a second,
|
||||
differently-named AVD was created (`AVD_NAME=ai-app-2-bench emu
|
||||
up`, `pixel_10`/`android-36`/`google_apis`/`x86_64`, cold boot, host
|
||||
GPU, no `EMU_GPU=software`), with 12GB of the VM's memory still
|
||||
available after both were up (this-machine-android's "two are
|
||||
comfortable" guidance). Installed via `adb -s emulator-5556 install
|
||||
-r`, launched, driven by `ui-trace record -s emulator-5556 --do
|
||||
"tap 'Run benchmark'"` (the control resolved by its accessibility
|
||||
label, per AGENTS.md's "no coordinate" rule), then read back over
|
||||
`adb logcat`:
|
||||
|
||||
iris bench report
|
||||
frames=372 janky%=56.99 p50=19.5ms p90=219.5ms p99=284.5ms worst=369.3ms (measures redraw-start to after present() is called, not GPU/compositor completion) cpu_p50=0.4ms gpu_wait_p50=13.9ms (redraw-start-to-submit vs. submit-to-after-present)
|
||||
scroll: 6 cycles (24 swipes), streamed 400/400 fixture events
|
||||
process CPU time over this run: 24665ms
|
||||
peak RSS: 224600kB
|
||||
battery current: mean 900000µA over 21 samples (min 900000, max 900000)
|
||||
|
||||
"Copy report" was pressed immediately after and logged `iris bench
|
||||
report: copied to clipboard` (`ClipboardManager.setPrimaryClip`
|
||||
succeeded). No crash (`adb logcat`'s `FATAL`/`AndroidRuntime` lines
|
||||
checked -- only `ui-trace`'s own runtime, unrelated), process alive
|
||||
throughout (`dumpsys activity processes`), 400/400 stream events
|
||||
confirmed sent.
|
||||
|
||||
Read this the same way the Compose half's own box already asks to
|
||||
read its number: this is software-rasterised (well, GLES-over-virgl
|
||||
under `force-gles`, per I5's "Where iris's frame time goes")
|
||||
emulator output, "the harness runs end to end and produces every
|
||||
field P0 asked for," not a phone number -- and the battery current
|
||||
is again the emulator's fixed 900000µA mocked charger reporting a
|
||||
constant, exactly what the Compose box's own run found, not a real
|
||||
battery answering. `cpu_p50=0.4ms` (iris's own per-frame CPU work)
|
||||
against a much larger `gpu_wait_p50`/`p50` again matches I5's "Where
|
||||
iris's frame time goes" finding under real GPU rendering (`-gpu
|
||||
host`, `force-gles`) -- the frame-time budget here is dominated by
|
||||
the driver/compositor wait, not by iris's layout or primitive
|
||||
building, though this run's `janky%`/`p90`/`p99` are considerably
|
||||
worse than that earlier isolated pass, most likely the cost of this
|
||||
AVD's very first cold boot plus running two emulators on this VM at
|
||||
once (a fair comparison against Compose would need both apps run
|
||||
back-to-back on the same freshly-booted device, not attempted this
|
||||
pass since the second AVD was torn down immediately after per
|
||||
AGENTS.md's "stop yours when you are done with it").
|
||||
|
||||
Copied to `~/host/bench/iris-bench-arm64.apk` (15,445,468 bytes) and
|
||||
`~/host/bench/README.md`'s "iris" section filled in (install, open,
|
||||
tap "Run benchmark", read the report from the on-screen text or
|
||||
logcat, tap "Copy report", paste back).
|
||||
|
||||
**Not done this pass**: the actual on-phone runs and Iris's
|
||||
pass/fail call between the two reports (P0's own pass condition) --
|
||||
that needs Iris's phone, which this session has no access to.
|
||||
`iris/src/android/view.rs` was touched (`AndroidAppState::
|
||||
platform_ready`, `new_peer`'s wiring) -- confirmed to not be one of
|
||||
the three files the concurrent `device_limits()` work on this
|
||||
branch was using (`iris/core/src/render/mod.rs`,
|
||||
`iris/src/android/render.rs`, `iris/src/default/render.rs`).
|
||||
|
||||
- [ ] **P1 — session screen parity.** History paging backward (with the
|
||||
page-boundary healing `client-core` does not have yet, below),
|
||||
`TranscriptSource`-backed cache/server stitching, jump-to-latest,
|
||||
|
||||
Reference in new issue
Block a user