Quantcast (Classic) Optimizing table search for auto dismount addon - WoWInterface
Thread Tools Display Modes
06-22-19, 07:59 AM   #1
DashingSplash
A Murloc Raider
Join Date: Jun 2019
Posts: 7
(Classic) Optimizing table search for auto dismount addon

Hi,

Been working on a new version of my Classic dismount addon; mostly, because it requires two button presses to activate an ability. In this version I register an OnKeyDown event and call for Dismount(). This works perfectly well in retail (BfA), but when my friend tries it out on the Classic Beta it requires two button presses for most abilities (excluding grenades, distract, and similar abilities that works fine). The only difference between the two of us is that I am working with a latency of 40 ms, and he around 120 ms. As such, I execute the OnKeyDown event in 1 ms and he around 3 ms.

Disclaimer: to try this addon out for yourself on retail you need to disable auto dismount, and use a flying mount (and be in the air).

Toc file:
Code:
## Interface: 80100
## Title: dashingDismount
## Notes: Dismount
## Author: DashingSplash
dashingDismount.lua
Lua file:
Code:
local dashingDismount = CreateFrame("Frame", nil, UIParent)
local dashingSplash = CreateFrame("Frame", nil, UIParent)

local table_1 = {t = {}, st = {}, ct = {}, at = {}}
local table_2 = {t2 = {}, st2 = {}, ct2 = {}, at2 = {}}
local key1, key2
local mod

dashingDismount:SetScript("OnKeyDown", function(self, key, ...)
	local beginTime = debugprofilestop()
	
	if IsMounted() then
		if GetActionBarPage() ~= 2 then
			if not IsModifierKeyDown() and tContains(table_1.t, key) then Dismount()
			
			elseif IsShiftKeyDown() and tContains(table_1.st, key) then Dismount()
			
			elseif IsControlKeyDown() and tContains(table_1.ct, key) then Dismount()
			
			elseif IsAltKeyDown() and tContains(table_1.at, key) then Dismount() end
		else
			if not IsModifierKeyDown() and tContains(table_2.t2, key) then Dismount()

			elseif IsShiftKeyDown() and tContains(table_2.st2, key) then Dismount()
			
			elseif IsControlKeyDown() and tContains(table_2.ct2, key) then Dismount()
			
			elseif IsAltKeyDown() and tContains(table_2.at2, key) then Dismount() end
		end
	end
	
	local timeUsed = debugprofilestop() - beginTime
	
	print(timeUsed)
end)

local function dashingKey(key)
	if GetActionBarPage() ~= 2 then
		if string.find(key, "SHIFT-") ~= nil then
			table_1.st[#table_1.st+1] = string.sub(key, 7)
		elseif string.find(key1, "CTRL-") ~= nil then
			table_1.ct[#table_1.ct+1] = string.sub(key, 6)
		elseif string.find(key1, "ALT-") ~= nil then
			table_1.at[#table_1.at+1] = string.sub(key, 5)
		else
			table_1.t[#table_1.t+1] = key
		end 
	else
		if string.find(key, "SHIFT-") ~= nil then
			table_2.st2[#table_2.st2+1] = string.sub(key, 7)
		elseif string.find(key1, "CTRL-") ~= nil then
			table_2.ct2[#table_2.ct2+1] = string.sub(key, 6)
		elseif string.find(key1, "ALT-") ~= nil then
			table_2.at2[#table_2.at2+1] = string.sub(key, 5)
		else
			table_2.t2[#table_2.t2+1] = key
		end 
	end
end

local function dashingBindings()
	local button, buttonName
	local slot
	local actionName
    local actionType, id
	
	local ActionBars		= {'Action', 'Action', 'Stance', 'MultiBarBottomLeft', 'MultiBarBottomRight', 'MultiBarRight', 'MultiBarLeft'}
	local ActionBarsBinding = {'ACTION', 'ACTION', 'SHAPESHIFT', 'MULTIACTIONBAR1', 'MULTIACTIONBAR2', 'MULTIACTIONBAR3', 'MULTIACTIONBAR4'}
	local protectedSkills	= {"Alchemy", "Cooking", "Enchanting", "Engineering", "Blacksmithing", "Tailoring", "First Aid"}

    for page, barName in ipairs(ActionBars) do	
	
		if page == 2 then 
			ChangeActionBarPage(2)
		else 
			ChangeActionBarPage(1)
		end
		
        for i = 1, 12 do
            button		= _G[barName .. 'Button' .. i]		
			buttonName	= ActionBarsBinding[page] .. "BUTTON" .. i
			
			if button ~= nil then
				slot = ActionButton_GetPagedID(button) or ActionButton_CalculateAction(button) or button:GetAttribute('action') or 0
				
				if HasAction(slot) then
					actionType, id	= GetActionInfo(slot)
					key1, key2		= GetBindingKey(buttonName)
					actionName		= GetSpellInfo(id)
					
					if not tContains(protectedSkills, actionName) then
						if key1 ~= nil then
							print(key1)
							dashingKey(key1)
						end
						if key2 ~= nil then
							print(key2)
							dashingKey(key2)
						end
					end	
				end
			end
        end
    end
end

local function dashingUpdate(self, event, ...)
	for _, tableWipe in pairs(table_1) do
		wipe(tableWipe)	
	end
	
	for _, tableWipe in pairs(table_2) do
		wipe(tableWipe)	
	end

	dashingBindings()
end

dashingSplash:RegisterEvent("MODIFIER_STATE_CHANGED")
dashingSplash:SetScript("OnEvent", function(self, event, key, state)
	if state == 1 then
		mod = string.sub(key, 2)
		print(mod)
	else
		mod = 'nil'
		print(mod)
	end
end)

dashingDismount:Show()
dashingDismount:EnableKeyboard(true)
dashingDismount:SetPropagateKeyboardInput(true)

dashingDismount:RegisterEvent("PLAYER_LOGIN")
dashingDismount:RegisterEvent("UPDATE_BINDINGS")
--dashingDismount:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
dashingDismount:SetScript("OnEvent", dashingUpdate)
Note: not using dashingSplash:RegisterEvent("MODIFIER_STATE_CHANGED") at this moment for anything. Leaving it in since the question that follows might give inspiration for solutions.

My question is the following: how can I make the following code more efficient? If you have any input regarding the rest of the addon feel free to provide constructive criticism.
Code:
dashingDismount:SetScript("OnKeyDown", function(self, key, ...)
	local beginTime = debugprofilestop()
	
	if IsMounted() then
		if GetActionBarPage() ~= 2 then
			if not IsModifierKeyDown() and tContains(table_1.t, key) then Dismount()
			
			elseif IsShiftKeyDown() and tContains(table_1.st, key) then Dismount()
			
			elseif IsControlKeyDown() and tContains(table_1.ct, key) then Dismount()
			
			elseif IsAltKeyDown() and tContains(table_1.at, key) then Dismount() end
		else
			if not IsModifierKeyDown() and tContains(table_2.t2, key) then Dismount()

			elseif IsShiftKeyDown() and tContains(table_2.st2, key) then Dismount()
			
			elseif IsControlKeyDown() and tContains(table_2.ct2, key) then Dismount()
			
			elseif IsAltKeyDown() and tContains(table_2.at2, key) then Dismount() end
		end
	end
	
	local timeUsed = debugprofilestop() - beginTime
	
	print(timeUsed)
end)

Last edited by DashingSplash : 06-22-19 at 08:01 AM.
  Reply With Quote
06-22-19, 10:52 AM   #2
DashingSplash
A Murloc Raider
Join Date: Jun 2019
Posts: 7
It appears that the majority of time spent is due to Dismount().

I would assume that there is no way to make that faster since it is latency dependent?
  Reply With Quote
06-23-19, 07:25 PM   #3
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 425
Oh, boy.

Lua Code:
  1. local frame = CreateFrame('Frame')
  2. local actionButtons = {}
  3. local stanceButtons = {}
  4. local protectedSkills = {}
  5.  
  6. ---------------------------------------------------
  7. for i, spell in ipairs({
  8.     ---------------------------------------------------
  9.     'Alchemy', 'Cooking', 'Enchanting', 'Engineering',
  10.     'Blacksmithing', 'Tailoring', 'First Aid',
  11.     ---------------------------------------------------
  12. }) do
  13.     local spellID = select(7, GetSpellInfo(spell))
  14.     if spellID then
  15.         protectedSkills[spellID] = spell
  16.     end
  17. end
  18.  
  19. ---------------------------------------------------
  20. for i, button in ipairs(StanceBarFrame.StanceButtons) do
  21.     stanceButtons['SHAPESHIFTBUTTON' .. i] = button
  22. end
  23.  
  24. for i=1, 12 do
  25.     actionButtons['ACTIONBUTTON' .. i] = _G['ActionButton' .. i]
  26. end
  27.  
  28. for i, button in ipairs(ActionBarButtonEventsFrame.frames) do
  29.     local barID = button.buttonType
  30.     local buttonID  = button:GetID()
  31.     local identifier = buttonID and barID and ( barID .. buttonID )
  32.     if identifier then
  33.         actionButtons[identifier] = button
  34.     end
  35. end
  36. ---------------------------------------------------
  37.  
  38. frame:EnableKeyboard(true)
  39. frame:SetPropagateKeyboardInput(true)
  40. frame:SetScript('OnKeyDown', function(self, key, down)
  41.     if IsMounted() then
  42.         local prefix = SecureButton_GetModifierPrefix()
  43.         local binding = GetBindingAction(prefix .. key)
  44.         local button = actionButtons[binding]
  45.  
  46.         if button then
  47.             local action = ActionButton_CalculateAction(button)
  48.             if HasAction(action) then
  49.                 local actionType, spellID = GetActionInfo(action)
  50.                 if actionType == 'spell' and not protectedSkills[spellID] then
  51.                     Dismount()
  52.                 end
  53.             end
  54.             return
  55.         end
  56.  
  57.         button = stanceButtons[binding]
  58.         if button and not button:GetChecked() then
  59.             Dismount()
  60.         end
  61.     end
  62. end)
__________________

Last edited by MunkDev : 06-23-19 at 07:36 PM.
  Reply With Quote
06-24-19, 11:28 AM   #4
DashingSplash
A Murloc Raider
Join Date: Jun 2019
Posts: 7
Originally Posted by MunkDev View Post
Awesome-code
Wow! Now that's some clean and good-looking code. Thank you for the help.

Was really surprised to see that

Lua Code:
  1. local prefix = SecureButton_GetModifierPrefix()
  2. local binding = GetBindingAction(prefix .. key)

was a thing. Although, I assume I'll get a better understanding of the API with some experience.

The only thing that had to be included in your code was to allow companion, item, and macro functionality. See modified function below. Any input on that?

Lua Code:
  1. frame:EnableKeyboard(true)
  2. frame:SetPropagateKeyboardInput(true)
  3. frame:SetScript('OnKeyDown', function(self, key, ...)  
  4.     if IsMounted() then
  5.         local prefix = SecureButton_GetModifierPrefix()
  6.         local binding = GetBindingAction(prefix .. key)
  7.         local button = actionButtons[binding]
  8.  
  9.         if button then
  10.             local action = ActionButton_CalculateAction(button)
  11.            
  12.             if HasAction(action) then
  13.                 local actionType, spellID = GetActionInfo(action)
  14.                
  15.                 if (actionType == 'spell' or actionType == 'item' or actionType == 'companion') and not protectedSkills[spellID] then
  16.                     Dismount()
  17.                 elseif actionType == "macro" then
  18.                     spellID, _ = GetMacroSpell(spellID)
  19.                     if spellID ~= nil and not protectedSkills[spellID] then
  20.                         Dismount()
  21.                     end
  22.                 end
  23.             end
  24.             return
  25.         end
  26.  
  27.         button = stanceButtons[binding]
  28.         if button and not button:GetChecked() then
  29.             Dismount()
  30.         end
  31.     end
  32. end)

The only thing I am unsure about is how

Lua Code:
  1. button = stanceButtons[binding]
  2. if button and not button:GetChecked() then
  3.       Dismount()
  4. end

works. When and how is the button state set to checked?

frame:EnableKeyboard(true) seems moot. Can set it to false (or even remove it completely) and the addon will still work. In my first version I had to set the frame to inherit from UIParent, and then show the frame for EnableKeyboard to actually work. Safe to remove it, or does it actually have some hidden function?

Last edited by DashingSplash : 06-24-19 at 11:48 AM.
  Reply With Quote
06-26-19, 08:05 AM   #5
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 425
Originally Posted by DashingSplash View Post
Was really surprised to see that

Lua Code:
  1. local prefix = SecureButton_GetModifierPrefix()
  2. local binding = GetBindingAction(prefix .. key)

was a thing. Although, I assume I'll get a better understanding of the API with some experience.
I didn't really know this either, but given a copy of the UI source code, there's a method to finding what you're looking for by going to the source of something you want to modify and looking at how it works. If you haven't already, get a copy of the source code and use an editor that allows you to search in the code for the things you're interested in. In this case, it seems likely that Blizzard would at some point implement a utility function that returns the current modifier chain, because it's useful in a lot of cases.


Originally Posted by DashingSplash View Post
The only thing that had to be included in your code was to allow companion, item, and macro functionality. See modified function below. Any input on that?
I would actually use a table for this, partly for code clarity, and also because it's easier to modify on a whim.
Lua Code:
  1. local dismountTypes = {
  2.     spell = true,
  3.     item = true,
  4.     companion = true,
  5.     macro = false,
  6. }
Lua Code:
  1. if HasAction(action) then
  2.     local actionType, spellID = GetActionInfo(action)
  3.    
  4.     if dismountTypes[actionType] and not protectedSkills[spellID] then
  5.         Dismount()
  6.     elseif actionType == "macro" then
  7.         spellID, _ = GetMacroSpell(spellID)
  8.         if spellID ~= nil and not protectedSkills[spellID] then
  9.             Dismount()
  10.         end
  11.     end
  12. end
  13. return


Originally Posted by DashingSplash View Post
The only thing I am unsure about is how

Lua Code:
  1. button = stanceButtons[binding]
  2. if button and not button:GetChecked() then
  3.       Dismount()
  4. end

works. When and how is the button state set to checked?
I'll give you an example of how to track execution path in this case, which I don't think any tutorial brings up. If you look in Bindings.xml, you'll find that e.g. SHAPESHIFTBUTTON1 is calling StanceBar_Select(1). A quick search for this function gives you this result:
Lua Code:
  1. -- Searching 1364 files for "function StanceBar_Select" (case sensitive)
  2. -- UISourceCode\StanceBar.lua:
  3. function StanceBar_Select (id)
  4.     StanceBarFrame.lastSelected = id;
  5.     CastShapeshiftForm(id);
  6. end

Casting the shapeshift form generates an event that updates the stance bar, which results in the current stance active having its button set to checked.
Lua Code:
  1. function StanceBar_OnEvent(self, event)
  2.     if(event == "UPDATE_SHAPESHIFT_COOLDOWN") then
  3.         StanceBar_UpdateState();
  4.     end
  5. end
  6.  
  7. function StanceBar_UpdateState ()
  8.     ...
  9.     if ( isActive ) then
  10.         StanceBarFrame.lastSelected = button:GetID();
  11.         button:SetChecked(true);
  12.     else
  13.         button:SetChecked(false);
  14.     end
  15.     ...
  16. end
__________________
  Reply With Quote
06-28-19, 12:33 PM   #6
DashingSplash
A Murloc Raider
Join Date: Jun 2019
Posts: 7
Originally Posted by MunkDev View Post
I didn't really know this either, but given a copy of the UI source code, there's a method to finding what you're looking for by going to the source of something you want to modify and looking at how it works. If you haven't already, get a copy of the source code and use an editor that allows you to search in the code for the things you're interested in. In this case, it seems likely that Blizzard would at some point implement a utility function that returns the current modifier chain, because it's useful in a lot of cases.




I would actually use a table for this, partly for code clarity, and also because it's easier to modify on a whim.
Lua Code:
  1. local dismountTypes = {
  2.     spell = true,
  3.     item = true,
  4.     companion = true,
  5.     macro = false,
  6. }
Lua Code:
  1. if HasAction(action) then
  2.     local actionType, spellID = GetActionInfo(action)
  3.    
  4.     if dismountTypes[actionType] and not protectedSkills[spellID] then
  5.         Dismount()
  6.     elseif actionType == "macro" then
  7.         spellID, _ = GetMacroSpell(spellID)
  8.         if spellID ~= nil and not protectedSkills[spellID] then
  9.             Dismount()
  10.         end
  11.     end
  12. end
  13. return




I'll give you an example of how to track execution path in this case, which I don't think any tutorial brings up. If you look in Bindings.xml, you'll find that e.g. SHAPESHIFTBUTTON1 is calling StanceBar_Select(1). A quick search for this function gives you this result:
Lua Code:
  1. -- Searching 1364 files for "function StanceBar_Select" (case sensitive)
  2. -- UISourceCode\StanceBar.lua:
  3. function StanceBar_Select (id)
  4.     StanceBarFrame.lastSelected = id;
  5.     CastShapeshiftForm(id);
  6. end

Casting the shapeshift form generates an event that updates the stance bar, which results in the current stance active having its button set to checked.
Lua Code:
  1. function StanceBar_OnEvent(self, event)
  2.     if(event == "UPDATE_SHAPESHIFT_COOLDOWN") then
  3.         StanceBar_UpdateState();
  4.     end
  5. end
  6.  
  7. function StanceBar_UpdateState ()
  8.     ...
  9.     if ( isActive ) then
  10.         StanceBarFrame.lastSelected = button:GetID();
  11.         button:SetChecked(true);
  12.     else
  13.         button:SetChecked(false);
  14.     end
  15.     ...
  16. end
Tack så mycket!

Ended up exporting the Interface code and adding them to Visual Studio with the NPL/Lua language service. Works well enough, but could be better. Serves its purpose at the very least, and I can actually follow the execution path for most things.

I am pleased with the dismount AddOn now, but the only downside with it is that it seems to be latency dependent (although, I haven't had an American try it on the beta yet). On retail at 40ms latency the Dismount() function seems to take about 1ms; on classic beta with 120ms latency the Dismount() function seems to take about 3ms. Which seems to cause most abilities to require two button presses, but could be due to other changes/differences between Classic and retail.

The only thing I can see myself adding to this AddOn is if there is a way to make the Dismount() function execute faster. I am not entirely sure how a hooksecurefunc works, but I would assume that it would always be executing slower than the actual Dismount() function?

Last edited by DashingSplash : 06-28-19 at 12:42 PM.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » (Classic) Optimizing table search for auto dismount addon

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off