Иногда бывает полезно продебажить код и узнать что же происходит/фаталит.
В луа есть встроенные средства - модуль debug.
В один момент, когда я делал карту на UjAPI, у меня очень подгорело, и я потратил 6 часов чтобы вкурить как это всё работает.
В итоге получилось вот это:
В луа есть встроенные средства - модуль debug.
В один момент, когда я делал карту на UjAPI, у меня очень подгорело, и я потратил 6 часов чтобы вкурить как это всё работает.
В итоге получилось вот это:
раскрыть
--------------------------------------------------------------------
-- 256head debugging
--------------------------------------------------------------------
--- https://wiki.facepunch.com/gmod/Structures/DebugInfo
local isDebug = true
local function InitDebugHooks()
function OnNewFunctionCall()
local debugInfo
local funcInfo
local paraminfo
local localargs
local string_format = string.format
stackDepth = 2
debugInfo = debug.getinfo(stackDepth)
if debugInfo == nil then return end
funcInfo = debug.getinfo(debugInfo.func)
if(funcInfo.what == "C")then --func from C - lua cant access that
paraminfo = "C func, ?"
else
paraminfo = funcInfo.nparams
end
printc(string_format("[%s] call: %s [%s args] | src: %s:%s ", stackDepth, debugInfo.name, paraminfo, debugInfo.source, debugInfo.linedefined))
localargs = ""
local arg_n = 0
--if we know that its not C - amount of params is greater than 0
if(funcInfo.nparams ~= 0)then
for i = 1, funcInfo.nparams do
local name, value = debug.getlocal(stackDepth, i)
localargs = localargs .. string_format("\n %s: [%s] = %s", i, name, value)
end
arg_n = funcInfo.nparams
printc(string_format("%s args: %s", arg_n, localargs))
else
-- if we dosent know amount of args, we print first N (10 is enough)
-- first N is args of function, rest are up-values
for i = 1, 10 do
local name, value = debug.getlocal(stackDepth, i)
if not name then break end
arg_n = i
localargs = localargs .. string_format("\n %s: [%s] = %s", i, name, value)
end
printc(string_format("first %s local variables: %s", arg_n, localargs))
end
printc("")
end
local TriggerAddAction_hook = TriggerAddAction
function TriggerAddAction(trigger, action)
local function executionHook()
debug.sethook(OnNewFunctionCall, "c", 0)
action()
end
TriggerAddAction_hook(trigger, executionHook)
end
local TimerStart_hook = TimerStart
function TimerStart(whichTimer, timeout, periodic, handlerFunc)
local function executionHook()
debug.sethook(OnNewFunctionCall, "c", 0)
handlerFunc()
end
TimerStart_hook(whichTimer, timeout, periodic, executionHook)
end
-- hook on new function call
debug.sethook(OnNewFunctionCall, "c", 0)
end
if(isDebug)then InitDebugHooks() end
Основные актёры:
debug.sethook(thread, hook, mask, count)
debug.getinfo(thread, f, what)
debug.getlocal(thread, f, var)
Что этот код собственно делает:
Одной переменной мы управляем дебагом - включать его или нет.
Включается он до того места, где вы хотите подебажить.
Одной переменной мы управляем дебагом - включать его или нет.
Включается он до того места, где вы хотите подебажить.
debug.sethook
У функции debug.sethook много разных вариаций использования.
В кратце - он выполняет прокинутую функцию каждый раз после определённого ивента, который посылается 2 аргументом.
В кратце - он выполняет прокинутую функцию каждый раз после определённого ивента, который посылается 2 аргументом.
debug.sethook(OnNewFunctionCall, "c", 0)
Эта строка вешает хук после выполнения любой функции.
Есть ещё другие варианты, но как показало тестирование, в моём случае они были не особо полезны. Почитать о них вы можете тут.
Есть ещё другие варианты, но как показало тестирование, в моём случае они были не особо полезны. Почитать о них вы можете тут.
debug.getinfo
Вариативная функция, возвращает разные данные в зависимости от аргументов.
Если отправить number - сканирует стек на number глубине, возвращая DebugInfo.
Если отправить функцию - возвращает известную информацию о функции.
Если отправить number - сканирует стек на number глубине, возвращая DebugInfo.
Если отправить функцию - возвращает известную информацию о функции.
Мы сканируем стек только на втором уровне - последняя выполненная функция (даже не спрашивайте почему)
(можно сканировать фул стек вызовов, но толку от этого не много)
(можно сканировать фул стек вызовов, но толку от этого не много)
Функция возвращает нам конструкцию типа DebugInfo про которую можно почитать например вот здесь
О подключённых функциях из С куда меньше данных
Нужные нам поля:
.func - Указатель на текущую функцию
.name - Имя текущей функции
.source - Файл в которой объявлена функция; неизвестен, если функция из другого языка
.linedefined - Строка, на которой идёт объявление функции; неизвестно если функция из другого языка
.name - Имя текущей функции
.source - Файл в которой объявлена функция; неизвестен, если функция из другого языка
.linedefined - Строка, на которой идёт объявление функции; неизвестно если функция из другого языка
После чего мы вызываем debug.getinfo но уже на эту функцию.
Мы снова получаем конструкцию debugInfo, но уже с данными функции, которые известны lua. Там есть много чего полезного, но нас интересуют только некоторые поля.
Мы снова получаем конструкцию debugInfo, но уже с данными функции, которые известны lua. Там есть много чего полезного, но нас интересуют только некоторые поля.
.what - Откуда эта функция - "C" или "Lua"
.nparams - Количество аргументов у функции, если это функция из С, то будет 0
.nparams - Количество аргументов у функции, если это функция из С, то будет 0
Дальше мы используем лунную магию функцию под названием debug.getlocal
debug.getlocal
debug.getlocal(thread, var_n)
С помощью неё можно достать локальные переменные по нужной глубине стека.
В первый аргумент отправляется глубина стека, во второй - номер переменной.
Она возвращает нам название переменной и значение. Если имени нет - значит переменные закончились.
В первый аргумент отправляется глубина стека, во второй - номер переменной.
Она возвращает нам название переменной и значение. Если имени нет - значит переменные закончились.
Если чуть раньше мы смогли узнать количество аргументов в функции - мы получаем только их. Если же количество аргументов неизвестно - мы сканируем стек, получая первые 10 переменных (или всё что там есть). В таком случае переменные сперва идут в порядке объявления функции - сначала N аргументов самой функции, потом up-values (что есть closure/замыкание) и локальные переменные объявленные внутри функции, также могут быть и временные переменные без названия.
Функция printc собственно есть вывод в консоль.
Всё это дело выводится в консоль вот в таком виде:
Так как это всё ещё варкрафт, то нам нужны хуки на другие потоки, создаваемые варкрафтом.
У меня используются только таймеры и триггеры, поэтому хуки расставлены только на них.
У меня используются только таймеры и триггеры, поэтому хуки расставлены только на них.
Внимание - Скорость выполнения кода при включёном дебаге падает примерно в 40 раз, так что проводить бенчмарки с включённым дебагом такого типа крайне не рекомендую
Ещё внимание - Если вы хотите подрубить это на рефордж - у вас не получится по 2 причинам - у вас нет консоли (но вы можете переделать этот код) и насколько я знаю там выключен модуль debug (да я зажал сколько то килограммов риса за рефордж)