Добавлен , опубликован
Для какой версии: 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
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
13
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

Но тут ничего не понятно стороннему человеку, как мне кажется.
24
Bergi_Bear, pcall и xpcall действительно довольно тяжелые. Динамическое включение-выключение дебаг режима это избыточно, а вот возможность включить-выключить его одним флагом вполне пригодится.
15
А TriggerAddCondition не нужно хукать?
prog:
а вот возможность включить-выключить его одним флагом вполне пригодится.
Просто выключить триггер в который код был скопирован? Или система подразумевает редактирование war3map.lua?
33
А TriggerAddCondition не нужно хукать?
Я вообще не использую кондишены.
GetLocalPlayer:
Просто выключить триггер в который код был скопирован? Или система подразумевает редактирование war3map.lua?
И так и так можно
15
Я вообще не использую кондишены.
То есть система работает полноценно только для тех, кто не использует кондишены?
33
А что это такое вообще? не нужный рудимент который я ещё перестал 8 лет назад использовать.
А если серьёзно, то в кондишенах обычно кода гораздо меньше и шанс там напортачить так же меньше, и + доказано что бульехкспры ничуть не быстрее, чтобы туда совать тонну действий, это было заблуждение
15
А что это такое вообще? не нужный рудимент
Это не рудимент, это подход к проектированию программы. Смысл его заключается в разделении и изоляции друг от друга логических блоков. Изоляции блока проверки произошедшего события и блока ответных на событие действий. Гарантия отсутствия мешанины из смеси проверок событий и непосредственного действия.
12
Я, конечно, знал, что динамическая типизация - это зло, но то, что Lua скомпилирует и запустит без выдачи ошибок функцию с заведомо недостаточным числом аргументов - это просто провал. Как такими языками программирования можно пользоваться в 2020-м году? Это, типа, фича такая, чтобы люди на грабли наступали побольше?
15
это просто провал.
Этот "провал" называется полиморфизмом. Причем истинной формой полиморфизма.
33
Sergarr, в точку, ну как бы есть ещё тайп скрипт, там таких ошибок не будет
28
Sergarr, это особенность языка. Компилятор Lua никак не может узнать, какая именно функция (и функция ли) будет на момент выполнения какого-то куска кода.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.