use std::hint::black_box;
use std::io::Cursor;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use zipatch_rs::ZiPatchReader;
use zipatch_rs::test_utils::{make_chunk, make_patch};
const REPEATS: &[usize] = &[1, 16, 256];
fn mixed_chunks() -> Vec<Vec<u8>> {
vec![
make_chunk(b"FHDR", &fhdr_v2_body()),
make_chunk(b"APLY", &aply_body(1, false)),
make_chunk(b"ADIR", &dir_body(b"sqpack/ffxiv")),
make_chunk(b"DELD", &dir_body(b"sqpack/ex1")),
make_chunk(b"SQPK", &sqpk_target_info_body()),
make_chunk(b"SQPK", &sqpk_delete_data_body()),
make_chunk(b"SQPK", &sqpk_expand_data_body()),
make_chunk(b"SQPK", &sqpk_file_makedir_body(b"sqpack/ex2")),
]
}
fn fhdr_v2_body() -> Vec<u8> {
let mut out = Vec::with_capacity(20);
out.extend_from_slice(&0x0002_0000u32.to_le_bytes());
out.extend_from_slice(b"D000"); out.extend_from_slice(&1u32.to_be_bytes()); out.extend_from_slice(&[0u8; 8]); out
}
fn aply_body(kind: u32, value: bool) -> Vec<u8> {
let mut out = Vec::with_capacity(12);
out.extend_from_slice(&kind.to_be_bytes());
out.extend_from_slice(&[0u8; 4]);
out.extend_from_slice(&u32::from(value).to_be_bytes());
out
}
fn dir_body(name: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + name.len());
let name_len = u32::try_from(name.len()).expect("bench name fits in u32");
out.extend_from_slice(&name_len.to_be_bytes());
out.extend_from_slice(name);
out
}
fn sqpk_target_info_body() -> Vec<u8> {
let mut cmd = Vec::with_capacity(27);
cmd.extend_from_slice(&[0u8; 3]); cmd.extend_from_slice(&0u16.to_be_bytes()); cmd.extend_from_slice(&(-1i16).to_be_bytes()); cmd.extend_from_slice(&0i16.to_be_bytes()); cmd.extend_from_slice(&0u16.to_be_bytes()); cmd.extend_from_slice(&0u64.to_le_bytes()); cmd.extend_from_slice(&0u64.to_le_bytes()); wrap_sqpk(b'T', &cmd)
}
fn sqpk_delete_data_body() -> Vec<u8> {
let mut cmd = Vec::with_capacity(23);
cmd.extend_from_slice(&[0u8; 3]); cmd.extend_from_slice(&0u16.to_be_bytes()); cmd.extend_from_slice(&0u16.to_be_bytes()); cmd.extend_from_slice(&0u32.to_be_bytes()); cmd.extend_from_slice(&0u32.to_be_bytes()); cmd.extend_from_slice(&1u32.to_be_bytes()); cmd.extend_from_slice(&[0u8; 4]); wrap_sqpk(b'D', &cmd)
}
fn sqpk_expand_data_body() -> Vec<u8> {
let mut cmd = Vec::with_capacity(23);
cmd.extend_from_slice(&[0u8; 3]);
cmd.extend_from_slice(&0u16.to_be_bytes());
cmd.extend_from_slice(&0u16.to_be_bytes());
cmd.extend_from_slice(&0u32.to_be_bytes());
cmd.extend_from_slice(&0u32.to_be_bytes());
cmd.extend_from_slice(&1u32.to_be_bytes());
cmd.extend_from_slice(&[0u8; 4]);
wrap_sqpk(b'E', &cmd)
}
fn sqpk_file_makedir_body(path: &[u8]) -> Vec<u8> {
let mut path_bytes = path.to_vec();
path_bytes.push(0); let path_len = u32::try_from(path_bytes.len()).expect("bench path fits in u32");
let mut cmd = Vec::with_capacity(27 + path_bytes.len());
cmd.push(b'M'); cmd.extend_from_slice(&[0u8; 2]); cmd.extend_from_slice(&0u64.to_be_bytes()); cmd.extend_from_slice(&0u64.to_be_bytes()); cmd.extend_from_slice(&path_len.to_be_bytes());
cmd.extend_from_slice(&0u16.to_be_bytes()); cmd.extend_from_slice(&[0u8; 2]); cmd.extend_from_slice(&path_bytes);
wrap_sqpk(b'F', &cmd)
}
fn wrap_sqpk(command: u8, cmd_body: &[u8]) -> Vec<u8> {
let inner_size = 5 + cmd_body.len();
let inner_size_u32 = u32::try_from(inner_size).expect("bench SQPK body fits in u32");
let mut out = Vec::with_capacity(inner_size);
out.extend_from_slice(&inner_size_u32.to_be_bytes());
out.push(command);
out.extend_from_slice(cmd_body);
out
}
fn build_patch(repeat: usize) -> Vec<u8> {
let one_round = mixed_chunks();
let mut chunks: Vec<Vec<u8>> = Vec::with_capacity(one_round.len() * repeat + 1);
for _ in 0..repeat {
chunks.extend(one_round.iter().cloned());
}
chunks.push(make_chunk(b"EOF_", &[]));
make_patch(&chunks)
}
fn bench_parse(c: &mut Criterion) {
let mut group = c.benchmark_group("parse/mixed");
for &repeat in REPEATS {
let patch = build_patch(repeat);
group.throughput(Throughput::Bytes(patch.len() as u64));
group.bench_with_input(BenchmarkId::from_parameter(repeat), &patch, |b, patch| {
b.iter(|| {
let reader =
ZiPatchReader::new(Cursor::new(patch.as_slice())).expect("magic is valid");
let mut count = 0usize;
for chunk in reader {
let chunk = chunk.expect("synthetic patch parses cleanly");
black_box(&chunk);
count += 1;
}
black_box(count);
});
});
}
group.finish();
}
criterion_group!(benches, bench_parse);
criterion_main!(benches);