Simplify Iris app initialization and task updates

This commit is contained in:
iris committed 2026-09-11 12:28:33 -04:00
1 parent 8218e84b62
commit ecf74055c7
32 files changed
+409 -377

No files matched your search

+1 -3
View File
@@ -52,7 +52,7 @@ impl DesktopAppState for Client {
))
}
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>) -> Self {
let screen = match ai_app::ui::fixture::open(rsc, &mut ui_state) {
Ok(opened) => {
if let Some(message) = message_argv() {
@@ -60,7 +60,6 @@ impl DesktopAppState for Client {
}
if let Some(text) = typed_argv() {
let field = opened.screen.composer.field;
let redraw = rsc.tasks.redraw_handle();
rsc.spawn_task(async move |mut ctx| {
for ch in text.chars() {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
@@ -73,7 +72,6 @@ impl DesktopAppState for Client {
}
edit.insert(&ch.to_string());
});
redraw.request_redraw();
}
});
}
+1 -1
View File
@@ -158,7 +158,7 @@ fn fold_event(items: Vec<Item>, seq: u64) -> Vec<Item> {
";
impl DesktopAppState for Client {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>) -> Self {
let mut screen = ai_app::ui::build(rsc, &mut ui_state, synthetic_rows());
screen.push_row(
rsc,
+17 -54
View File
@@ -235,13 +235,11 @@ impl AndroidAppState for BenchClient {
if ime_visible && !self.keyboard_was_visible {
self.keyboard_was_visible = true;
let redraw = rsc.tasks.redraw_handle();
rsc.spawn_task(async move |mut ctx| {
tokio::time::sleep(Duration::from_millis(KEYBOARD_DIAGNOSTICS_DELAY_MS)).await;
ctx.update(|state: &mut BenchClient, rsc| {
state.capture_keyboard_diagnostics(rsc);
});
redraw.request_redraw();
});
} else if !ime_visible {
self.keyboard_was_visible = false;
@@ -485,7 +483,6 @@ impl BenchClient {
self.android_state_mut().frame_report.reset();
self.report_display.edit(rsc).set("Running benchmark...");
let redraw = rsc.tasks.redraw_handle();
let platform = self.platform.clone();
let stream_tail = self.stream_tail.clone();
let ime_state = self.ime_state.clone();
@@ -518,9 +515,9 @@ impl BenchClient {
})
});
let travel = run_fling_phase(&mut ctx, &redraw).await;
let (sent, total) = run_stream_phase(&mut ctx, &redraw, stream_tail).await;
run_type_phase(&mut ctx, &redraw, &platform).await;
let travel = run_fling_phase(&mut ctx).await;
let (sent, total) = run_stream_phase(&mut ctx, stream_tail).await;
run_type_phase(&mut ctx, &platform).await;
let keyboard = run_keyboard_phase(&mut ctx, &platform, &ime_state).await;
sampler_done.store(true, Ordering::Relaxed);
@@ -626,32 +623,18 @@ impl BenchClient {
state.report_display.edit(rsc).set(&report);
state.last_report = Some(report);
});
redraw.request_redraw();
});
}
}
/// Runs `f` against the real `BenchClient`/`Rsc` on the main thread (the
/// same `ctx.update` every other mutation here goes through) and returns
/// its result to the caller's async task -- `ctx.update` alone has no way
/// to hand a value back, since the closure only actually runs once the
/// next frame callback drains `IrisViewPeer`'s task channel
/// (`drain_tasks`). **Must call `redraw.request_redraw()` itself, right
/// after enqueueing** -- `ctx.update` only ever pushes onto a channel;
/// nothing drains it until something schedules the frame callback that
/// calls `drain_tasks`, and a caller relying on some *earlier*,
/// already-in-flight `request_redraw()` to cover a *later* `ctx.update`
/// deadlocks the moment that earlier callback has already fired and
/// drained everything queued before this call existed. Cost a real hang
/// in this file's first version of the fling phase: every loop iteration
/// after the first sat forever with nothing scheduled to drain it.
/// its result to the caller's async task. `ctx.update` wakes the UI thread,
/// whose task callback drains Iris's update queue before checking whether the
/// retained widget tree needs another frame.
/// Polls rather than assuming one `POLL_MS` sleep is enough, since a
/// slow device's frame callback can lag further than that.
async fn read_from_state<T, F>(
ctx: &mut iris::task::TaskCtx<Rsc>,
redraw: &Arc<dyn RequestRedraw>,
f: F,
) -> T
async fn read_from_state<T, F>(ctx: &mut iris::task::TaskCtx<Rsc>, f: F) -> T
where
T: Send + 'static,
F: FnOnce(&mut BenchClient, &mut Rsc) -> T + Send + 'static,
@@ -660,7 +643,6 @@ where
ctx.update(move |state: &mut BenchClient, rsc| {
let _ = tx.send(f(state, rsc));
});
redraw.request_redraw();
loop {
if let Ok(value) = rx.try_recv() {
return value;
@@ -669,10 +651,7 @@ where
}
}
async fn run_fling_phase(
ctx: &mut iris::task::TaskCtx<Rsc>,
redraw: &Arc<dyn RequestRedraw>,
) -> String {
async fn run_fling_phase(ctx: &mut iris::task::TaskCtx<Rsc>) -> String {
ctx.update(|state: &mut BenchClient, _rsc| {
state.android_state_mut().frame_report.mark_phase("fling");
});
@@ -681,11 +660,10 @@ async fn run_fling_phase(
(screen.list)(rsc).jump_to_end();
}
});
redraw.request_redraw();
// Lets the next frame's `repair_anchor` resolve `jump_to_end`'s
// `anchor = None` into a real slot before `start` is read.
tokio::time::sleep(Duration::from_millis(POLL_MS * 2)).await;
let start = read_anchor_position(ctx, redraw).await;
let start = read_anchor_position(ctx).await;
for _ in 0..FLING_COUNT {
ctx.update(|state: &mut BenchClient, rsc| {
@@ -694,11 +672,10 @@ async fn run_fling_phase(
animate_scroll(screen.list, rsc);
}
});
redraw.request_redraw();
wait_for_fling_settle(ctx, redraw).await;
wait_for_fling_settle(ctx).await;
tokio::time::sleep(Duration::from_millis(FLING_PAUSE_MS)).await;
}
let outward = read_anchor_position(ctx, redraw).await;
let outward = read_anchor_position(ctx).await;
for _ in 0..FLING_COUNT {
ctx.update(|state: &mut BenchClient, rsc| {
@@ -707,20 +684,16 @@ async fn run_fling_phase(
animate_scroll(screen.list, rsc);
}
});
redraw.request_redraw();
wait_for_fling_settle(ctx, redraw).await;
wait_for_fling_settle(ctx).await;
tokio::time::sleep(Duration::from_millis(FLING_PAUSE_MS)).await;
}
let end = read_anchor_position(ctx, redraw).await;
let end = read_anchor_position(ctx).await;
format!("start={start} outward={outward} end={end} ticked=frame-loop")
}
async fn read_anchor_position(
ctx: &mut iris::task::TaskCtx<Rsc>,
redraw: &Arc<dyn RequestRedraw>,
) -> String {
read_from_state(ctx, redraw, |state, rsc| match &state.screen {
async fn read_anchor_position(ctx: &mut iris::task::TaskCtx<Rsc>) -> String {
read_from_state(ctx, |state, rsc| match &state.screen {
Some(screen) => (screen.list)(rsc).anchor_position_display(),
None => "idx=none".to_string(),
})
@@ -732,14 +705,11 @@ fn animate_scroll(scroll: iris::prelude::WeakWidget<iris::prelude::LazySpan>, rs
rsc.ui_mut().animate(id);
}
async fn wait_for_fling_settle(
ctx: &mut iris::task::TaskCtx<Rsc>,
redraw: &Arc<dyn RequestRedraw>,
) {
async fn wait_for_fling_settle(ctx: &mut iris::task::TaskCtx<Rsc>) {
let cap = Duration::from_millis(FLING_SETTLE_CAP_MS);
let started = Instant::now();
while started.elapsed() < cap {
let still_scrolling = read_from_state(ctx, redraw, |state, rsc| match &state.screen {
let still_scrolling = read_from_state(ctx, |state, rsc| match &state.screen {
Some(screen) => (screen.list)(rsc).is_scrolling(),
None => false,
})
@@ -753,7 +723,6 @@ async fn wait_for_fling_settle(
async fn run_stream_phase(
ctx: &mut iris::task::TaskCtx<Rsc>,
redraw: &Arc<dyn RequestRedraw>,
stream_tail: Vec<SeqEvent>,
) -> (usize, usize) {
ctx.update(|state: &mut BenchClient, _rsc| {
@@ -764,7 +733,6 @@ async fn run_stream_phase(
(screen.list)(rsc).jump_to_end();
}
});
redraw.request_redraw();
let total = (STREAM_EVENTS_PER_SEC * STREAM_SECONDS) as usize;
let mut sent = 0usize;
@@ -777,7 +745,6 @@ async fn run_stream_phase(
None => state.rebuild_transcript(rsc),
}
});
redraw.request_redraw();
sent += 1;
tokio::time::sleep(Duration::from_millis(1000 / STREAM_EVENTS_PER_SEC)).await;
}
@@ -787,7 +754,6 @@ async fn run_stream_phase(
async fn run_type_phase(
ctx: &mut iris::task::TaskCtx<Rsc>,
redraw: &Arc<dyn RequestRedraw>,
platform: &Option<Arc<PlatformHandle>>,
) {
ctx.update(|state: &mut BenchClient, _rsc| {
@@ -799,7 +765,6 @@ async fn run_type_phase(
state.set_focus(Some(screen.composer.field));
}
});
redraw.request_redraw();
if let Some(p) = platform {
p.show_ime();
}
@@ -814,7 +779,6 @@ async fn run_type_phase(
screen.composer.field.edit(rsc).set(&text);
}
});
redraw.request_redraw();
tokio::time::sleep(Duration::from_millis(TYPE_CHAR_MS)).await;
}
tokio::time::sleep(Duration::from_millis(200)).await;
@@ -826,7 +790,6 @@ async fn run_type_phase(
screen.composer.field.edit(rsc).set(&text);
}
});
redraw.request_redraw();
tokio::time::sleep(Duration::from_millis(TYPE_CHAR_MS)).await;
}
}
-7
View File
@@ -132,7 +132,6 @@ impl TranscriptClient {
}
fn spawn_fetch_sessions(&mut self, rsc: &mut StdRsc<Self>) {
let redraw = rsc.tasks.redraw_handle();
let my_generation = self.generation.load(Ordering::SeqCst);
let generation = self.generation.clone();
rsc.spawn_task(async move |mut ctx| {
@@ -156,7 +155,6 @@ impl TranscriptClient {
}
}
});
redraw.request_redraw();
});
}
@@ -166,7 +164,6 @@ impl TranscriptClient {
self.session_id = Some(session_id.clone());
self.show_message(rsc, "Loading transcript...");
let redraw = rsc.tasks.redraw_handle();
let live_generation = self.generation.clone();
rsc.spawn_task(async move |mut ctx| {
let transports =
@@ -180,7 +177,6 @@ impl TranscriptClient {
state.show_message(rsc, &message);
}
});
redraw.request_redraw();
return;
}
};
@@ -214,8 +210,6 @@ impl TranscriptClient {
}
});
}
redraw.request_redraw();
if live_generation.load(Ordering::SeqCst) != my_generation {
return;
}
@@ -239,7 +233,6 @@ impl TranscriptClient {
}
state.apply_event(rsc, &event);
});
redraw.request_redraw();
true
}
},
+45 -26
View File
@@ -45,7 +45,7 @@ struct Client {
ui_state: DesktopUiState,
api: Arc<ApiClient<UreqTransport>>,
stream_transport: Arc<UreqTransport>,
proxy: Proxy<AppEvent>,
updates: TaskCtx<StdRsc<Self>>,
sessions: Vec<SessionSummary>,
selected: Option<String>,
items: Vec<TranscriptItem>,
@@ -56,9 +56,7 @@ struct Client {
}
impl DesktopAppState for Client {
type Event = AppEvent;
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, proxy: Proxy<AppEvent>) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>) -> Self {
let (server, ca_pem) = super::startup::load_startup_config().unwrap_or_else(|e| {
eprintln!("desktop-app: {e}");
process::exit(2);
@@ -90,7 +88,7 @@ impl DesktopAppState for Client {
ui_state,
api,
stream_transport,
proxy,
updates: rsc.tasks.context(),
sessions: Vec::new(),
selected: None,
items: Vec::new(),
@@ -102,8 +100,10 @@ impl DesktopAppState for Client {
client.spawn_fetch_sessions();
client
}
}
fn event(&mut self, event: AppEvent, rsc: &mut StdRsc<Self>) {
impl Client {
fn apply_event(&mut self, event: AppEvent, rsc: &mut StdRsc<Self>) {
match event {
AppEvent::Sessions(Ok(sessions)) => {
self.sessions = sessions;
@@ -163,11 +163,8 @@ impl DesktopAppState for Client {
eprintln!("desktop-app: couldn't send: {message}");
}
}
self.ui_state.window.request_redraw();
}
}
impl Client {
fn current(&self, session_id: &str, generation: u64) -> bool {
self.selected.as_deref() == Some(session_id)
&& self.generation.load(Ordering::SeqCst) == generation
@@ -180,10 +177,12 @@ impl Client {
fn spawn_fetch_sessions(&self) {
let api = self.api.clone();
let proxy = self.proxy.clone();
let mut updates = self.updates.clone();
thread::spawn(move || {
let result = api.fetch_sessions().map_err(|e| e.to_string());
let _ = proxy.send_event(AppEvent::Sessions(result));
updates.update(move |state: &mut Client, rsc| {
state.apply_event(AppEvent::Sessions(result), rsc);
});
});
}
@@ -211,7 +210,7 @@ impl Client {
let api = self.api.clone();
let stream_transport = self.stream_transport.clone();
let proxy = self.proxy.clone();
let mut updates = self.updates.clone();
let live_generation = self.generation.clone();
thread::spawn(move || {
let page: Result<Vec<serde_json::Value>, String> = api
@@ -223,10 +222,16 @@ impl Client {
.and_then(|values| raw_seq(values.last()?))
.unwrap_or(0);
let result = page.and_then(|values| fold_page(&values));
let _ = proxy.send_event(AppEvent::TranscriptLoaded {
session_id: session_id.clone(),
generation,
result,
let loaded_session_id = session_id.clone();
updates.update(move |state: &mut Client, rsc| {
state.apply_event(
AppEvent::TranscriptLoaded {
session_id: loaded_session_id,
generation,
result,
},
rsc,
);
});
let stop = || live_generation.load(Ordering::SeqCst) != generation;
@@ -240,28 +245,42 @@ impl Client {
if stop() {
return false;
}
let _ = proxy.send_event(AppEvent::StreamEvent {
session_id: session_id.clone(),
generation,
event,
let event_session_id = session_id.clone();
updates.update(move |state: &mut Client, rsc| {
state.apply_event(
AppEvent::StreamEvent {
session_id: event_session_id,
generation,
event,
},
rsc,
);
});
true
}
});
let _ = proxy.send_event(AppEvent::StreamEnded {
session_id,
generation,
message: outcome.err().map(|e| e.to_string()),
updates.update(move |state: &mut Client, rsc| {
state.apply_event(
AppEvent::StreamEnded {
session_id,
generation,
message: outcome.err().map(|e| e.to_string()),
},
rsc,
);
});
});
}
fn send_message(&mut self, session_id: String, text: String) {
let api = self.api.clone();
let proxy = self.proxy.clone();
let mut updates = self.updates.clone();
thread::spawn(move || {
if let Err(e) = api.send_message(&session_id, &text, &[]) {
let _ = proxy.send_event(AppEvent::SendFailed(e.to_string()));
let message = e.to_string();
updates.update(move |state: &mut Client, rsc| {
state.apply_event(AppEvent::SendFailed(message), rsc);
});
}
});
}