Перейти к контенту

Уроки по модостроению


Zeka1996Korneev

Рекомендуемые сообщения

(изменено)

Все мы знаем, что движок ведёт себя, как капризная невеста, и вылетает от малейшего чиха.

1. Синтаксические ошибки в коде. Как правило, лог при этом содержит что-то вроде

attempt to index global '<имя файла>' (a nil value)

т.е. движку не удалось скомпилить модуль и его значение (модуля) равно нулю

 

2. Ошибки времени выполнения Lua. Лог обычно содержит указание ошибки модуль и строку, в которой ошибка произошла. Типа такого:

LUA error: ....k.e.r. 1.0004\gamedata\scripts\<имя файла>.script:<номер строки>: attempt to index global 'npc' (a nil value)

К типичным ошибка относятся попытки вызвать несуществующий метод класса (и вообще индексировать что-бы то ни было несуществующим ключом) или, к примеру, конкатенация строки с nil.

 

3. Ошибки времени выполнения самого игрового движка. Лога обычно нет, т.е. движок тихо слетает, сам в лог ничего не пишет, а забуферизированный до этого момента лог теряется. Иногда, правда, что-то в логе есть, но бывает это весьма редко. Ошибки движка вызваны неправильным использованием функций самого движка. Например, попытка заспавнить объект с несуществующей секцией.

Кроме того скрипты выполняются с прекомпиляцией, поэтому для запуска изменённого скрипта надо как минимум перегрузить сохранёнку. Кстати, для справки. в отличие от скриптов файлы конфигураций подгружаются уже при старте программы ещё до загрузки сохранения. Посему для тестирования изменения в файлах конфигураций надо перезапускать программу.

 

Довольно часто надо проверить некий фрагмент кода, заспавнить объект, проверить некую синтаксическую конструкцию и т.п.

1. Пишу скрипт и получаю вылет игры без лога, если влез в один из заранее прекомпилируемых файлов и сделал там тупую синтаксическую ошибку.

2. Выношу скрипт в отдельный файл и получаю вылет игры с до ужаса информативным сообщением в лог-файле "attempt to index global 'my_cool_script' (a nil value)".

Это значит, что тупую синтаксическую ошибку я сделал в файле my_cool_script.script

Можно до хрипоты говорить о пользе предварительной синтаксической проверки, но как часто мы её не делаем, не так ли?

3. Наконец-то умнею и проверяю скрипт на вшивость заранее. Запускаю игру, загружаю уровень.

загружаю уровень...

загружаю уровень......

Получаю вылет игры, лезу в лог и ищу в конце коротенькое сообщение, из которого следует, что надо поменять пару символов или добавить/стереть одну строчку.

4. Меняю скрипт, повторяю всё заново, скрипт отрабатывает, но делает не то, что мне надо. Чтобы изменения в скрипте вступили в силу, надо перегрузить уровень.

так что повторяю пп.3 и 4, пока не получу то, что мне было нужно.

 

Вот для частичного решения этой проблемы в своё время был сделан специальный тестовый полигон, который позволяет менять и вызывать определённый скрипт, не выходя из игры. Кроме того, часть ошибок (а именно ошибки Lua) блокируются и не приводят к вылетам. Полигон этот сэкономил мне кучу времени.

 

[spoiler=Тестирование скриптов без обрушения и перезапуска игры]Тестирование скриптов без обрушения и перезапуска игры

Ключевыми элементами являются две функции из стандартной библиотеки Lua:

dofile - позволяет загрузить и выполнить как chunk внешний файл.

pcall - позволяет выполнить произвольную функцию в т.н. защищённом режиме. Это значит, что любая ошибка интерпретатора Lua не выйдет за пределы функции, вызванной таким образом. Напомню, что обычно любая ошибка времени выполнения Lua передаётся хост-программе, т.е. игре, и вызывает её вылет.

 

Ну и собственно рецепт пилюли

следующий фрагмент может, скажем, выполняться по горячей клавише из главного меню.

 

    local res, err = pcall(my_proxy.start_chunk_from_file)
    get_console():execute(res and "Succesfull!" or string.gsub(err, " ", "_")) --имитация тернарного оператора

pcall возвращает два значения. Первое - результат запуска. Если все прошло пучком, то true. Второе - добавочное значение с сообщением об ошибке, если ошибка была. Иначе nil.

 

файл my_proxy.script содержит всего одну функцию

 

function start_chunk_from_file()
    local chunk, err = dofile("..\\gamedata\\scripts\\test.lua")
    if err then
        get_console():execute("err="..tostring(string.gsub(err, " ", "_")))
    end
end

dofile принимает имя файла со скриптом, загружает его и выполняет как функцию. Путём экспериментов я выяснил, что текущим каталогом Lua считает папку bin. Так что если хотим держать скрипт там, где и все остальные, то пишем путь относительно этой папки "..\\gamedata\\scripts\\test.lua".

test.lua - это отлаживаемый скрипт.

Обработка ошибок осуществляется аналогично pcall.

 

 

Так можно не выходя из игры и даже не перегружая уровень копаться в скрипте test.lua и запускать его по много раз, существенно менее рискуя обрушить игру. Особенно удобно, если есть два компа и сетка. В этом случае даже Alt-Tab не нужен. Редактируешь файл по сети на одном компе, а запускаешь в загруженной игре на другом.

Само-собой, если сделаем что-то, что фатально затронет игровую логику или что-то из ошибок категории 3, то обрушение может всё равно случиться. Сразу или в другом месте. В основном, в таких случаях уже ничего не поделаешь. Однако, довольно часто можно свести потенциальную ошибку времени выполнения движка к ошибке Lua. Например, если вызвать функцию get_console():execute(<аргумент>) с нулевым аргументом, то получим вылет без лога. Однако ничто не мешает сделать так:

get_console():execute("value="..obj)

В этом случае сначала будет выполняться конкатенация двух строк. Если obj будет равно nil, то получим ошибку Lua, а её тестовый полигон не пропустит, и обрушения игры не будет.

 

И ещё. Это всё работает для ТЧ. Для ЧН мне наладить такой же полигон не удалось. Что-то там изменено в движке, что вызывает у него аллергию на функцию pcall.

Изменено пользователем malandrinus
  • Нравится 1
  • Полезно 3
 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Поделиться этим сообщением


Ссылка на сообщение

Сразу оговорюсь, что вся информация ниже предназначена для ТЧ. В ЧН экспортировано пространство имён

io и проблемы как-бы и нет вовсе.

 

Имеются экспортированные в Lua функции log(string), error_log(string) и flush(). И эти функции не работают! По крайней мере никому до сих пор не удалось заставить их работать в релизной версии движка.

Так что общепринятым способом является использование консоли. Известно, что всё, что пишется в консоль, пишется одновременно и в лог-файл. Файл этот находится в месте, определяемом переменной $logs$, которая в свою очередь находится в файле fsgame.ltx.

Для управления консолью имеется класс CConsole. Экземпляр этого класса можно получить с помощью глобальной функции get_console().

В этом классе нет функций, которые позволили бы вывести на консоль (и соответственно в лог) произвольную строку. Однако имеется функция execute(string), которая предназначена для выполнения команд консоли. Разумеется, выполнить можно только те команды, которые поддерживает движок. Если команда не поддерживается, то в консоль выводится сообщение:

! Unknown command: <команда_которую_пытался_выполнить>

Вот так и можно вывести некий текст. Пишем его вместо команды и он попадёт в консоль и в лог в составе сообщения об ошибке. Т.е. можно выводить сообщения так:

get_console():execute(<моё_сообщение>)

У метода есть недостатки.

Первое, сообщение не должно быть командой. Обычно с этим не возникает проблем, но всё-таки следить надо.

Второе, если в сообщении есть пробельные символы, то движок воспринимает как команду только последовательность символов до первого такого символа. Остальное - это как бы аргументы команды. В лог выведется только "команда", а остальное пропадёт. Есть два способа для решения этой проблемы. Первый состоит в замене всех пробелов на другой символ, например знак подчеркивания. Тогда команда вывода в лог будет выглядеть так:

get_console():execute(string.gsub(<моё_сообщение>, " ", "_"))

Функция string.gsub(str, ptrn, rep) заменяет в строке str все вхождения строки ptrn на строку rep.

Второй способ решения проблемы заключается в использовании функции команды консоли load. Эта команда загружает сейв с определённым именем. Если такого нет, то в лог выводится ещё и имя не найденного сейва.

get_console():execute("load ~~~ "..<сообщение>)

Поскольку файла, начинающегося с "~~~" точно не существует, то это сработает всегда. Сообщение в логе в этом случае выглядит так:

! Cannot find saved game ~~~ моё сообщение

При таком способе в сообщении могут быть пробелы и они отобразятся нормально. Кроме того, нет риска, что сообщение окажется командой консоли.

Недостаток метода - лишнее обращение к жёсткому диску на предмет проверки существования сейва. Кроме того, этот метод аккуратно обходится только с пробелами. Прочие специальные символы конвертируются в пробелы.

Полезно также упомянуть про команду консоли "flush", которая позволяет принудительно записать на диск всё, что на данный момент было выведено в консоль. Это может быть весьма полезно, поскольку при краше игры лог потеряется, если только не был записан этой командой. Так что вот такую строчку рекомендуется ставить перед потенциально опасными местами:

get_console():execute("flush")

В любом случае описанные методы нагружают сообщение лишними частями. Кроме того, в тот же лог выводятся и другие сообщения от движка. Хотя указанные методы чрезвычайно полезны, их недостатки иногда сильно мешают.

 

Но есть и ещё один способ, который может компенсировать эти недостатки. Оказывается, можно перенаправить консольный вывод движка в текстовый файл. После этого в этот файл можно выводить что угодно функцией Lua print(). Описание этой функции можно посмотреть на сайте Lua. Скажу только, что в сочетании с функцией форматирования текста string.format() так можно вывести в файл совершенно произвольно оформленный текст.

Для реализации метода можно запускать игру через батник примерно такого содержания:

XR_3DA.exe -nointro >> log.txt

 

У метода замечен только один недостаток. Нет никакой возможности принудительно записать файл на диск. Функция Flush осталась в неэкспортированном пространстве имён io. Хотя в отличие от консольного лога этот файл записывается на диск сам, но из-за буферизации часть его может таки пропасть в случае краша игры. При нормальном выходе из игры всё естественно запишется как надо.

 

  • Спасибо 1
  • Полезно 3
 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Поделиться этим сообщением


Ссылка на сообщение
  • Недавно просматривали   0 пользователей

    Ни один зарегистрированный пользователь не просматривает эту страницу.

AMK-Team.ru

×
×
  • Создать...