,

WarCraft 3 Reforged: Дебаггер Lua

» Раздел: Основы
Для какой версии: 131+
Для какого языка: Lua
Назначение: Выводит сообщения об ошибке, причину и номер строчки в файле war3map.lua, где искать ошибку
Содержание:
local realTimerStart = TimerStart
TimerStart = function(timer, duration, repeating, callback)
	local pcallback = function()
		if callback == nil then return end
		local status, err = pcall(callback)
		if not status then
			print(err)
		end
	end
	realTimerStart(timer, duration, repeating, pcallback)
end

local realTriggerAddAction = TriggerAddAction
TriggerAddAction = function(trig, callback)
	local pcallback = function()
		local status, err = pcall(callback)
		if not status then
			print(err)
		end
	end
	realTriggerAddAction(trig, pcallback)
end
Установка: Скопировать код себе в карту в любое место
Примеры использования:
Пример №1
Делаем намеренную и частую ошибку, например забыли передать аргумент
A=nil+1
Предположим, у нас есть простенькая или не очень функция с кучей аргументов или минимум двумя
function sum(a,b)
	return a+b
end
Она понятно что делает, это просто пример и вот мы хотим её использовать
print(sum(1))
И ой, кажется случайно забыли передать второй аргумент, ни родной редактор ни vscode ни idea в режиме луа не выдадут никакой ошибки, потому что луа - язык с динамической типизаций, вот они проблемы это типизации. Надо быть на 200% внимательным чтобы не совершать таких ошибок.
По факту код без аргумента превращается в
print(sum(1,nil))
А внутри происходит
return a+nil
Что равно просто остановке выполнения потока, а чё-то мой код сохранился без ошибок, а карта не работает
А вот и то самое сообщение, где искать ошибку и почему она произошла
Карта от этого особо не сломается, но большое накопление таких ошибок способно вызывать фаталы или же десинхронизацию, особенно если поток прерывает внутри действий локального игрока
Пример №2
Поменяем аргументы в функции местами (случайно, по запаре, а последний вообще забудем написать)
Верный код
CreateUnit(Player(0),FourCC('hpea'),0,0,180)
Наш код с ошибкой
CreateUnit(FourCC('hpea'),Player(0),0,0)
Ну и разумеется получаем ошибку, что неправильно передали игрока, в какой строке и в каком аргументе
» код для теста
function sum(a,b)
	return a+b
end
do --Инициализация
	TimerStart(CreateTimer(), 0.1, false, function()
		print(sum(1))
		--CreateUnit(Player(0),FourCC('hpea'),0,0,180)
		--CreateUnit(FourCC('hpea'),Player(0),0,0)
	end)
end


Views: 246

Nelloy #1 - 2 months ago 0
Голосов: +0 / -0
Еще флаг дебага добавил бы, а так супер
Bergi_Bear #2 - 2 months ago 0
Голосов: +0 / -0
Nelloy, типа выключать и включать эту штуку? ну по факту если ошибок нет, дебаггер будет молчать, может слово дебаггер даже не сильно подходит, но я просто забыл, как эта штука правильно называется
Niken #3 - 2 months ago (изм. ) 0
Голосов: +0 / -0
Bergi_Bear, отладка кода - обнаружение причины ошибки.
Nelloy #4 - 2 months ago (изм. ) 0
Голосов: +0 / -0
Bergi_Bear, Да будет молчать, но pcall требует накладных расходов. Я ее использую немного по-другому, и заметил, что достаточно сильно пролагивает при выдаче ошибки с вложенностью больше 3. А значит функция не бесплатная.
Bergi_Bear #5 - 2 months ago 0
Голосов: +0 / -0
Nelloy, соглашусь, но можно на итоговом релизе просто удалять этот код, и скорее всего лагает большое число принтов, которые функция может пораждать, если функция и отбирает, то максимум 10% ресурсов, которые сложно заметить (вернее я вообще никогда не замечал подобного)

Я ее использую немного по-другому
покажи как используешь, будет полезно
Nelloy #6 - 2 months ago (изм. ) 2
Голосов: +2 / -0
Bergi_Bear, принтов там выходит 4-5 строк.
Есть класс Action с методом public:run(...). Для всех колбеков во всех системах использую его.
Похожую задачу решаю 3мя классами.
» Action
------=========
-- Include
--=========

local lib_path = Lib.curPath()
local lib_dep = Lib.curDepencies()

local Class = lib_dep.Class or error('')
---@type UtilsFunctions
local Functions = require(lib_path..'Functions') or error('')
local isTypeErr = Functions.isTypeErr or error('')
---@type UtilsSettings
local Settings = require(lib_path..'Settings') or error('')
local Log = Settings.default_logger or error('')

--=======
-- Class
--=======

local Action = Class.new('Action')
---@class Action
local public = Action.public
---@class ActionClass
local static = Action.static
---@type ActionClass
local override = Action.override
local private = {}

--========
-- Static
--========

---@alias Callback fun(vararg:any[]):any

---@param callback Callback
---@param owner any
---@param child Action | nil
---@return Action
function override.new(callback, owner, child)
    isTypeErr(callback, 'function', 'callback')
    if child then isTypeErr(child, 'Action', 'child') end

    local list = private.callback2list[callback]
    if list and list[owner] then
        return list[owner]
    end

    local instance = child or Class.allocate(Action)
    private.newData(instance, callback, owner)

    return instance
end

--========
-- Public
--========

---@return any
function public:run(...)
    if Settings.isDebug() then
        local success, result = pcall(private.data[self].callback, ...)
        if success then
            return result
        else
            Log:err(result)
        end
    else
        return private.data[self].callback(...)
    end
end

---@return any
function public:getOwner()
    return private.data[self].owner
end

--=========
-- Private
--=========

private.data = setmetatable({}, {__mode = 'k'})
private.callback2list = setmetatable({}, {__mode = 'v'})

---@param self Action
---@param callback Callback
---@param owner any
function private.newData(self, callback, owner)
    local priv = {
        callback = callback,
        owner = owner
    }

    private.data[self] = priv
    if not private.callback2list[callback] then
        private.callback2list[callback] = setmetatable({}, {__mode = 'v'})
    end
    local list = private.callback2list[callback]
    list[owner or ''] = self
end

return static
» ActionList
--=========
-- Include
--=========

local lib_path = Lib.curPath()
local lib_dep = Lib.curDepencies()

local Class = lib_dep.Class or error('')
---@type ActionClass
local Action = require(lib_path..'Action') or error('')
---@type UtilsFunctions
local Functions = require(lib_path..'Functions') or error('')
local isTypeErr = Functions.isTypeErr or error('')

--=======
-- Class
--=======

local ActionList = Class.new('ActionList')
---@class ActionList : Handle
local public = ActionList.public
---@class ActionListClass : HandleClass
local static = ActionList.static
---@type ActionListClass
local override = ActionList.override
local private = {}

--========
-- Static
--========

---@param owner any
---@param child ActionList | nil
---@return ActionList
function override.new(owner, child)
    if child then isTypeErr(child, ActionList, 'child') end

    local instance = child or Class.allocate(ActionList)
    private.newData(instance, owner)

    return instance
end

--========
-- Public
--========

---@param callback Callback
---@return Action
function public:add(callback)
    isTypeErr(callback, 'function', 'callback')
    local priv = private.data[self]

    local action = Action.new(callback, priv.owner)
    table.insert(priv.actions, action)

    return action
end

---@param action Action
---@return boolean
function public:remove(action)
    isTypeErr(action, Action, 'action')

    local priv = private.data[self]
    if action:getOwner() ~= priv.owner then return false end

    for i = 1, #priv.actions do
        if priv.actions[i] == action then
            table.remove(priv.actions, i)
            return true
        end
    end

    return false
end

---@param pos number
---@return Action | nil
function public:get(pos)
    return private.data[self].actions[pos]
end

---@return number
function public:count()
    return #private.data[self].actions
end

--- Remove all actions from list.
function public:clear()
    private.data[self].actions = {}
end

--- Run all actions.
---@return table<Action, any>
function public:run(...)
    local priv = private.data[self]

    local res = {}
    for i = 1, #priv.actions do
        res[priv.actions[i]] = priv.actions[i]:run(...)
    end

    return res
end

--=========
-- Private
--=========

private.data = setmetatable({}, {__mode = 'k'})

---@param self ActionList
---@param owner any
function private.newData(self, owner)
    local priv = {
        owner = owner,
        actions = {}
    }
    private.data[self] = priv
end

return static
» Trigger
--=========
-- Include
--=========

local lib_path = Lib.curPath()
local lib_dep = Lib.curDepencies()

local Class = lib_dep.Class or error('')
---@type UtilsLib
local UtilsLib = lib_dep.Utils or error('')
local ActionList = UtilsLib.ActionList or error('')
local isTypeErr = UtilsLib.isTypeErr or error('')

---@type HandleClass
local Handle = require(lib_path..'Base') or error('')

--=======
-- Class
--=======

local Trigger = Class.new('Trigger', Handle)
---@class Trigger : Handle
local public = Trigger.public
---@class TriggerClass : HandleClass
local static = Trigger.static
---@type TriggerClass
local override = Trigger.override
local private = {}

--========
-- Static
--========

---@param child Trigger | nil
---@return Trigger
function override.new(child)
    if child then isTypeErr(child, Trigger, 'child') end

    local instance = child or Class.allocate(Trigger)
    instance = Handle.new(CreateTrigger(), DestroyTrigger, instance)
    private.newData(instance)

    return instance
end

--========
-- Public
--========

---@param callback Callback
---@return Action
function public:addAction(callback)
    return private.data[self].action_list:add(callback)
end

---@param action Action
---@return boolean
function public:removeAction(action)
    return private.data[self].action_list:remove(action)
end

---@return number
function public:countActions()
    return private.data[self].action_list:count()
end

---Function removes all actions from trigger without removing trigger.
function public:clearActions()
    private.data[self].action_list:clear()
end

---Function executes trigger like event do.
function public:execute()
    TriggerExecute(self:getData())
end

---@param var_name string
---@param opcode limitop
---@param limitval number
function public:addVariableEvent(var_name, opcode, limitval)
    isTypeErr(var_name, 'string', 'var_name')
    isTypeErr(opcode, 'limitop', 'opcode')
    isTypeErr(limitval, 'number', 'limitval')
    TriggerRegisterVariableEvent(self:getData(), var_name, opcode, limitval)
end

---@param timeout number
---@param periodic boolean
function public:addTimerEvent(timeout, periodic)
    TriggerRegisterTimerEvent(self:getData(), timeout, periodic)
end

---@param timer timer
function public:addTimerExpireEvent(timer)
    TriggerRegisterTimerExpireEvent(self:getData(), timer)
end

---@param game_state gamestate
---@param opcode limitop
---@param limitval number
function public:addGameStateEvent(game_state, opcode, limitval)
    TriggerRegisterGameStateEvent(self:getData(), game_state, opcode, limitval)
end

---@param dialog dialog
function public:addDialogEvent(dialog)
    TriggerRegisterDialogEvent(self:getData(), dialog)
end

---@param button button
function public:addDialogButtonEvent(button)
    TriggerRegisterDialogButtonEvent(self:getData(), button)
end

---@param game_event gameevent
function public:addGameEvent(game_event)
    TriggerRegisterGameEvent(self:getData(), game_event)
end

---@param region region
function public:addEnterRegion(region)
    TriggerRegisterEnterRegion(self:getData(), region)
end

---@param region region
function public:addLeaveRegion(region)
    TriggerRegisterLeaveRegion(self:getData(), region)
end

---@param trackable trackable
function public:addTrackableHitEvent(trackable)
    TriggerRegisterTrackableHitEvent(self:getData(), trackable)
end

---@param trackable trackable
function public:addTrackableTrackEvent(trackable)
    TriggerRegisterTrackableTrackEvent(self:getData(), trackable)
end

---@param player_event_type playerevent
---@param player player
function public:addPlayerEvent(player_event_type, player)
    TriggerRegisterPlayerEvent(self:getData(), player, player_event_type)
end

---@param player_unit_event playerunitevent
---@param player player
function public:addPlayerUnitEvent(player_unit_event, player)
    TriggerRegisterPlayerUnitEvent(self:getData(), player, player_unit_event, nil)
end

---@param player player
---@param alliancetype alliancetype
function public:addPlayerAllianceChange(player, alliancetype)
    TriggerRegisterPlayerAllianceChange(self:getData(), player, alliancetype)
end

---@param player player
---@param player_state playerstate
---@param opcode limitop
---@param limitval number
function public:addPlayerStateEvent(player, player_state, opcode, limitval)
    TriggerRegisterPlayerStateEvent(self:getData(), player, player_state, opcode, limitval)
end

---@param player player
---@param message string
---@param exact_match boolean
function public:addPlayerChatEvent(player, message, exact_match)
    TriggerRegisterPlayerChatEvent(self:getData(), player, message, exact_match)
end

---@param widget widget
function public:addDeathEvent(widget)
    TriggerRegisterDeathEvent(self:getData(), widget)
end

---@param unit unit
---@param unit_state unitstate
---@param opcode limitop
---@param limitval number
function public:addUnitStateEvent(unit, unit_state, opcode, limitval)
    TriggerRegisterUnitStateEvent(self:getData(), unit, unit_state, opcode, limitval)
end

---@param unit_event unitevent
---@param unit unit
function public:addUnitEvent(unit_event, unit)
    TriggerRegisterUnitEvent(self:getData(), unit_event, unit)
end

---@param unit unit
---@param range number
function public:addUnitInRange(unit, range)
    TriggerRegisterUnitInRange(self:getData(), unit, range)
end

---@param frame framehandle
---@param frame_event frameeventtype
function public:addFrameEvent(frame, frame_event)
    BlzTriggerRegisterFrameEvent(self:getData(), frame, frame_event)
end

---@param player player
---@param prefix string
---@param from_server boolean
function public:addPlayerSyncEvent(player, prefix, from_server)
    BlzTriggerRegisterPlayerSyncEvent(self:getData(), player, prefix, from_server)
end

---@param player player
---@param key oskeytype
---@param meta_key integer
---@param key_down boolean
function public:addPlayerKeyEvent(player, key, meta_key, key_down)
    BlzTriggerRegisterPlayerKeyEvent(self:getData(), player, key, meta_key, key_down)
end

--=========
-- Private
--=========

private.data = setmetatable({}, {__mode = 'k'})

---@param self Trigger
function private.newData(self)
    local priv = {
        action_list = ActionList.new()
    }
    private.data[self] = priv

    TriggerAddAction(self:getData(), function() priv.action_list:run() end)
end

return static

Но тут ничего не понятно стороннему человеку, как мне кажется.
prog #7 - 2 months ago 1
Голосов: +1 / -0
Bergi_Bear, pcall и xpcall действительно довольно тяжелые. Динамическое включение-выключение дебаг режима это избыточно, а вот возможность включить-выключить его одним флагом вполне пригодится.
GetLocalPlayer #8 - 2 months ago (изм. ) 0
Голосов: +0 / -0
А TriggerAddCondition не нужно хукать?
prog:
а вот возможность включить-выключить его одним флагом вполне пригодится.
Просто выключить триггер в который код был скопирован? Или система подразумевает редактирование war3map.lua?
Bergi_Bear #9 - 2 months ago 0
Голосов: +0 / -0
А TriggerAddCondition не нужно хукать?
Я вообще не использую кондишены.
GetLocalPlayer:
Просто выключить триггер в который код был скопирован? Или система подразумевает редактирование war3map.lua?
И так и так можно
GetLocalPlayer #10 - 2 months ago 0
Голосов: +0 / -0
Я вообще не использую кондишены.
То есть система работает полноценно только для тех, кто не использует кондишены?
Bergi_Bear #11 - 2 months ago 0
Голосов: +0 / -0
А что это такое вообще? не нужный рудимент который я ещё перестал 8 лет назад использовать.
А если серьёзно, то в кондишенах обычно кода гораздо меньше и шанс там напортачить так же меньше, и + доказано что бульехкспры ничуть не быстрее, чтобы туда совать тонну действий, это было заблуждение
GetLocalPlayer #12 - 2 months ago 0
Голосов: +0 / -0
А что это такое вообще? не нужный рудимент
Это не рудимент, это подход к проектированию программы. Смысл его заключается в разделении и изоляции друг от друга логических блоков. Изоляции блока проверки произошедшего события и блока ответных на событие действий. Гарантия отсутствия мешанины из смеси проверок событий и непосредственного действия.
Sergarr #13 - 2 months ago 0
Голосов: +0 / -0
Я, конечно, знал, что динамическая типизация - это зло, но то, что Lua скомпилирует и запустит без выдачи ошибок функцию с заведомо недостаточным числом аргументов - это просто провал. Как такими языками программирования можно пользоваться в 2020-м году? Это, типа, фича такая, чтобы люди на грабли наступали побольше?
GetLocalPlayer #14 - 2 months ago 0
Голосов: +0 / -0
это просто провал.
Этот "провал" называется полиморфизмом. Причем истинной формой полиморфизма.
Bergi_Bear #15 - 2 months ago 0
Голосов: +0 / -0
Sergarr, в точку, ну как бы есть ещё тайп скрипт, там таких ошибок не будет
PT153 #16 - 2 months ago 0
Голосов: +0 / -0
Sergarr, это особенность языка. Компилятор Lua никак не может узнать, какая именно функция (и функция ли) будет на момент выполнения какого-то куска кода.
Bergi_Bear #17 - 2 months ago (изм. ) 0
Голосов: +0 / -0
Как такими языками программирования можно пользоваться в 2020-м году? Это, типа, фича такая, чтобы люди на грабли наступали побольше?
С другой стороны всё в порядке, те единицы, что сидят на луа - знают что делают
Doc #18 - 2 months ago 0
Голосов: +0 / -0
GetLocalPlayer:
это просто провал.
Этот "провал" называется полиморфизмом. Причем истинной формой полиморфизма.
Нет не называется, причем тут полиморфизм?

Sergarr:
Я, конечно, знал, что динамическая типизация - это зло, но то, что Lua скомпилирует и запустит без выдачи ошибок функцию с заведомо недостаточным числом аргументов - это просто провал. Как такими языками программирования можно пользоваться в 2020-м году? Это, типа, фича такая, чтобы люди на грабли наступали побольше?
Т.к. синтаксического различия между обращением к локальной и глобальной переменной нет, то в случае несуществующей локальной переменной идет обращение к глобальной таблице переменных _G. На этапе компиляции нельзя понять есть или нет там определенная переменная.
pro100master #19 - 2 weeks ago 0
Голосов: +0 / -0
ну а есть и продвинутый консоль для тестирование в игре
github.com/warcraft-iii/lib-console чтобы такой применить нужен установить github.com/warcraft-iii/warcraft-vscode в vscode и установить зависимость консоль
Не для слабонервных конечно =)

Тогда и можно ввыбрать из vscode режит разработчика или релиз
во время релиз убирает все рутинные работы проверка кода и всякие фичи в оптимизацию

А также укажет именно на файл где вы работаете src с нужным номер ошибки чем искать в итоговый результат в карте.

например main.lua ошибка будет там указано номер ошибки как и ты открыл редактор и писал