A third AndroidAppState (BenchClient) on top of transcript-screen: embeds app/bench-fixture/assets/transcript.jsonl with include_str! (no server, no enrollment), folds the first 3,200 lines through client_core's real fold_page as the opening backlog, and holds the rest back as a streaming tail. "Run benchmark" resets FrameReport, animates the same 24-swipe/ 6-cycle scroll BenchRun.kt drives (List::scroll in ~60Hz steps, since iris has no built-in tween), then replays the tail at 20/s through fold_event -- the same fold path a live SSE reply takes -- and shows a report in a selectable TextEdit. "Copy report" puts it on the clipboard. The report adds process CPU time (libc::getrusage), peak RSS (/proc/self/ status's VmHWM) and battery current (BatteryManager.getIntProperty via direct JNI, bench_jni.rs's PlatformHandle) to FrameStats's existing frames/janky%/percentiles/CPU-GPU-split line -- "unavailable" rather than a fabricated number wherever the platform can't answer. build.rs now exits early under the bench feature before requiring a live server's host/port/token/CA: BenchClient never calls build_transport(). app/build.gradle gains a signed `release` build type (previously only debug) so the cdylib cargo ndk builds can be packaged for a phone, the same key app/build-apk.sh generates. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
133 lines
5.6 KiB
Rust
133 lines
5.6 KiB
Rust
//! The android-view demo app: by default, iris's `tabs` widget tree
|
|
//! (`tabs_ui::build`, shared with the winit example) running through
|
|
//! `iris::android`'s `ViewPeer`. This is RUST.md's I2 pass condition made
|
|
//! concrete -- there is no UI here beyond what `tabs-ui` already draws.
|
|
//!
|
|
//! `JNI_OnLoad` and `new_view_peer` mirror android-view's own demo
|
|
//! (`~/src/android-view/demo/src/lib.rs`): the only android-view-specific
|
|
//! plumbing a real app needs is registering its `View` subclass and
|
|
//! wrapping `iris::android::new_peer`'s generic function in a concrete
|
|
//! `extern "system" fn`, since `register_view_class` wants a plain
|
|
//! function pointer.
|
|
//!
|
|
//! **`transcript-screen` feature (RUST.md's I5 Android integration):** with
|
|
//! `--features transcript-screen`, `new_view_peer` instantiates
|
|
//! `transcript_client::TranscriptClient` instead of the tabs `Client`
|
|
//! below, against a real `ai-server` (see that module's doc). Chosen over a
|
|
//! third shell crate: this one already has the Gradle project, the
|
|
//! `IrisView`/`MainActivity` Java, and the JNI registration I2 built and
|
|
//! measured against, and the only thing a transcript screen needs on top
|
|
//! is a different `AndroidAppState` -- the same axis `tabs_ui::build` vs.
|
|
//! `transcript_ui::build` already varies along on the winit side (compare
|
|
//! `iris/examples/tabs.rs` and `iris/transcript-ui/examples/transcript.rs`).
|
|
//! A build picks one screen or the other, never both, so `Client` and
|
|
//! `TranscriptClient` are cfg-gated apart rather than switched at runtime --
|
|
//! there is no in-app navigation to switch *to* on either side yet.
|
|
//!
|
|
//! **`bench` feature (P0's iris half, docs/RUST.md):** a third
|
|
//! `AndroidAppState`, `bench_client::BenchClient`, on the same axis --
|
|
//! `transcript_ui::build_tree` again, this time against the checked-in
|
|
//! fixture (`app/bench-fixture/assets/transcript.jsonl`) instead of a real
|
|
//! server, with a "Run benchmark" control that drives the same scroll loop
|
|
//! and streaming phase the Compose `bench` build type's `BenchRun.kt`
|
|
//! does. `bench` depends on `transcript-screen` (Cargo.toml) for
|
|
//! `transcript-ui`/`client-core`/`event-model`, so both features end up
|
|
//! enabled together -- `ActiveClient` below gives `bench` priority in that
|
|
//! case, the same way `transcript-screen` already takes priority over the
|
|
//! default `tabs-screen`.
|
|
|
|
use android_view::{
|
|
Context, View,
|
|
jni::{
|
|
JNIEnv, JavaVM,
|
|
sys::{JNI_VERSION_1_6, JavaVM as RawJavaVM, jint, jlong},
|
|
},
|
|
register_view_class,
|
|
};
|
|
#[cfg(not(feature = "transcript-screen"))]
|
|
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
|
#[cfg(not(feature = "transcript-screen"))]
|
|
use iris::prelude::*;
|
|
use log::LevelFilter;
|
|
use std::ffi::c_void;
|
|
|
|
#[cfg(feature = "bench")]
|
|
mod bench_client;
|
|
#[cfg(feature = "bench")]
|
|
mod bench_jni;
|
|
#[cfg(all(feature = "transcript-screen", not(feature = "bench")))]
|
|
mod transcript_client;
|
|
|
|
/// The app's `View` subclass, matching the Java side's package --
|
|
/// `app/src/main/java/dev/iris/android/demo/IrisView.java`.
|
|
const VIEW_CLASS: &str = "dev/iris/android/demo/IrisView";
|
|
|
|
#[cfg(not(feature = "transcript-screen"))]
|
|
pub struct Client {
|
|
ui_state: AndroidUiState,
|
|
}
|
|
|
|
#[cfg(not(feature = "transcript-screen"))]
|
|
impl HasAndroidUiState for Client {
|
|
fn android_state(&self) -> &AndroidUiState {
|
|
&self.ui_state
|
|
}
|
|
fn android_state_mut(&mut self) -> &mut AndroidUiState {
|
|
&mut self.ui_state
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "transcript-screen"))]
|
|
impl AndroidAppState for Client {
|
|
fn new(mut ui_state: AndroidUiState, rsc: &mut AndroidRsc<Self>) -> Self {
|
|
// `widgets.info` is the winit example's frame-debug readout, kept
|
|
// current from `DefaultAppState::window_event` -- android-view has
|
|
// no per-frame hook to drive the equivalent from here yet, so it
|
|
// is left at its built "" text rather than wired to nothing.
|
|
let _ = tabs_ui::build(rsc, &mut ui_state);
|
|
Self { ui_state }
|
|
}
|
|
|
|
fn back_pressed(&mut self, _rsc: &mut AndroidRsc<Self>, _render: &mut UiRenderState) -> bool {
|
|
// Nothing in the tabs example has a back stack of its own to pop --
|
|
// declining lets the activity finish, which is the same "no
|
|
// handler" behaviour the default impl gives. Present as an
|
|
// explicit override (rather than relying on the default) so a
|
|
// reader checking "does the back gesture reach this app" finds an
|
|
// answer here rather than nothing.
|
|
false
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "transcript-screen"))]
|
|
type ActiveClient = Client;
|
|
#[cfg(all(feature = "transcript-screen", not(feature = "bench")))]
|
|
type ActiveClient = transcript_client::TranscriptClient;
|
|
#[cfg(feature = "bench")]
|
|
type ActiveClient = bench_client::BenchClient;
|
|
|
|
extern "system" fn new_view_peer<'local>(
|
|
env: JNIEnv<'local>,
|
|
view: View<'local>,
|
|
context: Context<'local>,
|
|
) -> jlong {
|
|
iris::android::new_peer::<ActiveClient>(env, view, context)
|
|
}
|
|
|
|
/// # Safety
|
|
/// Interacting with JNI at load time is always unsafe at some level --
|
|
/// mirrors android-view's own demo, which carries the same comment.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "system" fn JNI_OnLoad(vm: *mut RawJavaVM, _: *mut c_void) -> jint {
|
|
android_logger::init_once(
|
|
android_logger::Config::default()
|
|
.with_max_level(LevelFilter::Debug)
|
|
.with_tag("iris-android-app"),
|
|
);
|
|
let vm = unsafe { JavaVM::from_raw(vm) }.unwrap();
|
|
let mut env = vm.get_env().unwrap();
|
|
register_view_class(&mut env, VIEW_CLASS, new_view_peer);
|
|
iris::android::register_native_methods(&mut env, VIEW_CLASS);
|
|
JNI_VERSION_1_6
|
|
}
|