liquids keep time in the shader, and the frame is measured CPU-bound
- water and lava keep time in the shader: the 24 Hz CPU tick that re-uploaded every liquid material is gone, −0.28 ms at the Stormwind gates, and lava scrolls smoothly instead of stepping - static transform tracking is pinned on — the adaptive check spent two full scans a frame to say yes in a world where movers are 1–3% of the rows - WOW_MEGA_STATIC merges static geometry per material and 133-yard cell — an experiment lever, priced at −1.15 ms CPU for +0.3 ms GPU at the Stormwind gates - WOW_GPU_MS times the whole frame on the GPU and reads 2.2 ms against 17.4 CPU at the Stormwind gates: the frame is CPU-bound, so the work ahead is on the CPU side Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,9 @@ impl UiScript {
|
||||
pub fn message_lines_needing_measure(&mut self) -> Vec<LineMeasureRequest> {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut model = self.model_mut();
|
||||
// A plain reborrow so the arena borrow inside the loop and the sweep-token field writes
|
||||
// split by FIELD instead of fighting over the RefMut.
|
||||
let model = &mut *model;
|
||||
let mut out = Vec::new();
|
||||
let frames: Vec<(FrameHandle, Rect)> = model
|
||||
.resolved
|
||||
@@ -185,7 +188,7 @@ impl UiScript {
|
||||
.frame(fh)
|
||||
.map(|f| f.effective_scale)
|
||||
.unwrap_or(1.0);
|
||||
let font = Self::message_frame_font(&model, fh);
|
||||
let font = Self::message_frame_font(model, fh);
|
||||
let frame_id = model.frame_id(fh);
|
||||
let Some(frame) = model.arena.frame(fh) else {
|
||||
continue;
|
||||
@@ -193,7 +196,30 @@ impl UiScript {
|
||||
let Some(lines) = frame.kind_state.message_lines() else {
|
||||
continue;
|
||||
};
|
||||
// The skip token (the W4 fix, 1410's lane one door over): a frame whose line set
|
||||
// (lines_gen) and measure environment (font/wrap/scale/outline) both match its last
|
||||
// CLEAN sweep can produce nothing — every line's rows_key was minted under exactly
|
||||
// these inputs and matched then. Environment changes (a resize, a font swap, a
|
||||
// SetScale) miss the env hash; text changes miss the generation. The token is only
|
||||
// stored by a zero-request sweep (below), so an unanswered request keeps
|
||||
// re-requesting — the same rule the region ledger runs under.
|
||||
let lines_gen = frame.kind_state.lines_gen().unwrap_or(0);
|
||||
let env = {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
font.path.hash(&mut h);
|
||||
font.height.map(f32::to_bits).hash(&mut h);
|
||||
wrap_width.to_bits().hash(&mut h);
|
||||
(font.outline as u8).hash(&mut h);
|
||||
scale.to_bits().hash(&mut h);
|
||||
h.finish()
|
||||
};
|
||||
if model.msg_swept.get(&fh) == Some(&(lines_gen, env)) {
|
||||
continue;
|
||||
}
|
||||
let requests_before = out.len();
|
||||
for (index, line) in lines.iter().enumerate() {
|
||||
model.msg_lines_hashed += 1;
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
line.text.hash(&mut hasher);
|
||||
font.path.hash(&mut hasher);
|
||||
@@ -217,6 +243,11 @@ impl UiScript {
|
||||
key,
|
||||
});
|
||||
}
|
||||
if out.len() == requests_before {
|
||||
model.msg_swept.insert(fh, (lines_gen, env));
|
||||
} else {
|
||||
model.msg_swept.remove(&fh);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -695,4 +695,57 @@ mod tests {
|
||||
s.run("CF:Clear()").unwrap();
|
||||
assert_eq!(s.eval::<i64>("return CF:GetNumMessages()").unwrap(), 0);
|
||||
}
|
||||
|
||||
/// The W4 skip token: a settled message frame's sweep hashes ZERO lines (the counter is the
|
||||
/// claim — 0735's counts rule), and each side of the token catches its own change class:
|
||||
/// text through the generation, the resolved width through the environment hash. The token
|
||||
/// is only stored by a zero-request sweep, so an UNANSWERED request keeps re-requesting —
|
||||
/// the region ledger's own rule (1410), inherited.
|
||||
#[test]
|
||||
fn a_settled_message_frame_hashes_no_lines_until_something_moves() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
"local f = CreateFrame('ScrollingMessageFrame', 'CF')\n\
|
||||
f:SetPoint('BOTTOMLEFT', 32, 90)\n\
|
||||
f:SetWidth(430)\n\
|
||||
f:SetHeight(34)",
|
||||
)
|
||||
.unwrap();
|
||||
s.run("CF:AddMessage('one', 1, 1, 1)").unwrap();
|
||||
s.run("CF:AddMessage('two', 1, 1, 1)").unwrap();
|
||||
s.resolve();
|
||||
// Unanswered requests re-request: no token may be stored while keys mismatch.
|
||||
let first = s.message_lines_needing_measure();
|
||||
assert_eq!(first.len(), 2);
|
||||
let again = s.message_lines_needing_measure();
|
||||
assert_eq!(again.len(), 2, "unanswered lines must keep re-requesting");
|
||||
let answers: Vec<(u32, u32, u16, u64)> =
|
||||
again.iter().map(|r| (r.frame, r.index, 1, r.key)).collect();
|
||||
s.set_message_line_rows(&answers);
|
||||
assert!(s.message_lines_needing_measure().is_empty());
|
||||
// Settled: the NEXT sweep must skip the frame whole — zero lines hashed.
|
||||
let hashed_before = s.model_mut().msg_lines_hashed;
|
||||
assert!(s.message_lines_needing_measure().is_empty());
|
||||
assert_eq!(
|
||||
s.model_mut().msg_lines_hashed,
|
||||
hashed_before,
|
||||
"a settled frame's sweep must hash no lines"
|
||||
);
|
||||
// Text change reopens through the GENERATION…
|
||||
s.run("CF:AddMessage('three', 1, 1, 1)").unwrap();
|
||||
let reqs = s.message_lines_needing_measure();
|
||||
assert_eq!(reqs.len(), 1, "the new line re-surfaces");
|
||||
assert_eq!(reqs[0].text, "three");
|
||||
let answers: Vec<(u32, u32, u16, u64)> =
|
||||
reqs.iter().map(|r| (r.frame, r.index, 1, r.key)).collect();
|
||||
s.set_message_line_rows(&answers);
|
||||
assert!(s.message_lines_needing_measure().is_empty());
|
||||
// …and a resolved-width change reopens through the ENVIRONMENT: every line re-keys.
|
||||
s.run("CF:SetWidth(300)").unwrap();
|
||||
s.resolve();
|
||||
let reqs = s.message_lines_needing_measure();
|
||||
assert_eq!(reqs.len(), 3, "a new wrap width re-measures every line");
|
||||
assert!(reqs.iter().all(|r| (r.wrap_width - 300.0).abs() < 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user