if Debug then Debug.beginFile "LookupTable" end
do
--[[
=============================================================================================================================================================
Lookup Table
by Antares
Requires:
TotalInitialization https://www.hiveworkshop.com/threads/total-initialization.317099/
Hook (optional) https://www.hiveworkshop.com/threads/hook.339153/
=============================================================================================================================================================
A P I
=============================================================================================================================================================
LookupTable(whichFunction, numArguments, setterFunction) -> table | nil
TempLookup(whichFunction, numArguments) -> table
=============================================================================================================================================================
Lookup Table adds a convenient way to store data returned from functions that need to do complex calculations, but it can also be used in a simple way to make
natives faster.
Adding a lookup table to a function overwrites the function call with a table lookup. The function is only executed on the first call with a given combination
of input arguments. This increases the speed of every subsequent function call. A native with a lookup table will be approximately 10x faster when called.
With a lookup table added to a function, you can call that function just like you would normally, but you can also treat it as a table, making it even faster.
To add a lookup table to a function, do myTable = LookupTable(myFunction). You can overwrite the original function or store the lookup table in a different
variable. Alternatively, you can do LookupTable("functionName"), if it is a global function, to overwrite it.
You can add a lookup table to functions with any number of arguments, but you need to specify how many arguments the function takes if it's not one:
LookupTable("functionName", numArgs)
LookupTable takes an optional third argument, the setter function. If a setter function is specified, Lookup Table will create a hook to that function that will
change the stored value in the lookup table whenever the setter function is called. If the getter function is a function with N parameters, the setter function
is expected to take N + 1 parameters, where the last parameter is the new value. This is the case for most natives. The input for the setter function must be a
global variable name.
Example:
LookupTable("FourCC")
LookupTable("BlzGetAbilityRealLevelField", 3, "BlzSetAbilityRealLevelField")
print( BlzGetAbilityRealField[FourCC.AHbz][ABILITY_RLF_AREA_OF_EFFECT][0] ) --prints 200
BlzSetAbilityRealLevelField(FourCC.AHbz][ABILITY_RLF_AREA_OF_EFFECT][0], 300)
print( BlzGetAbilityRealField[FourCC.AHbz][ABILITY_RLF_AREA_OF_EFFECT][0] ) --prints 300
TempLookup returns a lookup table for the specified function that only takes effect for the remainder of the current thread.
Example:
local x, y = TempLookup(GetUnitX), TempLookup(GetUnitY)
local caster = GetSpellAbilityUnit()
local target = GetSpellTargetUnit()
local dx = x[caster] - x[target]
local dy = y[caster] - y[target]
--Potential pitfall:
SetUnitX(caster, x[caster] + 100)
print(x[caster]) --prints the original value!
Temporary Lookup Tables will be recycled if used repeatedly and are very performant.
=============================================================================================================================================================
Limitations:
-You should be able to directly call AddLookupTable from the Lua root for your own functions as long as those functions and the Lookup Table library are above
the call, but it won't work for natives.
-Function arguments cannot be nil.
-Overwriting a global will make it incompatible with Hook.
=============================================================================================================================================================
]]
local callMethods = {} ---@type function[]
local CLEAR_TIMER = nil ---@type timer
local tempLookupTables = {} ---@type table[]
local parentKeys = {} ---@type table
local parentTables = {} ---@type table
---@param lookupTable table
---@param func function
---@param numArgs integer
local function CreateIndexMethod(lookupTable, func, numArgs)
--Iterate over the input arguments and add __index methods to create new subtables until you reach the end, where the __index method becomes calling the
--original function after retrieving all previous keys.
local iterateKeys
iterateKeys = function(whichTable, i)
if i > 1 then
setmetatable(whichTable, {__index = function(thisTable, thisKey)
local nextTable = {}
parentKeys[nextTable] = thisKey
parentTables[nextTable] = thisTable
thisTable[thisKey] = nextTable
iterateKeys(nextTable, i-1)
return nextTable
end})
else
setmetatable(whichTable, {__index = function(thisTable, thisKey)
local args = {}
local iteratedTable = thisTable
for j = numArgs - 1, 1, -1 do
args[j] = parentKeys[iteratedTable]
iteratedTable = parentTables[iteratedTable]
end
args[numArgs] = thisKey
thisTable[thisKey] = func(table.unpack(args))
return thisTable[thisKey]
end, __mode = "k"})
end
end
iterateKeys(lookupTable, numArgs)
return lookupTable
end
---@param numArgs integer
---@return function
local function CreateCallMethod(numArgs)
--The generated function looks like this: function(self, arg1, arg2) return self[arg1][arg2] end
local code = "return function(self, "
for i = 1, numArgs - 1 do
code = code .. "arg" .. i .. ", "
end
code = code .. "arg" .. numArgs .. ")\nreturn self"
for i = 1, numArgs do
code = code .. "[arg" .. i .. "]"
end
code = code .. "\nend"
callMethods[numArgs] = load(code)()
return callMethods[numArgs]
end
---@param numArgs integer
---@return function
local function CreateSetterHook(numArgs, lookupTable, argOrder)
--The generated function looks like this: function(self, arg1, arg2, arg3) lookupTable[arg1][arg2] = arg3 self.old(arg1, arg2, arg3) end
local code = "return function(self, "
for i = 1, numArgs do
code = code .. "arg" .. i .. ", "
end
code = code .. "arg" .. numArgs + 1 .. ")\nlookupTable"
for i = 1, numArgs do
code = code .. "[arg" .. i .. "]"
end
code = code .. " = arg" .. numArgs + 1 .. "\nself.old("
for i = 1, numArgs do
code = code .. "arg" .. i .. ", "
end
code = code .. "arg" .. numArgs + 1 .. ")\nend"
return load(code, nil, 't', {lookupTable = lookupTable})()
end
local function ClearTempLookupTables()
for i, whichTable in ipairs(tempLookupTables) do
for key, __ in pairs(whichTable) do
whichTable[key] = nil
end
tempLookupTables[i] = nil
end
end
---@param whichFunction function | string
---@param numArgs? integer
---@param setterFunction? string
---@return table | nil
function LookupTable(whichFunction, numArgs, setterFunction)
local isGlobalFunc = type(whichFunction) == "string"
numArgs = numArgs or 1
local func
if isGlobalFunc then
func = _G[whichFunction]
else
func = whichFunction
end
local lookupTable = {}
CreateIndexMethod(lookupTable, func, numArgs)
getmetatable(lookupTable).__call = (callMethods[numArgs] or CreateCallMethod(numArgs))
if setterFunction then
Hook.add(setterFunction, CreateSetterHook(numArgs, lookupTable))
end
if isGlobalFunc then
_G[whichFunction] = lookupTable
else
return lookupTable
end
end
---@param whichFunction function
---@param numArgs? integer
---@return table
function TempLookup(whichFunction, numArgs)
numArgs = numArgs or 1
local lookupTable = tempLookupTables[whichFunction]
if lookupTable == nil then
tempLookupTables[whichFunction] = {}
lookupTable = tempLookupTables[whichFunction]
CreateIndexMethod(tempLookupTables[whichFunction], whichFunction, numArgs)
getmetatable(tempLookupTables[whichFunction]).__call = (callMethods[numArgs] or CreateCallMethod(numArgs))
end
if #tempLookupTables == 0 then
TimerStart(CLEAR_TIMER, 0.0, false, ClearTempLookupTables)
end
tempLookupTables[#tempLookupTables + 1] = lookupTable
return lookupTable
end
OnInit.global(function()
CLEAR_TIMER = CreateTimer()
end)
end