<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="http://stalkerin.gameru.net/wiki/skins/common/feed.css?303"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://stalkerin.gameru.net/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=95.78.90.197&amp;*</id>
		<title>S.T.A.L.K.E.R. Inside Wiki - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://stalkerin.gameru.net/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=95.78.90.197&amp;*"/>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/95.78.90.197"/>
		<updated>2026-04-30T03:07:52Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.22.6</generator>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2011-01-12T19:53:12Z</updated>
		
		<summary type="html">&lt;p&gt;95.78.90.197: /* Вылеты при удалении объектов из игры */ фикс растяжки&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызванной функции, не передаётся наружу - вызывавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применяем полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно игнорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинале игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тексту. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая функция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда функции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой функции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту функцию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после проверки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По поводу частичной неработоспособности ф-ции '''abort''' я беседовал с Колмогором, и он пришёл к выводу, что видимо вылет игры должен был бы производиться при обработке функции '''printf(&amp;quot;%s&amp;quot;)''' - ей тут передаётся заведомо отсутствующий оператор и она по уму должна бы сразу крашить игру. Однако в релизе функция printf фактически не работает(она реализована в _g.script через вырезанную функцию log). В результате игра не крашится. Но что поделать, в итоге мне пришлось модифицировать его таким образом, чтобы вылет при его срабатывании был гарантирован:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
	local crash&lt;br /&gt;
	local ooops = 1/crash&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вылет происходит при попытке произвести арифметическую операцию с неинициализированной переменной crash.&lt;br /&gt;
&lt;br /&gt;
== Лечение зависаний алайфа при смерти персонажей ==&lt;br /&gt;
&lt;br /&gt;
Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Происходящий в функции '''smart_terrain.on_death( obj_id )''' - я тогда его заблокировал вызовом его внутри безопасного кода функцией '''pcall''', однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во-первых выяснилось, что изредка вызов '''obj:smart_terrain_id()''' вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами '''m_net_utils''' Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if (obj and obj.smart_terrain_id) then&lt;br /&gt;
			local strn_id = 65535  -- значение по умолчанию&lt;br /&gt;
			&lt;br /&gt;
			local t = nil -- сюда запихнём табличку из пакета&lt;br /&gt;
			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 &lt;br /&gt;
			-- вызываем парсинг пакета для неписей и монстров отдельно&lt;br /&gt;
			&lt;br /&gt;
			-- print_table_inlog(t)&lt;br /&gt;
			if t.smtrid then&lt;br /&gt;
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...&lt;br /&gt;
				local gulag = sim:object(strn_id)&lt;br /&gt;
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он&lt;br /&gt;
					sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. '''Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Вернёшь строки на место, перестроишь текст - работает снова. Совершенная чертовщина, шаманил я над этой функцией около получаса, перестраивая текст таким образом чтобы игра нормально запускалась.''' Имейте это в виду когда в неё полезете и если что не пугайтесь если игра начнёт так вылетать - в этом случае пошаманьте немного над ней, меняя её форматирование. О причинах такого поведения я не могу даже догадываться - то ли движок её вызывает по смещению внутри файла вручную, то ли это какой-то баг Lua-парсера, но факт фактом.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;sim:object(strn_id).gulag:clear_dead(obj_id)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И мне пришлость распутывать её по частям, доискиваясь до причины. Обработка честно говоря мерзкая, размазана по smart_terrain и xr_gulag, при этом взаимные вызовы идут не менее десятка раз, в следующем стиле: ф-ция в смарте вызывает ф-цию в гулаге, которая вызывает фцию в смарте, которая обрабатывает параметр фцией в гулаге, который передётся ф-цией в логике, которая получает её из смарта. Нечто подобное. Опуская все нецензурные выражения, употребленные мной при трассировке, я лучше расскажу что собственно вышло в итоге. А в итоге я вышел вот на этот код в xr_gulag, именно в нём при отрегистрации некоторых мёртвых неписей происходит зависание алайфа:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я, честно говоря, ни разу не понимаю, на кой чёрт нужно переконфигурировать схемы логики и выбирать из них активную '''трупу, который лежит себе спокойно и никого не трогает.''' Может быть в это есть некий высший смысл, или это было продиктовано неким аккуратизмом, однако одно я могу сказать однозначно - подобная переинициализация у некоторых трупов неписей приводит к глухому зависанию алайфа. При этом если для трупов эту обработку заблокировать, то трупы разрегистрируются вполне нормально и спокойно лежат с нетронутой логикой, не вызывая никаких проблем. Итоговый код после правок выглядит вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.&lt;br /&gt;
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект&lt;br /&gt;
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр&lt;br /&gt;
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику&lt;br /&gt;
			end&lt;br /&gt;
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.&lt;br /&gt;
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Проверка, запущена ли игра&lt;br /&gt;
function check_game()&lt;br /&gt;
	if level.present() and (db.actor ~= nil) and db.actor:alive() then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(alife():object(id), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра, находящегося в онлайне.&lt;br /&gt;
   Решение:&lt;br /&gt;
&lt;br /&gt;
с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, который жив и находится в онлайне, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение:&lt;br /&gt;
&lt;br /&gt;
такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение:&lt;br /&gt;
&lt;br /&gt;
аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Скрипты]]&lt;/div&gt;</summary>
		<author><name>95.78.90.197</name></author>	</entry>

	</feed>