-
Notifications
You must be signed in to change notification settings - Fork 0
SFUtils_HookManager
HookManager is a centralized registry-based utility for efficiently managing multiple Elder Scrolls Online (ESO) API hooks. Instead of tracking individual hook variables scattered throughout your code, HookManager stores all hooks in a single manager instance, enabling powerful batch control and lifecycle management.
| Feature | Benefit |
|---|---|
| Batch Control | Enable/disable all hooks at once (ideal for feature toggles) |
| Dynamic State | Toggle individual hooks on/off without re-registration |
| Safety | Wraps callbacks with sfutil.safeCall to prevent UI-breaking errors |
| Identification | Unique ID per hook for easy retrieval and manipulation |
| Centralized Tracking | Single source of truth for all addon hooks |
- Requires LibSFUtils (accessed via global
SForLibSFUtils)
-- Assuming LibSFUtils is already loaded local HookManager = LibSFUtils.HookManager -- Or using the global SF reference (common in ESO addons) local HookManager = SF.HookManager
local myHooks = HookManager:New("MyAddon_Hooks")
-- Register a pre-hook local mailHook = myHooks:PreHook(MAIL_INBOX, "SendMail", function(...) zo_dlog("Intercepting mail send...") return false -- Continue with original function end)
-- Later: disable hook temporarily myHooks:disable(mailHook.id)
-- Re-enable myHooks:enable(mailHook.id)
-- On addon unload myHooks:disableAll() -- Pause all hooks myHooks:disableAll() -- Clean up
Constructor local manager = HookManager:New(baseName) ParameterTypeDefaultDescriptionbaseNamestring"HookManager"Prefix for generating unique hook IDs Returns: New HookManager instance with internal state:{ base = "MyAddon_Hooks", -- Your custom prefix cnt = 1, -- Auto-incrementing counter hooks = {} -- Internal registry (id → hook table) } Example:-- Different base names allow multiple managers in same addon local combatHooks = HookManager:New("CombatModule") local uiHooks = HookManager:New("UI_Module")
All registration methods return a hook table object that represents the registered hook. All hooks are created with enabled = true by default. ###Pre-Hook Runs before the original function. Returning true from the callback cancels the original function execution.local hookObj = manager:PreHook(target, method, fn)ParameterTypeDescriptiontargettableObject containing the method (e.g., MAIL_INBOX, _G)methodstringMethod name (case-sensitive)fnfunctionCallback function (signature matches original) Returns: Hook table or nil if ID collision detected. Usage Example:local validateHook = myHooks:PreHook(MAIL_INBOX, "SendMail", function(self, ...) -- Validate mail before sending local subject = ... if #subject > 50 then uierror("Subject too long!") return true -- Cancel original end return false -- Allow original to run end)
Runs after the original function. The callback's return value is ignored (cannot cancel the original).
local hookObj = manager:PostHook(target, method, fn)ParameterTypeDescriptiontargettableObject containing the methodmethodstringMethod name (case-sensitive)fnfunctionCallback function Usage Example:
local logHook = myHooks:PostHook(SKILL_BAR, "RefreshSkills", function(...)
zo_dlog("Skill bar refreshed at " .. ZO_GetTimeString())
end)Uses SecurePostHook for combat/security-critical functions. Errors in the callback are swallowed via sfutil.safeCall10.
local hookObj = manager:SecurePostHook(target, method, fn)ParameterTypeDescriptiontargettableObject containing the method (e.g., COMBAT)methodstringMethod name (case-sensitive)fn functionCallback function Important: Secure hooks cannot be cancelled and use safeCall10 for error handling. Usage Example:
local combatHook = myHooks:SecurePostHook(_G, "CastAbility", function(...)
-- Track ability casts (secure context)
LogAbilityUsed(...)
end)##Hook Table Properties The object returned by registration methods contains: PropertyTypeDescriptionidstringUnique identifier (e.g., "MyAddon_Hooks_1")targettableThe object that owns the methodmethodstringMethod name (case-sensitive)fnfunctionOriginal callback functionkindstringHook type ("pre", "post", or "secure")enabledbooleanWhether hook is currently active Metatable: Hook objects inherit from HookManager, allowing method calls:-- You can call manager methods passing the hook table directly
-- (though typically you'll use the id)
local hook = myHooks:PreHook(...)
d(hook.id) -- "MyAddon_Hooks_1"
d(hook.kind) -- "pre"
d(hook.target) -- MAIL_INBOX (table)
d(hook.method) -- "SendMail" (string)Individual Hook Control MethodDescriptionmanager:get(id)Retrieve hook table by ID (returns nil if not found)manager:enable(id)Activate specific hookmanager:disable(id)Deactivate specific hook (callback skipped, but registered)manager:toggle(id)Flip hook state (active ↔ inactive)manager:remove(id)Completely remove hook from registry Examples:
-- Get hook info
local hook = myHooks:get("MyAddon_Hooks_1")
if hook then
d(hook.kind, hook.method, hook.enabled)
end
-- Toggle debug logging
myHooks:toggle(logHook.id)
-- Remove hook permanently
myHooks:remove(logHook.id)##Batch Control
| Method | Description |
|---|---|
| manager:enableAll() | Activate all registered hooks |
| manager:disableAll() | Deactivate all registered hooks |
| manager:toggleAll() | Flip state of all registered hooks |
Use Cases:
-- Pause all hooks when addon is disabled
local function OnAddonUnload(...)
myHooks:disableAll() -- Stop all callbacks
-- Optionally remove if cleanup needed
end
-- Resume all hooks when addon reloads
local function OnAddonLoaded(...)
myHooks:enableAll() -- Reactivate all
end
-- Toggle all debugging at once
local function ToggleDebug()
myHooks:toggleAll()
end-- =============================================================================
-- MyAddon Main File (EsoMain.txt / Init.lua)
-- =============================================================================
local MY_ADDON_NAME = "MyAddon"
local myHooks = nil
-- Event Handler for Addon Loading
local function OnAddonLoaded(eventCode, addonName)
if addonName ~= MY_ADDON_NAME then return end
-- Initialize HookManager
myHooks = HookManager:New(MY_ADDON_NAME .. "_Hooks")
-- Register hooks
myHooks:PreHook(MAIL_INBOX, "SendMail", OnMailValidate)
myHooks:PostHook(INVENTORY_MANAGER, "UpdateSlot", OnInventoryUpdated)
myHooks:SecurePostHook(_G, "CastAbility", OnAbilityUsed)
zo_dlog("MyAddon: All hooks registered")
end
-- Event Handler for Addon Unloading
local function OnAddonUnload(eventCode, addonName)
if addonName ~= MY_ADDON_NAME then return end
-- Disable all hooks before unload
if myHooks then
myHooks:disableAll()
myHooks = nil
end
zo_dlog("MyAddon: Hooks cleaned up")
end
-- Hook Callbacks
local function OnMailValidate(self, ...)
zo_dlog("Validating mail send...")
return false -- Allow original
end
local function OnInventoryUpdated(...)
zo_dlog("Inventory slot updated")
end
local function OnAbilityUsed(...)
zo_dlog("Player cast an ability")
end
-- Register for addon events
EVENT_MANAGER:RegisterForEvent(
MY_ADDON_NAME,
EVENT_ADD_ON_LOADED,
OnAddonLoaded
)
EVENT_MANAGER:RegisterForEvent(
MY_ADDON_NAME,
EVENT_ADD_ON_UNLOADING,
OnAddonUnload
)##Debugging & Inspection While there's no built-in describeAll method in the current implementation, you can add debugging utilities:
-- Add to your addon code
function InspectHooks(manager)
d("=== Hook Manager: " .. manager.base .. " ===")
for id, hook in pairs(manager.hooks) do
local status = hook.enabled and "ACTIVE" or "INACTIVE"
d(string.format(
"%s - %s.%s [%s]",
id,
hook.kind,
hook.method,
status
))
end
d("Total hooks: " .. #manager.hooks)
end
-- Usage
InspectHooks(myHooks)✅ Do
- Use unique base names for different modules (CombatModule_Hooks, UIModule_Hooks)
- Disable hooks on unload to prevent ghost callbacks
- Store hook IDs if you need to reference them later
- Test hooks with errors to ensure safeCall protects your game UI
-- Good: Store reference for later use
local inventoryHook = myHooks:PostHook(INVENTORY_MANAGER, "UpdateSlot", OnUpdate)
inventoryHookId = inventoryHook.id -- Save for later
-- Later...
myHooks:toggle(inventoryHookId)❌ Don't
- Register the same hook twice with same ID (will return nil)
- Assume hooks are always active (check enabled property)
- Forget to clean up on addon unload (memory leak risk)
- Mix hook types (pre/post/secure) without understanding behavior differences
##Limitations & Known Issues
| Issue | Workaround |
|---|---|
| Hook IDs must be unique per manager | Use different base names for separate managers |
| Hooks remain registered even when disabled | Use remove(id) if you need to truly unregister |
| No built-in hook description/debug output | Add your own inspection utility (see above) |
| Cannot retrieve original function (only callback) | Store original function reference separately if needed |
##API Reference Summary ###Class Methods Signature|Purpose HookManager:New(baseName?)|Create new instance manager:PreHook(target, method, fn)|Register pre-hook manager:PostHook(target, method, fn)|Register post-hook manager:SecurePostHook(target, method, fn)|Register secure hook ###Instance Methods Signature|Purpose manager:get(id)|Get hook by ID manager:enable(id)|Activate hook manager:disable(id)|Deactivate hook manager:toggle(id)|Flip hook state manager:remove(id)|Remove hook permanently manager:enableAll()|Activate all hooks manager:disableAll()|Deactivate all hooks manager:toggleAll()|Flip all hook states
Property|Type|Read/Write id|string|Read-only target|table|Read-only| method|string|Read-only fn|function|Read-only kind|string|Read-only enabled|boolean|Read/Write
##Related Resources
Full LibSFUtils Documentation - Complete library reference ESO API Reference - Official ESO addon documentation HookManager Source - Raw implementation
Generated from SFUtils_HookManager.lua. Last updated: July 2026