diff --git a/.gitattributes b/.gitattributes index fa1385d..dfe0770 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -* -text +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml deleted file mode 100644 index 2f93975..0000000 --- a/.gitea/workflows/release.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: release - -on: - push: - tags: - - '*-coa.*' # Asc-1.1.6-coa.2, 9.1.40-coa.3, etc. - - 'v*' # v0.3.0 for repos without an upstream version - -jobs: - release: - runs-on: linux-amd64 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 # build_zip uses git archive HEAD; full history is fine - - - name: Build per-addon zip(s) - run: bash tools/build_zip.sh - - - name: Publish release (Gitea API direct; no action dependency) - env: - GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - TAG: ${{ github.ref_name }} - API: ${{ github.server_url }}/api/v1 - # Gitea attachment ceiling is 200 MiB (see roles/gitea config). - # Skip anything larger so one oversized asset doesn't fail the job. - MAX_BYTES: 209715200 - run: | - set -euo pipefail - # Create the release (or reuse if it already exists for this tag). - RID=$(curl -s -H "Authorization: token $GITEA_TOKEN" \ - "$API/repos/$REPO/releases/tags/$TAG" 2>/dev/null \ - | jq -r '.id // empty') - if [ -z "$RID" ]; then - RID=$(curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - "$API/repos/$REPO/releases" \ - -d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false,hide_archive_links:true}')" \ - | jq -r '.id') - fi - echo "release id: $RID" - # Gitea honors hide_archive_links only on edit, not create — PATCH it - # so the auto-generated Source Code (zip/tar.gz) links stay hidden. - curl -sf -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ - "$API/repos/$REPO/releases/$RID" -d '{"hide_archive_links":true}' >/dev/null || true - # Upload every dist/*.zip. Per-asset failures don't fail the job — - # we want partial releases to still publish rather than block the - # whole pipeline on one big file. - failed=0 - uploaded=0 - for f in dist/*.zip; do - name=$(basename "$f") - size=$(stat -c '%s' "$f") - if [ "$size" -gt "$MAX_BYTES" ]; then - echo "::warning::skip $name (${size} B > ${MAX_BYTES} B Gitea limit; host on CDN instead)" - failed=$((failed+1)) - continue - fi - echo "uploading $name ($(numfmt --to=iec "$size"))" - if curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \ - -F "attachment=@$f" \ - "$API/repos/$REPO/releases/$RID/assets?name=$name" \ - | jq -r '" -> " + .browser_download_url'; then - uploaded=$((uploaded+1)) - else - echo "::warning::upload failed for $name" - failed=$((failed+1)) - fi - done - echo "release published: $uploaded uploaded, $failed skipped/failed" - # Only fail the job if NO assets uploaded — a release with zero - # attachments isn't useful to anyone. - [ "$uploaded" -gt 0 ] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..cb7b432 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,77 @@ +name: "Bug Report" +description: Create a report to help us improve this addon +labels: '🐛 Bug' +body: +- type: markdown + attributes: + value: | + Please search for existing issues before creating a new one. + +- type: textarea + attributes: + label: Description + description: What did you expect to happen and what happened instead? + validations: + required: true + +- type: dropdown + id: flavor + attributes: + label: Realm + description: What realm did this occur on? + options: + - Area 52 (Default) + - Seasonal + - Grizzly Hills + - Rexxar + - Other + validations: + required: true + +- type: checkboxes + id: testing + attributes: + label: Tested with only this addon + description: Did you try having just this addon as the only enabled addon and everything else disabled? + options: + - label: "Yes" + - label: "No" + validations: + required: true + +- type: textarea + attributes: + label: Lua Error + description: | + Do you have an error log of what happened? If you don't see any errors, make sure that error reporting is enabled (`/console scriptErrors 1`) + validations: + required: false + +- type: textarea + attributes: + label: Reproduction Steps + description: Please list out the steps to reproduce your bug. + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + +- type: input + attributes: + label: Last Good Version + description: | + Was it working in a previous version? If yes, which update did it stop working? If you don't know, when was the last date you were aware it was working + placeholder: "MM/DD/YYYY" + validations: + required: false + +- type: textarea + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem. + placeholder: Click here to attach your screenshots via the editor button in the top right. + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEPMLATE.md b/.github/PULL_REQUEST_TEPMLATE.md new file mode 100644 index 0000000..30d2afa --- /dev/null +++ b/.github/PULL_REQUEST_TEPMLATE.md @@ -0,0 +1,28 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes #(issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +## How Has This Been Tested + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + +## Checklist + + +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index ec92999..44356e3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ .install .lua/* .vscode -.idea -dist/ +.idea \ No newline at end of file diff --git a/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua b/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua index 00e4e48..6c89654 100644 --- a/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua +++ b/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua @@ -6,31 +6,31 @@ -- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. -- * **OnDisable**, which is only called when your addon is manually being disabled. -- @usage --- -- A small (but complete) addon, that doesn't do anything, +-- -- A small (but complete) addon, that doesn't do anything, -- -- but shows usage of the callbacks. -- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") --- +-- -- function MyAddon:OnInitialize() --- -- do init tasks here, like loading the Saved Variables, +-- -- do init tasks here, like loading the Saved Variables, -- -- or setting up slash commands. -- end --- +-- -- function MyAddon:OnEnable() -- -- Do more initialization here, that really enables the use of your addon. --- -- Register Events, Hook functions, Create Frames, Get information from +-- -- Register Events, Hook functions, Create Frames, Get information from -- -- the game that wasn't available in OnInitialize -- end -- -- function MyAddon:OnDisable() -- -- Unhook, Unregister Events, Hide frames that you created. --- -- You would probably only use an OnDisable if you want to +-- -- You would probably only use an OnDisable if you want to -- -- build a "standby" mode, or be able to toggle modules on/off. -- end -- @class file -- @name AceAddon-3.0.lua --- @release $Id$ +-- @release $Id: AceAddon-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ -local MAJOR, MINOR = "AceAddon-3.0", 13 +local MAJOR, MINOR = "AceAddon-3.0", 5 local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceAddon then return end -- No Upgrade needed. @@ -49,6 +49,10 @@ local select, pairs, next, type, unpack = select, pairs, next, type, unpack local loadstring, assert, error = loadstring, assert, error local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler + --[[ xpcall safecall implementation ]] @@ -58,12 +62,43 @@ local function errorhandler(err) return geterrorhandler()(err) end +local function CreateDispatcher(argCount) + local code = [[ + local xpcall, eh = ... + local method, ARGS + local function call() return method(ARGS) end + + local function dispatch(func, ...) + method = func + if not method then return end + ARGS = ... + return xpcall(call, eh) + end + + return dispatch + ]] + + local ARGS = {} + for i = 1, argCount do ARGS[i] = "arg"..i end + code = code:gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) +Dispatchers[0] = function(func) + return xpcall(func, errorhandler) +end + local function safecall(func, ...) -- we check to see if the func is passed is actually a function here and don't error when it isn't -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not -- present execution should continue without hinderance if type(func) == "function" then - return xpcall(func, errorhandler, ...) + return Dispatchers[select('#', ...)](func, ...) end end @@ -71,27 +106,17 @@ end local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype -- used in the addon metatable -local function addontostring( self ) return self.name end - --- Check if the addon is queued for initialization -local function queuedForInitialization(addon) - for i = 1, #AceAddon.initializequeue do - if AceAddon.initializequeue[i] == addon then - return true - end - end - return false -end +local function addontostring( self ) return self.name end --- Create a new AceAddon-3.0 addon. --- Any libraries you specified will be embeded, and the addon will be scheduled for +-- Any libraries you specified will be embeded, and the addon will be scheduled for -- its OnInitialize and OnEnable callbacks. -- The final addon object, with all libraries embeded, will be returned. -- @paramsig [object ,]name[, lib, ...] -- @param object Table to use as a base for the addon (optional) -- @param name Name of the addon object to create -- @param lib List of libraries to embed into the addon --- @usage +-- @usage -- -- Create a simple addon object -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") -- @@ -111,10 +136,10 @@ function AceAddon:NewAddon(objectorname, ...) if type(name)~="string" then error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end - if self.addons[name] then + if self.addons[name] then error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) end - + object = object or {} object.name = name @@ -124,15 +149,14 @@ function AceAddon:NewAddon(objectorname, ...) for k, v in pairs(oldmeta) do addonmeta[k] = v end end addonmeta.__tostring = addontostring - + setmetatable( object, addonmeta ) self.addons[name] = object object.modules = {} - object.orderedModules = {} object.defaultModuleLibraries = {} Embed( object ) -- embed NewModule, GetModule methods self:EmbedLibraries(object, select(i,...)) - + -- add to queue of addons to be initialized upon ADDON_LOADED tinsert(self.initializequeue, object) return object @@ -143,7 +167,7 @@ end -- Throws an error if the addon object cannot be found (except if silent is set). -- @param name unique name of the addon object -- @param silent if true, the addon is optional, silently return nil if its not found --- @usage +-- @usage -- -- Get the Addon -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") function AceAddon:GetAddon(name, silent) @@ -198,7 +222,7 @@ end -- @paramsig name[, silent] -- @param name unique name of the module -- @param silent if true, the module is optional, silently return nil if its not found (optional) --- @usage +-- @usage -- -- Get the Addon -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- -- Get the Module @@ -221,23 +245,23 @@ local function IsModuleTrue(self) return true end -- @param name unique name of the module -- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) -- @param lib List of libraries to embed into the addon --- @usage +-- @usage -- -- Create a module with some embeded libraries -- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") --- +-- -- -- Create a module with a prototype -- local prototype = { OnEnable = function(self) print("OnEnable called!") end } -- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") function NewModule(self, name, prototype, ...) if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end - + if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end - + -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) - + module.IsModule = IsModuleTrue module:SetEnabledState(self.defaultModuleState) module.moduleName = name @@ -252,24 +276,23 @@ function NewModule(self, name, prototype, ...) if not prototype or type(prototype) == "string" then prototype = self.defaultModulePrototype or nil end - + if type(prototype) == "table" then local mt = getmetatable(module) mt.__index = prototype setmetatable(module, mt) -- More of a Base class type feel. end - + safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. self.modules[name] = module - tinsert(self.orderedModules, module) - + return module end --- Returns the real name of the addon or module, without any prefix. -- @name //addon//:GetName --- @paramsig --- @usage +-- @paramsig +-- @usage -- print(MyAddon:GetName()) -- -- prints "MyAddon" function GetName(self) @@ -281,20 +304,15 @@ end -- and enabling all modules of the addon (unless explicitly disabled).\\ -- :Enable() also sets the internal `enableState` variable to true -- @name //addon//:Enable --- @paramsig --- @usage +-- @paramsig +-- @usage -- -- Enable MyModule -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyModule = MyAddon:GetModule("MyModule") -- MyModule:Enable() function Enable(self) self:SetEnabledState(true) - - -- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still - -- it'll be enabled after the init process - if not queuedForInitialization(self) then - return AceAddon:EnableAddon(self) - end + return AceAddon:EnableAddon(self) end --- Disables the Addon, if possible, return true or false depending on success. @@ -302,8 +320,8 @@ end -- and disabling all modules of the addon.\\ -- :Disable() also sets the internal `enableState` variable to false -- @name //addon//:Disable --- @paramsig --- @usage +-- @paramsig +-- @usage -- -- Disable MyAddon -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyAddon:Disable() @@ -316,7 +334,7 @@ end -- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. -- @name //addon//:EnableModule -- @paramsig name --- @usage +-- @usage -- -- Enable MyModule using :GetModule -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyModule = MyAddon:GetModule("MyModule") @@ -334,7 +352,7 @@ end -- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. -- @name //addon//:DisableModule -- @paramsig name --- @usage +-- @usage -- -- Disable MyModule using :GetModule -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyModule = MyAddon:GetModule("MyModule") @@ -353,7 +371,7 @@ end -- @name //addon//:SetDefaultModuleLibraries -- @paramsig lib[, lib, ...] -- @param lib List of libraries to embed into the addon --- @usage +-- @usage -- -- Create the addon object -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") -- -- Configure default libraries for modules (all modules need AceEvent-3.0) @@ -372,7 +390,7 @@ end -- @name //addon//:SetDefaultModuleState -- @paramsig state -- @param state Default state for new modules, true for enabled, false for disabled --- @usage +-- @usage -- -- Create the addon object -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") -- -- Set the default state to "disabled" @@ -392,7 +410,7 @@ end -- @name //addon//:SetDefaultModulePrototype -- @paramsig prototype -- @param prototype Default prototype for the new modules (table) --- @usage +-- @usage -- -- Define a prototype -- local prototype = { OnEnable = function(self) print("OnEnable called!") end } -- -- Set the default prototype @@ -424,8 +442,8 @@ end --- Return an iterator of all modules associated to the addon. -- @name //addon//:IterateModules --- @paramsig --- @usage +-- @paramsig +-- @usage -- -- Enable all modules -- for name, module in MyAddon:IterateModules() do -- module:Enable() @@ -434,13 +452,13 @@ local function IterateModules(self) return pairs(self.modules) end -- Returns an iterator of all embeds in the addon -- @name //addon//:IterateEmbeds --- @paramsig +-- @paramsig local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end --- Query the enabledState of an addon. -- @name //addon//:IsEnabled --- @paramsig --- @usage +-- @paramsig +-- @usage -- if MyAddon:IsEnabled() then -- MyAddon:Disable() -- end @@ -471,34 +489,32 @@ local pmixins = { -- target (object) - target object to embed aceaddon in -- -- this is a local function specifically since it's meant to be only called internally -function Embed(target, skipPMixins) +function Embed(target) for k, v in pairs(mixins) do target[k] = v end - if not skipPMixins then - for k, v in pairs(pmixins) do - target[k] = target[k] or v - end + for k, v in pairs(pmixins) do + target[k] = target[k] or v end end -- - Initialize the addon after creation. -- This function is only used internally during the ADDON_LOADED event --- It will call the **OnInitialize** function on the addon object (if present), +-- It will call the **OnInitialize** function on the addon object (if present), -- and the **OnEmbedInitialize** function on all embeded libraries. --- +-- -- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. -- @param addon addon object to intialize function AceAddon:InitializeAddon(addon) safecall(addon.OnInitialize, addon) - + local embeds = self.embeds[addon] for i = 1, #embeds do local lib = LibStub:GetLibrary(embeds[i], true) if lib then safecall(lib.OnEmbedInitialize, lib, addon) end end - + -- we don't call InitializeAddon on modules specifically, this is handled -- from the event handler and only done _once_ end @@ -506,7 +522,7 @@ end -- - Enable the addon after creation. -- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, -- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. --- It will call the **OnEnable** function on the addon object (if present), +-- It will call the **OnEnable** function on the addon object (if present), -- and the **OnEmbedEnable** function on all embeded libraries.\\ -- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. -- @@ -516,12 +532,12 @@ end function AceAddon:EnableAddon(addon) if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end if self.statuses[addon.name] or not addon.enabledState then return false end - + -- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. self.statuses[addon.name] = true - + safecall(addon.OnEnable, addon) - + -- make sure we're still enabled before continueing if self.statuses[addon.name] then local embeds = self.embeds[addon] @@ -529,11 +545,10 @@ function AceAddon:EnableAddon(addon) local lib = LibStub:GetLibrary(embeds[i], true) if lib then safecall(lib.OnEmbedEnable, lib, addon) end end - + -- enable possible modules. - local modules = addon.orderedModules - for i = 1, #modules do - self:EnableAddon(modules[i]) + for name, module in pairs(addon.modules) do + self:EnableAddon(module) end end return self.statuses[addon.name] -- return true if we're disabled @@ -541,41 +556,40 @@ end -- - Disable the addon -- Note: This function is only used internally. --- It will call the **OnDisable** function on the addon object (if present), +-- It will call the **OnDisable** function on the addon object (if present), -- and the **OnEmbedDisable** function on all embeded libraries.\\ -- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. -- --- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. -- Use :Disable on the addon itself instead. -- @param addon addon object to enable function AceAddon:DisableAddon(addon) if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end if not self.statuses[addon.name] then return false end - + -- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. self.statuses[addon.name] = false - + safecall( addon.OnDisable, addon ) - + -- make sure we're still disabling... - if not self.statuses[addon.name] then + if not self.statuses[addon.name] then local embeds = self.embeds[addon] for i = 1, #embeds do local lib = LibStub:GetLibrary(embeds[i], true) if lib then safecall(lib.OnEmbedDisable, lib, addon) end end -- disable possible modules. - local modules = addon.orderedModules - for i = 1, #modules do - self:DisableAddon(modules[i]) + for name, module in pairs(addon.modules) do + self:DisableAddon(module) end end - + return not self.statuses[addon.name] -- return true if we're disabled end --- Get an iterator over all registered addons. --- @usage +-- @usage -- -- Print a list of all installed AceAddon's -- for name, addon in AceAddon:IterateAddons() do -- print("Addon: " .. name) @@ -583,7 +597,7 @@ end function AceAddon:IterateAddons() return pairs(self.addons) end --- Get an iterator over the internal status registry. --- @usage +-- @usage -- -- Print a list of all enabled addons -- for name, status in AceAddon:IterateAddonStatus() do -- if status then @@ -597,20 +611,9 @@ function AceAddon:IterateAddonStatus() return pairs(self.statuses) end function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end --- Blizzard AddOns which can load very early in the loading process and mess with Ace3 addon loading -local BlizzardEarlyLoadAddons = { - Blizzard_DebugTools = true, - Blizzard_TimeManager = true, - Blizzard_BattlefieldMap = true, - Blizzard_MapCanvas = true, - Blizzard_SharedMapDataProviders = true, - Blizzard_CombatLog = true, -} - -- Event Handling local function onEvent(this, event, arg1) - -- 2020-08-28 nevcairiel - ignore the load event of Blizzard addons which occur early in the loading process - if (event == "ADDON_LOADED" and (arg1 == nil or not BlizzardEarlyLoadAddons[arg1])) or event == "PLAYER_LOGIN" then + if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration while(#AceAddon.initializequeue > 0) do local addon = tremove(AceAddon.initializequeue, 1) @@ -619,7 +622,7 @@ local function onEvent(this, event, arg1) AceAddon:InitializeAddon(addon) tinsert(AceAddon.enablequeue, addon) end - + if IsLoggedIn() then while(#AceAddon.enablequeue > 0) do local addon = tremove(AceAddon.enablequeue, 1) @@ -635,15 +638,5 @@ AceAddon.frame:SetScript("OnEvent", onEvent) -- upgrade embeded for name, addon in pairs(AceAddon.addons) do - Embed(addon, true) -end - --- 2010-10-27 nevcairiel - add new "orderedModules" table -if oldminor and oldminor < 10 then - for name, addon in pairs(AceAddon.addons) do - addon.orderedModules = {} - for module_name, module in pairs(addon.modules) do - tinsert(addon.orderedModules, module) - end - end + Embed(addon) end diff --git a/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua b/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua index faab36c..ad5268f 100644 --- a/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua +++ b/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua @@ -2,14 +2,14 @@ -- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\ -- **ChatThrottleLib** is of course being used to avoid being disconnected by the server. -- --- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by +-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceComm itself.\\ -- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you -- make into AceComm. -- @class file -- @name AceComm-3.0 --- @release $Id$ +-- @release $Id: AceComm-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ --[[ AceComm-3.0 @@ -17,23 +17,24 @@ TODO: Time out old data rotting around from dead senders? Not a HUGE deal since ]] -local CallbackHandler = LibStub("CallbackHandler-1.0") -local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") +local MAJOR, MINOR = "AceComm-3.0", 6 -local MAJOR, MINOR = "AceComm-3.0", 14 local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceComm then return end +local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") +local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") + -- Lua APIs local type, next, pairs, tostring = type, next, pairs, tostring local strsub, strfind = string.sub, string.find -local match = string.match local tinsert, tconcat = table.insert, table.concat local error, assert = error, assert --- WoW APIs -local Ambiguate = Ambiguate +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler AceComm.embeds = AceComm.embeds or {} @@ -41,32 +42,21 @@ AceComm.embeds = AceComm.embeds or {} local MSG_MULTI_FIRST = "\001" local MSG_MULTI_NEXT = "\002" local MSG_MULTI_LAST = "\003" -local MSG_ESCAPE = "\004" --- remove old structures (pre WoW 4.0) -AceComm.multipart_origprefixes = nil -AceComm.multipart_reassemblers = nil +AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix" +AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst" -- the multipart message spool: indexed by a combination of sender+distribution+ -AceComm.multipart_spool = AceComm.multipart_spool or {} +AceComm.multipart_spool = AceComm.multipart_spool or {} --- Register for Addon Traffic on a specified prefix --- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters +-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent) -- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived" function AceComm:RegisterComm(prefix, method) if method == nil then method = "OnCommReceived" end - if #prefix > 16 then -- TODO: 15? - error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters") - end - if C_ChatInfo then - C_ChatInfo.RegisterAddonMessagePrefix(prefix) - else - RegisterAddonMessagePrefix(prefix) - end - return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler end @@ -85,55 +75,57 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb if not( type(prefix)=="string" and type(text)=="string" and type(distribution)=="string" and - (target==nil or type(target)=="string" or type(target)=="number") and - (prio=="BULK" or prio=="NORMAL" or prio=="ALERT") + (target==nil or type(target)=="string") and + (prio=="BULK" or prio=="NORMAL" or prio=="ALERT") ) then error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2) end + + if strfind(prefix, "[\001-\009]") then + if strfind(prefix, "[\001-\003]") then + error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2) + elseif not warnedPrefix then + -- I have some ideas about future extensions that require more control characters /mikk, 20090808 + geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension") + warnedPrefix = true + end + end + local textlen = #text - local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327 - local queueName = prefix + local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message + local queueName = prefix..distribution..(target or "") local ctlCallback = nil if callbackFn then - ctlCallback = function(sent, sendResult) - return callbackFn(callbackArg, sent, textlen, sendResult) + ctlCallback = function(sent) + return callbackFn(callbackArg, sent, textlen) end end - local forceMultipart - if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character - -- we need to escape the first character with a \004 - if textlen+1 > maxtextlen then -- would we go over the size limit? - forceMultipart = true -- just make it multipart, no escape problems then - else - text = "\004" .. text - end - end - - if not forceMultipart and textlen <= maxtextlen then + if textlen <= maxtextlen then -- fits all in one message CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen) else - maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1) + maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix -- first part local chunk = strsub(text, 1, maxtextlen) - CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen) + CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen) -- continuation local pos = 1+maxtextlen + local prefix2 = prefix..MSG_MULTI_NEXT while pos+maxtextlen <= textlen do chunk = strsub(text, pos, pos+maxtextlen-1) - CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1) + CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1) pos = pos + maxtextlen end -- final part chunk = strsub(text, pos) - CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen) + CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen) end end @@ -146,17 +138,17 @@ do local compost = setmetatable({}, {__mode = "k"}) local function new() local t = next(compost) - if t then + if t then compost[t]=nil for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten t[i]=nil end return t end - + return {} end - + local function lostdatawarning(prefix,sender,where) DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")") end @@ -164,14 +156,14 @@ do function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender) local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender local spool = AceComm.multipart_spool - + --[[ - if spool[key] then + if spool[key] then lostdatawarning(prefix,sender,"First") -- continue and overwrite end --]] - + spool[key] = message -- plain string for now end @@ -179,7 +171,7 @@ do local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender local spool = AceComm.multipart_spool local olddata = spool[key] - + if not olddata then --lostdatawarning(prefix,sender,"Next") return @@ -200,14 +192,14 @@ do local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender local spool = AceComm.multipart_spool local olddata = spool[key] - + if not olddata then --lostdatawarning(prefix,sender,"End") return end spool[key] = nil - + if type(olddata) == "table" then -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat tinsert(olddata, message) @@ -230,31 +222,47 @@ end ---------------------------------------- if not AceComm.callbacks then + -- ensure that 'prefix to watch' table is consistent with registered + -- callbacks + AceComm.__prefixes = {} + AceComm.callbacks = CallbackHandler:New(AceComm, "_RegisterComm", "UnregisterComm", "UnregisterAllComm") end -AceComm.callbacks.OnUsed = nil -AceComm.callbacks.OnUnused = nil +function AceComm.callbacks:OnUsed(target, prefix) + AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix + AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst" + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix + AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext" + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix + AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast" +end -local function OnEvent(self, event, prefix, message, distribution, sender) +function AceComm.callbacks:OnUnused(target, prefix) + AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil + AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil + AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil + AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil +end + +local function OnEvent(this, event, ...) if event == "CHAT_MSG_ADDON" then - sender = Ambiguate(sender, "none") - local control, rest = match(message, "^([\001-\009])(.*)") - if control then - if control==MSG_MULTI_FIRST then - AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender) - elseif control==MSG_MULTI_NEXT then - AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender) - elseif control==MSG_MULTI_LAST then - AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender) - elseif control==MSG_ESCAPE then - AceComm.callbacks:Fire(prefix, rest, distribution, sender) - else - -- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!) - end + local prefix,message,distribution,sender = ... + local reassemblername = AceComm.multipart_reassemblers[prefix] + if reassemblername then + -- multipart: reassemble + local aceCommReassemblerFunc = AceComm[reassemblername] + local origprefix = AceComm.multipart_origprefixes[prefix] + aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender) else -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not AceComm.callbacks:Fire(prefix, message, distribution, sender) diff --git a/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua b/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua index 6e89a6a..b0afc92 100644 --- a/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua +++ b/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua @@ -3,7 +3,7 @@ -- -- Manages AddOn chat output to keep player from getting kicked off. -- --- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept +-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept -- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage. -- -- Priorities get an equal share of available bandwidth when fully loaded. @@ -20,10 +20,8 @@ -- -- Can run as a standalone addon also, but, really, just embed it! :-) -- --- LICENSE: ChatThrottleLib is released into the Public Domain --- -local CTL_VERSION = 31 +local CTL_VERSION = 21 local _G = _G @@ -73,8 +71,8 @@ local math_min = math.min local math_max = math.max local next = next local strlen = string.len -local GetFramerate = GetFramerate -local unpack,type,pairs,wipe = unpack,type,pairs,table.wipe +local GetFrameRate = GetFrameRate + ----------------------------------------------------------------------- @@ -113,41 +111,28 @@ function Ring:Remove(obj) end end --- Note that this is local because there's no upgrade logic for existing ring --- metatables, and this isn't present on rings created in versions older than --- v25. -local function Ring_Link(self, other) -- Move and append all contents of another ring to this ring - if not self.pos then - -- This ring is empty, so just transfer ownership. - self.pos = other.pos - other.pos = nil - elseif other.pos then - -- Our tail should point to their head, and their tail to our head. - self.pos.prev.next, other.pos.prev.next = other.pos, self.pos - -- Our head should point to their tail, and their head to our tail. - self.pos.prev, other.pos.prev = other.pos.prev, self.pos.prev - other.pos = nil - end -end - ----------------------------------------------------------------------- --- Recycling bin for pipes --- A pipe is a plain integer-indexed queue of messages --- Pipes normally live in Rings of pipes (3 rings total, one per priority) +-- Recycling bin for pipes +-- A pipe is a plain integer-indexed queue, which also happens to be a ring member ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different local PipeBin = setmetatable({}, {__mode="k"}) local function DelPipe(pipe) + for i = #pipe, 1, -1 do + pipe[i] = nil + end + pipe.prev = nil + pipe.next = nil + PipeBin[pipe] = true end local function NewPipe() local pipe = next(PipeBin) if pipe then - wipe(pipe) PipeBin[pipe] = nil return pipe end @@ -184,7 +169,7 @@ end -- Initialize queues, set up frame for OnUpdate, etc -function ChatThrottleLib:Init() +function ChatThrottleLib:Init() -- Set up queues if not self.Prio then @@ -194,13 +179,6 @@ function ChatThrottleLib:Init() self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 } end - if not self.BlockedQueuesDelay then - -- v25: Add blocked queues to rings to handle new client throttles. - for _, Prio in pairs(self.Prio) do - Prio.Blocked = Ring:New() - end - end - -- v4: total send counters per priority for _, Prio in pairs(self.Prio) do Prio.nTotalSent = Prio.nTotalSent or 0 @@ -223,7 +201,6 @@ function ChatThrottleLib:Init() self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD") self.OnUpdateDelay = 0 - self.BlockedQueuesDelay = 0 self.LastAvailUpdate = GetTime() self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup @@ -232,43 +209,14 @@ function ChatThrottleLib:Init() -- Use secure hooks as of v16. Old regular hook support yanked out in v21. self.securelyHooked = true --SendChatMessage - if _G.C_ChatInfo and _G.C_ChatInfo.SendChatMessage then - hooksecurefunc(_G.C_ChatInfo, "SendChatMessage", function(...) - return ChatThrottleLib.Hook_SendChatMessage(...) - end) - else - hooksecurefunc("SendChatMessage", function(...) - return ChatThrottleLib.Hook_SendChatMessage(...) - end) - end + hooksecurefunc("SendChatMessage", function(...) + return ChatThrottleLib.Hook_SendChatMessage(...) + end) --SendAddonMessage - hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...) + hooksecurefunc("SendAddonMessage", function(...) return ChatThrottleLib.Hook_SendAddonMessage(...) end) end - - -- v26: Hook SendAddonMessageLogged for traffic logging - if not self.securelyHookedLogged then - self.securelyHookedLogged = true - hooksecurefunc(_G.C_ChatInfo, "SendAddonMessageLogged", function(...) - return ChatThrottleLib.Hook_SendAddonMessageLogged(...) - end) - end - - -- v29: Hook BNSendGameData for traffic logging - if not self.securelyHookedBNGameData then - self.securelyHookedBNGameData = true - if _G.C_BattleNet and _G.C_BattleNet.SendGameData then - hooksecurefunc(_G.C_BattleNet, "SendGameData", function(...) - return ChatThrottleLib.Hook_BNSendGameData(...) - end) - else - hooksecurefunc("BNSendGameData", function(...) - return ChatThrottleLib.Hook_BNSendGameData(...) - end) - end - end - self.nBypass = 0 end @@ -297,12 +245,6 @@ function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destinati self.avail = self.avail - size self.nBypass = self.nBypass + size -- just a statistic end -function ChatThrottleLib.Hook_SendAddonMessageLogged(prefix, text, chattype, destination, ...) - ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...) -end -function ChatThrottleLib.Hook_BNSendGameData(destination, prefix, text) - ChatThrottleLib.Hook_SendAddonMessage(prefix, text, "WHISPER", destination) -end @@ -339,93 +281,27 @@ end ----------------------------------------------------------------------- -- Despooling logic --- Reminder: --- - We have 3 Priorities, each containing a "Ring" construct ... --- - ... made up of N "Pipe"s (1 for each destination/pipename) --- - and each pipe contains messages - -local SendAddonMessageResult = Enum.SendAddonMessageResult or { - Success = 0, - AddonMessageThrottle = 3, - NotInGroup = 5, - ChannelThrottle = 8, - GeneralError = 9, -} - -local function MapToSendResult(ok, ...) - local result - - if not ok then - -- The send function itself errored; don't look at anything else. - result = SendAddonMessageResult.GeneralError - else - -- Grab the last return value from the send function and remap - -- it from a boolean to an enum code. If there are no results, - -- assume success (true). - - result = select(-1, true, ...) - - if result == true then - result = SendAddonMessageResult.Success - elseif result == false then - result = SendAddonMessageResult.GeneralError - end - end - - return result -end - -local function IsThrottledSendResult(result) - return result == SendAddonMessageResult.AddonMessageThrottle -end - --- A copy of this function exists in FrameXML, but for clarity it's here too. -local function CallErrorHandler(...) - return geterrorhandler()(...) -end - -local function PerformSend(sendFunction, ...) - bMyTraffic = true - local sendResult = MapToSendResult(xpcall(sendFunction, CallErrorHandler, ...)) - bMyTraffic = false - return sendResult -end function ChatThrottleLib:Despool(Prio) local ring = Prio.Ring while ring.pos and Prio.avail > ring.pos[1].nSize do - local pipe = ring.pos - local msg = pipe[1] - local sendResult = PerformSend(msg.f, unpack(msg, 1, msg.n)) - - if IsThrottledSendResult(sendResult) then - -- Message was throttled; move the pipe into the blocked ring. + local msg = table_remove(Prio.Ring.pos, 1) + if not Prio.Ring.pos[1] then + local pipe = Prio.Ring.pos Prio.Ring:Remove(pipe) - Prio.Blocked:Add(pipe) + Prio.ByName[pipe.name] = nil + DelPipe(pipe) else - -- Dequeue message after submission. - table_remove(pipe, 1) - DelMsg(msg) - - if not pipe[1] then -- did we remove last msg in this pipe? - Prio.Ring:Remove(pipe) - Prio.ByName[pipe.name] = nil - DelPipe(pipe) - else - ring.pos = ring.pos.next - end - - -- Update bandwidth counters on successful sends. - local didSend = (sendResult == SendAddonMessageResult.Success) - if didSend then - Prio.avail = Prio.avail - msg.nSize - Prio.nTotalSent = Prio.nTotalSent + msg.nSize - end - - -- Notify caller of message submission. - if msg.callbackFn then - securecallfunction(msg.callbackFn, msg.callbackArg, didSend, sendResult) - end + Prio.Ring.pos = Prio.Ring.pos.next + end + Prio.avail = Prio.avail - msg.nSize + bMyTraffic = true + msg.f(unpack(msg, 1, msg.n)) + bMyTraffic = false + Prio.nTotalSent = Prio.nTotalSent + msg.nSize + DelMsg(msg) + if msg.callbackFn then + msg.callbackFn (msg.callbackArg) end end end @@ -445,7 +321,6 @@ function ChatThrottleLib.OnUpdate(this,delay) local self = ChatThrottleLib self.OnUpdateDelay = self.OnUpdateDelay + delay - self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay if self.OnUpdateDelay < 0.08 then return end @@ -457,60 +332,40 @@ function ChatThrottleLib.OnUpdate(this,delay) return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. end - -- Integrate blocked queues back into their rings periodically. - if self.BlockedQueuesDelay >= 0.35 then - for _, Prio in pairs(self.Prio) do - Ring_Link(Prio.Ring, Prio.Blocked) + -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop) + local n = 0 + for prioname,Prio in pairs(self.Prio) do + if Prio.Ring.pos or Prio.avail < 0 then + n = n + 1 end - - self.BlockedQueuesDelay = 0 end - -- See how many of our priorities have queued messages. This is split - -- into two counters because priorities that consist only of blocked - -- queues must keep our OnUpdate alive, but shouldn't count toward - -- bandwidth distribution. - local nSendablePrios = 0 - local nBlockedPrios = 0 - - for prioname, Prio in pairs(self.Prio) do - if Prio.Ring.pos then - nSendablePrios = nSendablePrios + 1 - elseif Prio.Blocked.pos then - nBlockedPrios = nBlockedPrios + 1 - end - - -- Collect unused bandwidth from priorities with nothing to send. - if not Prio.Ring.pos then + -- Anything queued still? + if n<1 then + -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing + for prioname, Prio in pairs(self.Prio) do self.avail = self.avail + Prio.avail Prio.avail = 0 end - end - - -- Bandwidth reclamation may take us back over the burst cap. - self.avail = math_min(self.avail, self.BURST) - - -- If we can't currently send on any priorities, stop processing early. - if nSendablePrios == 0 then - -- If we're completely out of data to send, disable queue processing. - if nBlockedPrios == 0 then - self.bQueueing = false - self.Frame:Hide() - end - + self.bQueueing = false + self.Frame:Hide() return end -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues - local avail = self.avail / nSendablePrios + local avail = self.avail/n self.avail = 0 for prioname, Prio in pairs(self.Prio) do - if Prio.Ring.pos then + if Prio.Ring.pos or Prio.avail < 0 then Prio.avail = Prio.avail + avail - self:Despool(Prio) + if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then + self:Despool(Prio) + -- Note: We might not get here if the user-supplied callback function errors out! Take care! + end end end + end @@ -519,6 +374,7 @@ end ----------------------------------------------------------------------- -- Spooling logic + function ChatThrottleLib:Enqueue(prioname, pipename, msg) local Prio = self.Prio[prioname] local pipe = Prio.ByName[pipename] @@ -535,6 +391,8 @@ function ChatThrottleLib:Enqueue(prioname, pipename, msg) self.bQueueing = true end + + function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg) if not self or not prio or not prefix or not text or not self.Prio[prio] then error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2) @@ -553,27 +411,20 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag -- Check if there's room in the global available bandwidth gauge to send directly if not self.bQueueing and nSize < self:UpdateAvail() then - local sendResult = PerformSend(_G.C_ChatInfo.SendChatMessage or _G.SendChatMessage, text, chattype, language, destination) - - if not IsThrottledSendResult(sendResult) then - local didSend = (sendResult == SendAddonMessageResult.Success) - - if didSend then - self.avail = self.avail - nSize - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - end - - if callbackFn then - securecallfunction(callbackFn, callbackArg, didSend, sendResult) - end - - return + self.avail = self.avail - nSize + bMyTraffic = true + _G.SendChatMessage(text, chattype, language, destination) + bMyTraffic = false + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + if callbackFn then + callbackFn (callbackArg) end + return end -- Message needs to be queued local msg = NewMsg() - msg.f = _G.C_ChatInfo.SendChatMessage or _G.SendChatMessage + msg.f = _G.SendChatMessage msg[1] = text msg[2] = chattype or "SAY" msg[3] = language @@ -583,36 +434,42 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or prefix, msg) + self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg) end -local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) - local nSize = #text + self.MSG_OVERHEAD +function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + end + if callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) + end + + local nSize = prefix:len() + 1 + text:len(); + + if nSize>255 then + error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2) + end + + nSize = nSize + self.MSG_OVERHEAD; -- Check if there's room in the global available bandwidth gauge to send directly if not self.bQueueing and nSize < self:UpdateAvail() then - local sendResult = PerformSend(sendFunction, prefix, text, chattype, target) - - if not IsThrottledSendResult(sendResult) then - local didSend = (sendResult == SendAddonMessageResult.Success) - - if didSend then - self.avail = self.avail - nSize - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - end - - if callbackFn then - securecallfunction(callbackFn, callbackArg, didSend, sendResult) - end - - return + self.avail = self.avail - nSize + bMyTraffic = true + _G.SendAddonMessage(prefix, text, chattype, target) + bMyTraffic = false + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + if callbackFn then + callbackFn (callbackArg) end + return end -- Message needs to be queued local msg = NewMsg() - msg.f = sendFunction + msg.f = _G.SendAddonMessage msg[1] = prefix msg[2] = text msg[3] = chattype @@ -622,65 +479,10 @@ local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or prefix, msg) + self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg) end -function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) - if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then - error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) - elseif callbackFn and type(callbackFn)~="function" then - error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) - elseif #text>255 then - error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) - end - - local sendFunction = _G.C_ChatInfo.SendAddonMessage - SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) -end - - -function ChatThrottleLib:SendAddonMessageLogged(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) - if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then - error('Usage: ChatThrottleLib:SendAddonMessageLogged("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) - elseif callbackFn and type(callbackFn)~="function" then - error('ChatThrottleLib:SendAddonMessageLogged(): callbackFn: expected function, got '..type(callbackFn), 2) - elseif #text>255 then - error("ChatThrottleLib:SendAddonMessageLogged(): message length cannot exceed 255 bytes", 2) - end - - local sendFunction = _G.C_ChatInfo.SendAddonMessageLogged - SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) -end - -local function BNSendGameDataReordered(prefix, text, _, gameAccountID) - local bnSendFunc = _G.C_BattleNet and _G.C_BattleNet.SendGameData or _G.BNSendGameData - return bnSendFunc(gameAccountID, prefix, text) -end - -function ChatThrottleLib:BNSendGameData(prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) - -- Note that this API is intentionally limited to 255 bytes of data - -- for reasons of traffic fairness, which is less than the 4078 bytes - -- BNSendGameData natively supports. Additionally, a chat type is required - -- but must always be set to 'WHISPER' to match what is exposed by the - -- receipt event. - -- - -- If splitting messages, callers must also be aware that message - -- delivery over BNSendGameData is unordered. - - if not self or not prio or not prefix or not text or not gameAccountID or not chattype or not self.Prio[prio] then - error('Usage: ChatThrottleLib:BNSendGameData("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype", gameAccountID)', 2) - elseif callbackFn and type(callbackFn)~="function" then - error('ChatThrottleLib:BNSendGameData(): callbackFn: expected function, got '..type(callbackFn), 2) - elseif #text>255 then - error("ChatThrottleLib:BNSendGameData(): message length cannot exceed 255 bytes", 2) - elseif chattype ~= "WHISPER" then - error("ChatThrottleLib:BNSendGameData(): chat type must be 'WHISPER'", 2) - end - - local sendFunction = BNSendGameDataReordered - SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) -end ----------------------------------------------------------------------- diff --git a/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua b/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua index 8e5ec81..c001123 100644 --- a/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua +++ b/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua @@ -2,14 +2,14 @@ -- You can register slash commands to your custom functions and use the `GetArgs` function to parse them -- to your addons individual needs. -- --- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by +-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceConsole itself.\\ -- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you -- make into AceConsole. -- @class file -- @name AceConsole-3.0 --- @release $Id$ +-- @release $Id: AceConsole-3.0.lua 878 2009-11-02 18:51:58Z nevcairiel $ local MAJOR,MINOR = "AceConsole-3.0", 7 local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR) @@ -29,6 +29,10 @@ local max = math.max -- WoW APIs local _G = _G +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList + local tmp={} local function Print(self,frame,...) local n=0 @@ -80,11 +84,11 @@ end -- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true) function AceConsole:RegisterChatCommand( command, func, persist ) if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end - + if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk - + local name = "ACECONSOLE_"..command:upper() - + if type( func ) == "string" then SlashCmdList[name] = function(input, editBox) self[func](self, input, editBox) @@ -128,11 +132,11 @@ local function nils(n, ...) return ... end end + - ---- Retreive one or more space-separated arguments from a string. +--- Retreive one or more space-separated arguments from a string. -- Treats quoted strings and itemlinks as non-spaced. --- @param str The raw argument string +-- @param string The raw argument string -- @param numargs How many arguments to get (default 1) -- @param startpos Where in the string to start scanning (default 1) -- @return Returns arg1, arg2, ..., nextposition\\ @@ -140,7 +144,7 @@ end function AceConsole:GetArgs(str, numargs, startpos) numargs = numargs or 1 startpos = max(startpos or 1, 1) - + local pos=startpos -- find start of new arg @@ -165,24 +169,24 @@ function AceConsole:GetArgs(str, numargs, startpos) else delim_or_pipe="([| ])" end - + startpos = pos - + while true do -- find delimiter or hyperlink - local _ + local ch,_ pos,_,ch = strfind(str, delim_or_pipe, pos) - + if not pos then break end - + if ch=="|" then -- some kind of escape - + if strsub(str,pos,pos+1)=="|H" then -- It's a |H....|hhyper link!|h pos=strfind(str, "|h", pos+2) -- first |h if not pos then break end - + pos=strfind(str, "|h", pos+2) -- second |h if not pos then break end elseif strsub(str,pos, pos+1) == "|T" then @@ -190,16 +194,16 @@ function AceConsole:GetArgs(str, numargs, startpos) pos=strfind(str, "|t", pos+2) if not pos then break end end - + pos=pos+2 -- skip past this escape (last |h if it was a hyperlink) - + else -- found delimiter, done with this arg return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1) end - + end - + -- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink) return strsub(str, startpos), nils(numargs-1, 1e9) end @@ -210,10 +214,10 @@ end local mixins = { "Print", "Printf", - "RegisterChatCommand", + "RegisterChatCommand", "UnregisterChatCommand", "GetArgs", -} +} -- Embeds AceConsole into the target object making the functions from the mixins list available on target:.. -- @param target target object to embed AceBucket in diff --git a/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua b/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua index f83a715..7a29450 100644 --- a/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua +++ b/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua @@ -10,7 +10,6 @@ -- * **race** Race-specific data. All of the players characters of the same race share this database. -- * **faction** Faction-specific data. All of the players characters of the same faction share this database. -- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. --- * **locale** Locale specific data, based on the locale of the players game client. -- * **global** Global Data. All characters on the same account share this database. -- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. -- @@ -40,19 +39,23 @@ -- end -- @class file -- @name AceDB-3.0.lua --- @release $Id$ -local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 33 -local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) +-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21 +local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) if not AceDB then return end -- No upgrade needed -- Lua APIs local type, pairs, next, error = type, pairs, next, error -local setmetatable, rawset, rawget = setmetatable, rawset, rawget +local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget -- WoW APIs local _G = _G +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub + AceDB.db_registry = AceDB.db_registry or {} AceDB.frame = AceDB.frame or CreateFrame("Frame") @@ -94,11 +97,11 @@ local function copyDefaults(dest, src) -- This is a metatable used for table defaults local mt = { -- This handles the lookup and creation of new subtables - __index = function(t,k2) - if k2 == nil then return nil end + __index = function(t,k) + if k == nil then return nil end local tbl = {} copyDefaults(tbl, v) - rawset(t, k2, tbl) + rawset(t, k, tbl) return tbl end, } @@ -111,15 +114,7 @@ local function copyDefaults(dest, src) end else -- Values are not tables, so this is just a simple return - -- (PR #10 backport: the old `k2~=nil and v or nil` short-circuits to - -- nil whenever the default `v` itself is falsy — so `["*"] = false` - -- defaults silently became nil. Make the read explicit instead.) - local mt = { - __index = function(t,k2) - if k2 == nil then return nil end - return v - end, - } + local mt = {__index = function(t,k) return k~=nil and v or nil end} setmetatable(dest, mt) end elseif type(v) == "table" then @@ -265,12 +260,6 @@ local _, classKey = UnitClass("player") local _, raceKey = UnitRace("player") local factionKey = UnitFactionGroup("player") local factionrealmKey = factionKey .. " - " .. realmKey -local localeKey = GetLocale():lower() - -local regionTable = { "US", "KR", "EU", "TW", "CN" } -local regionKey = regionTable[GetCurrentRegion()] or GetCurrentRegionName() or "TR" -local factionrealmregionKey = factionrealmKey .. " - " .. regionKey - -- Actual database initialization function local function initdb(sv, defaults, defaultProfile, olddb, parent) -- Generate the database keys for each section @@ -306,9 +295,7 @@ local function initdb(sv, defaults, defaultProfile, olddb, parent) ["race"] = raceKey, ["faction"] = factionKey, ["factionrealm"] = factionrealmKey, - ["factionrealmregion"] = factionrealmregionKey, ["profile"] = profileKey, - ["locale"] = localeKey, ["global"] = true, ["profiles"] = true, } @@ -365,10 +352,10 @@ local function logoutHandler(frame, event) for db in pairs(AceDB.db_registry) do db.callbacks:Fire("OnDatabaseShutdown", db) db:RegisterDefaults(nil) - + -- cleanup sections that are empty without defaults local sv = rawget(db, "sv") - for section in pairs(rawget(db, "keys")) do + for section in pairs(db.keys) do if rawget(sv, section) then -- global is special, all other sections have sub-entrys -- also don't delete empty profiles on main dbs, only on namespaces @@ -385,26 +372,6 @@ local function logoutHandler(frame, event) end end end - - -- second pass after everything else is cleaned up to remove empty namespaces - -- can't be run in-loop above since there is no guaranteed order - for db in pairs(AceDB.db_registry) do - local sv = rawget(db, "sv") - local namespaces = rawget(sv, "namespaces") - if namespaces then - for name in pairs(namespaces) do - -- cleanout empty profiles table, if still present - if namespaces[name].profiles and not next(namespaces[name].profiles) then - namespaces[name].profiles = nil - end - - -- remove entire namespace, if needed - if not next(namespaces[name]) then - namespaces[name] = nil - end - end - end - end end end @@ -421,7 +388,7 @@ AceDB.frame:SetScript("OnEvent", logoutHandler) -- @param defaults A table of defaults for this database function DBObjectLib:RegisterDefaults(defaults) if defaults and type(defaults) ~= "table" then - error(("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2) + error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2) end validateDefaults(defaults, self.keys) @@ -453,7 +420,7 @@ end -- @param name The name of the profile to set as the current profile function DBObjectLib:SetProfile(name) if type(name) ~= "string" then - error(("Usage: AceDBObject:SetProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) + error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2) end -- changing to the same profile, dont do anything @@ -495,7 +462,7 @@ end -- @param tbl A table to store the profile names in (optional) function DBObjectLib:GetProfiles(tbl) if tbl and type(tbl) ~= "table" then - error(("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected, got %q."):format(type(tbl)), 2) + error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2) end -- Clear the container table @@ -533,15 +500,15 @@ end -- @param silent If true, do not raise an error when the profile does not exist function DBObjectLib:DeleteProfile(name, silent) if type(name) ~= "string" then - error(("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) + error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) end if self.keys.profile == name then - error(("Cannot delete the active profile (%q) in an AceDBObject."):format(name), 2) + error("Cannot delete the active profile in an AceDBObject.", 2) end if not rawget(self.profiles, name) and not silent then - error(("Cannot delete profile %q as it does not exist."):format(name), 2) + error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) end self.profiles[name] = nil @@ -553,26 +520,6 @@ function DBObjectLib:DeleteProfile(name, silent) end end - -- remove from unloaded namespaces - if self.sv.namespaces then - for nsname, data in pairs(self.sv.namespaces) do - if self.children and self.children[nsname] then - -- already a mapped namespace - elseif data.profiles then - data.profiles[name] = nil - end - end - end - - -- switch all characters that use this profile back to the default - if self.sv.profileKeys then - for key, profile in pairs(self.sv.profileKeys) do - if profile == name then - self.sv.profileKeys[key] = nil - end - end - end - -- Callback: OnProfileDeleted, database, profileKey self.callbacks:Fire("OnProfileDeleted", self, name) end @@ -583,15 +530,15 @@ end -- @param silent If true, do not raise an error when the profile does not exist function DBObjectLib:CopyProfile(name, silent) if type(name) ~= "string" then - error(("Usage: AceDBObject:CopyProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) + error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) end if name == self.keys.profile then - error(("Cannot have the same source and destination profiles (%q)."):format(name), 2) + error("Cannot have the same source and destination profiles.", 2) end if not rawget(self.profiles, name) and not silent then - error(("Cannot copy profile %q as it does not exist."):format(name), 2) + error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) end -- Reset the profile before copying @@ -609,20 +556,6 @@ function DBObjectLib:CopyProfile(name, silent) end end - -- copy unloaded namespaces - if self.sv.namespaces then - for nsname, data in pairs(self.sv.namespaces) do - if self.children and self.children[nsname] then - -- already a mapped namespace - elseif data.profiles then - -- reset the current profile - data.profiles[self.keys.profile] = {} - -- copy data - copyTable(data.profiles[name], data.profiles[self.keys.profile]) - end - end - end - -- Callback: OnProfileCopied, database, sourceProfileKey self.callbacks:Fire("OnProfileCopied", self, name) end @@ -649,18 +582,6 @@ function DBObjectLib:ResetProfile(noChildren, noCallbacks) end end - -- reset unloaded namespaces - if self.sv.namespaces and not noChildren then - for nsname, data in pairs(self.sv.namespaces) do - if self.children and self.children[nsname] then - -- already a mapped namespace - elseif data.profiles then - -- reset the current profile - data.profiles[self.keys.profile] = nil - end - end - end - -- Callback: OnProfileReset, database if not noCallbacks then self.callbacks:Fire("OnProfileReset", self) @@ -671,8 +592,8 @@ end -- profile. -- @param defaultProfile The profile name to use as the default function DBObjectLib:ResetDB(defaultProfile) - if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then - error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) + if defaultProfile and type(defaultProfile) ~= "string" then + error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) end local sv = self.sv @@ -680,6 +601,8 @@ function DBObjectLib:ResetDB(defaultProfile) sv[k] = nil end + local parent = self.parent + initdb(sv, self.defaults, defaultProfile, self) -- fix the child namespaces @@ -706,13 +629,13 @@ end -- @param defaults A table of values to use as defaults function DBObjectLib:RegisterNamespace(name, defaults) if type(name) ~= "string" then - error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected, got %q."):format(type(name)), 2) + error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) end if defaults and type(defaults) ~= "table" then - error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2) + error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) end if self.children and self.children[name] then - error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace called %q already exists."):format(name), 2) + error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2) end local sv = self.sv @@ -736,10 +659,10 @@ end -- @return the namespace object if found function DBObjectLib:GetNamespace(name, silent) if type(name) ~= "string" then - error(("Usage: AceDBObject:GetNamespace(name): 'name' - string expected, got %q."):format(type(name)), 2) + error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2) end if not silent and not (self.children and self.children[name]) then - error(("Usage: AceDBObject:GetNamespace(name): 'name' - namespace %q does not exist."):format(name), 2) + error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2) end if not self.children then self.children = {} end return self.children[name] @@ -778,15 +701,15 @@ function AceDB:New(tbl, defaults, defaultProfile) end if type(tbl) ~= "table" then - error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected, got %q."):format(type(tbl)), 2) + error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) end if defaults and type(defaults) ~= "table" then - error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected, got %q."):format(type(defaults)), 2) + error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) end if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then - error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) + error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2) end return initdb(tbl, defaults, defaultProfile) diff --git a/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua b/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua index 9f96bf3..e9b18b1 100644 --- a/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua +++ b/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua @@ -2,17 +2,15 @@ -- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around -- CallbackHandler, and dispatches all game events or addon message to the registrees. -- --- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by +-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceEvent itself.\\ -- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you -- make into AceEvent. -- @class file -- @name AceEvent-3.0 --- @release $Id$ -local CallbackHandler = LibStub("CallbackHandler-1.0") - -local MAJOR, MINOR = "AceEvent-3.0", 4 +-- @release $Id: AceEvent-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $ +local MAJOR, MINOR = "AceEvent-3.0", 3 local AceEvent = LibStub:NewLibrary(MAJOR, MINOR) if not AceEvent then return end @@ -20,27 +18,29 @@ if not AceEvent then return end -- Lua APIs local pairs = pairs +local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") + AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib -- APIs and registry for blizzard events, using CallbackHandler lib if not AceEvent.events then - AceEvent.events = CallbackHandler:New(AceEvent, + AceEvent.events = CallbackHandler:New(AceEvent, "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents") end -function AceEvent.events:OnUsed(target, eventname) +function AceEvent.events:OnUsed(target, eventname) AceEvent.frame:RegisterEvent(eventname) end -function AceEvent.events:OnUnused(target, eventname) +function AceEvent.events:OnUnused(target, eventname) AceEvent.frame:UnregisterEvent(eventname) end -- APIs and registry for IPC messages, using CallbackHandler lib if not AceEvent.messages then - AceEvent.messages = CallbackHandler:New(AceEvent, + AceEvent.messages = CallbackHandler:New(AceEvent, "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages" ) AceEvent.SendMessage = AceEvent.messages.Fire @@ -55,7 +55,7 @@ local mixins = { } --- Register for a Blizzard Event. --- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied) +-- The callback will always be called with the event as the first argument, and if supplied, the `arg` as second argument. -- Any arguments to the event will be passed on after that. -- @name AceEvent:RegisterEvent -- @class function @@ -71,7 +71,7 @@ local mixins = { -- @param event The event to unregister --- Register for a custom AceEvent-internal message. --- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied) +-- The callback will always be called with the event as the first argument, and if supplied, the `arg` as second argument. -- Any arguments to the event will be passed on after that. -- @name AceEvent:RegisterMessage -- @class function diff --git a/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua b/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua index ae0f7f9..b072a2b 100644 --- a/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua +++ b/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua @@ -1,17 +1,17 @@ --- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format, --- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially +-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially -- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple -- references to the same table will be send individually. -- --- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by +-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceSerializer itself.\\ -- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you -- make into AceSerializer. -- @class file -- @name AceSerializer-3.0 --- @release $Id$ -local MAJOR,MINOR = "AceSerializer-3.0", 5 +-- @release $Id: AceSerializer-3.0.lua 910 2010-02-11 21:54:24Z mikk $ +local MAJOR,MINOR = "AceSerializer-3.0", 3 local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceSerializer then return end @@ -24,11 +24,9 @@ local pairs, select, frexp = pairs, select, math.frexp local tconcat = table.concat -- quick copies of string representations of wonky numbers -local inf = math.huge - -local serNaN -- can't do this in 4.3, see ace3 ticket 268 -local serInf, serInfMac = "1.#INF", "inf" -local serNegInf, serNegInfMac = "-1.#INF", "-inf" +local serNaN = tostring(0/0) +local serInf = tostring(1/0) +local serNegInf = tostring(-1/0) -- Serialization functions @@ -40,7 +38,7 @@ local function SerializeStringHelper(ch) -- Used by SerializeValue for strings return "\126\122" elseif n<=32 then -- nonprint + space return "\126"..strchar(n+64) - elseif n==94 then -- value separator + elseif n==94 then -- value separator return "\126\125" elseif n==126 then -- our own escape character return "\126\124" @@ -54,23 +52,19 @@ end local function SerializeValue(v, res, nres) -- We use "^" as a value separator, followed by one byte for type indicator local t=type(v) - + if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc) res[nres+1] = "^S" res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper) nres=nres+2 - + elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components) local str = tostring(v) - if tonumber(str)==v --[[not in 4.3 or str==serNaN]] then + if tonumber(str)==v or str==serNaN or str==serInf or str==serNegInf then -- translates just fine, transmit as-is res[nres+1] = "^N" res[nres+2] = str nres=nres+2 - elseif v == inf or v == -inf then - res[nres+1] = "^N" - res[nres+2] = v == inf and serInf or serNegInf - nres=nres+2 else local m,e = frexp(v) res[nres+1] = "^F" @@ -79,17 +73,17 @@ local function SerializeValue(v, res, nres) res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation nres=nres+4 end - + elseif t=="table" then -- ^T...^t = table (list of key,value pairs) nres=nres+1 res[nres] = "^T" - for key,value in pairs(v) do - nres = SerializeValue(key, res, nres) - nres = SerializeValue(value, res, nres) + for k,v in pairs(v) do + nres = SerializeValue(k, res, nres) + nres = SerializeValue(v, res, nres) end nres=nres+1 res[nres] = "^t" - + elseif t=="boolean" then -- ^B = true, ^b = false nres=nres+1 if v then @@ -97,15 +91,15 @@ local function SerializeValue(v, res, nres) else res[nres] = "^b" -- false end - + elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P) nres=nres+1 res[nres] = "^Z" - + else error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive end - + return nres end @@ -121,14 +115,14 @@ local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer -- @return The data in its serialized form (string) function AceSerializer:Serialize(...) local nres = 1 - + for i=1,select("#", ...) do local v = select(i, ...) nres = SerializeValue(v, serializeTbl, nres) end - + serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data - + return tconcat(serializeTbl, "", 1, nres+1) end @@ -149,12 +143,12 @@ local function DeserializeStringHelper(escape) end local function DeserializeNumberHelper(number) - --[[ not in 4.3 if number == serNaN then + if number == serNaN then return 0/0 - else]]if number == serNegInf or number == serNegInfMac then - return -inf - elseif number == serInf or number == serInfMac then - return inf + elseif number == serNegInf then + return -1/0 + elseif number == serInf then + return 1/0 else return tonumber(number) end @@ -175,9 +169,9 @@ local function DeserializeValue(iter,single,ctl,data) ctl,data = iter() end - if not ctl then + if not ctl then error("Supplied data misses AceSerializer terminator ('^^')") - end + end if ctl=="^^" then -- ignore extraneous data @@ -185,7 +179,7 @@ local function DeserializeValue(iter,single,ctl,data) end local res - + if ctl=="^S" then res = gsub(data, "~.", DeserializeStringHelper) elseif ctl=="^N" then @@ -218,7 +212,7 @@ local function DeserializeValue(iter,single,ctl,data) ctl,data = iter() if ctl=="^t" then break end -- ignore ^t's data k = DeserializeValue(iter,true,ctl,data) - if k==nil then + if k==nil then error("Invalid AceSerializer table format (no table end marker)") end ctl,data = iter() @@ -231,7 +225,7 @@ local function DeserializeValue(iter,single,ctl,data) else error("Invalid AceSerializer control code '"..ctl.."'") end - + if not single then return res,DeserializeValue(iter) else @@ -284,4 +278,4 @@ end -- Update embeds for target, v in pairs(AceSerializer.embeds) do AceSerializer:Embed(target) -end +end \ No newline at end of file diff --git a/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua b/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua index e84d420..c376f16 100644 --- a/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua +++ b/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua @@ -1,12 +1,12 @@ --- **AceTimer-3.0** provides a central facility for registering timers. -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient --- data structure that allows easy dispatching and fast rescheduling. Timers can be registered +-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered, rescheduled -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ --- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API --- restricts us to. +-- AceTimer is currently limited to firing timers at a frequency of 0.1s. This constant may change +-- in the future, but for now it seemed like a good compromise in efficiency and accuracy. -- -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you --- need to cancel the timer you just registered. +-- need to cancel or reschedule the timer you just registered. -- -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object @@ -15,110 +15,282 @@ -- make into AceTimer. -- @class file -- @name AceTimer-3.0 --- @release $Id$ +-- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ -local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes +--[[ + Basic assumptions: + * In a typical system, we do more re-scheduling per second than there are timer pulses per second + * Regardless of timer implementation, we cannot guarantee timely delivery due to FPS restriction (may be as low as 10) + + This implementation: + CON: The smallest timer interval is constrained by HZ (currently 1/10s). + PRO: It will still correctly fire any timer slower than HZ over a length of time, e.g. 0.11s interval -> 90 times over 10 seconds + PRO: In lag bursts, the system simly skips missed timer intervals to decrease load + CON: Algorithms depending on a timer firing "N times per minute" will fail + PRO: (Re-)scheduling is O(1) with a VERY small constant. It's a simple linked list insertion in a hash bucket. + CAUTION: The BUCKETS constant constrains how many timers can be efficiently handled. With too many hash collisions, performance will decrease. + + Major assumptions upheld: + - ALLOWS scheduling multiple timers with the same funcref/method + - ALLOWS scheduling more timers during OnUpdate processing + - ALLOWS unscheduling ANY timer (including the current running one) at any time, including during OnUpdate processing +]] + +local MAJOR, MINOR = "AceTimer-3.0", 5 local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceTimer then return end -- No upgrade needed -AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list -local activeTimers = AceTimer.activeTimers -- Upvalue our private data + +AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member) + -- Linked list gets around ACE-88 and ACE-90. +AceTimer.selfs = AceTimer.selfs or {} -- Array of [self]={[handle]=timerobj, [handle2]=timerobj2, ...} +AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") -- Lua APIs -local type, unpack, next, error, select = type, unpack, next, error, select +local assert, error, loadstring = assert, error, loadstring +local setmetatable, rawset, rawget = setmetatable, rawset, rawget +local select, pairs, type, next, tostring = select, pairs, type, next, tostring +local floor, max, min = math.floor, math.max, math.min +local tconcat = table.concat + -- WoW APIs -local GetTime, C_TimerAfter = GetTime, C_Timer.After +local GetTime = GetTime -local function new(self, loop, func, delay, ...) - if delay < 0.01 then - delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler + +-- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes. +local timerCache = nil + +--[[ + Timers will not be fired more often than HZ-1 times per second. + Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999) + If this is ever LOWERED, all existing timers need to be enforced to have a delay >= 1/HZ on lib upgrade. + If this number is ever changed, all entries need to be rehashed on lib upgrade. + ]] +local HZ = 11 + +--[[ + Prime for good distribution + If this number is ever changed, all entries need to be rehashed on lib upgrade. +]] +local BUCKETS = 131 + +local hash = AceTimer.hash +for i=1,BUCKETS do + hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes +end + +--[[ + xpcall safecall implementation +]] +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local xpcall, eh = ... -- our arguments are received as unnamed values in "..." since we don't have a proper function declaration + local method, ARGS + local function call() return method(ARGS) end + + local function dispatch(func, ...) + method = func + if not method then return end + ARGS = ... + return xpcall(call, eh) + end + + return dispatch + ]] + + local ARGS = {} + for i = 1, argCount do ARGS[i] = "arg"..i end + code = code:gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, { + __index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher end +}) +Dispatchers[0] = function(func) + return xpcall(func, errorhandler) +end - local timer = { - object = self, - func = func, - looping = loop, - argsCount = select("#", ...), - delay = delay, - ends = GetTime() + delay, - ... - } +local function safecall(func, ...) + return Dispatchers[select('#', ...)](func, ...) +end - activeTimers[timer] = timer +local lastint = floor(GetTime() * HZ) - -- Create new timer closure to wrap the "timer" object - timer.callback = function() - if not timer.cancelled then - if type(timer.func) == "string" then - -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil - -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue. - timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount)) - else - timer.func(unpack(timer, 1, timer.argsCount)) - end +-- -------------------------------------------------------------------- +-- OnUpdate handler +-- +-- traverse buckets, always chasing "now", and fire timers that have expired - if timer.looping and not timer.cancelled then - -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly - -- due to fps differences - local time = GetTime() - local ndelay = timer.delay - (time - timer.ends) - -- Ensure the delay doesn't go below the threshold - if ndelay < 0.01 then ndelay = 0.01 end - C_TimerAfter(ndelay, timer.callback) - timer.ends = time + ndelay - else - activeTimers[timer.handle or timer] = nil - end +local function OnUpdate() + local now = GetTime() + local nowint = floor(now * HZ) + + -- Have we passed into a new hash bucket? + if nowint == lastint then return end + + local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2 + + -- Pass through each bucket at most once + -- Happens on e.g. instance loads, but COULD happen on high local load situations also + for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration + local curbucket = (curint % BUCKETS)+1 + -- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks. + local nexttimer = hash[curbucket] + hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash + + while nexttimer do + local timer = nexttimer + nexttimer = timer.next + local when = timer.when + + if when < soon then + -- Call the timer func, either as a method on given object, or a straight function ref + local callback = timer.callback + if type(callback) == "string" then + safecall(timer.object[callback], timer.object, timer.arg) + elseif callback then + safecall(callback, timer.arg) + else + -- probably nilled out by CancelTimer + timer.delay = nil -- don't reschedule it + end + + local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback + + if not delay then + -- single-shot timer (or cancelled) + AceTimer.selfs[timer.object][tostring(timer)] = nil + timerCache = timer + else + -- repeating timer + local newtime = when + delay + if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.) + newtime = now + delay + end + timer.when = newtime + + -- add next timer execution to the correct bucket + local bucket = (floor(newtime * HZ) % BUCKETS) + 1 + timer.next = hash[bucket] + hash[bucket] = timer + end + else -- if when>=soon + -- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution) + timer.next = hash[curbucket] + hash[curbucket] = timer + end -- if whenhandle->timer registry + local handle = tostring(timer) + + local selftimers = AceTimer.selfs[self] + if not selftimers then + selftimers = {} + AceTimer.selfs[self] = selftimers + end + selftimers[handle] = timer + selftimers.__ops = (selftimers.__ops or 0) + 1 + + return handle end --- Schedule a new one-shot timer. -- The timer will fire once in `delay` seconds, unless canceled before. --- @param func Callback function for the timer pulse (funcref or method name). +-- @param callback Callback function for the timer pulse (funcref or method name). -- @param delay Delay for the timer, in seconds. --- @param ... An optional, unlimited amount of arguments to pass to the callback function. +-- @param arg An optional argument to be passed to the callback function. -- @usage --- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0") -- --- function MyAddOn:OnEnable() +-- function MyAddon:OnEnable() -- self:ScheduleTimer("TimerFeedback", 5) -- end -- --- function MyAddOn:TimerFeedback() +-- function MyAddon:TimerFeedback() -- print("5 seconds passed") -- end -function AceTimer:ScheduleTimer(func, delay, ...) - if not func or not delay then - error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) - end - if type(func) == "string" then - if type(self) ~= "table" then - error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2) - elseif not self[func] then - error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) - end - end - return new(self, nil, func, delay, ...) +function AceTimer:ScheduleTimer(callback, delay, arg) + return Reg(self, callback, delay, arg) end --- Schedule a repeating timer. -- The timer will fire every `delay` seconds, until canceled. --- @param func Callback function for the timer pulse (funcref or method name). +-- @param callback Callback function for the timer pulse (funcref or method name). -- @param delay Delay for the timer, in seconds. --- @param ... An optional, unlimited amount of arguments to pass to the callback function. +-- @param arg An optional argument to be passed to the callback function. -- @usage --- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0") -- --- function MyAddOn:OnEnable() +-- function MyAddon:OnEnable() -- self.timerCount = 0 -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) -- end -- --- function MyAddOn:TimerFeedback() +-- function MyAddon:TimerFeedback() -- self.timerCount = self.timerCount + 1 -- print(("%d seconds passed"):format(5 * self.timerCount)) -- -- run 30 seconds in total @@ -126,124 +298,129 @@ end -- self:CancelTimer(self.testTimer) -- end -- end -function AceTimer:ScheduleRepeatingTimer(func, delay, ...) - if not func or not delay then - error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) - end - if type(func) == "string" then - if type(self) ~= "table" then - error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2) - elseif not self[func] then - error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) - end - end - return new(self, true, func, delay, ...) +function AceTimer:ScheduleRepeatingTimer(callback, delay, arg) + return Reg(self, callback, delay, arg, true) end ---- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer` --- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid +--- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer` +-- Both one-shot and repeating timers can be canceled with this function, as long as the `handle` is valid -- and the timer has not fired yet or was canceled before. --- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` -function AceTimer:CancelTimer(id) - local timer = activeTimers[id] - - if not timer then - return false +-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +-- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled) +-- @return True if the timer was successfully cancelled. +function AceTimer:CancelTimer(handle, silent) + if not handle then return end -- nil handle -> bail out without erroring + if type(handle) ~= "string" then + error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway + end + local selftimers = AceTimer.selfs[self] + local timer = selftimers and selftimers[handle] + if silent then + if timer then + timer.callback = nil -- don't run it again + timer.delay = nil -- if this is the currently-executing one: don't even reschedule + -- The timer object is removed in the OnUpdate loop + end + return not not timer -- might return "true" even if we double-cancel. we'll live. else - timer.cancelled = true - activeTimers[id] = nil + if not timer then + geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered") + return false + end + if not timer.callback then + geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired") + return false + end + timer.callback = nil -- don't run it again + timer.delay = nil -- if this is the currently-executing one: don't even reschedule return true end end --- Cancels all timers registered to the current addon object ('self') function AceTimer:CancelAllTimers() - for k,v in next, activeTimers do - if v.object == self then - AceTimer.CancelTimer(self, k) + if not(type(self) == "string" or type(self) == "table") then + error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2) + end + if self == AceTimer then + error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2) + end + + local selftimers = AceTimer.selfs[self] + if selftimers then + for handle,v in pairs(selftimers) do + if type(v) == "table" then -- avoid __ops, etc + AceTimer.CancelTimer(self, handle, true) + end end end end ---- Returns the time left for a timer with the given id, registered by the current addon object ('self'). --- This function will return 0 when the id is invalid. --- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` --- @return The time left on the timer. -function AceTimer:TimeLeft(id) - local timer = activeTimers[id] - if not timer then - return 0 - else - return timer.ends - GetTime() +--- Returns the time left for a timer with the given handle, registered by the current addon object ('self'). +-- This function will raise a warning when the handle is invalid, but not stop execution. +-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +-- @return The time left on the timer, or false if the handle is invalid. +function AceTimer:TimeLeft(handle) + if not handle then return end + if type(handle) ~= "string" then + error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway end + local selftimers = AceTimer.selfs[self] + local timer = selftimers and selftimers[handle] + if not timer then + geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered") + return false + end + return timer.when - GetTime() end -- --------------------------------------------------------------------- --- Upgrading +-- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step +-- and clean it out - otherwise the table indices can grow indefinitely +-- if an addon starts and stops a lot of timers. AceBucket does this! +-- +-- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua --- Upgrade from old hash-bucket based timers to C_Timer.After timers. -if oldminor and oldminor < 10 then - -- disable old timer logic - AceTimer.frame:SetScript("OnUpdate", nil) - AceTimer.frame:SetScript("OnEvent", nil) - AceTimer.frame:UnregisterAllEvents() - -- convert timers - for object,timers in next, AceTimer.selfs do - for handle,timer in next, timers do - if type(timer) == "table" and timer.callback then - local newTimer - if timer.delay then - newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) - else - newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) - end - -- Use the old handle for old timers - activeTimers[newTimer] = nil - activeTimers[handle] = newTimer - newTimer.handle = handle - end - end - end - AceTimer.selfs = nil - AceTimer.hash = nil - AceTimer.debug = nil -elseif oldminor and oldminor < 17 then - -- Upgrade from old animation based timers to C_Timer.After timers. - AceTimer.inactiveTimers = nil - AceTimer.frame = nil - local oldTimers = AceTimer.activeTimers - -- Clear old timer table and update upvalue - AceTimer.activeTimers = {} - activeTimers = AceTimer.activeTimers - for handle, timer in next, oldTimers do - local newTimer - -- Stop the old timer animation - local duration, elapsed = timer:GetDuration(), timer:GetElapsed() - timer:GetParent():Stop() - if timer.looping then - newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount)) - else - newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount)) - end - -- Use the old handle for old timers - activeTimers[newTimer] = nil - activeTimers[handle] = newTimer - newTimer.handle = handle +local lastCleaned = nil + +local function OnEvent(this, event) + if event~="PLAYER_REGEN_ENABLED" then + return end - -- Migrate transitional handles - if oldminor < 13 and AceTimer.hashCompatTable then - for handle, id in next, AceTimer.hashCompatTable do - local t = activeTimers[id] - if t then - activeTimers[id] = nil - activeTimers[handle] = t - t.handle = handle - end - end - AceTimer.hashCompatTable = nil + -- Get the next 'self' to process + local selfs = AceTimer.selfs + local self = next(selfs, lastCleaned) + if not self then + self = next(selfs) end + lastCleaned = self + if not self then -- should only happen if .selfs[] is empty + return + end + + -- Time to clean it out? + local list = selfs[self] + if (list.__ops or 0) < 250 then -- 250 slosh indices = ~10KB wasted (max!). For one 'self'. + return + end + + -- Create a new table and copy all members over + local newlist = {} + local n=0 + for k,v in pairs(list) do + newlist[k] = v + n=n+1 + end + newlist.__ops = 0 -- Reset operation count + + -- And since we now have a count of the number of live timers, check that it's reasonable. Emit a warning if not. + if n>BUCKETS then + DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(self).."' has "..n.." live timers. Surely that's not intended?") + end + + selfs[self] = newlist end -- --------------------------------------------------------------------- @@ -259,20 +436,38 @@ local mixins = { function AceTimer:Embed(target) AceTimer.embeds[target] = true - for _,v in next, mixins do + for _,v in pairs(mixins) do target[v] = AceTimer[v] end return target end --- AceTimer:OnEmbedDisable(target) +-- AceTimer:OnEmbedDisable( target ) -- target (object) - target object that AceTimer is embedded in. -- -- cancel all timers registered for the object -function AceTimer:OnEmbedDisable(target) +function AceTimer:OnEmbedDisable( target ) target:CancelAllTimers() end -for addon in next, AceTimer.embeds do + +for addon in pairs(AceTimer.embeds) do AceTimer:Embed(addon) end + +-- --------------------------------------------------------------------- +-- Debug tools (expose copies of internals to test suites) +AceTimer.debug = AceTimer.debug or {} +AceTimer.debug.HZ = HZ +AceTimer.debug.BUCKETS = BUCKETS + +-- --------------------------------------------------------------------- +-- Finishing touchups + +AceTimer.frame:SetScript("OnUpdate", OnUpdate) +AceTimer.frame:SetScript("OnEvent", OnEvent) +AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED") + +-- In theory, we should hide&show the frame based on there being timers or not. +-- However, this job is fairly expensive, and the chance that there will +-- actually be zero timers running is diminuitive to say the lest. diff --git a/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua index 05fb9d2..9016d1e 100644 --- a/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua +++ b/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua @@ -1,26 +1,61 @@ ---[[ $Id: CallbackHandler-1.0.lua 25 2022-12-12 15:02:36Z nevcairiel $ ]] -local MAJOR, MINOR = "CallbackHandler-1.0", 8 +--[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 3 local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) if not CallbackHandler then return end -- No upgrade needed local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} --- Lua APIs -local securecallfunction, error = securecallfunction, error -local setmetatable, rawget = setmetatable, rawget -local next, select, pairs, type, tostring = next, select, pairs, type, tostring +local type = type +local pcall = pcall +local pairs = pairs +local assert = assert +local concat = table.concat +local loadstring = loadstring +local next = next +local select = select +local type = type +local xpcall = xpcall - -local function Dispatch(handlers, ...) - local index, method = next(handlers) - if not method then return end - repeat - securecallfunction(method, ...) - index, method = next(handlers, index) - until not method +local function errorhandler(err) + return geterrorhandler()(err) end +local function CreateDispatcher(argCount) + local code = [[ + local next, xpcall, eh = ... + + local method, ARGS + local function call() method(ARGS) end + + local function dispatch(handlers, ...) + local index + index, method = next(handlers) + if not method then return end + local OLD_ARGS = ARGS + ARGS = ... + repeat + xpcall(call, eh) + index, method = next(handlers, index) + until not method + ARGS = OLD_ARGS + end + + return dispatch + ]] + + local ARGS, OLD_ARGS = {}, {} + for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end + code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) + -------------------------------------------------------------------------- -- CallbackHandler:New -- @@ -29,7 +64,9 @@ end -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. -function CallbackHandler.New(_self, target, RegisterName, UnregisterName, UnregisterAllName) +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) + -- TODO: Remove this after beta has gone out + assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") RegisterName = RegisterName or "RegisterCallback" UnregisterName = UnregisterName or "UnregisterCallback" @@ -51,19 +88,19 @@ function CallbackHandler.New(_self, target, RegisterName, UnregisterName, Unregi local oldrecurse = registry.recurse registry.recurse = oldrecurse + 1 - Dispatch(events[eventname], eventname, ...) + Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) registry.recurse = oldrecurse if registry.insertQueue and oldrecurse==0 then -- Something in one of our callbacks wanted to register more callbacks; they got queued - for event,callbacks in pairs(registry.insertQueue) do - local first = not rawget(events, event) or not next(events[event]) -- test for empty before. not test for one member after. that one member may have been overwritten. - for object,func in pairs(callbacks) do - events[event][object] = func + for eventname,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for self,func in pairs(callbacks) do + events[eventname][self] = func -- fire OnUsed callback? if first and registry.OnUsed then - registry.OnUsed(registry, target, event) + registry.OnUsed(registry, target, eventname) first = nil end end @@ -109,9 +146,9 @@ function CallbackHandler.New(_self, target, RegisterName, UnregisterName, Unregi regfunc = function(...) self[method](self,...) end end else - -- function ref with self=object or self="addonId" or self=thread - if type(self)~="table" and type(self)~="string" and type(self)~="thread" then - error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) + -- function ref with self=object or self="addonId" + if type(self)~="table" and type(self)~="string" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) end if select("#",...)>=1 then -- this is not the same as testing for arg==nil! diff --git a/ProfessionMenu/Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua b/ProfessionMenu/Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua index f47c0cd..364602e 100644 --- a/ProfessionMenu/Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua +++ b/ProfessionMenu/Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua @@ -2,7 +2,7 @@ assert(LibStub, "LibDataBroker-1.1 requires LibStub") assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") -local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) +local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 3) if not lib then return end oldminor = oldminor or 0 @@ -64,27 +64,3 @@ if oldminor < 1 then return self.namestorage[dataobject] end end - -if oldminor < 4 then - local next = pairs(attributestorage) - function lib:pairs(dataobject_or_name) - local t = type(dataobject_or_name) - assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") - - local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name - assert(attributestorage[dataobj], "Data object not found") - - return next, attributestorage[dataobj], nil - end - - local ipairs_iter = ipairs(attributestorage) - function lib:ipairs(dataobject_or_name) - local t = type(dataobject_or_name) - assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") - - local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name - assert(attributestorage[dataobj], "Data object not found") - - return ipairs_iter, attributestorage[dataobj], 0 - end -end diff --git a/ProfessionMenu/Libs/LibDataBroker-1.1/README.textile b/ProfessionMenu/Libs/LibDataBroker-1.1/README.textile deleted file mode 100644 index ef16fed..0000000 --- a/ProfessionMenu/Libs/LibDataBroker-1.1/README.textile +++ /dev/null @@ -1,13 +0,0 @@ -LibDataBroker is a small WoW addon library designed to provide a "MVC":http://en.wikipedia.org/wiki/Model-view-controller interface for use in various addons. -LDB's primary goal is to "detach" plugins for TitanPanel and FuBar from the display addon. -Plugins can provide data into a simple table, and display addons can receive callbacks to refresh their display of this data. -LDB also provides a place for addons to register "quicklaunch" functions, removing the need for authors to embed many large libraries to create minimap buttons. -Users who do not wish to be "plagued" by these buttons simply do not install an addon to render them. - -Due to it's simple generic design, LDB can be used for any design where you wish to have an addon notified of changes to a table. - -h2. Links - -* "API documentation":http://github.com/tekkub/libdatabroker-1-1/wikis/api -* "Data specifications":http://github.com/tekkub/libdatabroker-1-1/wikis/data-specifications -* "Addons using LDB":http://github.com/tekkub/libdatabroker-1-1/wikis/addons-using-ldb diff --git a/ProfessionMenu/Libs/LibStub/LibStub.lua b/ProfessionMenu/Libs/LibStub/LibStub.lua index d50c267..0a41ac0 100644 --- a/ProfessionMenu/Libs/LibStub/LibStub.lua +++ b/ProfessionMenu/Libs/LibStub/LibStub.lua @@ -7,24 +7,24 @@ if not LibStub or LibStub.minor < LIBSTUB_MINOR then LibStub = LibStub or {libs = {}, minors = {} } _G[LIBSTUB_MAJOR] = LibStub LibStub.minor = LIBSTUB_MINOR - + function LibStub:NewLibrary(major, minor) assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") - minor = assert(tonumber(string.match(minor, "%d+")), "Minor version must either be a number or contain a number.") - + minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") + local oldminor = self.minors[major] if oldminor and oldminor >= minor then return nil end self.minors[major], self.libs[major] = minor, self.libs[major] or {} return self.libs[major], oldminor end - + function LibStub:GetLibrary(major, silent) if not self.libs[major] and not silent then error(("Cannot find a library instance of %q."):format(tostring(major)), 2) end return self.libs[major], self.minors[major] end - + function LibStub:IterateLibraries() return pairs(self.libs) end setmetatable(LibStub, { __call = LibStub.GetLibrary }) end diff --git a/ProfessionMenu/Libs/LibStub/LibStub.toc b/ProfessionMenu/Libs/LibStub/LibStub.toc new file mode 100644 index 0000000..17cf732 --- /dev/null +++ b/ProfessionMenu/Libs/LibStub/LibStub.toc @@ -0,0 +1,13 @@ +## Interface: 20400 +## Title: Lib: LibStub +## Notes: Universal Library Stub +## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel +## X-Website: http://jira.wowace.com/browse/LS +## X-Category: Library +## X-License: Public Domain +## X-Curse-Packaged-Version: 1.0 +## X-Curse-Project-Name: LibStub +## X-Curse-Project-ID: libstub +## X-Curse-Repository-ID: wow/libstub/mainline + +LibStub.lua diff --git a/ProfessionMenu/ProfessionMenu.lua b/ProfessionMenu/ProfessionMenu.lua index 5e0363b..d072ad1 100644 --- a/ProfessionMenu/ProfessionMenu.lua +++ b/ProfessionMenu/ProfessionMenu.lua @@ -197,13 +197,12 @@ local profList = { 3274, -- Journeyman 150 3273, -- Apprentice 75 }, --FIRSTAID - {13977860, Name = "Woodcutting"}, --WOODCUTTING + {13977860}, --WOODCUTTING { 1005011, -- Artisan 300 1005010, -- Expert 225 1005009, -- Journeyman 150 1005008, -- Apprentice 75 - Name = "Woodworking", }, --WOODWORKING } @@ -373,7 +372,7 @@ function PM:AddProfessions() local function getProfessionRanks(compName) for skillIndex = 1, GetNumSkillLines() do local name, _, _, rank, _, _, maxRank, _, _, _, _, _, _ = GetSkillLineInfo(skillIndex) - if compName == name then + if compName:match(name) then return rank, maxRank end end @@ -392,13 +391,13 @@ function PM:AddProfessions() end local rank, maxRank = getProfessionRanks(profName) if not self.db.hideRank and self.db.hideMaxRank then - if rank then name = name .. " |cFF00FFFF("..rank..")" end + name = name .. " |cFF00FFFF("..rank..")" end if not self.db.hideMaxRank and self.db.hideRank then - if maxRank then name = name .. " |cFF00FFFF("..maxRank..")" end + name = name .. " |cFF00FFFF("..maxRank..")" end if not self.db.hideMaxRank and not self.db.hideRank then - if rank and maxRank then name = name .. " |cFF00FFFF("..rank.."/"..maxRank..")" end + name = name .. " |cFF00FFFF("..rank.."/"..maxRank..")" end local secure = { type1 = 'spell', @@ -605,13 +604,11 @@ function PM:CreateUI() end PM:CreateUI() -if InterfaceOptionsFrame then - InterfaceOptionsFrame:HookScript("OnShow", function() - if InterfaceOptionsFrame and ProfessionMenuOptionsFrame:IsVisible() then - ProfessionMenu_OpenOptions() - end - end) -end +InterfaceOptionsFrame:HookScript("OnShow", function() + if InterfaceOptionsFrame and ProfessionMenuOptionsFrame:IsVisible() then + ProfessionMenu_OpenOptions() + end +end) -- toggle the main button frame function PM:ToggleMainFrame() diff --git a/ProfessionMenu/ProfessionMenuOptions.lua b/ProfessionMenu/ProfessionMenuOptions.lua index 2a3c2a6..bf5a1a9 100644 --- a/ProfessionMenu/ProfessionMenuOptions.lua +++ b/ProfessionMenu/ProfessionMenuOptions.lua @@ -1,18 +1,15 @@ local PM = LibStub("AceAddon-3.0"):GetAddon("ProfessionMenu") function PM:Options_Toggle() - if not InterfaceOptionsFrame then return end if InterfaceOptionsFrame:IsVisible() then InterfaceOptionsFrame:Hide() else - if InterfaceOptionsFrame_OpenToCategory then - InterfaceOptionsFrame_OpenToCategory("ProfessionMenu") - end + InterfaceOptionsFrame_OpenToCategory("ProfessionMenu") end end function ProfessionMenu_OpenOptions() - if InterfaceOptionsFrame and InterfaceOptionsFrame:GetWidth() < 850 then InterfaceOptionsFrame:SetWidth(850) end + if InterfaceOptionsFrame:GetWidth() < 850 then InterfaceOptionsFrame:SetWidth(850) end ProfessionMenu_DropDownInitialize() UIDropDownMenu_SetText(ProfessionMenuOptions_TxtSizeMenu, PM.db.txtSize) end @@ -20,16 +17,14 @@ end --Creates the options frame and all its assets function PM:CreateOptionsUI() - if InterfaceOptionsFrame and InterfaceOptionsFrame:GetWidth() < 850 then InterfaceOptionsFrame:SetWidth(850) end + if InterfaceOptionsFrame:GetWidth() < 850 then InterfaceOptionsFrame:SetWidth(850) end local mainframe = {} mainframe.panel = CreateFrame("FRAME", "ProfessionMenuOptionsFrame", UIParent, nil) local fstring = mainframe.panel:CreateFontString(mainframe, "OVERLAY", "GameFontNormal") fstring:SetText("Profession Menu Settings") fstring:SetPoint("TOPLEFT", 15, -15) mainframe.panel.name = "ProfessionMenu" - if InterfaceOptions_AddCategory then - InterfaceOptions_AddCategory(mainframe.panel) - end + InterfaceOptions_AddCategory(mainframe.panel) local hideMenu = CreateFrame("CheckButton", "ProfessionMenuOptions_HideMenu", ProfessionMenuOptionsFrame, "UICheckButtonTemplate") hideMenu:SetPoint("TOPLEFT", 15, -60) @@ -148,9 +143,10 @@ PM:CreateOptionsUI() for i = 10, 25 do info = { text = i; - func = function() - PM.db.txtSize = i - UIDropDownMenu_SetSelectedID(ProfessionMenuOptions_TxtSizeMenu, i - 9) + func = function() + PM.db.txtSize = i + local thisID = this:GetID(); + UIDropDownMenu_SetSelectedID(ProfessionMenuOptions_TxtSizeMenu, thisID) end; }; UIDropDownMenu_AddButton(info); diff --git a/tools/build_zip.sh b/tools/build_zip.sh deleted file mode 100755 index 45d1400..0000000 --- a/tools/build_zip.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash -# Build per-addon zip artefacts from HEAD via git-archive. -# -# - Discovers top-level addon folders (Foo/Foo.toc). -# - Re-creates dist/ each run. -# - Always archives HEAD, so the working tree state is irrelevant. -# - If more than one addon folder is present, also emits -all.zip -# with every addon folder side-by-side at the zip root. -# - When run inside Gitea Actions the working tree lives under a -# per-job dir like /var/lib/act_runner/work/.../hostexecutor, so the -# repo name comes from $GITHUB_REPOSITORY (set by the runner) and -# only falls back to the toplevel basename for local invocations. -set -euo pipefail - -root=$(git rev-parse --show-toplevel) -cd "$root" - -# Gitea Actions sets GITHUB_REPOSITORY=owner/repo. The basename of -# `git rev-parse --show-toplevel` inside the runner is the worker dir -# (e.g. `hostexecutor`), which would name the bundle wrong. -if [ -n "${GITHUB_REPOSITORY:-}" ]; then - repo_name="${GITHUB_REPOSITORY##*/}" -else - repo_name=$(basename "$root") -fi -dist="$root/dist" - -# Find Foo/Foo.toc pairs at depth 1; ignore libs nested deeper. -addons=() -while IFS= read -r toc; do - dir=$(dirname "$toc") - folder=$(basename "$dir") - base=$(basename "$toc" .toc) - # Accept Foo.toc and Foo_Wrath.toc style flavour variants; folder must match - # at least one toc basename prefix (Foo). - case "$base" in - "$folder"|"$folder"_*) addons+=("$folder") ;; - esac -done < <(command find . -mindepth 2 -maxdepth 2 -type f -name '*.toc' | sed 's|^\./||' | sort) - -# Dedupe (a folder with Foo.toc + Foo_Wrath.toc shows up twice). -if [ ${#addons[@]} -gt 0 ]; then - mapfile -t addons < <(printf '%s\n' "${addons[@]}" | awk '!seen[$0]++') -fi - -if [ ${#addons[@]} -eq 0 ]; then - echo "no addon folders found (looking for */Foo.toc with matching folder name)" >&2 - exit 1 -fi - -rm -rf "$dist" -mkdir -p "$dist" - -for folder in "${addons[@]}"; do - out="$dist/$folder.zip" - # No --prefix: the folder already sits at the repo root, so git-archive - # emits entries as /... which is exactly what - # Interface/AddOns/ expects after extraction. - git archive HEAD --format=zip -o "$out" -- "$folder" - echo "built dist/$folder.zip" -done - -# Combined bundle only makes sense when there are multiple addons. -if [ ${#addons[@]} -gt 1 ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - git archive HEAD --format=tar -- "${addons[@]}" | tar -x -C "$tmp" - out="$dist/$repo_name-all.zip" - ( cd "$tmp" && zip -qr "$out" "${addons[@]}" ) - echo "built dist/$repo_name-all.zip" -fi