Files

179 lines
5.8 KiB
Rust

use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
use iris::harness::Harness;
use iris::prelude::{GlyphPrimitive, Primitive, RectPrimitive};
use iris::widget::Scrollable;
use ui_profile::stats::pct_u64;
const PASSES: usize = 8;
const VELOCITY: f32 = 12_000.0;
const PASS_CAP_MS: u64 = 4_000;
#[derive(Default)]
struct Tally {
name: &'static str,
stride: usize,
prev: Vec<u8>,
changed: Vec<u64>,
uploaded: Vec<u64>,
whole: Vec<u64>,
calls: Vec<u64>,
}
impl Tally {
fn new(name: &'static str, stride: usize) -> Self {
Self {
name,
stride,
..Default::default()
}
}
fn frame(&mut self, bytes: &[u8], ranges: Vec<std::ops::Range<usize>>) {
let n = self.prev.len().min(bytes.len()) / self.stride;
let mut changed = (0..n)
.filter(|i| {
let r = i * self.stride..(i + 1) * self.stride;
self.prev[r.clone()] != bytes[r]
})
.count();
changed += bytes.len() / self.stride - n;
self.changed.push((changed * self.stride) as u64);
self.uploaded
.push(ranges.iter().map(|r| (r.len() * self.stride) as u64).sum());
self.calls.push(ranges.len() as u64);
self.whole.push(bytes.len() as u64);
self.prev.clear();
self.prev.extend_from_slice(bytes);
}
fn report(&mut self) {
let sum = |v: &[u64]| v.iter().sum::<u64>();
let (changed, uploaded, whole) =
(sum(&self.changed), sum(&self.uploaded), sum(&self.whole));
println!(
" {:<10} whole {:>7.1} MB | uploaded {:>7.1} MB ({:>5.1}%) | floor {:>7.1} MB ({:>5.1}%)",
self.name,
whole as f64 / 1e6,
uploaded as f64 / 1e6,
100.0 * uploaded as f64 / whole.max(1) as f64,
changed as f64 / 1e6,
100.0 * changed as f64 / whole.max(1) as f64,
);
println!(
" {:<10} uploaded per frame p50 {:>7} p90 {:>7} p99 {:>7} max {:>7} B | \
writes p50 {} p90 {} max {}",
"",
pct_u64(&mut self.uploaded.clone(), 0.5),
pct_u64(&mut self.uploaded.clone(), 0.9),
pct_u64(&mut self.uploaded.clone(), 0.99),
pct_u64(&mut self.uploaded.clone(), 1.0),
pct_u64(&mut self.calls.clone(), 0.5),
pct_u64(&mut self.calls.clone(), 0.9),
pct_u64(&mut self.calls.clone(), 1.0),
);
}
}
struct Arenas {
instances: Tally,
rects: Tally,
glyphs: Tally,
}
impl Arenas {
fn new() -> Self {
Self {
instances: Tally::new("instances", 0),
rects: Tally::new("rects", size_of::<RectPrimitive>()),
glyphs: Tally::new("glyphs", size_of::<GlyphPrimitive>()),
}
}
fn frame(&mut self, h: &mut Harness) {
let count = h.render.primitives.instances().len();
let (entries, dirty) = h.render.primitives.instances_for_upload();
let bytes: Vec<u8> = bytemuck::cast_slice(entries).to_vec();
self.instances.stride = bytes.len().checked_div(count).unwrap_or(48);
let ranges = dirty.ranges(count, 1024 / self.instances.stride);
dirty.clear();
self.instances.frame(&bytes, ranges);
let data = h.render.primitives.data_mut();
let (entries, dirty) = RectPrimitive::vec(data).for_upload();
let bytes: Vec<u8> = bytemuck::cast_slice(entries).to_vec();
let ranges = dirty.ranges(entries.len(), 1024 / size_of::<RectPrimitive>());
dirty.clear();
self.rects.frame(&bytes, ranges);
let (entries, dirty) = GlyphPrimitive::vec(data).for_upload();
let bytes: Vec<u8> = bytemuck::cast_slice(entries).to_vec();
let ranges = dirty.ranges(entries.len(), 1024 / size_of::<GlyphPrimitive>());
dirty.clear();
self.glyphs.frame(&bytes, ranges);
}
fn report(&mut self, h: &Harness) {
println!(
" arena {} slots for {} live primitives",
h.render.primitives.instances().len(),
h.render.primitives.live_count(),
);
self.instances.report();
self.rects.report();
self.glyphs.report();
}
}
#[test]
#[ignore]
fn what_a_fling_uploads() {
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 arenas = Arenas::new();
arenas.frame(&mut h);
let mut t = PHONE_FRAME_MS;
for pass in 0..PASSES {
let velocity = if pass % 2 == 0 { VELOCITY } else { -VELOCITY };
(opened.screen.list)(&mut h.rsc).fling(velocity);
let end = t + PASS_CAP_MS;
while t <= end {
t += PHONE_FRAME_MS;
h.frame(t);
arenas.frame(&mut h);
if !(opened.screen.list)(&mut h.rsc).is_scrolling() {
break;
}
}
t += 200;
}
println!("\na fling, {PASSES} passes:");
arenas.report(&h);
}
#[test]
#[ignore]
fn what_a_streamed_reply_uploads() {
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 arenas = Arenas::new();
arenas.frame(&mut h);
let mut items = opened.items;
let mut t = PHONE_FRAME_MS;
for event in opened.stream_tail.iter() {
let old = items.clone();
items = ai_app::client::transcript_fold::fold_event(&items, event);
opened.screen.apply(&mut h.rsc, &old, &items);
t += PHONE_FRAME_MS;
h.frame(t);
arenas.frame(&mut h);
}
println!("\na reply streaming in, one event per frame:");
arenas.report(&h);
}