API:Correct Hooking Procedure
From TheWarWiki
WAR API Help
From Wikipedia
Hooking in programming is a technique employing so-called hooks to make a chain of procedures as an event handler. Thus, after the handled event occurs, control flow follows the chain in specific order. The new hook registers its own address as handler for the event and is expected to call the original handler at some point, usually at the end. Each hook is required to pass execution to the previous handler, eventually arriving to the default one, otherwise the chain is broken.
For example, if you add a GUI element to, the Tome Of Knowledge, you want it to hide and show along with the Tome, you would hook the Tome's hide and show functions.
In the case of WAR, we use hooking when the Event system is not comprehensive enough for our needs. For example, the Tome of Knowledge opening and closing. If the Event system broadcast those events, we would just register into that event and hide and show our elements with it. However, if the Event system does not cater for the particular event you're after, you will have to hook the open and close functions in order to have your elements follow them.
Simple Hooking
local hookOpen local hookClose function MyAddon.Initialise() hookOpen = TomeWindow.OnOpen -- First we store the original function in our local hook hookClose = TomeWindow.OnClose TomeWindow.OnOpen = MyAddon.OnOpen -- Then we take the current open and close functions and redirect them to our control TomeWindow.OnClose = MyAddon.OnClose ... end function MyAddon.OnOpen(...) hookOpen(...) -- This is important. We're calling the original function (which may have other hooks already put in) to run the chain -- MyAddon's opening code here end function MyAddon.OnClose(...) hookClose(...) -- This is important. We're calling the original function (which may have other hooks already put in) to run the chain -- MyAddon's closing code here end
For straightforward hooking like the above, where you're merely hiding and showing elements and those elements will always hide and show alongside the originals, that code is sufficient. If, however, you need to hook a function only at certain times, more work is required to make the hook viable.
Medium Complexity Hook
Say you wish to hook the right click functionality on an inventory item when the Cultivating window is open, and only then.
local hookRightClick -- Hook for the inventory rightclick
local hookLeftClick -- Hook for the inventory leftclick
function MyAddon.Setup()
hookRightClick = EA_Window_Backpack.EquipmentRButtonUp
EA_Window_Backpack.EquipmentRButtonUp = MyAddon.CheckItem
hookLeftClick = EA_Window_Backpack.EquipmentLButtonUp
EA_Window_Backpack.EquipmentLButtonUp = MyAddon.ChooseItem
...
end
function MyAddon.CheckItem(...)
if not WindowGetShowing("CultivationWindow") then
return hookRightClick(...)
end
... -- Do your processing here
return hookRightClick(...) --Prehook
end
function MyAddon.ChooseItem(...)
if not WindowGetShowing("CultivationWindow") then
return hookLeftClick(...)
end
hookLeftClick() -- Posthook
... -- Do your processing here
end
As you can see, it does a check to see if the cultivation window is open, and if not, pass the call directly up the chain, so the hook will only ever call your extra functionality if the set of prerequisites is met. Always do your checking for viability before anything else, so if it's not required, there is less processing done to get past your function.
The CheckItem function uses a 'prehook' which means it runs your custom code before it executes the rest of the chain. The ChooseItem function uses a 'posthook', in which it calls the function chain first, and then your work gets processed. Use a posthook if you're not concerned about modifying the arguments of the function, and only want to use the hook as an Event registration replacement. With a prehook, you can modify the arguments passed in so that any function further down the chain gets your modified information.
| One thing that cannot be overstated at this point is that you must not unhook. We do not currently have a central hooking repository where such methods can be cleanly dealt with. If you unhook your function, it breaks the chain, and will cause other addons who also hook into that original function to stop working. Instead, use a flag as outlined below to make your hooking function a stub that passes control back down the chain. |
Now, if your situation is that a clear cut set of events like "Cultivation window is open" is not enough, or, for example, you have a toggle for when your functionality is to run or not, then you can use a flag to bypass the processing, and leave your hooked function as a stub.
local hookRightClick -- Hook for the inventory rightclick
local flagMyAddonRunning
function MyAddon.Setup()
hookRightClick = EA_Window_Backpack.EquipmentRButtonUp
EA_Window_Backpack.EquipmentRButtonUp = MyAddon.CheckItem
flagMyAddonRunning = false -- Start it up not being run
...
end
function MyAddon.CheckItem(...)
if not flagMyAddonRunning then
return hookRightClick(...)
end
... -- Do your processing here
return hookRightClick(...)
end
Other notes:
- If you're not sure of the signature of the function you're replacing, use (...) to pass the arguments straight through.
- Try not to completely replace the functionality of a UI Element without making sure that yours will only ever be the only addon modifying it. Tooltips are a good example. Many addons modify them.
