S.T.A.L.K.E.R. 2
Моды Тень Чернобыля Моды Зов Припяти Моды Чистое Небо
Форум Тень Чернобыля Форум Чистое Небо Форум Зов Припяти

☢ Информационный портал по модификациям к серии игр S.T.A.L.K.E.R. ☢

                                                                                                                                                                                         

Суббота, 18.05.2024, 13:21 | Приветствую Вас Гость | RSS | Главная | Регистрация | Вход

PDA сайта


Разделы каталога
Обзор игр [29]
Статьи, информация по играм, системные данные для установки игр.
Информация, рассказы - все что касается STALKER [84]
Книги - тематика STALKER [28]
Новинки модов
LOST ALPHA ENHANCED EDITION
Просмотров: 23
Добавлено: 16.05.2024

WOODEN ADDON
Просмотров: 52
Добавлено: 14.05.2024

СТРАЖ НА КОСТЯХ ХИМЕРЫ
Просмотров: 96
Добавлено: 14.05.2024

СВЯЗНОЙ + GUNSLINGER MOD - TORRENT - REPACK
Просмотров: 87
Добавлено: 06.05.2024

STALKER CS LFO MOD + ADVANCED X-RAY
Просмотров: 84
Добавлено: 06.05.2024

ОЗВУЧКА ДИАЛОГОВ ДЛЯ DOLLCHAN 8 INFINITY
Просмотров: 191
Добавлено: 04.05.2024

HIBERNATION EVIL - ЭПИЗОД IV. ЧАСТЬ 3
Просмотров: 160
Добавлено: 02.05.2024

BOSSPACK 1.5
Просмотров: 113
Добавлено: 02.05.2024

Anomaly Lost Zone
Просмотров: 167
Добавлено: 02.05.2024

NLC 7 - HARD EDITION 2.3
Просмотров: 126
Добавлено: 01.05.2024

Лидеры просмотров
SOC WEAPON PACK
Просмотров: 39110
Добавлено: 23.05.2018

Новый Арсенал 5.1 + Спавнер
Просмотров: 30499
Добавлено: 21.06.2018

Правки для ОП 2.1 с патчем 8.2
Просмотров: 26367
Добавлено: 02.07.2019

Спавнер - Пространственная аномалия 4.1
Просмотров: 24916
Добавлено: 22.05.2018

СТАЛКЕР - ТЕНЬ ЧЕРНОБЫЛЯ (Чистая игра)
Просмотров: 24022
Добавлено: 08.10.2018

Дополнения - Объединенный Пак 2.1
Просмотров: 23607
Добавлено: 05.01.2019

Тень Вонючки 0.03+
Просмотров: 21275
Добавлено: 21.05.2018

Сталкер - Приключения Толика
Просмотров: 18866
Добавлено: 19.01.2019

Спавн-Меню - Новый Арсенал 5.0
Просмотров: 18386
Добавлено: 23.05.2018

Спавнер для ОП 2.1
Просмотров: 15753
Добавлено: 03.01.2019

Главная » Статьи » Информация, рассказы - все что касается STALKER

Делаем скрипты без вылетов
Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь скриптописателям избежать потенциальных пробем. Сразу оговорюсь - я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам - сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков - старайтесь придерживаться нижеприведённого стиля программирования.

Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке Lua - разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:

Особенность первая. Типы данных и западло с nil.

Сначала сделаю маленькое отступление, чтобы понятнее было. Итак, в Lua используются следующие операторы:

= оператор присваивания
== сравнение, равно ли значение
~= сравнение, НЕ равно ли значение
< сравнение, меньше ли значение
> сравнение, больше ли значение
<= сравнение, меньше ли значение или равно
>= сравнение, больше ли значение или равно
and логический оператор И
not логический оператор НЕ
or логический оператор ИЛИ



Всё. Теперь поехали дальше... Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке - это типы данных, т.е. по сути либо переменные, либо константы. Это касается так же и функций! Поэтому даже с функциями в Lua можно и нужно обращатся как с константами. Далее. Касательно, собственно "привычных" переменных. Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно). При этом, в Lua есть такой важный и полезный тип данных как nil. nil - Это "пустота", т.е. отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для "сбора мусора", т.е. для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:

local reminder_count
local exposure_count = 0

Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая - создана и инициализирована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения - nil, т.е. пустоту. Если же мы обратимся к переменной exposure_count - то получим, как и ожидали, 0 - так как мы это значение проинициализировали ЗАРАНЕЕ. При этом не надо путать nil и 0 - так как 0 - это всё-таки какая-то информация, а вот nil - это её полное отсутствие. Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так - когда вам переменная уже не нужна, вы просто пишете:

test_variable = nil

И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, "абонент вне зоны действия сети"). Ну так собственно вот, зачем я это всё рассказываю... если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам. Происходит это следующим образом: допустим, reminder_count - это у нас счётчик напоминаний. Т.е. мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, т.е. не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше.
Выглядит это примерно так:
function check_antirad_supplies()
if auto_injection_active then
if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) > 60 then
if not db.actor:object("antirad") and not (use_scientific_kit and db.actor:object("medkit_scientic")) then
--обратите внимание сюда
if reminder and reminder_count == 0 or reminder_count >= mins_till_next_remind then
--обратите внимание сюда
if use_text then
local news_text = "%c[255,160,160,160]Автоматическая система ввода медицинских препаратов\\n".."%c[default]Напоминаю: %c[255,230,0,0]Противорадиационные препараты отстутствуют! %c[default]Автоматический ввод препаратов невозможен."
db.actor:give_game_news(news_text, "ui\\ui_iconsTotal", Frect():set(0,188,83,47), 0, 3000)
end
if use_sounds then
local snd_obj
if use_custom_sounds then
snd_obj = xr_sound.get_safe_sound_object( [[HEV\no-anti-rad]] )
else
snd_obj = xr_sound.get_safe_sound_object( [[device\pda\pda_tip]] )
end
if snd_obj then
snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)
end
end
reminder_count = 1
elseif reminder then
reminder_count = reminder_count + 1
end
else
radiation_warning = true
reminder_count = 0
end
antirad_check_delay = game.get_game_time()
end
end
end

Во время игры всё это скорее всего отработает хорошо, но вот при загрузке... тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение!
В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует - он равен nil, и в итоге проверка

if reminder and reminder_count == 0 or reminder_count >= mins_till_next_remind then
выдаст ошибку и приведёт к вылету!
Почему? Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.
Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации.
Поверьте - в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.
begin xStream upd. ---
Простой способ обойти такую кучу проверок и лишних инициализаций - использование or-оператора. пример
if (reminder_count or 0) >= mins_till_next_remind then
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно
end xStream upd. ---
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем... покажу конкретнее каких:
1) "Убийство" переменных
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:
antirad_check_delay = reminder_count
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку - у нас это выражение сработает как
antirad_check_delay = nil
В результате чего переменная antirad_check_delay "сдохнет" и будет деловито убрана "сборщиком мусора" из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.
2) Ошибки деления на 0.
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:
antirad_check_delay = exposure_count/reminder_count
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil.
В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим "классический" программистский вылет из-за ошибки деления на ноль.
3) Ошибки присвоения.
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:

function weapon_manager:set_weapon(wpn)
local enemy = self.npc:best_enemy()
if wpn then
--обратите внимание сюда
self.weapon_id = wpn:id()
--обратите внимание сюда
self:return_items(self.weapon_id)
-- printf("set_weapon[%s]:set %s[%s]",self.npc:character_name(),wpn:id(),wpn:section())
else
printw("set_wpn:weapon not exist")
end
if self.modes.process_mode == "3" and enemy then
for k,v in pairs(self.weapons) do
for i,w in ipairs(v) do
if w.id ~= self.weapon_id then
local item = level.object_by_id(w.id)
if item and item:parent() and item:parent():id() == self.npc_id then
printw("set_weapon[%s]:process %s[%s]",self.npc:character_name(),w.id,w.sec)
self:process_item(item)
end
end
end
end
end
if enemy then
self.npc:set_item(object.idle,wpn)
end
self.weapons = nil
end
В помеченном выражении self.weapon_id = wpn:id(), чтобы было понятнее, self.weapon_id - это оружие, которое сейчас в руках у персонажа, а wpn:id() ссылается на лучшее оружие из инвентаря NPC. Т.е. это выражение заставляет NPC заменить ТЕКУЩЕЕ оружие на лучшее. Всё было бы отлично если бы не одна характерная вещь - когда NPC сидит например у костра отдыхает, У НЕГО НЕТ В РУКАХ СТВОЛА! И разумеется, в этом случае значение self.weapon_id не существует! Оно равно nil. В итоге при подобном выражении
self.weapon_id = wpn:id()
произойдёт попытка присвоить значение ПУСТОТЕ, т.е. nil = wpn:id(), и это МГНОВЕННО приведёт к вылету...
Как с этим бороться.
Бороться с такими ситуациями очень просто на самом деле. Во-первых:
ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ.
Т.е. вот такое описание переменной:
local reminder_count
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ! Если переменная числовая, сделайте это так:
local reminder_count = 0
Если строчная - то сделайте это так:
local reminder_count = "" (получится вместо nil пустая строка)
Если логическая, то так:
local reminder_count = false
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!
И во-вторых:
ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ! Т.е. в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:
Код: Выделить всё
if self.weapon_id ~= nil and wpn:id() ~= nil then
self.weapon_id = wpn:id()
end
Тогда сначала выпонится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст - то функция просто пропустит этот код.
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:
Код: Выделить всё
if reminder_count ~= nil and reminder_count ~= 0 then
antirad_check_delay = exposure_count/reminder_count
end
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем. Настоятельно советую всем, кто уже писал скрипты, внимательно их просмотреть, и вставить в "опасные" места подобные проверки, и в будущем делать это сразу, чтобы самим себе облегчить жизнь...
16/02/2009 KamikaZze


Дополнения:
Небольшое дополнение итак, вы вставили обходные проверки вот таким образом:
if self.weapon_id ~= nil and wpn:id() ~= nil then
self.weapon_id = wpn:id()
end
Или таким:

if reminder_count ~= nil and reminder_count ~= 0 then
antirad_check_delay = exposure_count/reminder_count
end
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:
if self.weapon_id ~= nil and wpn:id() ~= nil then
self.weapon_id = wpn:id()
elseif self.weapon_id == nil then
--вставьте сюда код, что делать если self.weapon_id не существует
elseif wpn:id() == nil then
--а сюда, если wpn:id() не существует
end
И для второго случая аналогично:

if reminder_count ~= nil and reminder_count ~= 0 then
antirad_check_delay = exposure_count/reminder_count
else
--а тут что мы сделаем если reminder_count не существует
--я бы сделал например вот так:
antirad_check_delay = exposure_count/1
--и все дела
end
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать - тут уже вам виднее...
Категория: Информация, рассказы - все что касается STALKER | Добавил: ferr-um (30.04.2011) | Автор: ferr-um E
Просмотров: 643 | Теги: все для создания скриптов, создание скриптов, информация про скрипты, скрипты
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
PDA Stalker-Mods


Форма входа
Поиск по сайту
ADVERT
Популярные моды
Повелитель Зоны v. 1.1/ Gode of zone v. 1.1
Просмотров: 11741
Добавлено: 26.11.2011

Новый Арсенал 5.1 + Спавнер
Просмотров: 30499
Добавлено: 21.06.2018

COP Weapons Mod
Просмотров: 9028
Добавлено: 19.06.2011

Оружейный пак от Moonlight1986
Просмотров: 12008
Добавлено: 25.06.2012

Спавнер - Пространственная аномалия 4.1
Просмотров: 24916
Добавлено: 22.05.2018

Winter of Death: Ultimatum (FINAL)
Просмотров: 7858
Добавлено: 26.09.2011

S.T.A.L.K.E.R.: Зов Припяти - Оружейный аддон
Просмотров: 9156
Добавлено: 23.12.2011

SOC WEAPON PACK
Просмотров: 39110
Добавлено: 23.05.2018

"Повелитель зоны" для FERR-UM MOD ver.0.4
Просмотров: 9345
Добавлено: 15.08.2012

S.T.A.L.K.E.R. "Зов Припяти" - Новые иконки
Просмотров: 7666
Добавлено: 27.08.2011

Статистика

Онлайн всего: 3
Гостей: 3
Пользователей: 0

Моды Тень Чернобыля Форум Тень Чернобыля Моды Чистое Небо Форум Чистое Небо Моды Зов Припяти Форум Зов Припяти Моды Зов Чернобыля Форум Зов Чернобыля Сталкер - Другие платформы
Картинка в качестве фона





Картинка в качестве фона
Картинка в качестве фона
Stalker-Mods © 2024 Используются технологии uCoz