OnUpdate walks strata, level and link order, as 1.12.1 does (#868)
OnUpdate ran in frame-creation order, from a list built before the walk, so a frame hidden by an earlier handler still updated, and strata and level played no part. The reference's update walk `0x765650` goes through the nine strata from the bottom, each stratum's levels from 0, and each level's list oldest link first. It saves the next frame before each call, so a hide skips a frame before its turn. A frame linked into the level being walked waits a tick; one linked into a level still ahead runs there. The walk now follows that order, using the link stamps shows and level changes already keep. It checks each frame at its turn and places frames linked or given an `OnUpdate` mid-walk. An EditBox still flushes right after its own `OnUpdate`. Tests cover the order, mid-walk hides and shows, and a box's flush. Fixes #742 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c9ddd80342
commit
16affa8d45
@@ -93,6 +93,19 @@ fn the_flush_follows_the_boxs_own_on_update_cursor_then_text() {
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The box's place in the walk is its stratum's, not its creation's: a BACKGROUND box updates and
|
||||
/// flushes before the MEDIUM frames made around it (`0x7656c8`).
|
||||
#[test]
|
||||
fn a_box_flushes_after_its_on_update_in_its_strata_place() {
|
||||
let mut s = script();
|
||||
logged_box(&mut s);
|
||||
s.run(r#"E:SetFrameStrata("BACKGROUND") E:SetText("abc")"#)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "update cursor@21 text:abc early late");
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// An edit a box makes in its own `OnUpdate` is flushed by the same update, so its events fire in
|
||||
/// the same tick.
|
||||
#[test]
|
||||
|
||||
@@ -133,6 +133,7 @@ mod tooltip_spell;
|
||||
mod tooltip_unit;
|
||||
mod ui_errors;
|
||||
mod ui_scale;
|
||||
mod update_walk;
|
||||
pub use tooltip_unit::TooltipTint;
|
||||
mod trade;
|
||||
mod tradeskill;
|
||||
|
||||
@@ -176,8 +176,11 @@ pub(crate) struct Model {
|
||||
|
||||
/// Which handlers each frame has; the closures themselves live in Lua's `REG_SCRIPTS` table.
|
||||
pub(crate) scripts: HashMap<FrameHandle, HashSet<&'static str>>,
|
||||
/// Frames with an `OnUpdate` script, kept by `SetScript`; the tick sorts them by frame id.
|
||||
/// Frames with an `OnUpdate` script, kept by `SetScript`, from which the update walk starts.
|
||||
pub(crate) on_update_frames: Vec<FrameHandle>,
|
||||
/// Bumped each time a frame joins [`Self::on_update_frames`], so the walk notices a frame that
|
||||
/// gains an `OnUpdate` before its turn.
|
||||
pub(crate) on_update_joins: u64,
|
||||
/// Frames with an `OnSizeChanged` script, kept by `SetScript`, for the resolve's size snapshot.
|
||||
pub(crate) on_size_changed_frames: Vec<FrameHandle>,
|
||||
/// Frames with an `OnUpdateModel` script, fired as a visible model pane paints (`0x76d1a0`).
|
||||
@@ -1098,6 +1101,7 @@ impl Model {
|
||||
region_names: HashMap::new(),
|
||||
scripts: HashMap::new(),
|
||||
on_update_frames: Vec::new(),
|
||||
on_update_joins: 0,
|
||||
on_size_changed_frames: Vec::new(),
|
||||
on_update_model_frames: Vec::new(),
|
||||
model_facts: HashMap::new(),
|
||||
|
||||
@@ -240,7 +240,10 @@ fn set_script(lua: &Lua, this: &Table, name: &str, func: Option<Function>) -> ml
|
||||
// The first handler of a kind joins that kind's list; nothing else writes `scripts`.
|
||||
if model.scripts.entry(h).or_default().insert(kind) {
|
||||
match kind {
|
||||
"OnUpdate" => model.on_update_frames.push(h),
|
||||
"OnUpdate" => {
|
||||
model.on_update_frames.push(h);
|
||||
model.on_update_joins += 1;
|
||||
}
|
||||
"OnSizeChanged" => model.on_size_changed_frames.push(h),
|
||||
"OnUpdateModel" => model.on_update_model_frames.push(h),
|
||||
_ => {}
|
||||
|
||||
@@ -52,6 +52,7 @@ mod tooltip_item;
|
||||
mod tooltip_spell;
|
||||
mod tooltip_unit;
|
||||
mod toplevel;
|
||||
mod update_walk;
|
||||
mod visibility;
|
||||
mod widget_surface;
|
||||
mod worldframe;
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
//! The update walk (`0x765650`): strata from the bottom up (`0x7656c8`–`0x765793`), each
|
||||
//! stratum's levels from 0 up (`0x765774`), and each level's list of linked frames oldest link
|
||||
//! first, the list being head-inserted (`0x7648c9`) and walked from its tail (`0x7656e5`–`0x765707`).
|
||||
//! The walk saves the next frame before each call (`0x765707`), a hide repairs that cursor
|
||||
//! (`0x76498f`), and a frame linked into the level being walked is staged until the level ends
|
||||
//! (`0x76489b`, `0x76573e`).
|
||||
|
||||
use crate::script::UiScript;
|
||||
|
||||
use super::common::script;
|
||||
|
||||
/// `LOG` joined by spaces, then emptied.
|
||||
fn take_log(s: &UiScript) -> String {
|
||||
s.eval::<String>("local l = table.concat(LOG, ' ') LOG = {} return l")
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Lua that makes a frame `name`, logging `tag` from its `OnUpdate`.
|
||||
const LOGGED: &str = r#"
|
||||
LOG = {}
|
||||
function Logged(name, tag)
|
||||
local f = CreateFrame("Frame", name)
|
||||
f:SetScript("OnUpdate", function() table.insert(LOG, tag) end)
|
||||
return f
|
||||
end
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn the_walk_runs_strata_then_level_not_creation_order() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("WalkTip", "tooltip"):SetFrameStrata("TOOLTIP")
|
||||
Logged("WalkBack", "background"):SetFrameStrata("BACKGROUND")
|
||||
Logged("WalkHigh", "level5"):SetFrameLevel(5)
|
||||
Logged("WalkLow", "level1"):SetFrameLevel(1)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
take_log(&s);
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "background level1 level5 tooltip");
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// Within a level the oldest link goes first, and a show links at the end: a frame hidden and
|
||||
/// shown again walks after one created later.
|
||||
#[test]
|
||||
fn a_reshown_frame_walks_after_its_levels_older_links() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("LinkA", "a")
|
||||
Logged("LinkB", "b")
|
||||
LinkA:Hide()
|
||||
LinkA:Show()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "b a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_frame_hidden_by_an_earlier_on_update_is_not_updated() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("HideA", "a"):SetScript("OnUpdate", function()
|
||||
table.insert(LOG, "a")
|
||||
HideB:Hide()
|
||||
end)
|
||||
Logged("HideB", "b")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "a");
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// A frame hidden and shown again before its turn relinks into the level being walked, which
|
||||
/// stages it: it waits for the next tick. (A blink every tick would stage it every tick.)
|
||||
#[test]
|
||||
fn a_frame_reshown_before_its_turn_in_the_walked_level_waits_a_tick() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("BlinkA", "a"):SetScript("OnUpdate", function()
|
||||
table.insert(LOG, "a")
|
||||
if not BLINKED then
|
||||
BLINKED = 1
|
||||
BlinkB:Hide()
|
||||
BlinkB:Show()
|
||||
end
|
||||
end)
|
||||
Logged("BlinkB", "b")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "a");
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "a b");
|
||||
}
|
||||
|
||||
/// A frame shown into a level the walk has not reached yet is updated that tick; one shown into
|
||||
/// the level being walked or one already walked waits for the next.
|
||||
#[test]
|
||||
fn a_frame_shown_mid_walk_is_updated_only_if_its_level_is_still_ahead() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("ShowA", "a"):SetScript("OnUpdate", function()
|
||||
table.insert(LOG, "a")
|
||||
ShowTip:Show()
|
||||
ShowSame:Show()
|
||||
ShowBack:Show()
|
||||
end)
|
||||
Logged("ShowTip", "tip"):SetFrameStrata("TOOLTIP")
|
||||
Logged("ShowSame", "same")
|
||||
Logged("ShowBack", "back"):SetFrameStrata("BACKGROUND")
|
||||
ShowTip:Hide()
|
||||
ShowSame:Hide()
|
||||
ShowBack:Hide()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "a tip");
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "back a same tip");
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// A level change relinks a shown frame at the head of its new level (`0x76a54c`), so a frame that
|
||||
/// moves itself to a level still ahead is walked again there.
|
||||
#[test]
|
||||
fn a_frame_that_moves_itself_to_a_level_ahead_is_walked_again() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("Mover", "m"):SetScript("OnUpdate", function()
|
||||
table.insert(LOG, "m" .. this:GetFrameLevel())
|
||||
if this:GetFrameLevel() == 0 then this:SetFrameLevel(3) end
|
||||
end)
|
||||
Logged("Mid", "mid"):SetFrameLevel(2)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "m0 mid m3");
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "mid m3");
|
||||
}
|
||||
|
||||
/// The update reads the frame's handler when its turn comes (`0x76b2c3`), so an `OnUpdate` set by
|
||||
/// an earlier frame's runs that tick.
|
||||
#[test]
|
||||
fn an_on_update_set_before_the_frames_turn_runs_that_tick() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
Logged("Setter", "setter"):SetScript("OnUpdate", function()
|
||||
table.insert(LOG, "setter")
|
||||
if not SET then
|
||||
SET = true
|
||||
Earlier:SetScript("OnUpdate", function() table.insert(LOG, "earlier") end)
|
||||
Later:SetScript("OnUpdate", function() table.insert(LOG, "later") end)
|
||||
end
|
||||
end)
|
||||
CreateFrame("Frame", "Earlier"):SetFrameStrata("BACKGROUND")
|
||||
CreateFrame("Frame", "Later"):SetFrameStrata("HIGH")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "setter later");
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "earlier setter later");
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// "Ahead" is by stratum first: a frame shown into a later stratum is updated that tick even at a
|
||||
/// lower level than the one being walked, as the reference walks every stratum after this one.
|
||||
#[test]
|
||||
fn a_frame_shown_into_a_later_stratum_at_a_lower_level_runs_that_tick() {
|
||||
let mut s = script();
|
||||
s.run(LOGGED).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
local late = Logged("LateHigh", "high")
|
||||
late:SetFrameStrata("HIGH")
|
||||
late:SetFrameLevel(1)
|
||||
late:Hide()
|
||||
local shower = Logged("ShowerMed", "medium")
|
||||
shower:SetFrameLevel(5)
|
||||
shower:SetScript("OnUpdate", function()
|
||||
table.insert(LOG, "medium")
|
||||
LateHigh:Show()
|
||||
end)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
take_log(&s);
|
||||
s.tick(0.016);
|
||||
assert_eq!(take_log(&s), "medium high");
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use mlua::Lua;
|
||||
|
||||
use crate::widget::FrameHandle;
|
||||
|
||||
use super::{editbox, event, tooltip, Model, ScriptValue, UiScript};
|
||||
use super::{event, tooltip, Model, ScriptValue, UiScript};
|
||||
|
||||
impl UiScript {
|
||||
/// Queue an event for the start of the next tick, behind everything queued earlier and before
|
||||
@@ -70,10 +70,10 @@ pub(crate) fn fire_event_into(lua: &Lua, event: &str, args: Vec<ScriptValue>) {
|
||||
}
|
||||
|
||||
impl super::UiScript {
|
||||
/// Advance a frame: the `GetTime()` clock and queued events, then the walk (`0x765650`),
|
||||
/// each shown frame's update in turn: its `OnUpdate(self, elapsed)` (`0x704f10`), and for an
|
||||
/// EditBox its flush and caret blink after it; then the engine's own fades, model panes and
|
||||
/// hover.
|
||||
/// Advance a frame: the `GetTime()` clock and queued events, then the update walk
|
||||
/// (`0x765650`), each linked frame's update in strata, level and link order: its
|
||||
/// `OnUpdate(self, elapsed)` (`0x704f10`), and for an EditBox its flush and caret blink after
|
||||
/// it; then the engine's own fades, model panes and hover.
|
||||
pub fn tick(&mut self, elapsed: f32) {
|
||||
self.model_mut().now += f64::from(elapsed);
|
||||
// Events queued since the last tick fire before this tick's OnUpdate.
|
||||
@@ -81,74 +81,7 @@ impl super::UiScript {
|
||||
for (event, args) in pending {
|
||||
self.fire_event(&event, args);
|
||||
}
|
||||
// Each entry: the frame's id, whether it has an `OnUpdate`, and the box if an EditBox.
|
||||
let walk: Vec<(u32, bool, Option<FrameHandle>)> = {
|
||||
let mut model = self.model_mut();
|
||||
// `SetScript` keeps the OnUpdate list; a destroyed frame's handle compacts out here.
|
||||
let shown = |h: FrameHandle| model.arena.frame(h).is_some_and(|f| f.effective_visible);
|
||||
let mut frames: Vec<(FrameHandle, bool)> = model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&h| shown(h))
|
||||
.map(|h| (h, false))
|
||||
.collect();
|
||||
// The walk reaches every shown frame, not only those with a script, and an EditBox's
|
||||
// update (`0x77a790`, vtable `+0x38`) flushes it whether or not it has an `OnUpdate`.
|
||||
frames.extend(
|
||||
model
|
||||
.arena
|
||||
.editbox_kinds()
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&h| shown(h))
|
||||
.map(|h| (h, true)),
|
||||
);
|
||||
if model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.any(|&h| model.arena.frame(h).is_none())
|
||||
{
|
||||
let arena = &model.arena;
|
||||
let live: Vec<FrameHandle> = model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&h| arena.frame(h).is_some())
|
||||
.collect();
|
||||
model.on_update_frames = live;
|
||||
}
|
||||
let mut walk: Vec<(u32, bool, Option<FrameHandle>)> = frames
|
||||
.into_iter()
|
||||
.map(|(h, editbox)| (model.frame_id(h), !editbox, editbox.then_some(h)))
|
||||
.collect();
|
||||
// Creation order, by frame id: getters settle on demand, so a handler sees what an
|
||||
// earlier one did this sweep, and the order must be stable. The reference walks each
|
||||
// strata level's list of shown frames (`0x765650`). A box with an `OnUpdate` is
|
||||
// listed twice, and the two entries merge.
|
||||
walk.sort_unstable_by_key(|&(id, ..)| id);
|
||||
walk.dedup_by(|later, earlier| {
|
||||
let same = later.0 == earlier.0;
|
||||
if same {
|
||||
earlier.1 |= later.1;
|
||||
earlier.2 = earlier.2.or(later.2);
|
||||
}
|
||||
same
|
||||
});
|
||||
walk
|
||||
};
|
||||
for (id, on_update, editbox) in walk {
|
||||
if on_update {
|
||||
if let Err(e) = event::fire_update_handler(&self.lua, id, elapsed) {
|
||||
self.push_error(e);
|
||||
}
|
||||
}
|
||||
// The box's own update runs its flush after its `OnUpdate` (`0x77a79a`, then
|
||||
// `0x77a7a1`), so the events an edit raised fire in the box's place in the walk.
|
||||
if let Some(h) = editbox {
|
||||
editbox::update(&self.lua, h, elapsed);
|
||||
}
|
||||
}
|
||||
self.update_walk(elapsed);
|
||||
self.tick_model_panes(elapsed);
|
||||
// The engine's line fades, apart from any OnUpdate script: ScrollingMessageFrame's
|
||||
// (`0x788460`) and MessageFrame's (`0x786200`), which also keeps only the lines that fit
|
||||
@@ -255,7 +188,7 @@ impl UiScript {
|
||||
})
|
||||
.collect();
|
||||
let mut ids: Vec<u32> = frames.into_iter().map(|h| model.frame_id(h)).collect();
|
||||
ids.sort_unstable(); // creation order, as in the OnUpdate sweep
|
||||
ids.sort_unstable(); // creation order
|
||||
ids
|
||||
};
|
||||
for id in update_ids {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//! The update walk (`0x765650`), each linked frame's per-frame update in the reference's order: the
|
||||
//! nine strata from the bottom up (`0x7656c8`–`0x765793`), each stratum's levels from 0 up
|
||||
//! (the bound re-read after each level, `0x765774`), and each level's list of linked frames from its
|
||||
//! tail (`0x7656e5`–`0x765707`). Every link is a head insert (`0x7648c9`), so a level walks oldest
|
||||
//! link first: the order of [`Frame::insertion_seq`](crate::widget::Frame::insertion_seq).
|
||||
//!
|
||||
//! The walk is live. It saves the next frame before each call (`0x765707`), and a hide that
|
||||
//! unlinks that frame moves the cursor past it (`0x76498f`), so a frame hidden before its turn is
|
||||
//! not updated. A frame linked while its own level is being walked is staged until that level
|
||||
//! ends (`0x76489b`, spliced at `0x76573e`) and so waits a tick, as does one linked into a level
|
||||
//! already walked; one linked into a level still ahead is updated there, after that level's
|
||||
//! older links.
|
||||
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
use crate::widget::{FrameHandle, FrameKind};
|
||||
|
||||
use super::{editbox, event, UiScript};
|
||||
|
||||
/// A place in the walk: stratum, level, then within the level the frames linked before the walk
|
||||
/// began (0, by link stamp) ahead of those linked during it (1, by the order they linked).
|
||||
type Place = (u8, u16, u8, u64);
|
||||
|
||||
impl UiScript {
|
||||
/// Run the walk: each frame's `OnUpdate(self, elapsed)` (`0x76b2c0`, reading the handler when
|
||||
/// its turn comes), and for an EditBox its flush after it (`0x77a79a`, then `0x77a7a1`), so the
|
||||
/// events an edit raised fire in the box's place.
|
||||
pub(super) fn update_walk(&mut self, elapsed: f32) {
|
||||
let mut ahead: BTreeMap<Place, FrameHandle> = BTreeMap::new();
|
||||
let (renumbers, mut joins, mut seen) = {
|
||||
let mut model = self.model_mut();
|
||||
// `SetScript` keeps the OnUpdate list; a destroyed frame's handle compacts out here.
|
||||
if model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.any(|&h| model.arena.frame(h).is_none())
|
||||
{
|
||||
let arena = &model.arena;
|
||||
let live: Vec<FrameHandle> = model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&h| arena.frame(h).is_some())
|
||||
.collect();
|
||||
model.on_update_frames = live;
|
||||
}
|
||||
// Only frames with an `OnUpdate` or an EditBox's flush do anything when walked.
|
||||
let mut seen = HashSet::new();
|
||||
for &h in model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.chain(model.arena.editbox_kinds())
|
||||
{
|
||||
if let Some(f) = model.arena.frame(h).filter(|f| f.effective_visible) {
|
||||
let place = (f.strata as u8, f.level, 0, u64::from(f.insertion_seq));
|
||||
ahead.insert(place, h);
|
||||
seen.insert(h);
|
||||
}
|
||||
}
|
||||
model.arena.open_link_log();
|
||||
(model.arena.renumbers(), model.on_update_joins, seen)
|
||||
};
|
||||
// Each frame linked during the walk, by the number of its latest link.
|
||||
let mut relinked: HashMap<FrameHandle, u64> = HashMap::new();
|
||||
let mut links = 0u64;
|
||||
while let Some((place, h)) = ahead.pop_first() {
|
||||
let (on_update, editbox) = {
|
||||
let mut model = self.model_mut();
|
||||
let Some(f) = model.arena.frame(h) else {
|
||||
continue;
|
||||
};
|
||||
// Still at this place: linked, and not relinked since the place was taken.
|
||||
let here = f.effective_visible
|
||||
&& match place.2 {
|
||||
0 => !relinked.contains_key(&h),
|
||||
_ => relinked.get(&h) == Some(&place.3),
|
||||
};
|
||||
let editbox = f.kind == FrameKind::EditBox;
|
||||
let on_update = here
|
||||
&& model
|
||||
.scripts
|
||||
.get(&h)
|
||||
.is_some_and(|s| s.contains("OnUpdate"));
|
||||
(on_update.then(|| model.frame_id(h)), here && editbox)
|
||||
};
|
||||
if let Some(id) = on_update {
|
||||
if let Err(e) = event::fire_update_handler(&self.lua, id, elapsed) {
|
||||
self.push_error(e);
|
||||
}
|
||||
}
|
||||
if editbox {
|
||||
editbox::update(&self.lua, h, elapsed);
|
||||
}
|
||||
let mut model = self.model_mut();
|
||||
for linked in model.arena.take_linked() {
|
||||
links += 1;
|
||||
relinked.insert(linked, links);
|
||||
let Some(f) = model.arena.frame(linked) else {
|
||||
continue;
|
||||
};
|
||||
if (f.strata as u8, f.level) > (place.0, place.1) {
|
||||
ahead.insert((f.strata as u8, f.level, 1, links), linked);
|
||||
}
|
||||
}
|
||||
// A frame that gained an `OnUpdate` keeps its place if the walk has not passed it. Its
|
||||
// stamp compares with the walk's only while no renumber came between; else it waits.
|
||||
if model.on_update_joins != joins {
|
||||
joins = model.on_update_joins;
|
||||
if model.arena.renumbers() == renumbers {
|
||||
let joined: Vec<FrameHandle> = model
|
||||
.on_update_frames
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|h| !seen.contains(h) && !relinked.contains_key(h))
|
||||
.collect();
|
||||
for j in joined {
|
||||
seen.insert(j);
|
||||
let Some(f) = model.arena.frame(j).filter(|f| f.effective_visible) else {
|
||||
continue;
|
||||
};
|
||||
let at = (f.strata as u8, f.level, 0, u64::from(f.insertion_seq));
|
||||
if at > place {
|
||||
ahead.insert(at, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.model_mut().arena.close_link_log();
|
||||
}
|
||||
}
|
||||
@@ -250,6 +250,12 @@ pub struct WidgetArena {
|
||||
regions: Arena<Region>,
|
||||
names: HashMap<String, FrameHandle>,
|
||||
next_insertion: u32,
|
||||
/// How many times the link stamps were renumbered at the cap, so a reader holding stamps can
|
||||
/// tell they are still comparable with the live ones.
|
||||
renumbers: u32,
|
||||
/// While the update walk runs, every frame linked into a bucket since it opened, in link order
|
||||
/// ([`Self::open_link_log`]).
|
||||
link_log: Option<Vec<FrameHandle>>,
|
||||
/// The draw list's fingerprint cache ([`crate::order::traversal`]).
|
||||
pub(crate) order_cache: crate::order::OrderCache,
|
||||
/// Minimap frames ever created, so the state feed notices a new one without a walk.
|
||||
@@ -301,6 +307,8 @@ impl WidgetArena {
|
||||
regions: Arena::new(),
|
||||
names: HashMap::new(),
|
||||
next_insertion: 0,
|
||||
renumbers: 0,
|
||||
link_log: None,
|
||||
minimap_created: 0,
|
||||
order_cache: Default::default(),
|
||||
ticked_kinds: Vec::new(),
|
||||
@@ -381,14 +389,42 @@ impl WidgetArena {
|
||||
}
|
||||
}
|
||||
self.next_insertion = order.len() as u32;
|
||||
self.renumbers += 1;
|
||||
}
|
||||
let seq = self.next_insertion;
|
||||
if let Some(f) = self.frame_mut(h) {
|
||||
f.insertion_seq = seq;
|
||||
self.next_insertion += 1;
|
||||
if let Some(log) = &mut self.link_log {
|
||||
log.push(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start recording every link into a bucket, for the update walk (`0x765650`), which must see
|
||||
/// the frames a handler links while it runs.
|
||||
pub(crate) fn open_link_log(&mut self) {
|
||||
self.link_log = Some(Vec::new());
|
||||
}
|
||||
|
||||
/// The frames linked since the last call, in link order, the log staying open.
|
||||
pub(crate) fn take_linked(&mut self) -> Vec<FrameHandle> {
|
||||
self.link_log
|
||||
.as_mut()
|
||||
.map(std::mem::take)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Stop recording links.
|
||||
pub(crate) fn close_link_log(&mut self) {
|
||||
self.link_log = None;
|
||||
}
|
||||
|
||||
/// How many times the link stamps have been renumbered; stamps read under one count compare.
|
||||
pub(crate) fn renumbers(&self) -> u32 {
|
||||
self.renumbers
|
||||
}
|
||||
|
||||
/// The first frame created with `name`; a later duplicate never takes it over (`0x701bd0`).
|
||||
pub fn lookup(&self, name: &str) -> Option<FrameHandle> {
|
||||
self.names.get(name).copied()
|
||||
@@ -502,8 +538,14 @@ impl WidgetArena {
|
||||
| FrameKind::DressUpModel
|
||||
| FrameKind::TabardModel
|
||||
);
|
||||
let visible = frame.effective_visible;
|
||||
let (index, generation) = self.frames.insert(frame);
|
||||
let handle = FrameHandle { index, generation };
|
||||
if visible {
|
||||
if let Some(log) = &mut self.link_log {
|
||||
log.push(handle);
|
||||
}
|
||||
}
|
||||
if ticked {
|
||||
self.ticked_kinds.push(handle);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user