hotfix: the cold-breath classifier survives a despawning unit
- entering the world or teleporting could crash: the cold-breath classifier touched a unit the streamed world had just despawned, and the deferred insert panicked on the dead entity — it now tolerates the race Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -233,10 +233,21 @@
|
||||
-- page 5, else the paged/bonus formula); we carry the fork as data — a fixed per-button
|
||||
-- page base set at OnLoad (MultiBars.xml), nil for the bonus-paged main bar.
|
||||
function BenillaActionButton_Action(button)
|
||||
-- `index` falls back to the button's ID, which is where the REFERENCE reads it from:
|
||||
-- `ActionButton_GetPagedID` uses `this:GetID()`, and our `.index` is only that value
|
||||
-- cached at OnLoad. A button that inherits `ActionBarButtonTemplate` and wires its own
|
||||
-- OnLoad — zBar.xml:7 does exactly this — never runs ours, so `.index` is nil while
|
||||
-- `GetID()` is set from the XML `id=`. Before the fallback, our own handler raised
|
||||
-- `ActionBar.xml:228: arithmetic on local 'index'` on somebody else's button.
|
||||
--
|
||||
-- Still nothing? Action 0, which is not a real slot: every reader answers nil for it
|
||||
-- and the button draws empty. Deliberately not an early `return nil` — the callers are
|
||||
-- arithmetic all the way down, and a nil would move the raise rather than remove it.
|
||||
local index = button.index or button:GetID() or 0
|
||||
if button.base then
|
||||
return button.base + button.index
|
||||
return button.base + index
|
||||
end
|
||||
return BenillaActionBar_ActionFor(button.index)
|
||||
return BenillaActionBar_ActionFor(index)
|
||||
end
|
||||
|
||||
-- Hover: SetAction's pure delegation (decision 0276 — a SPELL slot renders the spell
|
||||
@@ -991,7 +1002,19 @@
|
||||
ButtonHilight-Square ADD (l.87); CheckedTexture CheckButtonHilight ADD (l.88 — the
|
||||
IsCurrentAction/IsAutoRepeatAction ring). Only Name (macros) stays OUT.
|
||||
-->
|
||||
<CheckButton name="BenillaActionButtonTemplate" virtual="true">
|
||||
<!-- **The reference's own two-level split, which ours used to conflate into one template.**
|
||||
`ActionButtonTemplate` (ref ActionButtonTemplate.xml:3) is REGIONS ONLY and carries no
|
||||
scripts at all; `ActionBarButtonTemplate` (ref ActionBarFrame.xml:4) inherits it and adds
|
||||
the handler set. Addons rely on both halves being separately inheritable: `zBar.xml:7` is
|
||||
`<CheckButton name="zBarButtonTemplate" inherits="ActionBarButtonTemplate">` and then
|
||||
wires its OWN OnLoad, and its per-button code reads
|
||||
`getglobal(button:GetName().."NormalTexture")` — a derived name it only gets by inheriting
|
||||
a template that declares `$parentNormalTexture`.
|
||||
2 corpus addons name `ActionBarButtonTemplate` in an XML `inherits=`; with neither name
|
||||
published they got a bare frame, no art, no regions, and no error (1203's silent shape).
|
||||
`BenillaActionButtonTemplate` survives below as a pure alias so every one of our own
|
||||
`inherits=` sites is untouched. -->
|
||||
<CheckButton name="ActionButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="36" y="36"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
@@ -1010,6 +1033,16 @@
|
||||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<!-- ref ActionButtonTemplate.xml's `$parentName` — the macro-name line along the
|
||||
button's bottom edge. Empty by default, so it draws nothing until something
|
||||
sets it; declared because it is part of the template's published contract and
|
||||
an addon indexing `<button>Name` would otherwise get nil. -->
|
||||
<FontString name="$parentName" inherits="GameFontHighlightSmallOutline">
|
||||
<Size><AbsDimension x="36" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentBorder" file="Interface\Buttons\UI-ActionButton-Border" hidden="true" alphaMode="ADD">
|
||||
@@ -1037,6 +1070,10 @@
|
||||
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
|
||||
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
|
||||
</CheckButton>
|
||||
|
||||
<!-- The bar half: the reference's `ActionBarButtonTemplate`, regions inherited, handlers added. -->
|
||||
<CheckButton name="ActionBarButtonTemplate" inherits="ActionButtonTemplate" virtual="true">
|
||||
<Scripts>
|
||||
<OnClick>BenillaActionButton_OnClick(self)</OnClick>
|
||||
<OnDragStart>BenillaActionButton_OnDragStart(self)</OnDragStart>
|
||||
@@ -1047,6 +1084,11 @@
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<!-- Pure alias, so every existing `inherits="BenillaActionButtonTemplate"` in this file,
|
||||
MultiBars.xml, StanceBar.xml and PetActionBar.xml keeps resolving to exactly what it did
|
||||
before the split. -->
|
||||
<CheckButton name="BenillaActionButtonTemplate" inherits="ActionBarButtonTemplate" virtual="true"/>
|
||||
|
||||
<!-- The bar: 1024x53 anchored BOTTOM of the screen (ref-MainMenuBar.xml l.4-10). -->
|
||||
<Frame name="MainMenuBar">
|
||||
<Size><AbsDimension x="1024" y="53"/></Size>
|
||||
|
||||
@@ -169,7 +169,9 @@ pub(super) fn classify_breath(
|
||||
Some(id) if areas.0.is_cold(id) => Breath::Cold,
|
||||
_ => Breath::None,
|
||||
};
|
||||
commands.entity(entity).insert(BreathEnv {
|
||||
// try_insert: a streamed unit can despawn (teardown, teleport, range-out) between this
|
||||
// system's parallel run and its sync point — a plain insert panics on the dead entity.
|
||||
commands.entity(entity).try_insert(BreathEnv {
|
||||
kind,
|
||||
stale_at: now + RECLASSIFY_SECS,
|
||||
});
|
||||
|
||||
@@ -1073,3 +1073,85 @@ fn the_reference_action_bar_constants_are_defined() {
|
||||
"CURRENT_ACTIONBAR_PAGE is mutable page state we do not keep — nil fails loudly, a frozen 1 lies"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The reference's two-level action-button split, both halves inheritable by name.**
|
||||
///
|
||||
/// `ActionButtonTemplate` (ref ActionButtonTemplate.xml:3) is regions only and carries NO scripts;
|
||||
/// `ActionBarButtonTemplate` (ref ActionBarFrame.xml:4) inherits it and adds the handlers. Ours
|
||||
/// conflated them under one `Benilla*` name, so an addon inheriting either reference name got a
|
||||
/// bare frame — no art, no regions, and no error (1203's silent shape).
|
||||
///
|
||||
/// `zBar.xml:7` is the corpus shape: it inherits `ActionBarButtonTemplate`, wires its own OnLoad,
|
||||
/// and then reads `getglobal(button:GetName().."NormalTexture")` — a derived name it only has
|
||||
/// because the template declares `$parentNormalTexture`. That read is where it died.
|
||||
///
|
||||
/// The last assertion is the one that keeps our own bars safe: the alias must still resolve to the
|
||||
/// full thing, or 48 `inherits=` sites across four files silently lose their handlers.
|
||||
#[test]
|
||||
fn both_reference_action_button_templates_are_inheritable() {
|
||||
let s = UiScript::new().unwrap();
|
||||
load_action_bar(&s);
|
||||
|
||||
// zBar's exact shape: inherit the bar template, supply your own OnLoad.
|
||||
let doc = benilla_ui::framexml::parse(
|
||||
r#"<Ui>
|
||||
<CheckButton name="ZLikeButton" inherits="ActionBarButtonTemplate" id="1">
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BareLikeButton" inherits="ActionButtonTemplate" id="1">
|
||||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||||
</CheckButton>
|
||||
</Ui>"#,
|
||||
)
|
||||
.unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(
|
||||
report.errors.is_empty(),
|
||||
"loader errors: {:?}",
|
||||
report.errors
|
||||
);
|
||||
assert!(
|
||||
!report
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.contains("unknown template")),
|
||||
"both names must resolve: {:?}",
|
||||
report.warnings
|
||||
);
|
||||
|
||||
// The derived name zBar reads, on a button built from each half.
|
||||
for owner in ["ZLikeButton", "BareLikeButton"] {
|
||||
assert!(
|
||||
s.eval::<bool>(&format!("return {owner}NormalTexture ~= nil"))
|
||||
.unwrap(),
|
||||
"{owner}NormalTexture — zBar.lua:88's read"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>(&format!(
|
||||
"return {owner}Icon ~= nil and {owner}Cooldown ~= nil"
|
||||
))
|
||||
.unwrap(),
|
||||
"{owner} must carry the template's regions"
|
||||
);
|
||||
}
|
||||
|
||||
// The base half carries NO handlers, exactly as the reference's does — an addon inheriting it
|
||||
// wires its own, and must not silently receive ours.
|
||||
assert!(
|
||||
!s.eval::<bool>("return BareLikeButton:GetScript(\"OnClick\") ~= nil")
|
||||
.unwrap(),
|
||||
"ActionButtonTemplate is regions only; handlers belong to the bar half"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>("return ZLikeButton:GetScript(\"OnClick\") ~= nil")
|
||||
.unwrap(),
|
||||
"ActionBarButtonTemplate carries the handler set"
|
||||
);
|
||||
|
||||
// ...and our own alias still resolves to the full thing.
|
||||
assert!(
|
||||
s.eval::<bool>("return ActionButton1NormalTexture ~= nil and ActionButton1:GetScript(\"OnClick\") ~= nil")
|
||||
.unwrap(),
|
||||
"BenillaActionButtonTemplate's 48 inherits= sites must be untouched by the split"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user