From 83367549c492c0ad890ec63fbc6ef1981678921e Mon Sep 17 00:00:00 2001 From: Dusk <24+dusk@noreply.octowow.st> Date: Mon, 14 Sep 2026 09:08:50 +0000 Subject: [PATCH] Optimize GetContainerItemInfo hook and bump to 1.4.11 - Reduce temporary table allocations in GetContainerItemInfo - Preserve the client return signature safely - Fall back to the original hook if the signature is unexpected - Keep mailbox, Open All and send-mail behavior unchanged - Bump version to 1.4.11 --- AUDIT.md | 6 +++- README.md | 8 +++-- TurtleMail.toc | 2 +- TurtleMailFix.lua | 82 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/AUDIT.md b/AUDIT.md index b34b55e..058c615 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -31,6 +31,10 @@ Target: Turtle WoW 1.18.x / Octo client. - **First mailbox-open freeze/stutter after the 1.4.9 recovery fix:** 1.4.9 called `ensure_open_mail_button()` on every `MAIL_INBOX_UPDATE`. Initial inbox population can emit a burst of those events, so the button was repeatedly re-parented, re-anchored, re-labeled, resized, and given a new frame level. 1.4.10 removes that work from `MAIL_INBOX_UPDATE` and makes `ensure_open_mail_button()` state-aware, so it only mutates the UI when the button is missing, hidden, moved, re-parented, or otherwise incorrect. +## Fixed in 1.4.11 + +- **Residual mailbox hitch / excessive garbage generation:** the upstream `GetContainerItemInfo` hook used `pack()` + `unpack()` for every bag query, allocating a temporary table each time. Bag/mailbox refreshes can call this path very frequently and trigger avoidable garbage collection pauses. 1.4.11 replaces the installed hook after login with a fixed-local wrapper that performs no per-call table allocation. The native return count is detected once and the exact arity is preserved for signatures of up to 12 return values; unusual signatures fall back to the previous hook. + ## Remaining architectural risks upstream These are intentionally not rewritten in the compatibility layer because changing them would be much more invasive and could alter mail behavior: @@ -52,4 +56,4 @@ The compatibility build was tested in-game on Turtle WoW 1.18.x and the reported ## Status -**1.4.10 is the current stable release.** The fixes remain isolated in `TurtleMailFix.lua` so upstream code stays easy to compare and future upstream changes remain easier to merge. +**1.4.11 is the current test build pending in-game validation.** The fixes remain isolated in `TurtleMailFix.lua` so upstream code stays easy to compare and future upstream changes remain easier to merge. diff --git a/README.md b/README.md index 86df378..fe476c1 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ When enabled, TurtleMail records sent and received mail information in its dedic ## 🔧 Turtle WoW compatibility -Version **1.4.10** extends the compatibility and safety layer for Turtle WoW 1.18.x environments. +Version **1.4.11** extends the compatibility, safety, and performance layer for Turtle WoW 1.18.x environments. The compatibility work is isolated in `TurtleMailFix.lua` so the original TurtleMail code remains easier to compare with upstream versions. @@ -130,14 +130,16 @@ TurtleMail includes localization support for: ## 📜 Version -Current stable version: +Current test version: -**1.4.10** +**1.4.11** Based on upstream **TurtleMail 1.4.5**. Version 1.4.10 keeps the **Open Mail** recovery from 1.4.9 but makes it lightweight: the button is repaired on load/mailbox open only when needed, instead of being re-parented and re-anchored on every inbox refresh. This avoids repeated layout work during the initial mailbox update burst. +Version 1.4.11 also removes the per-call `pack()`/`unpack()` allocation from TurtleMail's `GetContainerItemInfo` hook. The native return count is detected once at login and preserved exactly (up to 12 values), while normal bag queries use fixed local values. This reduces garbage collection pressure during bag/mailbox refreshes without changing mail behavior. + ## 🖼️ Screenshots ![TurtleMail](https://i.imgur.com/H0MUmXd.png) diff --git a/TurtleMail.toc b/TurtleMail.toc index 2f6bbde..8f736f3 100644 --- a/TurtleMail.toc +++ b/TurtleMail.toc @@ -2,7 +2,7 @@ ## Interface: 11200 ## Title: |cffabd473Turtle|cffffffffMail ## Author: shirsig/sica, Dusk-92 (compatibility fixes) -## Version: 1.4.10 +## Version: 1.4.11 ## Notes: Mailbox enhancement - Turtle WoW 1.18.x compatibility ## SavedVariables: TurtleMail_AutoCompleteNames ## SavedVariablesPerCharacter: TurtleMail_To TurtleMail_Point TurtleMail_Log diff --git a/TurtleMailFix.lua b/TurtleMailFix.lua index 65d032d..2d0e2d9 100644 --- a/TurtleMailFix.lua +++ b/TurtleMailFix.lua @@ -8,7 +8,7 @@ local m = TurtleMail local getn = table.getn local function pack( ... ) return arg end -m.compat_version = "1.4.10" +m.compat_version = "1.4.11" local DEFAULT_SENT_FILTERS = { Money = 1, @@ -910,3 +910,83 @@ do end end end + +-- Performance compatibility fix inspired by Otari98/TurtleMail: the upstream +-- GetContainerItemInfo hook builds a temporary table with pack()/unpack() on +-- every bag query. Bag and mailbox refreshes can call this hook very frequently, +-- creating avoidable garbage and occasional GC hitches. Detect the native return +-- count once after the normal PLAYER_LOGIN hook setup, then use a fixed-local +-- wrapper with no per-call temporary table. Exact return arity is preserved for +-- up to 12 values so extended/custom clients remain compatible; if detection is +-- inconclusive, the existing upstream hook is left untouched. +do + local original_player_login = m.PLAYER_LOGIN + local legacy_hook = m.hooks and m.hooks.GetContainerItemInfo or nil + local detected_return_count + + local function count_returns( ... ) + return arg.n or getn( arg ) + end + + local function detect_container_info_return_count() + local fn = m.orig and m.orig.GetContainerItemInfo + if type( fn ) ~= "function" then return nil end + + -- Prefer an occupied inventory slot so clients that return no values for an + -- empty slot cannot make us detect the wrong arity. + if m.api.GetContainerNumSlots then + for bag = 0, 4 do + local slots = tonumber( m.api.GetContainerNumSlots( bag ) ) or 0 + for slot = 1, slots do + local texture = fn( bag, slot ) + if texture then + return count_returns( fn( bag, slot ) ) + end + end + end + end + + -- A normal character should always have at least one item, but retain a + -- conservative fallback for unusual test clients or empty inventories. + local count = count_returns( fn( 0, 1 ) ) + if count and count > 0 then return count end + return nil + end + + local function optimized_get_container_item_info( bag, slot ) + local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12 = + m.orig.GetContainerItemInfo( bag, slot ) + + if not r3 and m.sendmail_attached( bag, slot ) then + r3 = 1 + end + + if detected_return_count == 1 then return r1 end + if detected_return_count == 2 then return r1, r2 end + if detected_return_count == 3 then return r1, r2, r3 end + if detected_return_count == 4 then return r1, r2, r3, r4 end + if detected_return_count == 5 then return r1, r2, r3, r4, r5 end + if detected_return_count == 6 then return r1, r2, r3, r4, r5, r6 end + if detected_return_count == 7 then return r1, r2, r3, r4, r5, r6, r7 end + if detected_return_count == 8 then return r1, r2, r3, r4, r5, r6, r7, r8 end + if detected_return_count == 9 then return r1, r2, r3, r4, r5, r6, r7, r8, r9 end + if detected_return_count == 10 then return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10 end + if detected_return_count == 11 then return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11 end + if detected_return_count == 12 then return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12 end + + -- Should not normally be reached; keep the previous behavior if a custom + -- client exposes an unexpected signature. + if legacy_hook then return legacy_hook( bag, slot ) end + return r1, r2, r3, r4, r5 + end + + function m.PLAYER_LOGIN() + if original_player_login then original_player_login() end + + detected_return_count = detect_container_info_return_count() + if detected_return_count and detected_return_count >= 1 and detected_return_count <= 12 then + m.api.GetContainerItemInfo = optimized_get_container_item_info + end + end +end +