181 lines
5.8 KiB
Rust
181 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 id = opened.screen.list.id();
|
|
h.rsc.ui.animate(id);
|
|
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);
|
|
}
|