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

Народная 2010 разработка


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

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

Тема НЕ является ни столом заказов, ни службой техподдержки, ни справочным бюро.

Изменено пользователем Dennis_Chikin
  • Нравится 2
  • Полезно 1
Ссылка на комментарий

Kolmogor,

  Цитата
Странно, что никто не вспомнил, что в АМК давным давно уже есть детектор зависания биндера актора :)

Метода у меня появилась как побочный результат моих изысканий. Я выяснил природу функций level.set_call и захотелось применить на практике. Интересно, кстати, что природа этих функций та же, что и fastcall-ов. И, как мне кажется, они ставят колбек на шаг решателя физики. Это вероятно для более точного управления физическими объектами, но можно использовать для организации независимых от апдейта актора периодических событий. Вот здесь на этом сделан сторожевой таймер, что на мой взгляд удобнее, чем делать это на апдейтах монстров.

  Полезный утиль (Показать)
Ссылка на комментарий

Чую недоброе :russian_ru:

А всё - из-за времени и его старых и кое-где до сих пор существующих проблем с переполнением целого 32-разрядного.

Например, заглянул в скрипт level_weathers.script новой версии погоды Beautiful Weather и увидел там использование неподходящей функции времени и тут же - сохранение переменных с значением разного времени нужного для скрипта. А сохранение происходит с помощью функции w_u32, которая сохраняет 32-разрядные значения. Также и в скрипте xr_gulag.script, в котором я было изменил получение времени на 64-разрядный результат, но не заметил, что переменные со значением времени сохраняются с помощью w_u32. Тоесть когда значение станет большим за максимально возможное для 32-разрядного целого, то сохранится результат переполнения, а не нужное нам число. Выходит, что нужно всюду ещё и изменить функции сохранения и чтения переменных с временем на их 64-разрядные варианты...

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

P.S. После изменения функций сохранения/загрузки 32-разрядных чисел на сохраняющие 64-разрядные понадобится новая игра.

 

Shadowman, есть соответсвующие функции:

    number r_u64();
    void w_u64(unsigned __int64);
    number r_s64();
    void w_s64(__int64);

 

Shadowman, да - переписать и требовать новой игры. Или сделать два варианта скрипта:

1. только с 64-разрядной записью;

2. с 64-разрядной записью и чтением.

 

1-й положить в геймдату, загрузить сохранение, сохранится.

2-й положить в геймдату, загрузить сохранение сделанное с 1-м и играть дальше.

И так нужно сделать для каждого такого срипта.

Изменено пользователем sapsan
Ссылка на комментарий

sapsan, а как сохранить 64-разрядный результат ?

 

Ясно. А переконвертить 32-битные таймеры в 64-битные разве нельзя?

 

Применительно к погоде, эти две ф-ции

function WeatherManager:load(F)
    self.update_level         = F:r_stringZ();
    self.update_time         = F:r_u32();
end

function WeatherManager:save(F)
    F:w_stringZ                (self.update_level);
    F:w_u32                    (self.update_time);
end

 

переписываем в такой вид:

function WeatherManager:load(F)
    self.update_level         = F:r_stringZ();
    self.update_time         = F:r_u64();
end

function WeatherManager:save(F)
    F:w_stringZ                (self.update_level);
    F:w_u64                    (self.update_time);
end

Или для конвертации сейва, вначале запись, а потом и чтение, как ты написал здесь ?

ЗЫ: ";" в конце строк - это ведь в люа необязательно вроде?

 

========== перенес из "багов и вылетов" ===========

 

[spoiler=продолжение по поводу этого способа решения зависа:]

  Цитата
У меня получилось убрать вылет/завис даже без "убийства респавна". Но не имею большого количества зависающих сейвов для проверки.

Если прокатит - можно просто милли-патчик сделать.

Для проверки нужен сейв перед зависом (вылетом).

 

"Пробуем побороть завис"

 

0. Делаем копию ...S.T.A.L.K.E.R\gamedata\scripts\se_respawn.script для возможности восстановления.

 

1. В файле se_respawn.script в function se_respawn:create(prob) ищем строку

 

amk.on_REspawn(obj,self)

 

2. Перед этой строкой добавляем строку (строка будет выводить имена spawner->object в консоль красным, не смущайтесь, и в лог)

 

get_console():execute("load ~ Spawn now ["..tostring(self:name()).."] -> ["..obj:name().."]")

 

3. Больше ничего править не нужно - только добавить строку. Никакие проверки не нужны.

Сохраняем сделанные изменения, пробуем с сейва ДО зависа/вылета.

 

WhatAbout, хочешь сказать, что вывод отладочной строки в лог предотвращает вылет? :russian_ru:

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

 

WhatAbout,

  get_console():execute(...) (Показать)
Изменено пользователем Shadowman

Железо: Intel Core i5 9400F / 16Gb DDR4 2400MHz / SSD NVMe M.2 Samsung 970 EVO Plus 256Gb / GF GTX 1050Ti 4Gb Ось: Win10x64

Ссылка на комментарий

Shadowman,

 

Ну, и я перенес.

 

Повторюсь.

Сам удивлен :wacko2:

Но пока подтверждается на нескольких версиях Соли, даже прошлогодней...

Не очень силен в луа, поэтому объяснить достоверно не могу.

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

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

Что там "внутре" при вызове метода execute - чесно, не разбирался. Но что-то же дает такой эффект.

Может, иннициализация какая внутренняя, или проверка корректности чего-то с подрихтовочкой.

Был бы признателен, если бы кто разобрался до конца.

А пока - по правилу "работает-не трогай".

 

P.S. Добавлю. Характерно, что использование вместо прямого вызова

get_console():execute("load ~ Spawn now ["..tostring(self:name()).."] -> ["..obj:name().."]")

функции abort

abort("Spawn now ["..tostring(self:name()).."] -> ["..obj:name().."]")

не спасает - вылет в smart_terrain.script по attempt to index a function value.

Хотя что там у нас в abort? Ну, почти то же, если не считать локальную переменную. :wacko2:

Как тут быть с "предположением про простую задержку"?

 

Кстати, параметры в функцию по адресу передаются или по значению?

 

dimos,

Я это и имел ввиду туманно написав "или проверка корректности чего-то с подрихтовочкой" :rolleyes:

 

dimos,

Как проверить сработал ли респавн до конца? Существование объекта проверять? Не имел, к сожалению, достаточно времени, чтобы углубиться.

se_respawn.script: вызов amk.on_REspawn(obj,self) - amk.script: вызов mod_call("respawned",obj,respawner) - amk_mod.script local sini = respawner:spawn_ini() - строка, на которой и получался завис - отрабатывает.

Больше пока не проверял.

Изменено пользователем WhatAbout
  Моё (Показать)
Ссылка на комментарий

Вешается meceniy_art.art_respawn()

 

Симптоматика: строго после 22 часов; внезапно, либо после перехода между локациями - сразу, при загрузке сохранений - сразу - вешается актор. Поставленный watchdog показывает, что "ушел и не вернулся" именно этот вызов.

 

BTW, в оригинале из OGSM спавн производился только после выброса, только на текущем уровне и только 4-х артов с небольшими задержками между каждым.

 

 

Workaround: перенес все содержимое meceniy_utils.on_actor_update_callback() непосредственно в биндер актора на 10 секундный апдейт, для спавна "черной энергии" прикрутил оригинальный ogsm_surge.script

 

Тестирую.

Из положительных эффектов - ЧЭ перестала спавниться "кучками".

 

Shadowman,

У меня больше НЕТ этого конкретного зависа. Остались вылеты по стеку при завершении игры из Лабиринта и при проходе бандюков через ж/д на Кордоне на границе радиуса а-лайфа.

 

Необходимость переделки не отменяет то, что подобный спавн надо переносить в куда-нибудь в новости.

 

В качестве средства диагностики зависа - сообщение в ньюсы перед началом и после отработки. Если видим первое, но нет второго - висим именно здесь. Себе такое поставил, видно абсолютно четко.

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

 

Со снятием координат есть слабооформленная мысль о том, как без этого обойтись. Когда пойму спавн/отключение аномалий - попробую. Или когда разберусь с путешествиями монстров. Перевесить спавн артефактов прямо на них.

Изменено пользователем Dennis_Chikin
Ссылка на комментарий

Dennis_Chikin, этот спавн в meceniy_art нужно вообще переделать, потому что там несколько скользких моментов. Во-первых, там рандомное вычисление вертекстов зачем-то, да еще и без последующих проверок на валидность (в ogsm_surge - аналогично, кстати). Нужен просто набор выверенных координат и рандомный выбор между ними перед спавном. Сейчас - там для нескольких лок по одному левел/гейм вертексу.

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

 

ЗЫ: в ogsm_surge принципиально ничего не отличается - те же кривые моменты. И главное - проверки существования вертексов, полученных путём рандомайзеера - нет.

 

dimos, да в том-то и дело что этой строкой выводится лишь отладка в лог - прочти, что я написал в предыдущем посте.

 

----

WhatAbout, не думаю. Возможен только вылет в случае, если в строку попытаемся запихать переменную, которая нил :)

 

7.9, есть такой момент: при сохранении может, какая-то из функций, запускаемых при этом и приводит в чувство игру.

Может, даже простое высвобождение (или заполнение) памяти даёт такое эффект - кто ж его знает, как там внутри движка всё это варится :)

Изменено пользователем Shadowman

Железо: Intel Core i5 9400F / 16Gb DDR4 2400MHz / SSD NVMe M.2 Samsung 970 EVO Plus 256Gb / GF GTX 1050Ti 4Gb Ось: Win10x64

Ссылка на комментарий

Shadowman,

 

Возможно ли такое, что в момент подготовки параметров для вызова get_console():execute каким либо образом валидируются некие данные, относящиеся к объекту-респавнеру. Ну, навроде добавления отсутствующего символа конца строки или иннициализации неопределенного явно свойства, etc.

Простите мой французский...

 

Shadowman,

  Цитата
WhatAbout, не думаю. Возможен только вылет в случае, если в строку попытаемся запихать переменную, которая нил

Тогда мне не совсем понятно такое:

1)

get_console():execute("load ~ Spawn now ["..self:name().."]") -- Ok

2)

local some = tostring(self:name())

get_console():execute("load ~ Spawn now ["..some.."]") -- висим

 

К слову,

3)

get_console():execute("load ~ Spawn now ["..level:name().."]") -- висим

зато!

4)

get_console():execute("", self:name()) -- Ok

 

dimos,

Насколько я смог проверить респавн происходит. Статистики, правда, не набрал. Проверял и по id и по name.

Изменено пользователем WhatAbout
  Моё (Показать)
Ссылка на комментарий

Ещё одна оптимизация.

[spoiler=Код в _g.script]

IAmAStalker = {}
function IAmAStalkerInit()
    IAmAStalker = {
        [clsid.actor] = true,
        [clsid.script_stalker] = true
    }
end
IAmAMonster = {}
function IAmAMonsterInit()
    IAmAMonster = {
       [clsid.boar_s] = true,
       [clsid.bloodsucker_s] = true,
       [clsid.dog_s] = true,
       [clsid.flesh_s] = true,
       [clsid.pseudodog_s] = true,
       [clsid.psy_dog_s] = true,
       [clsid.burer_s] = true,
       [clsid.cat_s] = true,
       [clsid.chimera_s] = true,
       [clsid.controller_s] = true,
       [clsid.fracture_s] = true,
       [clsid.poltergeist_s] = true,
       [clsid.gigant_s] = true,
       [clsid.zombie_s] = true,
       [clsid.tushkano_s] = true,
       [clsid.snork_s] = true
    }
end

[spoiler=Код в amk.script]

-- Эта функция вызывается самой первой. Онлайновые объекты недоступны! db.actor недоступен!
function on_game_start()
    mod_call("on_game_start")
    ver = get_ver()
    getStartTime()
    IAmAStalkerInit()
    IAmAMonsterInit()
end

  Тестирующий код (Показать)
Изменено пользователем sapsan
Ссылка на комментарий

WhatAbout, к вопросу про респавн и внутренних корректоров: увидел недавно в логе такую штуку:

  штука (Показать)
Изменено пользователем sapsan
Цензура ограничивает творчество © by me
Ссылка на комментарий

[spoiler=Вопрос к знатокам_g.script есть функция

--// Является ли оbj монстром
function is_object_monster(obj)
  local otype = get_clsid(obj)
  if(otype == clsid.crow      or
    otype == clsid.zombie   or
    otype == clsid.flesh    or
    otype == clsid.controller or
    otype == clsid.bloodsucker  or
    otype == clsid.burer    or
    otype == clsid.fracture    or
    otype == clsid.chimera    or
    otype == clsid.boar     or
    otype == clsid.dog_red    or
    otype == clsid.dog_black  or
    otype == clsid.poltergeist  or
    otype == clsid.pseudo_gigant  )
  then
    return true
  end

  return false
end

, которая используется лишь один раз в скрипте gulag_military.script в функции

-------------------------------------------------------------------------------------------
-- Проверка, имеется ли у гулага враг
-------------------------------------------------------------------------------------------
function check_enemy (gulag)
    for k,v in pairs(gulag.Object) do
        if v ~= true then
           if v:best_enemy () ~= nil and v:story_id () ~= 710 and is_object_monster (v:best_enemy ()) == false then 
              return true
           end
        end
    end       
    return false
end

и больше нигде.

В то же время в _g.script есть функция

function IsMonster (object, class_id)
    local id = class_id or get_clsid (object)
    local monsters = {
        [clsid.boar_s] = true,
        [clsid.bloodsucker_s] = true,
        [clsid.dog_s] = true,
        [clsid.flesh_s] = true,
        [clsid.pseudodog_s] = true,
        [clsid.psy_dog_s] = true,
        [clsid.burer_s] = true,
        [clsid.cat_s] = true,
        [clsid.chimera_s] = true,
        [clsid.controller_s] = true,
        [clsid.fracture_s] = true,
        [clsid.poltergeist_s] = true,
        [clsid.gigant_s] = true,
        [clsid.zombie_s] = true,
        [clsid.tushkano_s] = true,
        [clsid.snork_s] = true
    }
    return monsters[id] or false
end

, которая используется во всём остальном коде и в которой идёт проверка по совершенно другим классам.

Так какие классы корректны ?

 

Ссылка на комментарий

sapsan, если кратко, то IsMonster рабочая, а is_object_monster всегда будет возвращать false

 

А если длинно, надо проверить в конфигах монстров параметр class, найти его в class_registrator.script, посмотреть соответствующий ему script_clsid, и использовать в скриптах его

Например, для кабана в конфиге:

  Цитата
class = SM_BOARW ; AI class

в class_registrator.script строка:

  Цитата
cs_register (object_factory, "CAI_Boar", "se_monster.se_monster", "SM_BOARW", "boar_s")

значит в скрипте используется clsid.boar_s

Ссылка на комментарий

Dennis_Chikin,

 

Я как-то проводил замеры и выяснил, что выгода в применении таблицы в этом случае начинается при количестве ветвлений где-то в районе 7-8 (точной цифры не помню). Если всего два варианта, как в твоём примере, то смысла использовать таблицу нет, посколку время выборки (с использованием хеш-таблицы, помните?) хоть и почти постоянное, но больше, чем просто ветвление по условию.

 

Но надо не попасть в ловушку. Используя цепочку if-else плюс незатейливый копипаст можно запросто написать что-то в этом роде:

 

if <вычисление длинного выражения> then
elseif <вычисление того-же длинного выражения> then
и т.д.

Ясно, что в этом случае условное выражение будет вычисляться в среднем "длина цепочки пополам" раз, что может эффективно убить всю производительность.

 

Добавил:

ожидаю 1 на пару сотен позиций, или штуки 4-6 до полусотни

Признаться, не понял, что имеется в виду.

 

Добавил 2:

 

Думал сначала, что понимаю о чём ты, но теперь запутался окончательно. Вот расстреляй меня, но не понимаю твой вопрос. Какой-то запутанный пример, и мысль про "одну большую таблицу", и что имеется в виду под "делить её"... Не въезжаю =(

Изменено пользователем malandrinus
  Полезный утиль (Показать)
Ссылка на комментарий

Dennis_Chikin,

  Цитата
Это - развитие упаковщика предметов в инвентаре. Я попробовал с боеприпасами - мне понравилось. Лаги от "патронных" нычек исчезли. Хочу запихать туда остальное.

Опять меня запутал. Разве в упаковщике патронов лишние пачки не удаляются? Но ведь с остальными предметами так не выйдет. И при чём здесь таблицы?

 

sapsan,

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

 

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

 

Ну и естественно, доступ по ключу-строке на порядок медленнее.

 

Медленнее только для случая, если точно знаешь индекс. Если же не знаешь - нужно искать перебором, что будет медленнее доступа по индексу-строке (если перед этим таблица не была отсортирована и не использовать бинарный поиск, хотя, при большой таблице, даже в этом случае хеш-доступ может выиграть за счет одноразового расчета местоположения данных по ключу) sapsan

Изменено пользователем sapsan
  Полезный утиль (Показать)
Ссылка на комментарий

Бывает такой вылет (и не только в Солянке):

  вылет (Показать)
Ссылка на комментарий

V92,

Позволю себе вставить пять копеек.

Уже насколько камрадов попадают на вылет Not enough IDs после установки последнего патча (от 27.06).

При этом на Соли с предыдущим патчем сейвы, приводящие к вылету (а у некоторых зависанию), работают.

Количество объектов в этих сейвах до вылета не очень большое ~30 тыс.

Т.е. в какой-то момент имеет место старт создания огромного количества объектов.

 

sapsan,

Есть подозрение на нечто se_respawn.script, он ведь правился в последнем патче. Один из пострадавших от Not enough IDs "неспециально" вернул сей скрипт из пред.патча, пока играет.

 

Изменено пользователем WhatAbout
  Моё (Показать)
Ссылка на комментарий
  sapsan писал(а):
В скрипте se_respawn.script строку 351
table.insert(self.spawned_obj ,obj.id)

заменить на

self.spawned_obj[#self.spawned_obj+1] = obj.id

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

Зацикливается походу на этом коде

388      -- экстренный спаун минимального количества объектов
389      if table.getn(self.spawned_obj) < self.min_count then 
390        while table.getn(self.spawned_obj) < self.min_count do
391          --sak.dbglog("RESPAWN: [%s] very small object", tostring(self:name()))
392          if self:create(100) == false then
393            return
394          end
395        end
396        return
397      end

Спасибо за подсказку. Я об этом догадывался. И думаю, что причина в нестыковке table.getn с новым способом вставки в таблицу. Сейчас заменю table.getn на # и проверю. Эта мысль пришла вот только что. sapsan

Изменено пользователем sapsan
Ссылка на комментарий

sapsan

 

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

 

  Цитата
Если вы строите цикл по таблице следующим образом:

for i=1, table.getn(table_name) do

 

То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:

table_name[i] = nil

 

или

table.remove(table_name, index)
table.insert(table_name, index)

 

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

 

Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:

for k, v in pairs(table_name)

 

А удаление строк внутри них делайте вот так:

table_name[k] = nil

 

Циклы же вида for i=1, table.getn(table_name) do следует использовать только для операций, не изменяющих структуру изменяемой таблицы!

 

UPD:

 

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

 

388      -- экстренный спаун минимального количества объектов
389      if table.getn(self.spawned_obj) < self.min_count then 
390        while table.getn(self.spawned_obj) < self.min_count do
391          --sak.dbglog("RESPAWN: [%s] very small object", tostring(self:name()))
392          if self:create(100) == false then
393            return
394          end
395        end
396        return
397      end

 

Просто обязан исполняться бесконечно. Так как table.getn(self.spawned_obj) не меняет в данном цикле значения, self.min_count - тоже. Условие всегда выполняется, это добро будет вертеться до второго пришествия.

 

Это понятно. А где там попытка выйти за границы таблицы (в сообщении от Kolmogor указан именно проблемный кусок кода)?

Почему это table.getn не меняет значения ? Он расчитывается при каждой проверке, в отличии от случая с for sapsan

 

Ага, точно, меняет. Тады тот самый случай и есть

Изменено пользователем kamikazze

Отладчик и скриптер мода OGSE. Автор схемы "Компаньоны", стреляющего БТРа и многих других полезностей :wink:

Ссылка на комментарий

kamikazze,

Почему не меняется? Меняется - в таблицу добавляется элемент в функции se_respawn:create

По-моему нормальный цикл - собственно этот цикл еще с оригинальной ТЧ идет :)

Единственное изменение написано выше(заменен table.insert(t, el) на t[#t+1] = el)

 

Собственно со всем тобой сказанным согласен, но этот цикл из другой оперы - индекс не используется - в таблицу добавляются элементы и проверяется количество элементов в таблице

 

Ну и кстати, я по-моему понял :) в сталкер старый луа, где getn сделан через внутреннюю переменную n у таблицы.

table.insert ее меняет, а t[#t+1] = el не меняет

 

sapsan, если интересно, добавь вывод в лог еще self.spawned_obj.n

и верни назад все table.insert :)

 

А если не вернуть, а заменить table.getn() на #. Этого будет не достаточно ? sapsan

По идее на table.remove еще можно нарваться - она по той же идее смотрит на переменную n и должна считать таблицу пустой :)

То есть table.remove(t, pos) надо заменять на t[pos] = nil

 

Мне просто кажется:

table.insert, table.remove, table.getn и таблицы-массивы отдельно(здесь именно такая)

и таблицы-словари с ключами отдельно

 

Хотя может и наоборот уйти ото всех функций table.*

Изменено пользователем Kolmogor
Ссылка на комментарий
Гость
Эта тема закрыта для публикации сообщений.
  • Недавно просматривали   0 пользователей

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