Skip to content

Runtime Adapters

Chronic Tinkerer edited this page May 6, 2026 · 1 revision

Runtime Adapters

Adapters are the third leg of LibCodex's hybrid load model. While bundled seeds and SavedVariables are static (loaded at startup, frozen during the session), adapters run during play and capture data from live game events.

For the registration API, see API Reference: Adapters. This page covers the built-in Runtime adapter and the patterns for writing your own.

What an adapter is

An adapter is a function with the signature function(LC) ... end. You register it via LibCodex:RegisterAdapter(name, fn) from anywhere — your module file, an addon's OnLoad, even a ## OptionalDeps-loaded helper. Adapters run once at PLAYER_LOGIN via LC:RunAdapters() (called by the library's bootstrap event handler).

The LC argument is the library object. From there you reach modules with the typed accessors (LC:NPCs(), LC:Items(), etc.) and call :Add(entry) to push data in.

Adapters that need ongoing capture (most do) install their own event hooks during the initial run. The library doesn't manage that lifecycle — your adapter owns its event frame.

Failure isolation

RunAdapters() wraps each call in pcall. An adapter that errors logs a chat warning, gets marked ran = true, and is not retried. Other adapters keep running. This is intentional: a misbehaving adapter shouldn't block the entire library load.

The built-in Runtime adapter

Adapters/Runtime.lua ships with the library and is registered under the name "Runtime". It captures the kind of data you'd otherwise need offline scrapes for:

Event What it captures
NAME_PLATE_UNIT_ADDED NPC id, label, level, classification, faction side, current map position
UPDATE_MOUSEOVER_UNIT Same as above, plus first-time GameObjects via tooltip
PLAYER_TARGET_CHANGED Same as nameplate path
PLAYER_FOCUS_CHANGED Same as nameplate path
BAG_UPDATE_DELAYED Every itemID currently in player bags
GET_ITEM_INFO_RECEIVED Drains async item-info requests and enriches Items entries
LOOT_OPENED / LOOT_READY Forward + reverse pointers: item-from-source, source-drops-item
CHAT_MSG_LOOT Backup capture for items obtained via auto-loot AoE pulls
COMBAT_LOG_EVENT_UNFILTERED Records most-recent kill GUID and position (used as loot fallback)
QUEST_ACCEPTED Quest pickup point (mapID, x, y), faction side, chromie/expansion
QUEST_TURNED_IN Marks the quest as turned-in
QUEST_LOG_UPDATE Throttled refresh of every active quest entry (10s minimum)
TAXIMAP_OPENED Walks every visible flight node, tags with player faction
ENCOUNTER_START / ENCOUNTER_END Boss encounter id/name/difficulty + per-character kill counter

The adapter also performs an initial pass 2 seconds after login: a full bag scan, connected-realm sync, and a nameplate sweep. The 2-second delay lets WoW's APIs settle.

GUID decoding

Runtime.lua includes a generic GUID decoder that classifies entities into npc, gameobject, or unknown. Both NPC and GameObject capture paths use it. GUIDs starting with Creature-, Vehicle-, or Pet- are treated as NPCs; GameObject- prefixes go to the GameObjects module.

Faction inference

Faction sides ("A", "H", "B") are inferred from UnitReaction relative to the player's faction. Friendly = same side, hostile = opposite, neutral = both. This is a heuristic; cross-faction zones can mislabel. Hand-curated entries with _handcrafted = true override.

GameObject tooltip hook

GameObjects (chests, herb nodes, ore veins, mailboxes) aren't units, so UnitGUID and the nameplate system never see them. The adapter installs a TooltipDataProcessor post-call hook on Enum.TooltipDataType.GameObject (10.0.2+). On older clients without TooltipDataProcessor, the hook is a no-op.

Configuration

The Runtime adapter reads settings from LibCodexDB:

  • autoScan (default true) — periodic nameplate sweep ticker.
  • autoScanInterval (default 5 seconds, minimum 2) — interval for the auto-scan ticker.

Both can be toggled at runtime:

local R = LibCodex.Runtime
R.SetAutoScan(true, 5)        -- enable, 5-second interval
R.SetAutoScan(false)          -- disable
print(R.IsAutoScanning())     -- true / false

Verbose mode

For debugging or just curiosity about what the Runtime adapter is seeing:

LibCodex.Runtime.SetVerbose(true)

Or via slash command: /codex verbose on. Captures get logged to LibCodex.Log as they happen. Off by default.

Public helpers

The Runtime adapter exposes a few helpers for slash commands and other addons to call:

Function Notes
R.ScanNow() One-shot scan: bags + every active unit token + nameplates + realms. Returns a report table with counts.
R.ReadUnit(unit) Capture a single unit token ("target", "mouseover", etc.).
R.ScanBag(bagID) Scan one bag.
R.ScanAllBags() Scan bags 0..4.
R.ScanNameplates() Walk every visible nameplate + boss frames + pet/pettarget.
R.SyncRealms() Re-pull connected realms via GetAutoCompleteRealms.
R.EnableFriendlyNameplates() Set the nameplateShow* CVars so friendly NPCs (vendors, quest-givers) show plates.

Self-protection: tainted-event skip list

In modern Retail, calling RegisterEvent on certain events from inside an addon-owned frame can trigger ADDON_ACTION_FORBIDDEN. Once an event is forbidden, repeated registration attempts pollute logs and can taint the secure execution path.

The Runtime adapter installs an ADDON_ACTION_FORBIDDEN watcher and persists offending event names to LibCodexDB.runtimeBadEvents. Future sessions skip those events silently. The adapter also checks C_EventUtils.IsEventValid (when available) before any RegisterEvent call — calling RegisterEvent with an unknown event name itself sets taint, even when wrapped in pcall.

Writing your own adapter

The pattern, distilled to a sketch:

local LC = LibStub("LibCodex-1.0")

LC:RegisterAdapter("MyAddon.Quests", function(LC)
    -- Anonymous frames: named frames can flag ADDON_ACTION_FORBIDDEN
    -- in modern Retail. Anonymous is safer.
    local f = CreateFrame("Frame")
    f:RegisterEvent("QUEST_ACCEPTED")
    f:SetScript("OnEvent", function(_, evt, questID)
        local Quests = LC:Quests()
        if Quests and type(questID) == "number" then
            Quests:Add({
                id = questID,
                label = (C_QuestLog.GetTitleForQuestID
                          and C_QuestLog.GetTitleForQuestID(questID)) or nil,
                sources = { "MyAddon.Quests" },
            })
        end
    end)
end)

Tag every entry your adapter pushes with a unique source name. That way, if a downstream consumer ever needs to filter or audit data by origin, your contributions are identifiable.

When NOT to write an adapter

If you're enriching a single addon's local cache, just call Add directly from your own code. Adapters are for data you want LibCodex itself to retain across sessions and surface to other consumers. The persistence and merge plumbing all flow through the registered modules — anything you push via Add becomes part of LibCodexDB at logout.

Clone this wiki locally