API:Correct Hooking Procedure

From TheWarWiki

Jump to: navigation, search

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.
Personal tools