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

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

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

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

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

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

Kolmogor,

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

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

 

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

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

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

 

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

Чую недоброе :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("load ~ Spawn now "... означает, что ты пытаешься загрузить игру с именем, являющимся всем тем, что идет после "load". Поскольку такой сохраненной игры, ясное дело, - нет, в консоль и выдаётся "Cannot load saved game" а дальше - все то, что мы написали в этой отладке. Почему именно load ? Потому что в ругательство тогда выдаётся в точности та строка, которая была задана в качестве имени сохранения :)

Я думаю, что тут еще дело в том, что наша конструкция "<переменная>..<переменная>" собирается последовательно, во столько шагов, сколько есть присоединений "..". Если же просто написать стринг, полученная задержка будет меньше.

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

Например, level:name(), db.actor:id() или еще что-нибудь отвлеченное и точно существующее (game.time(), level.get_game_difficulty()).

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

 

 

Изменено пользователем 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

Компьютер: Intel CoreDuo E8200 2,66GHz, GeForge 9800 GT 1024 mb, 4 Gb ОЗУ, Windows 7 64 bit.

Солянка: Народная Солянка от 19.04.2010, 14.08.10 + 3.09.10 + широкий монитор + 18.11.10

 

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

Вешается 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

Компьютер: Intel CoreDuo E8200 2,66GHz, GeForge 9800 GT 1024 mb, 4 Gb ОЗУ, Windows 7 64 bit.

Солянка: Народная Солянка от 19.04.2010, 14.08.10 + 3.09.10 + широкий монитор + 18.11.10

 

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

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

[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

    local g, se_obj
    local sim = alife()
    local t = profile_timer()
    t:start()
    for i = 1, 65535 do
        se_obj = sim:object(i)
        if se_obj then
            g = IsStalker(se_obj)
        end
    end
    t:stop()
    get_console():execute("load ~~~ TIME SPEND g = IsStalker(se_obj): "..t:time())
    t = profile_timer()
    t:start()
    for i = 1, 65535 do
        se_obj = sim:object(i)
        if se_obj then
            g = IAmAStalker[se_obj:clsid()]
        end
    end
    t:stop()
    get_console():execute("load ~~~ TIME SPEND g = IAmAStalker[se_obj:clsid()]: "..t:time())
    se_obj = sim:object(0)
    if se_obj then
        t = profile_timer()
        t:start()
        for i = 1, 65535 do
            g = IsStalker(se_obj)
        end
        t:stop()
        get_console():execute("load ~~~ TIME SPEND only g = IsStalker(se_obj): "..t:time())
        t = profile_timer()
        t:start()
        for i = 1, 65535 do
            g = IAmAStalker[se_obj:clsid()]
        end
        t:stop()
        get_console():execute("load ~~~ TIME SPEND only g = IAmAStalker[se_obj:clsid()]: "..t:time())
    end

! Cannot find saved game ~~~ time spend g = isstalker(se_obj): 130028.640625
! Cannot find saved game ~~~ time spend g = iamastalker[se_obj:clsid()]: 106638.2890625
! Cannot find saved game ~~~ time spend only g = isstalker(se_obj): 38834.3515625
! Cannot find saved game ~~~ time spend only g = iamastalker[se_obj:clsid()]: 23809.611328125

 

В чистом виде имеем ускорение в 1,63 раза.

P.S. Но удалять оригинальные функции IsStalker() и IsMonster() нельзя - они используются в оригинальных нераспакованных скриптах.

 

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

[spoiler=Код в _g.script]

iniLines = {}

function getIniValueFloat(sect, line, default)
    if not iniLines[sect] or not iniLines[sect][line] then
        if not iniLines[sect] then
            iniLines[sect] = {}
        end
        local ini = system_ini()
        if ini and ini:section_exist(sect) and ini:line_exist(sect, line) then
            iniLines[sect][line] = ini:r_float(sect, line)
        else
            iniLines[sect][line] = default
        end
    end
    return iniLines[sect][line]
end

    local sect = "alife"
    local line = "time_factor"
    local g
    t = profile_timer()
    t:start()
    for i = 1, 65535 do
        local ini = system_ini()
        if ini and ini:section_exist(sect) and ini:line_exist(sect, line) then
            g = ini:r_float(sect, line)
        end
    end
    t:stop()
    get_console():execute("load ~~~ TIME SPEND ini:r_float(sect, line): "..t:time())
    t = profile_timer()
    t:start()
    for i = 1, 65535 do
        g = getIniValueFloat(sect, line, 0)
    end
    t:stop()
    get_console():execute("load ~~~ TIME SPEND getIniValueFloat(sect, line, 0): "..t:time())

! Cannot find saved game ~~~ time spend ini:r_float(sect, line): 158315.3125
! Cannot find saved game ~~~ time spend getinivaluefloat(sect, line, 0): 9088.375

Новый способ быстрее в 17.4 раза! Это, конечно, лабораторный вариант. В реальности при первом обращении к данным будет происходить точно такое же чтение из конфига и внесение их в таблицу, но при наличии данных в таблице ускорение именно таким и будет. Это больше отразится на "сглаживании" игры (снятии заметных и не очень подтормаживаний), чем на реальных FPS. Хотя кто его знает как там всё написано...

P.S. При надобности создаются аналогичные функции для других типов данных (getIniValueString(), getIniValueBool(), getIniValueSInt32(), getIniValueUInt32() и т.д.).

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

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

FATAL ERROR

 

[error]Expression : fatal error

[error]Function : CScriptEngine::lua_error

[error]File : E:\stalker\patch_1_0004\xr_3da\xrGame\script_engine.cpp

[error]Line : 73

[error]Description : <no expression>

[error]Arguments : LUA error: e:\s.t.a.l.k.e.r\gamedata\scripts\watcher_act.script:112: bad argument #1 to 'find' (string expected, got nil)

 

 

stack trace:

 

Scheduler tried to update object esc_stalker_respawn_145328

 

FATAL ERROR

 

[error]Expression : fatal error

[error]Function : CScriptEngine::lua_error

[error]File : E:\stalker\patch_1_0004\xr_3da\xrGame\script_engine.cpp

[error]Line : 73

[error]Description : <no expression>

[error]Arguments : LUA error: e:\s.t.a.l.k.e.r\gamedata\scripts\watcher_act.script:112: bad argument #1 to 'find' (string expected, got nil)

 

 

stack trace:

 

Scheduler tried to update object gar_stalker_respawn_145322

 

 

Самое интересное, что вылета не было и игра пошла спокойненько дальше, чего-то планировщик там проапдейтил... Кто-нибудь в курсе, что это за Sheduler и с чем его едят?

 

Dennis_Chikin, скажи хоть как звать этого нехорошего товарища, народ жалуется на этот вылет и с радостью отправит его к праотцам. А потом уже может и поправят ему мозги.

dimos, ошибка по watcher_act.script:112 исправлена и войдёт в следующий патч. sapsan

Изменено пользователем 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
 

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

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

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

 

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

Dennis_Chikin,

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

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

 

sapsan,

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

 

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

 

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

 

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

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

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

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

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

 

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

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

ловлю на его сейве такой вылет:
Expression    : fatal error
Function      : CScriptEngine::lua_error
File          : E:\stalker\sources\trunk\xr_3da\xrGame\script_engine.cpp
Line          : 73
Description   : <no expression>
Arguments     : LUA error: j:\s.t.a.l.k.e.r\gamedata\scripts\rx_wmgr.script:596: attempt to get length of field '?' (a nil value)

строка:
self.weapons[typ][#self.weapons[typ]+1] = {sec = sec,id = id,prm = prm}

 

Текст ошибки отличается от "обычного" из-за того, что в этой строке была проведена оптимизация (была замена table.insert()), но суть остаётся та же - таблица self.weapons[typ] == nil

В другом моде по такому вылету советовали пройти проблемное место без нового оружия. Тоесть проблема в конфигах нового оружия.

Вот нашел описание типов:

параметр ef_weapon_type отвечает в первую очередь за то как NPC будет стрелять из оружия

5 - стрелять одиночными

6 - стрелять очередями

7 - прицелиться и выстрелить одиночным

8 - ОЧЕНЬ долго целиться и выстрелить (снайпер)

9 - стрельба из гранатомета

 

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

то есть там тоже надо менять значения на эквивалентные значениям в ef_weapon_type.

пояснение значений ef_main_weapon_type:

0 - пистолет

1 - дробовик

2 - автомат

3 - винтовка

4 - гранатомёт

 

Кстати кому интересно вот инфа об отличии классов оружия - что дает каждый.

 

WP_AK74 - автоматическое оружие с возможностью установить ПБС, прицел, гранатомет

WP_LR300 - автоматическое оружие с возможностью установить ПБС, прицел, невозможно установить гранатомет (да да подствольник ставить нельзя - а у самой винтовки LR300 стоит класс WP_AK74 - который как раз разрешает подствольник)

WP_PM, WP_HPSA, WP_USP45, WP_WALTH - пистолеты, чем отличаются - неизвестно

WP_SVD - винтовка, одиночный огонь, установка ПБС невозможна

WP_SVU - винтовка, одиночный огонь, ПБС интегрирован

WP_VAL - автоматический огонь, возможна установка прицела, ПБС интегрирован, невозможно установить гранатомет

WP_RG6 и WP_RPG7 - отличия неизвестны

WP_VINT - автоматический огонь

 

Я в этом не гуру, но вот что нашел странного:

1. винтовка Мосина по сути - снайперка и типы у неё правильные

ef_main_weapon_type         = 3
ef_weapon_type             = 8

, а класс

class                   = WP_SHOTG

2. у некоторых пистолетов есть тип стрельбы из оружия

ef_weapon_type                = 5

, но нет типа оружия ef_main_weapon_type (как я понимаю - в этом случае берётся значение по-умолчанию 0 ?)

3. болт и зомби (m_zombie_e) имеет тип

ef_weapon_type        = 1

как у всего холодного оружия

 

Есть, конечно, железный метод if-а - отрезать код предварительной проверкой, но в самом коде и так есть abort(), который на данный момент в Солянке не выбрасывает, а лишь в лог сообщение пишет,

        if rx_utils.item_is_fa(item) then
            local sec = item:section()
            local cnd = item:condition()
            if wm_modes.forbiddens[sec] ~= true and cnd >= wm_modes.min_cond and self:have_ammo(item) then
                local params = read_wpn_params(sec)
                local typ = params.typ
                if not self.weapons[typ] then
                    abort("weapon_manager: not registered weapon type '%s' in [%s]",typ,sec)
                end
                local prm = self:get_weapon_prior(item)
                self.weapons[typ][#self.weapons[typ]+1] = {sec = sec,id = id,prm = prm}
            end
        end

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

Кто-то знает где копать ? Сам смотрел конфиги оружия в геймдате Солянки соответсвенно классам

function item_is_fa(o,c)
    if not c then
        c = o and o:clsid()
    end
    local t = {
    [clsid.wpn_pm_s] = true,
    [clsid.wpn_walther_s] = true,
    [clsid.wpn_usp45_s] = true,
    [clsid.wpn_hpsa_s] = true,
    [clsid.wpn_bm16_s] = true,
    [clsid.wpn_shotgun_s] = true,
    [clsid.wpn_ak74_s] = true,
    [clsid.wpn_lr300_s] = true,
    [clsid.wpn_groza_s] = true,
    [clsid.wpn_val_s] = true,
    [clsid.wpn_vintorez_s] = true,
    [clsid.wpn_svu_s] = true,
    [clsid.wpn_svd_s] = true,
    [clsid.wpn_rg6_s] = true,
    [clsid.wpn_rpg7_s] = true,
    [clsid.wpn_knife_s] = true}
    if c and t[c] then
        return true
    end
    return false
end

, но левого типа так и не нашел.

Чуть не забыл - в этом моде используются такие типы: 1,5,6,7,8,9.

 

Добавлено через 105 мин.:

Нашел интересный геноцид при начале НИ:

    for a=1000,20000,1 do
        local obj=alife():object(a)
        if obj and ((string.find(obj:name(),"af_") and not string.find(obj:name(),"esc_af_")  and not string.find(obj:name(),"pri_af_" ) and not string.find(obj:name(),"af_dumm")) or string.find(obj:name(),"esc_wpn")) then
            alife():release(obj,true)
        end
    end

Что там за неугодные объекты были ? :)

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

V92,

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

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

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

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

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

 

sapsan,

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

 

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

Компьютер: Intel CoreDuo E8200 2,66GHz, GeForge 9800 GT 1024 mb, 4 Gb ОЗУ, Windows 7 64 bit.

Солянка: Народная Солянка от 19.04.2010, 14.08.10 + 3.09.10 + широкий монитор + 18.11.10

 

Ссылка на комментарий
В скрипте 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 пользователей

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

AMK-Team.ru

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