diff --git a/OctoLatency.lua b/OctoLatency.lua
index 6da3ac7..931691c 100644
--- a/OctoLatency.lua
+++ b/OctoLatency.lua
@@ -6,40 +6,72 @@
local elapsedTimer = 0
local UPDATE_INTERVAL = 2.0 -- update every 2 seconds
+local MAX_ERROR_LOG = 20
+
+local function OctoLatency_EnsureDB()
+ if (not OctoLatencyDB) then
+ OctoLatencyDB = {}
+ end
+ if (not OctoLatencyDB.errorLog) then
+ OctoLatencyDB.errorLog = {}
+ end
+ return OctoLatencyDB
+end
+
+-- Records an error to SavedVariables (visible in-game via /octolat errors,
+-- and readable straight out of the WTF SavedVariables file after logout)
+-- and pings the chat frame so it isn't silently swallowed.
+function OctoLatency_LogError(context, err)
+ local db = OctoLatency_EnsureDB()
+ local entry = {
+ time = date("%Y-%m-%d %H:%M:%S"),
+ context = context,
+ message = tostring(err),
+ }
+ table.insert(db.errorLog, entry)
+ while (table.getn(db.errorLog) > MAX_ERROR_LOG) do
+ table.remove(db.errorLog, 1)
+ end
+ DEFAULT_CHAT_FRAME:AddMessage("|cffff5555OctoLatency error|r in " .. context .. ": " .. tostring(err) .. " (type /octolat errors)")
+end
+
+-- Runs func in protected mode so one bad tick can't break the addon,
+-- and captures the error for later reporting instead of losing it.
+local function OctoLatency_SafeCall(context, func, ...)
+ local ok, err = pcall(func, ...)
+ if (not ok) then
+ OctoLatency_LogError(context, err)
+ end
+ return ok
+end
+
function OctoLatency_OnLoad(self)
- -- Register events
- self:RegisterEvent("VARIABLES_LOADED")
-
-- Tooltip on hover
self:SetScript("OnEnter", function()
- GameTooltip:SetOwner(this, "ANCHOR_TOP")
- GameTooltip:AddLine("OctoLatency", 0, 1, 1)
- local bandwidth, latency, homeLatency, worldLatency = GetNetStats()
- GameTooltip:AddLine("Home Latency: " .. homeLatency .. " ms", 1, 1, 1)
- GameTooltip:AddLine("World Latency: " .. worldLatency .. " ms", 1, 1, 1)
- GameTooltip:AddLine("Bandwidth: " .. string.format("%.2f", bandwidth) .. " KB/s", 1, 1, 1)
- GameTooltip:AddLine("Left-click and drag to move.", 0.7, 0.7, 0.7)
- GameTooltip:Show()
+ OctoLatency_SafeCall("OnEnter", function()
+ GameTooltip:SetOwner(this, "ANCHOR_TOP")
+ GameTooltip:AddLine("OctoLatency", 0, 1, 1)
+ local bandwidth, latency, homeLatency, worldLatency = GetNetStats()
+ GameTooltip:AddLine("Home Latency: " .. homeLatency .. " ms", 1, 1, 1)
+ GameTooltip:AddLine("World Latency: " .. worldLatency .. " ms", 1, 1, 1)
+ GameTooltip:AddLine("Bandwidth: " .. string.format("%.2f", bandwidth) .. " KB/s", 1, 1, 1)
+ GameTooltip:AddLine("Left-click and drag to move.", 0.7, 0.7, 0.7)
+ GameTooltip:Show()
+ end)
end)
-
+
self:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
end
-function OctoLatency_OnUpdate(elapsed)
- elapsedTimer = elapsedTimer + elapsed
- if (elapsedTimer < UPDATE_INTERVAL) then
- return
- end
- elapsedTimer = 0
-
+local function OctoLatency_UpdateText()
-- GetNetStats returns: bandwidth, latency, homeLatency, worldLatency
local _, _, homeLatency, worldLatency = GetNetStats()
-
+
-- We'll display world latency (or max of home/world)
local latency = worldLatency or homeLatency or 0
-
+
-- Color thresholds: Green < 150ms, Yellow 150-350ms, Red > 350ms
local colorHex
if (latency < 150) then
@@ -49,10 +81,28 @@ function OctoLatency_OnUpdate(elapsed)
else
colorHex = "|cffff0000" -- Red
end
-
+
OctoLatencyText:SetText(colorHex .. latency .. " ms|r")
end
+function OctoLatency_OnUpdate(elapsed)
+ elapsedTimer = elapsedTimer + elapsed
+ if (elapsedTimer < UPDATE_INTERVAL) then
+ return
+ end
+ elapsedTimer = 0
+
+ OctoLatency_SafeCall("OnUpdate", OctoLatency_UpdateText)
+end
+
+function OctoLatency_StopMoving(self)
+ if (self.isMoving) then
+ self:StopMovingOrSizing()
+ self.isMoving = false
+ OctoLatency_SavePosition()
+ end
+end
+
function OctoLatency_SavePosition()
if (not OctoLatencyDB) then
OctoLatencyDB = {}
@@ -68,8 +118,35 @@ end
local loadFrame = CreateFrame("Frame")
loadFrame:RegisterEvent("PLAYER_LOGIN")
loadFrame:SetScript("OnEvent", function()
- if (OctoLatencyDB and OctoLatencyDB.point) then
- OctoLatencyFrame:ClearAllPoints()
- OctoLatencyFrame:SetPoint(OctoLatencyDB.point, UIParent, OctoLatencyDB.relativePoint, OctoLatencyDB.xOfs, OctoLatencyDB.yOfs)
- end
+ OctoLatency_SafeCall("PLAYER_LOGIN", function()
+ if (OctoLatencyDB and OctoLatencyDB.point) then
+ OctoLatencyFrame:ClearAllPoints()
+ OctoLatencyFrame:SetPoint(OctoLatencyDB.point, UIParent, OctoLatencyDB.relativePoint, OctoLatencyDB.xOfs, OctoLatencyDB.yOfs)
+ end
+ end)
end)
+
+--------------------------------------------------------------------------------
+-- Slash command: /octolat errors | clearerrors
+--------------------------------------------------------------------------------
+SLASH_OCTOLATENCY1 = "/octolat"
+SlashCmdList["OCTOLATENCY"] = function(msg)
+ local db = OctoLatency_EnsureDB()
+ msg = string.lower(msg or "")
+
+ if (msg == "errors") then
+ if (table.getn(db.errorLog) == 0) then
+ DEFAULT_CHAT_FRAME:AddMessage("OctoLatency: no errors logged.")
+ return
+ end
+ DEFAULT_CHAT_FRAME:AddMessage("OctoLatency: last " .. table.getn(db.errorLog) .. " error(s):")
+ for i, entry in ipairs(db.errorLog) do
+ DEFAULT_CHAT_FRAME:AddMessage("[" .. entry.time .. "] " .. entry.context .. ": " .. entry.message)
+ end
+ elseif (msg == "clearerrors") then
+ db.errorLog = {}
+ DEFAULT_CHAT_FRAME:AddMessage("OctoLatency: error log cleared.")
+ else
+ DEFAULT_CHAT_FRAME:AddMessage("OctoLatency commands: /octolat errors, /octolat clearerrors")
+ end
+end
diff --git a/OctoLatency.xml b/OctoLatency.xml
index 7ecf9bf..609e72e 100644
--- a/OctoLatency.xml
+++ b/OctoLatency.xml
@@ -47,12 +47,13 @@
end
- if (arg1 == "LeftButton" and this.isMoving) then
- this:StopMovingOrSizing();
- this.isMoving = false;
- OctoLatency_SavePosition();
+ if (arg1 == "LeftButton") then
+ OctoLatency_StopMoving(this);
end
+
+ OctoLatency_StopMoving(this);
+
diff --git a/README.md b/README.md
index 628ad10..30f79ff 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,38 @@
-## OctoLatency Addon Optimization Review & Recommendations
----
+## OctoLatency
-### New Addon: `OctoLatency`
-We are creating a dedicated, highly optimized, lightweight Vanilla WoW addon called **OctoLatency**.
-- It provides a movable, clean text indicator showing current latency (home and world latency via `GetNetStats()`).
-- Color codes the latency (tuned for private servers where up to 250ms is common/acceptable):
- - **Green**: < 150 ms (Good)
- - **Yellow**: 150 - 350 ms (Warning / Moderate)
- - **Red**: > 350 ms (Critical / High)
-- Fully supports drag-and-drop repositioning with position saving via SavedVariables.
-- Throttled updates (updates every 2 seconds rather than every frame) to ensure zero impact on game performance.
+A lightweight latency monitor for Vanilla WoW (1.12.1). Shows a small, movable,
+color-coded latency readout on your screen.
+
+### What it does
+
+- Displays your current world latency (falls back to home latency if world
+ isn't available) as `NNN ms` next to your screen.
+- Color-codes the number so you can tell your connection quality at a glance:
+ - **Green** — under 150 ms (good)
+ - **Yellow** — 150-350 ms (moderate)
+ - **Red** — over 350 ms (critical)
+- Hover the frame for a tooltip with home latency, world latency, and
+ bandwidth.
+- Updates every 2 seconds (not every frame), so it has no meaningful impact on
+ game performance.
+
+### Options
+
+There's no settings panel — the addon is intentionally minimal. The only
+"option" is position:
+
+- **Move it**: left-click and drag the frame anywhere on screen. Its position
+ is saved automatically and restored on your next login.
+
+### Error reporting
+
+If something inside the addon errors (e.g. an unexpected `GetNetStats()`
+response), it's caught instead of spamming a Lua error popup, and logged for
+you to report:
+
+- `/octolat errors` — print the last 20 logged errors (with timestamps) to
+ chat.
+- `/octolat clearerrors` — clear the error log.
+
+The log is also saved to your `SavedVariables` file, so it survives
+logout/relog.