Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)

Материал из Mod Wiki.

Перейти к: навигация, поиск

Начало в первой части статьи



Содержание

Как безопасно использовать коллбэки и таймерные события

В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:

xr_motivator.script -
function motivator_binder:death_callback(victim, who)

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

Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет. Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных "мусорных" вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих "родных" вылетов). Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие "зависшие" коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций. Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.

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

Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback

1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.

2. Частые вылеты во время интенсивных боёв с логами типа

   Sheduler tried to update object...
   smart_terrain:1145(1146)
   LUA: out of memory
   любой_модуль_логики:любая_cтрока - stack overflow

3. Частые "родные" вылеты в момент смерти непися или попадания по нему

4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий

Использование защищённого кода в LUA

Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал death_callback неписей, я периодически сталкивался с тем, что обращение к методу smart_terrain_id() при смерти непися иногда вызывало вылет

smart_terrain:1143 "attempt to index a nil value"

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

Вот код, в котором происходил вылет:

function on_death( obj_id )
--	printf( "on_death obj_id=%d", obj_id )

	local sim = alife()

	if sim then
		local obj     = sim:object( obj_id )
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут

		if strn_id ~= 65535 then
			sim:object( strn_id ).gulag:clear_dead(obj_id)
		end
	end
end

Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:

pcall (f, arg1, ···)

Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого pcall перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае pcall сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то pcall вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)

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

Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:

--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.
function prot_smt_td(obj)
	if IsStalker(obj) or IsMonster(obj) then
		return obj:smart_terrain_id()
	else	
		return 65535
	end
end


function on_death( obj_id )
--	printf( "on_death obj_id=%d", obj_id )
	local sim = alife()
	if sim then
		local obj = sim:object( obj_id )
		if obj then
			local strn_id = 65535  --- предварительно проинитим переменную, на 
						--- случай если у нас prot_smt_td выдаст ошибку
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме 
									--- и сразу присваиваем его вывод переменным
			if result then --- если pcall выдало true
				strn_id = smt_id  --- тогда применям полученное значение
			end
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...
			if strn_id ~= 65535 then
				sim:object(strn_id).gulag:clear_dead(obj_id)
			end
		end
	end
end

В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто.

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

Скрытые критические проблемы в обработке вылетов игрой

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

В одном из логов нашего бета-тестера я увидел стандартное сообщение о вылете внутри рабочего лога... да-да, то самое которое FATAL ERROR и дальше по тектсу. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая ф-ция abort вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол максимум только 3 раза из 10 вызовов. Вылет НЕ происходит обычно, когда ф-ции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой ф-ции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и игра после критических ошибок продолжается как ни в чём ни бывало. А приводит это вот к чему... Внутри xr_logic в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту ф-цию (xr_logic.pstor_store(obj, varname, val)), явно и думать не думал что abort может не сработать. У него запись в пстор стояла после провеки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:

function pstor_store(obj, varname, val)
	local npc_id = obj:id()
	if db.storage[npc_id].pstor == nil then
		db.storage[npc_id].pstor = {}
	end
	local tv = type(val)
	if not pstor_is_registered_type(tv) then
		abort("xr_logic: pstor_store: not registered type '%s' encountered", tv) --- вот тут мы должны если что вылететь
	end
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути
end

Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - "битые" (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла.

Решение этой проблемы оказалось достаточно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них.

Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией abort в Чистом Небе, и если да, то останутся ли в Зове Припяти?


Необходимые для стабилизации игры правки в модулях

Эта правка предотвращает запись в пстор если не сработал аборт:

xr_logic.script

Было:

function pstor_store(obj, varname, val)
	local npc_id = obj:id()
	if db.storage[npc_id].pstor == nil then
		db.storage[npc_id].pstor = {}
	end
	local tv = type(val)
	if not pstor_is_registered_type(tv) then
		abort("xr_logic: pstor_store: not registered type '%s' encountered", tv)
	end
	db.storage[npc_id].pstor[varname] = val
end

Стало:

function pstor_store(obj, varname, val)
	if not obj then return end
	local npc_id = obj:id()
	if db.storage[npc_id].pstor == nil then
		db.storage[npc_id].pstor = {}
	end
	local tv = type(val)
	if not pstor_is_registered_type(tv) then
		dgblog("xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled")
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.
	else
		db.storage[npc_id].pstor[varname] = val
		-- вот так и только так. Если значение не валидно, ничего не происходит.
	end	
end

А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал

Было:

function pstor_load_all(obj, reader)
	local npc_id = obj:id()
	local pstor = db.storage[npc_id].pstor
	if not pstor then
		pstor = {}
		db.storage[npc_id].pstor = pstor
	end
	local ctr = reader:r_u32()
	for i = 1, ctr do
		local varname = reader:r_stringZ()
		local tn = reader:r_u8()
		if tn == pstor_number then
			pstor[varname] = reader:r_float()
		elseif tn == pstor_string then
			pstor[varname] = reader:r_stringZ()
		elseif tn == pstor_boolean then
			pstor[varname] = reader:r_bool()
		else
			abort("xr_logic: pstor_load_all: not registered type N %d encountered", tn)
		end
		printf("_bp: pstor_load_all: loaded [%s]='%s'", varname, utils.to_str(pstor[varname]))
	end
end

Стало:

function pstor_load_all(obj, reader)
	local npc_id = obj:id()
	local pstor = db.storage[npc_id].pstor
	if not pstor then
		pstor = {}
		db.storage[npc_id].pstor = pstor
	end
	local ctr = reader:r_u32()
	if tonumber(ctr) > 20 and tostring(obj:name()) ~= "single_player" and npc_id ~= db.actor:id() then
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет
		dgblog("ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: "..tostring(obj:name())..
" БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ")
		ctr = 20 
	end
	for i = 1, ctr do
		local varname = reader:r_stringZ()
		local tn = reader:r_u8()
		if tn == pstor_number then
			pstor[varname] = reader:r_float()
		elseif tn == pstor_string then
			pstor[varname] = reader:r_stringZ()
		elseif tn == pstor_boolean then
			pstor[varname] = reader:r_bool()
		else
			-- не надо пытаться вылетать - просто не пишем поврежденные данные
			-- при этом обязательно удалять саму переменную - в результате записи
 			-- мусора в пстор одно только ее название может повесить загрузку
			pstor[varname] = nil
		end
	end
end

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

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

Я это сделал вот так:

_g.script

-- Крешнуть игру (после вывода сообщения об ошибке в лог)
function abort(fmt, msg)
	local message = tostring(msg)
	dbglog("ERROR PATTERN: "..tostring(fmt))
	dbglog("ERROR REASON: "..message)
	local reason = string.format(fmt, message)
	assert("ERROR: " .. reason)
	printf("ERROR: " .. reason)
	dbglog("%s", reason)
	printf("%s")
end

Лечение зависаний алайфа при смерти персонажей

Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:

smart_terrain:1143 "attempt to index a nil value"

Происходящий в функции smart_terrain.on_death( obj_id ) - я тогда его заблокировал вызовом его внутри безопасного кода функцией pcall, однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:

function on_death( obj_id )
--	printf( "on_death obj_id=%d", obj_id )

	local sim = alife()

	if sim then
		local obj     = sim:object( obj_id )
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут

		if strn_id ~= 65535 then
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.
		end
	end
end

Во-первых выяснилось, что изредка вызов obj:smart_terrain_id() вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами m_net_utils Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:

function on_death( obj_id )
--	printf( "on_death obj_id=%d", obj_id )
	local sim = alife()
	if sim then
		local obj = sim:object( obj_id )
		if (obj and obj.smart_terrain_id) then
			local strn_id = 65535  -- значение по умолчанию
			
			local t = nil -- сюда запихнём табличку из пакета
			if IsStalker(obj) then t = m_net_utils.get_stalker_data(obj) elseif IsMonster(obj) then t = m_net_utils.get_monster_data(obj) end 
			-- вызываем парсинг пакета для неписей и монстров отдельно
			
			-- print_table_inlog(t)
			if t.smtrid then
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535
			end
			
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...
				local gulag = sim:object(strn_id)
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он
					sim:object(strn_id).gulag:clear_dead(obj_id)
				end
			end
		end
	end
end

При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:

Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]

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

Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки

sim:object(strn_id).gulag:clear_dead(obj_id)

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

-- освободить объект от работы и переинициализировать логику.
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения
-- как будто он только что загрузился
function gulag:free_obj_and_reinit( obj_id )
	self:free_obj(obj_id)

	local t = self.Object[obj_id]
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф
	end
end

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

-- освободить объект от работы и переинициализировать логику.
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения
-- как будто он только что загрузился
function gulag:free_obj_and_reinit( obj_id )
	self:free_obj(obj_id)
	local t = self.Object[obj_id]
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику
			end
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )
		end
	end
end

-- Проверка, запущена ли игра
function check_game()
	if level.present() and (db.actor ~= nil) and db.actor:alive() then
		return true
	end
	return false
end

После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.

Другие частые проблемы

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

Вылеты при удалении объектов из игры

При использовании для удаления объектов родной движковой функции alife():release(obj:id(), true) возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:

1) Вылет при удалении непися или монстра.

  Решение: с помощью alife():release можно удалять только мёртвые объекты. Поэтому если вам нужно удалить с её помощью непися или монстра, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.

2) Вылет при удалении оружия или артефакта.

  Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:
	local obj = alife():object(i)
	if obj then
		alife():release(obj, true)
	end

Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.

3) Вылет при удалении аномалии.

  Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией disable_anomaly, и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.

Вылет при открытии закладки "Контакты" в ПДА

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

Вылеты при вызове несуществующих функций из XML

В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:

	<info_portion id="barman_document_have">
		<action>dialogs.set_actor_prebandit1</action>
		<action>bar_spawn.bandits2</action>
		<action>bar_spawn.bandits3</action>
		<action>bar_spawn.bandit7</action>
		<action>bar_spawn.bandit8</action>
	</info_portion>

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

--KamikaZze (OGSE Team) 11:04, 3 сентября 2009 (UTC)

Авторы

Статья создана: Kamikazze

Личные инструменты