view Init.lua @ 37:e84d645c8ab8

- revised the tracker update function to build its complete data list up front and use the values as points of comparison for determining possible out of place blocks, which will be iterated over afterward to remove what wasn't re-used - also entailed revising the exact role of global event handlers and function hooks, limiting their directions of communication so one doesn't end up calling the other multiple or inifinity times - schema handling polish
author Nenue
date Mon, 18 Apr 2016 07:56:23 -0400
parents 66b927b46776
children 1f8f9cc3d956
line wrap: on
line source
--- Modulizer framework
-- OnInitialize
-- OnUpdate
-- OnEnable     -- run when GetSpecialization() returns true

local ADDON, A = ...
Veneer = Veneer or CreateFrame('Frame', 'Veneer', UIParent)
local B = Veneer
local wipe, min, max, random, tinsert, tremove = table.wipe, math.min, math.max, math.random, table.insert, table.remove
local pairs, ipairs, select, unpack, _G = pairs, ipairs, select, unpack, _G
local type, tostring, format = type, tostring, string.format
A.frame = B

--- Cache tables
local initOnced
local modules = {}
local queuedModules = {}
local checkForConfig = {}
local moduleStack = {
}

--- Utilities
B.wipeall = function (...)
  for i = 1, select('#', ...) do
    wipe(select(i, ...))
  end
end

--- Various region categories
B.displays = {}
B.configLayers = {}
B.configLayersRef = {}


--@debug@
--- Generates a print handler pointing to a static channel signature
-- @usage func = B.print(sig)
-- @param sig channel name or number
local printfuncs = {}
B.print = function(pref, ...)
  if Devian and Devian.InWorkspace() then
      printfuncs[pref] = printfuncs[pref] or function(...) print(pref,  ...) end

    return printfuncs[pref]
  else
   return function () end
  end
end

local rgb = {}
local getcolor = function()
  local n, p = 0, 4
  for i = 1, 3 do
    rgb[i] = min(random(n,p) * 64, 255)
    if rgb[i] == 255 then
      p = 4
    elseif rgb[i] > 0 then
      n = 2
    end
  end
  return unpack(rgb)
end

local color = {}
local fprints = {}
B.fprint = function()
  if not (Devian and Devian.InWorkspace()) then
    return function() end
  end


  local sig = debugstack(2,1)
  if fprints[sig] then
    return fprints[sig]
  end

  local func = sig:match("%`(%a+)%'")
  if not func then
    func = sig:match("<(.-)>")
  end
  func = func:gsub('(%l+)(%u)', function(a, b) return a:sub(0,2) .. b  end, 1)
  func = func:gsub('^.+%\\', '')
  if not func then
    func = 'noname'
  end

  local r, g, b = getcolor()
  color[sig] = color[sig] or format('|cFF%02X%02X%02X%s|r', r, g, b, func)

  --print(color[func] .. ' ( ' .. table.concat(args, ', ')..' )' )
  func = B.print(func)
  fprints[sig] = func
  return func
end

--@end-debug@
--[=[@non-debug@
B.print = function() end
--@end-non-debug@]=]

-- for the Mikk script
-- GLOBALS: NUM_LE_RAID_BUFF_TYPES
-- GLOBALS: BUFF_FLASH_TIME_ON, BUFF_FLASH_TIME_OFF, BUFF_MIN_ALPHA, BUFF_WARNING_TIME, BUFF_DURATION_WARNING_TIME
-- GLOBALS: BUFFS_PER_ROW, BUFF_MAX_DISPLAY, BUFF_ACTUAL_DISPLAY, DEBUFF_MAX_DISPLAY, DEBUFF_ACTUAL_DISPLAY, BUFF_ROW_SPACING
-- GLOBALS: CONSOLIDATED_BUFFS_PER_ROW, CONSOLIDATED_BUFF_ROW_HEIGHT, NUM_TEMP_ENCHANT_FRAMES
-- GLOBALS: BUFF_BUTTON_HEIGHT, BUFF_FRAME_BASE_EXTENT, BUFF_HORIZ_SPACING

local print = B.print('Bfl')

--- Template for making perpendicular traversals of the displays structure; also makes sure the table is there
B.Abstract = function(dest, key, table)
  if table then
    for _, v in pairs(dest) do
      v[key] = {}
    end
  end
  B[key] = setmetatable({}, {
    __index = function(t, k)
      return dest[k][key]
    end,
    __newindex = function(_, k, v)
      print('abstract write ('..key..'):', k)
      dest[k][key] = v
    end,
    __tostring = function() return 'Abstract:'..key..'' end
  })


  return B[key]
end


--- localize for speed
local layers, refs, displays = B.configLayers, B.configLayersRef, B.displays

local ModulesCall = function(func, flag)

  local n = 0
  for i = 1, #moduleStack do
    print('calling level '..i)
    local stackset = moduleStack[i]

    for name, module in pairs(stackset) do
      n = n + 1


      if module[func] then
        -- nil = pass
        if not flag or module.Conf[flag] then
          if (flag) then
            print('  check', flag, '=', module.Conf[flag])
          end

          print(' ',n..'  '..name..'.'..func..'()')
          module[func](module, module.Conf)
        end

      end
    end
  end
end


local Enable = function()
end

--- The things that happen repeatedly
local Init = function ()
end


--- Things that happen immediately upon entering world
local InitOnce = function()
  print('entering world first time')
  local defaults = B.ConfDefaults
  print('|cFFFFFF00Veneer|r')
  if not VeneerData then
    VeneerData = {}
    for k,v in pairs(defaults) do


      VeneerData[k] = v
    end
    print('Veneer defaults being used.')
  end

  B.Conf = setmetatable(VeneerData, {__index = function(_, k) return defaults[k] end})



  -- suffix tables
  for name, display in pairs(displays) do
    display.conf = setmetatable({}, {
      __index = function(_, k)
        --print('config check '.. name .. k)
        return B.Conf[name .. k] or B.Conf['BuffButton' .. k]
      end,
      __newindex = function(_, k , v)
        B.Conf[name..k] = v
      end,
    })
  end

  -- To ensure that modules are run in controlled order, walk the dependency list; if the dep shows up
  -- in the loaded manifest, remove the value. If the dep list isn't empty, move that module to the next
  -- layer.
  local loaded = {}
  local stackLevels = #moduleStack
  local i = 1
  moduleStack[1] = modules
  repeat
    print('setting init level '.. i)
    local queue = moduleStack[i]
    for name, module in pairs(queue) do

      if queuedModules[name] and #queuedModules[name] > 0 then
        local p = #queuedModules[name]
        for j = 1, p do
          local dep = queuedModules[name][j]

          if loaded[dep] then
            print( '    ' .. dep .. ' OK')
            queuedModules[name][j] = nil
            for k = j, p do
              print('   shift ' .. (k+1)  .. ' ('..tostring(queuedModules[name][k+1])..') to ' .. k ..'')
              queuedModules[name][k] = queuedModules[name][k+1]
            end
          end
        end

        if #queuedModules[name] == 0 then
          queuedModules[name] = nil
          print('  |cFF00FFFF'.. name ..'|r deps OK')
          loaded[name] = true
        else

          print('  |cFFFF8800' .. name ..'|r pending')
          local next = i+1
          if not  moduleStack[next] then
            moduleStack[next] = {}
          end
          stackLevels = next
          moduleStack[next][name] = module
          queue[name] = nil
        end

      else
        print('  |cFF00FF00'.. name ..'|r no deps')
        loaded[name] = true
      end
   end
    i = i + 1
  until i > stackLevels


  for level, batch in ipairs(moduleStack) do
    print('config level', level)
    for name, module in pairs(batch) do
      if not VeneerData[name] then
        VeneerData[name] = {}
      end

      if module.defaults then
        print('setting defaults for module', name)
        --[===[@non-debug@
        if not VeneerData[name] then
        --@end-non-debug@]===]
          VeneerData[name] = {}
        --[===[@non-debug@
        end
        --@end-non-debug@]===]
        for k,v in pairs(module.defaults) do
          VeneerData[name][k] = v
        end
        module.Conf = VeneerData[name]
      end

      if VeneerData[name].enabled == nil then
        VeneerData[name].enabled = true
      end

    end
  end


  if #checkForConfig >= 1 then
    local queuedFrame = tremove(checkForConfig)
    while queuedFrame do
      B.SetConfigLayers(queuedFrame)
      B.InitXMLFrame(queuedFrame)
      queuedFrame = tremove(checkForConfig)
    end
  end
  -- remove from existing
end

--- Fires an update to all modules
local lastUpdate
function B.UpdateAll(...)
  lastUpdate = GetTime()
  ModulesCall('OnUpdate')
end

B:RegisterEvent('PLAYER_ENTERING_WORLD')
B:SetScript('OnEvent', function(self, event)
  if event == 'PLAYER_ENTERING_WORLD' then
    if not initOnced then
      InitOnce()
      ModulesCall('OnInitialize')
      initOnced = true
      C_Timer.After(1, function()
        if GetSpecialization() then
          print(GetSpecialization(), 'enabling')

          ModulesCall('OnEnable', 'enabled')
          B:SetScript('OnUpdate', nil)
        end
      end)
    end
  end

  B.UpdateAll()

  if event == 'PLAYER_ENTERING_WORLD' then
    B.UpdateConfigLayers()
  end

end)

--- Modulizer method
--
function B:RegisterModule (name, module, ...)
  if modules[name] then
    print('pulling modules[|cFFFF8800'.. tostring(name) ..'|r]')
    return modules[name]
  end

  print('new module |cFF00BBFF'.. tostring(name) ..'|r')
  if module then
    if modules[name] then
      error("Module table for '"..tostring(name).."' already exists.")
    end
  else
    module = CreateFrame('Frame', 'Veneer' .. tostring(name) .. 'Handler', B, 'VeneerHandlerTemplate')
  end
  modules[name] = module
  B[name] = module
  if select('#', ...) >= 1 then
    local numDeps = select('#', ...)
    print('  '..numDeps..' deps detected')
    for i = 1, numDeps do
      local dep = select(i, ...)
        -- means that init/enable funcs are ordered to run after deps do their things
      queuedModules[name] = queuedModules[name]  or {}
      tinsert(queuedModules[name], dep)
      print('  needs '..dep)
    end
  end
  return module
end


B.SetConfigLayers =  function(frame)
  local print = B.fprint()
  if not frame.config then
    --print(frame:GetName(), 'has no config layers')
    return
  end
  --print('Registering config layers from', frame:GetName())

  for i, subframe in ipairs(frame.config) do
    -- make sure there are no duplicates
    if not refs[subframe] then
      local key = #layers+1
      layers[key] = subframe
      refs[subframe] = key
    end
    --print(' ', i, subframe:GetName())
  end
end

B.RemoveConfigLayers = function(frame)

  local print = B.fprint()
  print('|cFFFF0000RemoveConfigLayers', frame:GetName())
  for i, subframe in pairs(layers) do
    if subframe:GetParent() == frame then
      print('|cFFFF8800  ', subframe:GetParent():GetName(), '|cFFFFFF00', subframe:GetName())
      layers[i]:Hide()
      layers[i] = nil
      refs[subframe] = nil
    end
  end
end

B.UpdateConfigLayers = function()
  local print = B.fprint()
  local func = B.Conf.GuidesMode and 'Show' or 'Hide'
  local numAnchors = 0
  for name, display in pairs(displays) do
    numAnchors = numAnchors + 1
    display.anchor:EnableMouse(B.Conf.GuidesMode)
    if B.Conf.GuidesMode then
      display.anchor:SetScript('OnUpdate', display.anchor.OnUpdate)
    else
      display.anchor:SetScript('OnUpdate', nil)

      for i, anchorButton in ipairs(display.anchor.anchorButton) do
        anchorButton:Hide()
      end

    end
    print(B.Conf.ConfigMode)
    display.anchor:EnableMouse(B.Conf.ConfigMode)
  end
  for id, region in pairs(layers) do
    print(id, region:GetName(), func)
    region[func](region)
  end

  print('['..func..'] updated', #layers, 'regions,', numAnchors, 'frames')
end

local XMLFrame_SetEnabled = function(self, value)
  local name = self:GetName()


  if not B.Conf[name] then
    B.Conf[name] = {
      enabled = true
    }
  end

  print()
  local enabled
  if value == nil then
    if B.Conf[name].enabled == nil then
      print('toggle based on visibility')
      enabled = (not self:IsVisible()) and true or false
    else
      print('toggle a config value =', B.Conf[name].enabled)
      enabled = B.Conf[name].enabled
    end

    enabled = (enabled ~= true) and true or false
  else
      print('use argument value', value)
      enabled = value
  end

  print('arg =', value, 'conf =', B.Conf[name].enabled, 'result=', enabled)

  B.Conf[name].enabled = enabled

  local stateFunc = enabled and 'Show' or 'Hide'
  local eventFunc = enabled and 'OnToggle' or 'OnToggle'
  for i, region in pairs(self.toggled) do
    region[stateFunc](region)
  end
  if self.OnToggle then
    self:OnToggle(B.Conf[name].enabled)
  end
  if B.Conf[name].enabled then
    if self.OnEnable then
      self:OnEnable()
    end
  else
    if self.OnDisable then
      self:OnDisable()
    end
  end


end
--- Generic handlers for keeping track of XML-defined frames
B.OnLoad = function(self)
  tinsert(checkForConfig, self)
  self.SetEnabled = XMLFrame_SetEnabled
end

B.InitXMLFrame = function(self)
  local name = self:GetName()
  print('|cFF00FF00hello from '.. name)

  if self.drag then
    self:RegisterForDrag('LeftButton')
  else
    self:EnableMouse(false)
  end

  if not B.Conf[name] then
    B.Conf[name] = {
      enabled = true,
    }
  end
  local c = B.Conf[name]

  if c.position then
    print('restoring frame position', unpack(c.position))
    self:ClearAllPoints()
    local anchorTo, relativePoint, x, y = unpack(c.position)
    self:SetPoint(anchorTo, UIParent, relativePoint, x, y)
  else
    local a, _, b, c, d = self:GetPoint(1)
    print('seeding default position', a, b, c, d)
    c.position = {a, b, c, d}
  end
  local state = c.enabled
  self:SetEnabled(state)
end

B.OnDragStart = function(self)
  self.xA = self:GetLeft()
  self.yA = self:GetBottom()
  self.anchorTo, self.relativeTo, self.relativePoint, self.x, self.y = self:GetPoint(1)
  print('acquire anchor', self:GetPoint(1))
  print(self:GetName(), 'start moving ('..self.x..', '..self.y..')')
  self:StartMoving()
end

B.OnDragStop = function(self)
  local name = self:GetName()
  print(name, 'stop moving ('..self:GetLeft()..', '..self:GetBottom()..')')
  local xB = self:GetLeft() - self.xA
  local yB = self:GetBottom() - self.yA
  print('storing anchor point', self.anchorTo, self.relativePoint, self.x + xB, self.y + yB)

  self:StopMovingOrSizing()
  B.Conf[name].position = {self.anchorTo, self.relativePoint, self.x + xB, self.y + yB}
  B.InitXMLFrame(self)
end