You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
7.7 KiB

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This is loaded before anything else and simply sets up the addon table
local ADDON_NAME = select(1, ...)
local TSM = select(2, ...) ---@class TSM
local private = {
context = {},
initOrder = {},
loadOrder = {},
frame = nil,
gotAddonLoaded = false,
gotPlayerLogin = false,
gotPlayerLogout = false,
}
-- ============================================================================
-- Module Metatable
-- ============================================================================
local MODULE_METHODS = {
OnModuleLoad = function(self, func)
local context = private.context[self]
assert(context and not context.moduleLoadFunc and not context.moduleLoadTime and type(func) == "function")
context.moduleLoadFunc = func
end,
OnSettingsLoad = function(self, func)
local context = private.context[self]
assert(context and not context.settingsLoadFunc and not context.settingsLoadTime and type(func) == "function")
context.settingsLoadFunc = func
end,
OnGameDataLoad = function(self, func)
local context = private.context[self]
assert(context and not context.gameDataLoadFunc and not context.gameDataLoadTime and type(func) == "function")
context.gameDataLoadFunc = func
end,
OnModuleUnload = function(self, func)
local context = private.context[self]
assert(context and not context.moduleUnloadFunc and not context.moduleUnloadTime and type(func) == "function")
context.moduleUnloadFunc = func
end,
}
local MODULE_MT = {
__index = MODULE_METHODS,
__newindex = function(self, key, value)
local context = private.context[self]
assert(context and not MODULE_METHODS[key] and not context.moduleLoadTime)
rawset(self, key, value)
end,
__metatable = false,
}
-- ============================================================================
-- Addon Object Functions
-- ============================================================================
---Creates a new TSM module.
---@param path string The name of the module
---@return table
function TSM.Init(path)
assert(type(path) == "string")
if private.context[path] then
error("Module already exists for path: "..tostring(path))
end
local moduleObj = setmetatable({}, MODULE_MT)
private.context[path] = {
path = path,
module = moduleObj,
moduleLoadFunc = nil,
moduleLoadTime = nil,
settingsLoadFunc = nil,
settingsLoadTime = nil,
gameDataLoadFunc = nil,
gameDataLoadTime = nil,
moduleUnloadFunc = nil,
moduleUnloadTime = nil,
}
-- store a reference to the context by both the module object and the path
private.context[moduleObj] = private.context[path]
tinsert(private.initOrder, path)
return moduleObj
end
---Returns an existing TSM module.
---@generic T
---@param path `T`
---@return T
function TSM.Include(path)
local context = private.context[path]
if not context then
error("Module doesn't exist for path: "..tostring(path))
end
private.ProcessModuleLoad(path)
return context.module
end
---Returns an iterator over all available modules.
---@return fun(): number, string, number, number, number, number # An iterator with fields: `index, loadTime, settingsLoadTime, gameDataLoadTime, moduleUnloadTime`
function TSM.ModuleInfoIterator()
return private.ModuleInfoIterator, nil, 0
end
---Unloads all modules to simulate a logout.
function TSM.DebugLogout()
private.UnloadAll()
end
---Checks if LibTSM code has loaded.
---@return boolean
function TSM.IsLibTSMLoaded()
return private.gotAddonLoaded
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.ModuleInfoIterator(_, index)
index = index + 1
local path = private.initOrder[index]
if not path then
return
end
local context = private.context[path]
assert(context)
return index, path, context.moduleLoadTime, context.settingsLoadTime, context.gameDataLoadTime, context.moduleUnloadTime
end
function private.ProcessModuleLoad(path)
local context = private.context[path]
assert(context)
if context.moduleLoadTime then
-- already loaded
return
end
tinsert(private.loadOrder, path)
context.moduleLoadTime = 0
if context.moduleLoadFunc then
local st = GetTimePreciseSec()
context.moduleLoadFunc()
context.moduleLoadTime = GetTimePreciseSec() - st
end
end
function private.ProcessSettingsLoad(path)
local context = private.context[path]
assert(context)
if context.settingsLoadTime then
-- already loaded
return
end
context.settingsLoadTime = 0
if context.settingsLoadFunc then
local st = GetTimePreciseSec()
context.settingsLoadFunc()
context.settingsLoadTime = GetTimePreciseSec() - st
end
end
function private.ProcessGameDataLoad(path)
local context = private.context[path]
assert(context)
if context.gameDataLoadTime then
-- already loaded
return
end
context.gameDataLoadTime = 0
if context.gameDataLoadFunc then
local st = GetTimePreciseSec()
context.gameDataLoadFunc()
context.gameDataLoadTime = GetTimePreciseSec() - st
end
end
function private.ProcessModuleUnload(path)
local context = private.context[path]
assert(context)
if context.moduleUnloadTime then
-- already unloaded
return
end
context.moduleUnloadTime = 0
if context.moduleUnloadFunc then
local st = GetTimePreciseSec()
context.moduleUnloadFunc()
context.moduleUnloadTime = GetTimePreciseSec() - st
end
end
function private.UnloadAll()
-- unload in the opposite order we loaded
for i = #private.loadOrder, 1, -1 do
private.ProcessModuleUnload(private.loadOrder[i])
end
end
function private.OnEvent(_, event, arg)
if event == "ADDON_LOADED" and arg == ADDON_NAME and not private.gotAddonLoaded then
assert(not private.gotAddonLoaded and not private.gotPlayerLogin and not private.gotPlayerLogout)
private.gotAddonLoaded = true
-- load any module which haven't already
for _, path in ipairs(private.initOrder) do
private.ProcessModuleLoad(path)
end
-- settings are now available
for _, path in ipairs(private.loadOrder) do
private.ProcessSettingsLoad(path)
end
private.frame:UnregisterEvent("ADDON_LOADED")
elseif event == "PLAYER_LOGIN" then
assert(private.gotAddonLoaded and not private.gotPlayerLogin and not private.gotPlayerLogout)
private.gotPlayerLogin = true
-- game data is now available
for _, path in ipairs(private.loadOrder) do
private.ProcessGameDataLoad(path)
end
elseif event == "PLAYER_LOGOUT" then
assert(private.gotAddonLoaded and not private.gotPlayerLogout)
private.gotPlayerLogout = true
if not private.gotPlayerLogin then
-- this can happen if the player exists the game during the loading screen, in which case we just ignore it
return
end
private.UnloadAll()
end
end
-- ============================================================================
-- Initialization Code
-- ============================================================================
-- Only do initialization if we're loaded in a WoW environment
if ADDON_NAME then
-- Create frame to listen for lifecycle events
private.frame = CreateFrame("Frame")
private.frame:RegisterEvent("ADDON_LOADED")
private.frame:RegisterEvent("PLAYER_LOGIN")
private.frame:RegisterEvent("PLAYER_LOGOUT")
private.frame:SetScript("OnEvent", private.OnEvent)
-- Manually register LibTSMClass
local libTSMClassModule = TSM.Init("LibTSMClass")
local libTable = LibStub("LibTSMClass")
for k, v in pairs(libTable) do
libTSMClassModule[k] = v
end
end