mirror of
https://github.com/FluxaMedia/fluxa-core.git
synced 2026-07-26 20:02:13 +00:00
Per the robustness plan's §9 (flagged as the single highest-leverage
perf change in the crate): headless_engine_dispatch_json used to clone
the entire EngineState before the reducer ran, clone it again after,
then StatePatch::diff did a deep PartialEq walk across all 16 domains
and cloned each changed one a third time — cost scaled with everything
the user has ever loaded (full catalogs, episode lists), not with what
the action actually touched.
Introduces Tracked<T> (src/headless_engine/state.rs): wraps each
EngineState domain field so any mutable access — a field write, a
whole-value replacement via DerefMut, a method call taking &mut —
flips a dirty bit automatically. Serialize/Deserialize delegate
straight to the wrapped value, so the wire format is byte-for-byte
unchanged (verified by the golden wire fixtures added in the prior
commit, which pass unmodified). EngineState::diff_dirty() replaces
StatePatch::diff(before, after): it clones only domains whose dirty
bit is set and clears it, so both the pre-dispatch full clone and the
post-dispatch PartialEq walk are gone entirely. The ~16 call sites that
did whole-domain replacement (`engine.state.detail = DetailState {
...}`) needed `*engine.state.detail = ...` instead — the compiler
catches any site the migration missed as a type error, so there's no
way for this rewrite to silently skip a domain the way a hand-added
mark_dirty() call could.
Also fixes headless_engine_snapshot_json, which used to serialize
while still holding the engines mutex; it now clones the state and
drops the lock before calling serde_json::to_string.
Adds benches/ (criterion, `--features bench` dev-only surface via a
new bench_targets re-export module mirroring the existing fuzz_targets
pattern): headless_engine_dispatch benches a NavigationRequested
dispatch against a 500-stream detail state (a domain the action
doesn't touch, so the win is directly visible), and stream_ranking
benches player_source_sidebar_plan_json grouping 500 streams. CI gets
a bench-check job compiling and smoke-running both (`--test`) without
paying for full timing runs on every push.
46 lines
1.3 KiB
Rust
46 lines
1.3 KiB
Rust
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
|
|
use fluxa_core::bench_targets::{
|
|
create_headless_engine, destroy_headless_engine, headless_engine_dispatch_json,
|
|
};
|
|
use serde_json::json;
|
|
|
|
fn large_initial_state() -> String {
|
|
let streams: Vec<_> = (0..500)
|
|
.map(|i| {
|
|
json!({
|
|
"id": i,
|
|
"name": format!("Stream {i}"),
|
|
"url": format!("https://cdn.example/{i}.mkv"),
|
|
})
|
|
})
|
|
.collect();
|
|
json!({
|
|
"detail": {
|
|
"id": "tt1",
|
|
"contentType": "movie",
|
|
"streams": streams,
|
|
}
|
|
})
|
|
.to_string()
|
|
}
|
|
|
|
fn navigation_dispatch_on_large_state(c: &mut Criterion) {
|
|
let initial = large_initial_state();
|
|
c.bench_function("navigation_dispatch_on_large_state", |b| {
|
|
b.iter_batched(
|
|
|| create_headless_engine(&initial),
|
|
|handle| {
|
|
let result = headless_engine_dispatch_json(
|
|
handle,
|
|
r#"{"type":"navigationRequested","route":"home","params":null}"#,
|
|
);
|
|
destroy_headless_engine(handle);
|
|
black_box(result)
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
}
|
|
|
|
criterion_group!(benches, navigation_dispatch_on_large_state);
|
|
criterion_main!(benches);
|