texture miss: '' was three setters storing an empty path
`Texture:SetTexture("")` already cleared, and so did a button's slot setters — the reference treats
nil and `""` alike on the path arm (`0x770200`). But `SetStatusBarTexture`, `SetThumbTexture` and
the two `ColorSelect` setters stored the empty string, so the region reached the extractor as
`path: Some("")`, and the renderer asked the patch chain for a nameless file.
What that cost is less than the warning suggested, and the comments say so rather than the opposite:
both the decode and the warning are cached per key, so it was one chain lock and two candidate
reads, once. The damage was the line itself — a defect-shaped warning with nothing under it, in a
client where a real `texture miss` is worth reading. pfUI blanks textures this way in a dozen
places, which is why it showed up at all.
Measured both ways: the new test reports `[""]` with any one guard removed, and three UI captures
(`ui-char`, `ui-options`, `ui-actionbar`) now log zero `texture miss` of any kind. His own
2026-10-28 log has the line once, at 20.7 s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -144,6 +144,14 @@ fn install_slot_texture(
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let data = model.region_data.entry(rh).or_default();
|
||||
match &a1 {
|
||||
// `""` clears, as `SetTexture`'s own path arm does (`0x770200`). See the
|
||||
// note on `SetStatusBarTexture`: stored, an empty path reaches the extractor
|
||||
// as `path: Some("")` and the renderer logs a `texture miss: ''` for a file
|
||||
// nobody named.
|
||||
Value::String(s) if s.to_str()?.is_empty() => {
|
||||
data.texture = None;
|
||||
data.fill = None;
|
||||
}
|
||||
Value::String(s) => {
|
||||
data.texture = Some(s.to_str()?.to_string());
|
||||
data.fill = None;
|
||||
|
||||
@@ -169,6 +169,14 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let data = model.region_data.entry(rh).or_default();
|
||||
match &a1 {
|
||||
// `""` clears, as `SetTexture`'s own path arm does (`0x770200`). See the
|
||||
// note on `SetStatusBarTexture`: stored, an empty path reaches the extractor
|
||||
// as `path: Some("")` and the renderer logs a `texture miss: ''` for a file
|
||||
// nobody named.
|
||||
Value::String(s) if s.to_str()?.is_empty() => {
|
||||
data.texture = None;
|
||||
data.fill = None;
|
||||
}
|
||||
Value::String(s) => {
|
||||
data.texture = Some(s.to_str()?.to_string());
|
||||
data.fill = None;
|
||||
|
||||
@@ -160,6 +160,17 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let data = model.region_data.entry(rh).or_default();
|
||||
match &a1 {
|
||||
// `""` clears, as `SetTexture`'s path arm does (`0x770200`: "nil or `\"\"`
|
||||
// clear it") and as the button slot setter does. Stored, it reached the
|
||||
// extractor as `path: Some("")`, so the renderer asked the patch chain for a
|
||||
// nameless file and logged `texture miss: ''`. Both the decode and the warning
|
||||
// are cached per key, so the cost was one lock and two candidate reads, once —
|
||||
// the damage was the warning itself, a real-looking signal with no defect
|
||||
// under it. pfUI blanks textures this way in a dozen places.
|
||||
Value::String(s) if s.to_str()?.is_empty() => {
|
||||
data.texture = None;
|
||||
data.fill = None;
|
||||
}
|
||||
Value::String(s) => {
|
||||
data.texture = Some(s.to_str()?.to_string());
|
||||
data.fill = None;
|
||||
|
||||
@@ -195,3 +195,62 @@ fn statusbar_get_texture_returns_the_bar_region() {
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// `""` clears, on every setter that takes a texture path — not just `SetTexture`.
|
||||
///
|
||||
/// The reference's path arm treats nil and `""` alike (`0x770200`), and ours did for
|
||||
/// `Texture:SetTexture` and for a button's slot textures while `SetStatusBarTexture`,
|
||||
/// `SetThumbTexture` and the two `ColorSelect` setters stored the empty string. Stored, it reached
|
||||
/// the extractor as `path: Some("")`, and the renderer then asked the patch chain for a nameless
|
||||
/// file and logged `texture miss: ''`. The decode and the warning are both cached per key, so the
|
||||
/// cost was one lock and two candidate reads once — what it really produced was a defect-shaped
|
||||
/// warning with no defect under it, in a client where a real `texture miss` is worth reading.
|
||||
/// pfUI blanks textures this way in a dozen places.
|
||||
#[test]
|
||||
fn an_empty_path_clears_every_texture_setter_not_only_set_texture() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.run(
|
||||
r#"
|
||||
Blanked = {}
|
||||
local b = CreateFrame("StatusBar", "SbBlank")
|
||||
b:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0)
|
||||
b:SetWidth(200); b:SetHeight(20)
|
||||
b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||||
b:SetMinMaxValues(0, 100) b:SetValue(100)
|
||||
table.insert(Blanked, function() b:SetStatusBarTexture("") end)
|
||||
|
||||
local sl = CreateFrame("Slider", "SlBlank")
|
||||
sl:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 40)
|
||||
sl:SetWidth(100); sl:SetHeight(16)
|
||||
sl:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Horizontal")
|
||||
table.insert(Blanked, function() sl:SetThumbTexture("") end)
|
||||
|
||||
local cs = CreateFrame("ColorSelect", "CsBlank")
|
||||
cs:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 80)
|
||||
cs:SetWidth(128); cs:SetHeight(128)
|
||||
cs:SetColorWheelTexture("Interface\\Buttons\\UI-ColorPicker-Buttons")
|
||||
table.insert(Blanked, function() cs:SetColorWheelTexture("") end)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
let paths = |s: &UiScript| -> Vec<String> {
|
||||
s.extract()
|
||||
.iter()
|
||||
.filter_map(|q| match &q.content {
|
||||
QuadContent::Texture { path: Some(p), .. } => Some(p.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
assert_eq!(paths(&s).len(), 3, "three textured regions to begin with");
|
||||
|
||||
s.run("for _, f in ipairs(Blanked) do f() end").unwrap();
|
||||
s.resolve();
|
||||
assert!(
|
||||
paths(&s).is_empty(),
|
||||
"an empty path must leave no textured quad at all, let alone a nameless one: {:?}",
|
||||
paths(&s)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user