294 lines
9.3 KiB
Rust
294 lines
9.3 KiB
Rust
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
|
use iris::harness::{Harness, TouchScript};
|
|
use iris::prelude::*;
|
|
use std::time::{Duration, Instant};
|
|
use ui_profile::stats::summarise;
|
|
|
|
const PASSES: usize = 8;
|
|
const VELOCITY: f32 = 12_000.0;
|
|
const PASS_CAP_MS: u64 = 4_000;
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn what_a_fling_frame_costs() {
|
|
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
|
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
|
h.frame(0);
|
|
h.frame(PHONE_FRAME_MS);
|
|
|
|
let flick =
|
|
TouchScript::parse(include_str!("../../../../app-rust/touch/flick-120hz.touch")).unwrap();
|
|
h.replay(&flick);
|
|
println!(
|
|
"recorded flick released at {:?}px/s; scripted passes run at {VELOCITY}px/s",
|
|
(opened.screen.list)(&mut h.rsc).fling_velocity(),
|
|
);
|
|
|
|
let mut t = flick.end_ms();
|
|
let mut all_frames = Vec::new();
|
|
let mut all_layouts = Vec::new();
|
|
for pass in 0..PASSES {
|
|
let velocity = if pass % 2 == 0 { VELOCITY } else { -VELOCITY };
|
|
let list = (opened.screen.list)(&mut h.rsc);
|
|
list.fling(velocity);
|
|
let id = opened.screen.list.id();
|
|
h.rsc.ui.animate(id);
|
|
|
|
let mut frames = Vec::new();
|
|
let mut layouts = Vec::new();
|
|
let end = t + PASS_CAP_MS;
|
|
while t <= end {
|
|
let at = Instant::now();
|
|
h.frame(t);
|
|
frames.push(at.elapsed());
|
|
layouts.push(h.render.last_layout_duration());
|
|
t += PHONE_FRAME_MS;
|
|
if !(opened.screen.list)(&mut h.rsc).is_scrolling() {
|
|
break;
|
|
}
|
|
}
|
|
let laid_out = layouts
|
|
.iter()
|
|
.filter(|d| **d > Duration::from_micros(20))
|
|
.count();
|
|
println!(
|
|
"pass {pass} ({}): {} frames, {laid_out} did layout",
|
|
if velocity > 0.0 { "out " } else { "back" },
|
|
frames.len(),
|
|
);
|
|
summarise("frame", &frames);
|
|
all_frames.extend(frames);
|
|
all_layouts.extend(layouts);
|
|
t += 200;
|
|
}
|
|
|
|
println!(
|
|
"\nall {PASSES} passes, primitives={}",
|
|
h.render.active_primitive_count()
|
|
);
|
|
summarise("frame", &all_frames);
|
|
summarise("layout", &all_layouts);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn what_a_streamed_event_costs() {
|
|
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
|
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
|
h.frame(0);
|
|
h.frame(PHONE_FRAME_MS);
|
|
|
|
let mut items = opened.items;
|
|
println!(
|
|
"backlog: {} items, then {} streamed events",
|
|
items.len(),
|
|
opened.stream_tail.len()
|
|
);
|
|
|
|
let mut fold = Vec::new();
|
|
let mut apply = Vec::new();
|
|
let mut frame = Vec::new();
|
|
let mut frame_same_block = Vec::new();
|
|
let mut frame_new_block = Vec::new();
|
|
let mut t = PHONE_FRAME_MS;
|
|
let block_count = |items: &[ai_app::client::transcript_fold::TranscriptItem]| {
|
|
use ai_app::client::markdown_blocks::split_blocks;
|
|
use ai_app::client::transcript_fold::TranscriptItem;
|
|
match items.last() {
|
|
Some(TranscriptItem::AssistantMsg { text, .. }) => split_blocks(text).len(),
|
|
_ => 0,
|
|
}
|
|
};
|
|
for (n, event) in opened.stream_tail.iter().enumerate() {
|
|
let blocks_before = block_count(&items);
|
|
let old = items.clone();
|
|
let at = Instant::now();
|
|
let folded = ai_app::client::transcript_fold::fold_event(&items, event);
|
|
fold.push(at.elapsed());
|
|
items = folded;
|
|
let added_block = block_count(&items) > blocks_before;
|
|
|
|
let at = Instant::now();
|
|
opened.screen.apply(&mut h.rsc, &old, &items);
|
|
apply.push(at.elapsed());
|
|
|
|
t += PHONE_FRAME_MS;
|
|
let at = Instant::now();
|
|
h.frame(t);
|
|
let took = at.elapsed();
|
|
frame.push(took);
|
|
if added_block {
|
|
frame_new_block.push(took);
|
|
} else {
|
|
frame_same_block.push(took);
|
|
}
|
|
|
|
if n == 0 || n == opened.stream_tail.len() - 1 {
|
|
println!(
|
|
" event {n:>3} of {}: items={} fold {:?} apply {:?}",
|
|
opened.stream_tail.len(),
|
|
items.len(),
|
|
fold[n],
|
|
apply[n],
|
|
);
|
|
}
|
|
}
|
|
|
|
summarise("fold", &fold);
|
|
summarise("apply", &apply);
|
|
summarise("frame", &frame);
|
|
summarise("frame/same-block", &frame_same_block);
|
|
summarise("frame/new-block", &frame_new_block);
|
|
println!(
|
|
" primitives on screen at the end: {}",
|
|
h.render.active_primitive_count()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn what_reshaping_a_growing_message_costs() {
|
|
use iris::prelude::*;
|
|
|
|
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
|
const DELTA: &str = "the quick brown fox jumps over the lazy dog ";
|
|
let attrs = TextAttrs::default();
|
|
let width = Some(phone_size().x);
|
|
|
|
let mut buffer = iris::prelude::TextBuffer::new_empty();
|
|
let mut text = String::new();
|
|
let mut per_delta = Vec::new();
|
|
let mut total = Duration::ZERO;
|
|
for n in 1..=400 {
|
|
text.push_str(DELTA);
|
|
buffer.set_text(text.clone());
|
|
let at = Instant::now();
|
|
buffer.shape(&mut h.rsc.ui.text, &attrs, width, PHONE_SCALE);
|
|
let took = at.elapsed();
|
|
per_delta.push(took);
|
|
total += took;
|
|
if n % 100 == 0 {
|
|
println!(
|
|
" after {n:>3} deltas ({:>6} chars): this reshape {:?}",
|
|
text.len(),
|
|
took
|
|
);
|
|
}
|
|
}
|
|
println!(
|
|
" 400 deltas: {:?} of shaping in total, {:?} per delta at the end",
|
|
total,
|
|
per_delta.last().unwrap()
|
|
);
|
|
summarise("reshape", &per_delta);
|
|
|
|
println!(" at the sizes real replies reach:");
|
|
for chars in [147usize, 449, 836, 1580] {
|
|
let mut sample = String::new();
|
|
while sample.len() < chars {
|
|
sample.push_str(DELTA);
|
|
}
|
|
sample.truncate(chars);
|
|
let mut buffer = iris::prelude::TextBuffer::new(sample);
|
|
let at = Instant::now();
|
|
buffer.shape(&mut h.rsc.ui.text, &attrs, width, PHONE_SCALE);
|
|
println!(" {chars:>5} chars: {:?}", at.elapsed());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn where_a_streamed_deltas_cost_is() {
|
|
use ai_app::client::markdown_blocks::{common_prefix, split_blocks};
|
|
|
|
const SENTENCE: &str = "The quick brown fox jumps over the lazy dog. ";
|
|
let mut src = String::new();
|
|
let mut blocks = Vec::new();
|
|
|
|
let mut split = Vec::new();
|
|
let mut compare = Vec::new();
|
|
for n in 1..=400 {
|
|
src.push_str(SENTENCE);
|
|
if n % 8 == 0 {
|
|
src.push_str("\n\n");
|
|
}
|
|
|
|
let at = Instant::now();
|
|
let new_blocks = split_blocks(&src);
|
|
split.push(at.elapsed());
|
|
|
|
let at = Instant::now();
|
|
let _ = common_prefix(&blocks, &new_blocks);
|
|
compare.push(at.elapsed());
|
|
|
|
blocks = new_blocks;
|
|
if n % 100 == 0 {
|
|
println!(
|
|
" after {n:>3} deltas ({:>6} chars, {} blocks): split {:?} compare {:?}",
|
|
src.len(),
|
|
blocks.len(),
|
|
split[n - 1],
|
|
compare[n - 1],
|
|
);
|
|
}
|
|
}
|
|
summarise("split_blocks", &split);
|
|
summarise("common_prefix", &compare);
|
|
let total: Duration = split.iter().chain(compare.iter()).sum();
|
|
println!(" 400 deltas: {total:?} in block-splitting and comparison alone");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn what_the_fixture_streams() {
|
|
use ai_app::client::markdown_blocks::split_blocks;
|
|
use ai_app::client::transcript_fold::TranscriptItem;
|
|
|
|
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
|
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
|
let backlog = opened.items.clone();
|
|
let mut items = opened.items;
|
|
let before = items.len();
|
|
for event in &opened.stream_tail {
|
|
items = ai_app::client::transcript_fold::fold_event(&items, event);
|
|
}
|
|
println!("{} items -> {}", before, items.len());
|
|
let biggest = backlog
|
|
.iter()
|
|
.filter_map(|item| match item {
|
|
TranscriptItem::AssistantMsg { text, .. } => Some(text),
|
|
_ => None,
|
|
})
|
|
.map(|text| {
|
|
let blocks = split_blocks(text);
|
|
(
|
|
text.len(),
|
|
blocks.len(),
|
|
blocks.iter().map(|b| b.source.len()).max().unwrap_or(0),
|
|
)
|
|
})
|
|
.max_by_key(|(_, _, longest)| *longest);
|
|
if let Some((chars, blocks, longest)) = biggest {
|
|
println!(
|
|
" backlog's largest single block: {longest} chars (in a {chars}-char message of {blocks} blocks)"
|
|
);
|
|
}
|
|
for item in items.iter().rev().take(4) {
|
|
let (kind, text) = match item {
|
|
TranscriptItem::AssistantMsg { text, .. } => ("AssistantMsg", text.clone()),
|
|
TranscriptItem::UserMsg { text, .. } => ("UserMsg", text.clone()),
|
|
TranscriptItem::ToolRun { input, output, .. } => {
|
|
("ToolRun", format!("{input}{output}"))
|
|
}
|
|
_ => ("other", String::new()),
|
|
};
|
|
let blocks = split_blocks(&text);
|
|
println!(
|
|
" tail {kind}: {} chars in {} blocks; longest block {} chars",
|
|
text.len(),
|
|
blocks.len(),
|
|
blocks.iter().map(|b| b.source.len()).max().unwrap_or(0),
|
|
);
|
|
}
|
|
}
|