Files
benilla-octo/crates/benilla-ui/src/script/tests/dispatch_bench.rs
T
SamandClaude Fable 5.1 aff5203393 a fresh clone gets the whole project, and the maintainer's tooling moves out
What a clone lacked: the gates went green with a thousand skipped tests and said nothing, every
login script assumed a worktree-pool slot and its probe account, the client defaulted to the
maintainer's account, a docker path and a home directory were in the code, the temp files broke
on GNU coreutils, and two generators could only run against a sibling repo.

Now the test gates print how many data-gated tests skipped and why; a scripted run logs in as
the account its checkout declares in `.probe-identity` or as the three variables name, never a
default, and the client refuses a scripted login on any other account; the event and message
tables regenerate from vendored inputs, byte for byte; the docs state the real prerequisites.

Out of the tree, into the maintainer's own tooling: the worktree pool, its hooks, the perf leg
runner, the cross-platform compile and the Vulkan container leg — a way of working, and setups
not every machine has. One hook stays: no writes into the install.

Two tests that could never run are gone with their subject: the quest-timer geometry diff
compared a copy of the window that no longer exists (and joined a chain path onto a folder, so
it always skipped), and the FrameXML flag gate now reads the stock files off the player's own
patch chain instead of an extracted folder only one machine had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 17:27:14 +07:00

196 lines
7.9 KiB
Rust

//! **What one widget method lookup costs** — the bench behind the `__index` table (decision 2310).
//!
//! Every `frame:SetPoint(...)` in FrameXML and in every addon begins with a table index on the
//! wrapper, which misses and falls to the metatable's `__index`. That path is on the hottest edge
//! this engine has: Questie redraws its map notes with ~19 widget calls per cluster over hundreds
//! of clusters, and a live sample of a quest-accept spike put **20 % of the whole UI tick** inside
//! the metamethod dispatch alone — before any method body ran.
//!
//! The two rows below are the measurement that claim rests on, and the regression guard for it:
//!
//! - `miss` — `local m = f.NoSuchMethod`, the duck-type probe addons use (`if f.SetValue then`).
//! It is the WORST case: a dispatcher walks every registry in the kind chain and then the shared
//! table before answering nil.
//! - `hit` — `local m = f.GetWidth`, the lookup every real call pays.
//!
//! Run: `cargo test --release -p benilla-ui --lib -- --ignored --nocapture dispatch_bench`.
use std::time::Instant;
use super::common::script;
/// Lookups per timed loop — big enough that the loop's own overhead is a rounding error at the
/// nanosecond scale these rows live at.
const N: usize = 400_000;
/// Timed repeats per row; the reported number is the fastest (see [`dispatch_bench`]'s `floor`).
const ROUNDS: usize = 5;
fn ns_per(t: Instant, n: usize) -> f64 {
t.elapsed().as_secs_f64() * 1e9 / n as f64
}
#[test]
#[ignore = "bench — run explicitly with --release --nocapture"]
fn dispatch_bench() {
let s = script();
s.run(
r#"
F = CreateFrame("Frame")
B = CreateFrame("Button")
T = F:CreateTexture()
S = F:CreateFontString()
"#,
)
.expect("fixtures");
// A plain Lua table with a table `__index` — the floor this path could reach, and what the
// reference client's flat `{name, lua_CFunction}` probe costs in C.
// **The MINIMUM of [`ROUNDS`] runs, never the mean.** This machine builds while it measures —
// another build on the same machine saturates ten cores — and a mean of a contended loop reads
// whatever else was running. The minimum is the closest thing to the uncontended cost that a
// shared machine can report, and it is what makes two rows of one run comparable at all.
let floor = |what: &str, chunk: &str| {
let mut best = f64::INFINITY;
for _ in 0..ROUNDS {
let t = Instant::now();
s.run(chunk).expect("bench chunk");
best = best.min(ns_per(t, N));
}
println!("[dispatch] {what:>26} {best:7.1} ns");
};
floor(
"baseline (table meta)",
&format!(
"local base = {{ GetWidth = function() end }}
local o = setmetatable({{}}, {{ __index = base }})
for _ = 1, {N} do local m = o.GetWidth end"
),
);
floor(
"Frame hit",
&format!("for _ = 1, {N} do local m = F.GetWidth end"),
);
floor(
"Frame miss",
&format!("for _ = 1, {N} do local m = F.NoSuchMethod end"),
);
floor(
"Button hit",
&format!("for _ = 1, {N} do local m = B.GetWidth end"),
);
floor(
"Button own hit",
&format!("for _ = 1, {N} do local m = B.SetText end"),
);
floor(
"Button miss",
&format!("for _ = 1, {N} do local m = B.NoSuchMethod end"),
);
floor(
"Texture hit",
&format!("for _ = 1, {N} do local m = T.GetWidth end"),
);
floor(
"Texture miss",
&format!("for _ = 1, {N} do local m = T.NoSuchMethod end"),
);
floor(
"FontString hit",
&format!("for _ = 1, {N} do local m = S.GetWidth end"),
);
// The whole call, not just the lookup — what an addon's `f:GetWidth()` actually costs, split
// so the two taxes are separable: `GetFrameLevel` is a frame-only binding and measures one
// Rust hop, while `GetWidth` is one of [`crate::script::region_map`]'s 19 shared names and
// pays a SECOND hop — the bridge re-enters Lua to call the per-side arm.
floor(
"GetFrameLevel() call",
&format!("for _ = 1, {N} do local l = F:GetFrameLevel() end"),
);
floor(
"GetWidth() call (bridged)",
&format!("for _ = 1, {N} do local w = F:GetWidth() end"),
);
floor(
"SetPoint() call (bridged)",
&format!("for _ = 1, {N} do F:SetPoint(\"CENTER\", 0, 0) end"),
);
// **The settle tax.** Every geometry GETTER (`GetWidth`, `GetHeight`, `GetLeft`, `GetPoint`,
// `GetCenter`) calls `layout_methods::settle`, i.e. a whole `resolve_layout`. The tier-1 epoch
// gate makes that free while nothing has moved — which is what the `GetWidth() call` row above
// measures — but ANY layout write in between reopens it, so the read-after-write idiom pays a
// resolve per read. That idiom is not exotic: Astrolabe repositions a minimap icon with
// `GetWidth` → `ClearAllPoints` → `SetPoint`, per icon, and Questie places dozens.
//
// Here the model holds four frames, so a resolve is nearly free and the gap is the mechanism
// alone. In a live client the roster is thousands of frames and ten thousand anchored regions
// — the same gap, three orders of magnitude wider.
floor(
"write only",
&format!("for i = 1, {N} do F:SetPoint(\"CENTER\", i, 0) end"),
);
floor(
"write + read (settles)",
&format!("for i = 1, {N} do F:SetPoint(\"CENTER\", i, 0) local w = F:GetWidth() end"),
);
floor(
"write + read x3 (settles)",
&format!(
"for i = 1, {N} do F:SetPoint(\"CENTER\", i, 0) local w = F:GetWidth() local h = F:GetHeight() local l = F:GetLeft() end"
),
);
// **The control that says WHERE the bridged calls' extra microsecond goes.** `__direct` is one
// mlua closure; `__wrapped` is one mlua closure that calls another through `Function::call` —
// the exact shape `region_map`'s shared arm has. The gap between these two rows is what a
// re-entrant Lua call costs, and it is the number that decides whether those 19 names are worth
// restructuring into a single Rust body with an internal branch (the reference's own shape: one
// C function doing a virtual dispatch on the receiver).
{
let lua = s.lua();
let direct = lua
.create_function(|_, n: i64| Ok(n + 1))
.expect("direct fn");
lua.globals().set("__direct", direct.clone()).expect("set");
let wrapped = lua
.create_function(move |_, n: i64| direct.call::<i64>(n))
.expect("wrapped fn");
lua.globals().set("__wrapped", wrapped).expect("set");
// The same pair again in the SHAPE the bridge actually has: a variadic `MultiValue` whose
// first value is the receiver table. `Table` and `MultiValue` are the two conversions the
// scalar controls above leave out, and between them they are most of the gap.
let mv_direct = lua
.create_function(|_, args: mlua::MultiValue| Ok(args.len() as i64))
.expect("mv direct");
lua.globals()
.set("__mv_direct", mv_direct.clone())
.expect("set");
let mv_relay = lua
.create_function(move |_, args: mlua::MultiValue| {
mv_direct.call::<mlua::MultiValue>(args)
})
.expect("mv relay");
lua.globals().set("__mv_relay", mv_relay).expect("set");
}
floor(
"ctrl: 1 mlua hop",
&format!("for _ = 1, {N} do local v = __direct(1) end"),
);
floor(
"ctrl: mlua hop + relay",
&format!("for _ = 1, {N} do local v = __wrapped(1) end"),
);
floor(
"ctrl: variadic hop",
&format!("for _ = 1, {N} do local v = __mv_direct(F, 1, 2) end"),
);
floor(
"ctrl: variadic hop + relay",
&format!("for _ = 1, {N} do local v = __mv_relay(F, 1, 2) end"),
);
}