Lua verbs fire their events before returning, in the reference's order (#1015)
In 1.12.1 an event a Lua verb raises runs its handlers before the verb returns: `SignalEvent` (`0x703e50`) walks the listeners in place. About 35 benilla binding sites queued theirs for a later drain instead. - Verbs now raise inside `tick::verb` steps, and the events fire as each step's model borrow drops. A step follows the reference's split wherever it signals before changing state a getter reads: the money pickup, bag pickup and place, merchant grab, repair cursor, item use, action and pet-bar pickup and drop. - The grid events fire before `CURSOR_UPDATE`, and a clear's unlock before both, as at `0x4952b3`/`0x495455`. `ITEM_LOCK_CHANGED` carries no arguments. - `pending_events` is host-only now. Tests pin handler order per family, nesting from a handler, and the order changes. Fixes #999 Fixes #582 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
24c7cab983
commit
aba120cdcb
@@ -377,14 +377,13 @@ fn selected_row(model: &Model) -> Option<usize> {
|
||||
.checked_add(1)
|
||||
}
|
||||
|
||||
/// Queue `TRAINER_UPDATE`, which the reference fires from its mask commits, not from Lua:
|
||||
/// `0x4d8c90` writes the filter mask, re-runs the finalizer and fires event `0x136`, and the
|
||||
/// skill-line and expand-mask commits (`0x4d8cb0`, `0x4d8cd0`, the collapse verbs') do the same.
|
||||
/// The stock window's filter click (`Blizzard_TrainerUI.lua:524`) does not repaint.
|
||||
fn queue_trainer_update(model: &mut Model) {
|
||||
model
|
||||
.pending_events
|
||||
.push(("TRAINER_UPDATE".to_string(), Vec::new()));
|
||||
/// Raise `TRAINER_UPDATE`, as the reference's mask commits do after the re-filter: `0x4d8c90`
|
||||
/// writes the type mask, re-runs the finalizer `0x4d8410` and signals at `0x4d8ca0`; the
|
||||
/// skill-line and expand-mask commits (`0x4d8cb0`, `0x4d8cd0`, the collapse verbs') do the same
|
||||
/// (`0x4d8cc0`, `0x4d8ce0`). The stock window's filter click (`Blizzard_TrainerUI.lua:524`) does
|
||||
/// not repaint.
|
||||
fn raise_trainer_update(model: &mut Model) {
|
||||
model.raise("TRAINER_UPDATE", Vec::new());
|
||||
}
|
||||
|
||||
fn set_collapsed(model: &mut Model, id: usize, collapse: bool) {
|
||||
@@ -660,13 +659,14 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
g.set(
|
||||
"SetTrainerServiceTypeFilter",
|
||||
lua.create_function(|lua, (kind, on): (String, Value)| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
if let Some(c) = TrainerServiceCategory::from_filter_str(&kind) {
|
||||
// The stock window passes 1 or 0; nil, false and 0 disable.
|
||||
let enable = !matches!(on, Value::Nil | Value::Integer(0) | Value::Boolean(false));
|
||||
model.trainer_filter[c.filter_slot()] = enable;
|
||||
// Fires even when the bit did not move, as the reference's thunk does.
|
||||
queue_trainer_update(&mut model);
|
||||
super::tick::verb(lua, |model| {
|
||||
model.trainer_filter[c.filter_slot()] = enable;
|
||||
// Fires even when the bit did not move, as the reference's setter does.
|
||||
raise_trainer_update(model);
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?,
|
||||
@@ -677,18 +677,20 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
g.set(
|
||||
"CollapseTrainerSkillLine",
|
||||
lua.create_function(|lua, id: usize| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
set_collapsed(&mut model, id, true);
|
||||
queue_trainer_update(&mut model);
|
||||
super::tick::verb(lua, |model| {
|
||||
set_collapsed(model, id, true);
|
||||
raise_trainer_update(model);
|
||||
});
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
g.set(
|
||||
"ExpandTrainerSkillLine",
|
||||
lua.create_function(|lua, id: usize| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
set_collapsed(&mut model, id, false);
|
||||
queue_trainer_update(&mut model);
|
||||
super::tick::verb(lua, |model| {
|
||||
set_collapsed(model, id, false);
|
||||
raise_trainer_update(model);
|
||||
});
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
@@ -685,3 +685,19 @@ fn talent_order_sorts_on_state_within_a_group() {
|
||||
let got: Vec<String> = visible(&mut s).into_iter().map(|(n, _)| n).collect();
|
||||
assert_eq!(got, ["Riding", "Aye", "Bee", "Cee"]);
|
||||
}
|
||||
|
||||
/// The mask commits fire `TRAINER_UPDATE` inside the verb, after the re-filter (`0x4d8410`, then
|
||||
/// `0x4d8ca0`/`0x4d8ce0`): a handler reads the filtered tree before the next Lua line runs.
|
||||
#[test]
|
||||
fn the_filter_and_collapse_verbs_fire_trainer_update_in_place() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_trainer(Some(trainer()));
|
||||
crate::script::tick::heard::listen(&s, &["TRAINER_UPDATE"], Some("GetNumTrainerServices()"));
|
||||
let seen: Vec<String> = s
|
||||
.eval(
|
||||
"SetTrainerServiceTypeFilter('used', 0) local h = HEARD HEARD = {} \
|
||||
CollapseTrainerSkillLine(1) table.insert(h, HEARD[1]) return h",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(seen, ["TRAINER_UPDATE 4", "TRAINER_UPDATE 3"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user