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

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

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

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

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

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

Просматривал тут код на предмет послепроверки object_by_id и нашел вот такой кусок кода:

        local enemy=false
        for a=0,65534 do
            local obj=level.object_by_id(a)
            if obj then
                if (( IsStalker(obj) and amk.get_npc_relation(obj,db.actor)=="enemy" ) or 
                    IsMonster(obj)) and obj:position():distance_to(db.actor:position())<40 
                    and obj:see(db.actor) then
                    enemy=true
                    break
                end
            end
        end

Он мне не понравился этим:

local obj=level.object_by_id(a)

Полез в документацию по Луа и нашел вот это:

Обработка каждого объявления local ведет к созданию новой локальной переменной. Рассмотрим следующий пример:

     a = {}
     local x = 20
     for i=1,10 do
       local y = 0
       a[i] = function () y=y+1; return x+y end
     end

Цикл создает 10 экземпляров функции, в которых используются различные переменные y и один и тот же x.

Как я понимаю, в результате до срабатывания убощика памяти (о нём ещё не читал :)) такой код наплодит в памяти 10 переменный y. В случае же кода из скриптов Солянки таких переменных obj будет 65535. :russian_ru:

 

P.S. Почитал о сборщике мусора в Луа. Он действительно запускается через промежутки времени или по достижению определённого объёма занимаемой памяти. Выходит, что циклы с такими объявлениями будут кратковременно засорять память.

А чтобы всё было красиво нужно сделать так:

        local enemy=false
        local obj
        for a=0,65534 do
            obj=level.object_by_id(a)
            if obj then
                if (( IsStalker(obj) and amk.get_npc_relation(obj,db.actor)=="enemy" ) or 
                    IsMonster(obj)) and obj:position():distance_to(db.actor:position())<40 
                    and obj:see(db.actor) then
                    enemy=true
                    break
                end
            end
        end

P.P.S. А такого в скриптах - валом...

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

Arhara, закончил с мутатором - решил добавить в таблицы рецептов секции новых аномалий, на будущее, так сказать, чтобы имена опознавались....

"Монолит" и "Жар" - с именем sakbuzz. Как различать?

Вобщем, Пузырь, Торнадо, Очаговый туман - определяются. Лифт не встречал (_no_gravity?). Жар и монолит - пока в пролете.

 

Теперь главный вопрос: как тебе это всё давать? У меня стоит правка скриптов, - похоже, уникальная :)

Так что лучше всего дай мне свои скрипты - я вставлю нужное. Там полностью новая варка артов Сапсана и мои наработки.

Затронуты несколько глобальных скриптов, добавлено несколько новых

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

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

Monnoroch, malandrinus, да, это всего лишь указатель и много места не занимает. Но самих переменных-указателей Луа насоздаёт и забудет столько, сколько раз эта переменная будет объявлятся. А уже по срабатыванию сборщика мусора освободит память, которую занимали эти все переменные. Это ведь явно описано в родной документации по Луа, выдержку из которой я приводил в предыдущем сообщении.

Механизм по каждому проходу:

- объявляется перменная Х (ей отводится память) и туда заносится информация;

- переменная Х используется в коде;

- возврат на начало цикла с забыванием предыдущей переменной (новая переменная Х указывает на новую область памяти);

...........

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

...........

- запуск сборщика мусора и освобождение помеченной памяти.

Вот ещё цитата из документации по Луа:

Lua осуществляет автоматичекое управление памятью. Это означает, что вам не нужно думать о выделении памяти при создании новых объектов и ее освобождении, когда объект становится ненужным. Lua время от времени автоматически запускает процедуру сборки мусора для удаления устаревших объектов (то есть объектов, которые более недоступны из Lua). Сборщик мусора обрабатывает все объекты Lua: таблицы, данные типа userdata, функции, потоки и строки.

 

В Lua реализован инкрементный сборщик по принципу пометить-очистить.

 

Ещё по теме оптимизации: раз, два, три.

P.S. Почитал это всё - сколько в скиптах ещё неоптимального кода... :blink:

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

sapsan,

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

 

Кроме того, для переменных типа userdata всё не так просто как для скажем таблиц или строк. Таблицы и строки создаются самим Lua, им же и удаляются (с помощью сборщика мусора). А userdata как выясняется бывают двух типов full userdata и light userdata. Разницу видно со стороны хост-программы. Со стороны скрипта заметить сложно.

full userdata - это такой объект, память для которого выделяет Lua, соответственно и освобождает он же с помощью сборщика мусора.

light userdata - это в чистом виде указатели на объекты, созданные вне Lua. Для Lua - это просто указатель, о природе которого он ничего не знает, и никакого удаления он естественно не делает. Как созданием так и удалением в этом случае занимается хост-программа.

Я попробовал выяснить и получилось, что вроде как все объекты userdata - это full userdata, поскольку они имеют метатаблицу и в ней поле __gc, что указывает на собираемость этого объекта сборщиком мусора. С другой стороны, это ничего не значит, поскольку для экспорта классов здесь используется технология Luabind, где походу для всех реальных объектов создаются дополнительно обёртки, и объекты userdata, которые мы наблюдаем - это не внутренние объекты, а эти Luabind-овые временные объекты. Они и впрямь собираются сборщиком мусора, но что происходит при этом с "настоящими" объектами не вполне ясно. Это в данном случае зависит от Luabind. Там есть соответствующая настройка при экспорте класса. Естественно, что именно включено при экспорте конкретного класса - не известно.

 

Остаётся полагаться на здравый смысл и известное поведение: vector и прочее в этом роде создаётся из Lua и собирается сборщиком мусора, когда не нужен. game_object и серверные объекты создаются вне Lua и соответственно их удаление явно (alife():release()) или неявно (выход в оффлайн) выполняется в движке. Не вполне понятно, как себя ведут объекты типа звуков и партиклов. К примеру particles_object. Вроде создаётся из скрипта наподобие того же vector. Но потом можно запустить его играть и забыть о нём. Вот не помню, надо ли хранить ссылку на объект в этом случае. Если не надо, то кто тогда займется его удалением? И когда?

 

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

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

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

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

 

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

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

Они то погибают, но место своё не освобождают и ждут своего часа Х, когда за ними придет страшный и ужасный сборщик, чтобы онести их к свободной памяти. Он это делает не сразу, а когда запустится, а запускается он когда сойдутся все условия:
Цикл работы сборщика мусора зависит от двух параметров: пауза сборки мусора и коэффициент шага сборки. Паузой определяется время между запусками циклов сборки. Большие значения этого параметра делают сборку мусора менее активной. Значения меньше 1 означают, что между запусками циклов сборки паузы нет. При значении 2 сборщик перед следующим запуском ждет удвоения объема использованой памяти. Коэффициент шага сборки управляет скоростью сборки в зависимости от интенсивности выделения памяти. Большие значения параметра ускоряют работу сборщика, но при этом увеличивается размер каждого шага. Значения меньше 1 делают сборщик медленным и могут привести к тому, что цикл сборки никогда не закончится. По умолчанию используется значение 2, в этом случае сборщик работает вдвое быстрее процесса выделения памяти.
По памяти - очень интересное замечание и не извесно какой именно коэфициент стоит в игре.

К тому же судя по этому:

Perhaps you have already read somewhere that, since version 5.0, Lua uses a register-based virtual machine. The “registers” of this virtual machine do not correspond to real registers in the CPU, because this correspondence would be not portable and quite limited in the number of registers available. Instead, Lua uses a stack (implemented as an array plus some indices) to accommodate its registers. Each active function has an activation record, which is a stack slice wherein the function stores its registers. So, each function has its own registers2 . Each function may use up to 250 registers, because each instruction has only 8 bits to refer to a register. Given that large number of registers, the Lua precompiler is able to store all local variables in registers. The result is that access to local variables is very fast in Lua.
мест в стеке функции всего 250 (видать 6 идёт на что-то служебное) и всё остальное, что в него не умещается, пойдёт в обычную память (что-то похожее и в языке С - сколько не указывай компилятору переменных для помещения в регистры, что он может - толкает в регистры, остальное - в обычную память).

Кроме того сам факт создания переменной - это лишния операция.

 

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

sapsan,

Они то погибают, но место своё не освобождают

это не так. Рассмотрим простой пример:

local b = 1 -- (1)
do
    local a = {1,2,3} -- (2)
    local b -- (3)
    ...
    b = 2 -- (4)
end -- (5)
print(b) -- (6)

В этом примере есть блок, который ограничивает область существования переменной a. Теперь по порядку:

в точке (2) происходит создание двух сущностей: локальной переменной a и таблицы {1,2,3}. В этой же строке в переменную a пишется ссылка на эту таблицу (переменная инициализируется таблицей). Таблица создаётся в динамической памяти и за её удаление отвечает сборщик мусора, а переменная a создаётся в стеке блока и существует до конца этого блока. Поскольку стек по выходу из блока освобождается, то переменная a прекращает своё существование в точке (5). Именно в этой точке, не раньше и не позже. И память освобождается в этот же момент. И это не имеет вообще никакого отношения к сборщику мусора.

Ещё пример. Переменная b, которая создаётся в точке (3), не инициализируется ничем, а значит принимает значение по умолчанию nil. И она опять же живёт в стеке, а значит существует до конца блока. То, что она существует и никто её не удаляет подтверждает простой эксперимент. В точке (4) я пишу в переменную b значение. Если бы b удалялась, то я бы писал в переменную, объявленную в строке (1), но если проверить, то в строке (6) выводится по прежнему значение 1, а не 2. Т.е. я писал в b из строки (3), которая всё ещё существует, но в точке (5) удаляется и вместо ней становится видна b из строки (1).

 

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

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

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

 

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

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

И память освобождается в этот же момент. И это не имеет вообще никакого отношения к сборщику мусора.
Где бы об этом почитать? :russian_ru:

А то из документации по Луа это никак не следует:

Сборщик мусора обрабатывает все объекты Lua: таблицы, данные типа userdata, функции, потоки и строки.

И как тогда объяснить вот это:

Обработка каждого объявления local ведет к созданию новой локальной переменной. Рассмотрим следующий пример:

 

     a = {}
     local x = 20
     for i=1,10 do
       local y = 0
       a[i] = function () y=y+1; return x+y end
     end

Цикл создает 10 экземпляров функции, в которых используются различные переменные y и один и тот же x.

Само повторное объвление в цикле local y к чему ведёт в плане использования стека/памяти?

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

malandrinus, sapsan, а работа с локальными и глобальными переменными в ЛУА проходит так же как и в других языках высокого уровня (например С++)?

Если да, то код

     for i=1,10 do
       local y = 0
       a[i] = function () y=y+1; return x+y end
     end

должен работать принципу: на каждой новой итерации перезаписывать переменную y, то есть их будет 10 штук, но оно будут занимать одно и то же место в памяти.

 

Проверил на примере в С++:

#include "stdafx.h"
#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    for (int i=1; i <=10; i++)
    {
        int y = 0;
        cout << i << ": " << &y << endl;
    }
           return 0;
}

На каждой итерации я объявляю и инициализирую переменную y и вывожу ее адрес (комбинация &y). В результате я вижу 10 раз один и тот же адрес.

 

10 разных переменных (имеется в виду с разными адресами в памяти) может быть в случае рекурсии (вот тут реально юзается стек). Пример на рекурсию ниже

#include "stdafx.h"
#include <iostream>
using namespace std;

void rec(int i)
{
    int y = 0;
    cout << i << ": " << &y << endl;
    if (--i > 0)
        rec(i);
}

int _tmain(int argc, _TCHAR* argv[])
{
    rec(10);
    return 0;
}

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

Athlon X2 5000 (Phenom FX-5000@3630 МГц, разлочены 2 ядра и 6Мб кеша), ZALMAN CNPS10X Performa, GA-MA770-UD3 rev2, 2x2048 G.Skill, Inno3D GeForce GTX650Ti Boost Herculez 2GB, Chieftec APS-550S.

 

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

sapsan,

то, что локальные переменные живут в стеке из документации понять можно. Загляни в часть, где описывается интерфейс с СИ. Хотя это и так довольно очевидно. Локальные переменные в стеке - это настолько очевидная идея, что делать на этом акцент никому даже в голову не придёт, разве что на особенностях реализации. Вот например здесь стек реализован свой, а не используется процессорный. Что они точно удаляются там конечно не пишут, поскольку это самоочевидно. Ты представляешь себе, что такое стек? Как может там остаться значение, когда мы его освободили?

 

Сборщик мусора обрабатывает все объекты Lua: таблицы, данные типа userdata, функции, потоки и строки.

Я пытался донести мысль, что есть переменные и есть значения. Переменные типа не имеют, живут в стеке и представляют собой ссылки. Значения имеют тип (и не могут его менять) и живут в динамической памяти.

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

Конечно, в документации нет такого уж акцента на этом разделении, поскольку это в сущности детали реализации. Опять же многословность не помогает новичкам в освоении нового языка.

 

a = {}
local x = 20
for i=1,10 do
     local y = 0 -- (1)
     a[i] = function () y=y+1; return x+y end -- (2)
end -- (3)

Цикл создает 10 экземпляров функции, в которых используются различные переменные y и один и тот же x.

 

Само повторное объвление в цикле local y к чему ведёт в плане использования стека/памяти?

по примеру. на каждой итерации:

(1) создаётся ссылка на y (в стеке) + значение "0" (в динамической памяти)

(2) создаётся (инстанцируется) новая функция (в динамической памяти), внутри функции идёт ссылка на значение "0"

(3) освобождается стек тела цикла, ссылка из (1) теряется, но значение остаётся, поскольку на него есть ссылка из функции, а сама функция остаётся потому, что на неё есть ссылка из массива

всё это повторяется 10 раз: десять разных стеков, 10 дубликатов функций, 10 дубликатов значения "0"

Все ссылки на значение x естественно ссылаются на одно и то же значение "20"

 

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

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

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

 

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

malandrinus, а если это переписать в виде

function fun(y)
   y=y+1; 
   return x+y 
end 

a = {}
local x = 20
for i=1,10 do
     local y = 0
     a[i] = fun(y)
end

то все равно будет по 10 копий всего?

Athlon X2 5000 (Phenom FX-5000@3630 МГц, разлочены 2 ядра и 6Мб кеша), ZALMAN CNPS10X Performa, GA-MA770-UD3 rev2, 2x2048 G.Skill, Inno3D GeForce GTX650Ti Boost Herculez 2GB, Chieftec APS-550S.

 

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

Ray,

если тебе нужна аналогия из С++, то это будет нет так:

for (int i=1; i <=10; i++)
{
    int y = 0;
    ...
}

а вот так:

for (int i=1; i <=10; i++)
{
    int *y = new int(0);
    ...
}

На каждой итерации я объявляю и инициализирую переменную y и вывожу ее адрес (комбинация &y). В результате я вижу 10 раз один и тот же адрес.

Ну и что? Просто новый кадр стека при очередной итерации попал в точности на место старого. Так уж вышло из-за того, что старый кусок стека был в точности того же размера, что и новый.

 

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

Ray,

нет, в этот раз fun инстанцирована всего один раз

 

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

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

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

 

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

malandrinus, то есть на это идет динамическая память?

 

----------------------

 

Тогда надо переписать так и не напрягаться с расходом памяти.

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

Athlon X2 5000 (Phenom FX-5000@3630 МГц, разлочены 2 ядра и 6Мб кеша), ZALMAN CNPS10X Performa, GA-MA770-UD3 rev2, 2x2048 G.Skill, Inno3D GeForce GTX650Ti Boost Herculez 2GB, Chieftec APS-550S.

 

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

Товарищи!

Значитс вопрос таков:

Рыжий лес в солянку как был вставлен?

Методом переноса? Или же перекомпиляцией?

И как решили проблему проваливания актора сквозь землю на болотах?

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

Monnoroch, это из АМК2 от Рефреша, но переделанный под Соль. Функциональность увеличена в сторону удобства.

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

Фактически, получилась продвинутая версия предыдущего артмод_пда (в плане идеи).

 

Кстати, по поводу "безголовых" вылетов (когда последними оказываются строки вида "* [x-ray]: economy: strings[12028 K], smem[0 K]", - т.е. то, что в логе всегда присутствует после успешной загрузки сейва).

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

Например, совершенно безобидное на первый взгляд game.translate_string(секция) с неправильным именем секции как раз и вызывает безлоговый вылет. Другое дело, что это точно не единственная причина, но одна из вполне возможных. Задав по ошибке как параметр в этот game.translate_string ключ вместо значения из таблицы, я выяснил это абсолютно точно :)

Вы скажете, что тогда бы вылетов было бы на порядки больше? Совсем необязательно! Если подобная ошибочка сделана в таком месте кода, в который игра попадает довольно редко (нечастое стечение обстоятельств) - вот и будет, как у нас в Соли. Еще замечено, что вылеты подобного рода учащаются с прогрессом прохождения - тоже теоретически объяснимо - где-то начинают теряться данные из-за кривой отработки калбэков, дропов и прочего, в свою очередь по причине постепенно повышающейся нагрузки на движок и железо.

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

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

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

TREWKO,

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

Еще 10 лет таких цен, зарплат и пенсий, и вместо переписи населения будет перекличка

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

Monnoroch, ну, я ведь не претендовал на полноту исследования - просто поделился наблюдением :)

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

 

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

 

-----------------

Monnoroch, нет, а чего, - получилось :)

передал ключ (цифровой) вместо стринга (значения). И фиг бы так просто нашел, если бы написал большой кусок кода, а потом тестить начал :)

Т.е. если был бы стринг, но неверный или пустая строка - был бы вылет с ругательством на несуществующую секцию, а раз интежер вместо стринга - то безголовый вылет - ты это имел в виду? Именно в конкретном примере.

 

Естественно, что везде проверки ставить даже вредно, но может в каких-то подозрительных местах имеет смысл (ведь именно это и поможет исправить ошибки)? Ясное дело, что речь об уже написанном ранее коде. И не сейчас написанном, а который уже "давно тут сидит" :)

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

Я вот первый раз поработал с классно написанным чужим кодом с целью доработки - мозг посушить пришлось основательно, но зато кое-чему научился :)

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

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

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

Shadowman, :D

Это значит,где вызывается любой метод движка?

Нет,проще грамотно писать код.

 

И тем более если мы таки поставим проверки ВЕЗДЕ то ФПС раза в 3 упадет...

 

 

Дело не в ниле,дело в несоответствии типа.

В С++ типов до кучи и больше,в ЛУА 7 или 8,не помню.Короче мало. (ну там нельзя несколько операций делать,но это просто смешно,по сравнению с Сёй.И логи есть всегда.)

Так вот в твоем случае ты передал тип number скорее всего а надо string.

Намбер = сишное дубле,как я понимаю.

Стринг он и в африке стринг.Но там тоже нюансы....

 

Короче нет,тут только один вариант - писать код грамотно.

Переменные ЛУА можно крутить как хочется,а вот в движок посылать надо строго определенные вещи.

Уж ничего не поделаешь с этим - хоть движок переписывай.

Нельзя в функции движка написать "if not a then return end" - еще до срабатывания вылет,как только оно возьмет из стека переменную,посланую в функцию и попробует присвоить ее аргументу сишной функции.

А там бац - тип не совпадает,Си мрет,игра вылетает.

 

Короче вот,попытался я тут изобразить из себя прогера и изложить что и почему,чую не очень получилось... :D

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

Monnoroch,

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

Насчёт проверок - это вообще тема бездонная, о которой пишут толстые книги

Я бы разделял проверки как минимум на две категории по типу ошибок: исправляемые и неисправляемые.

Проверка неисправляемых ошибок - это то, что называют assertion. Выглядит примерно так:

if <условие ошибки> then <обрушить игру с сообщением> end

смысл в том, чтобы так проверять ошибки, которые принципиально исправить нельзя. Ни в коем случае нельзя ставить в этом месте некую заглушку и продолжить выполнение. Разумеется, такие ошибки должны быть исправлены до выхода релиза, а все проверки в релизной версии могут быть удалены. В языках C/C++ это обычно оформляется в макрос ASSERT(), который в релизной версии удалается препроцессором. В Lua препроцессора нет, поэтому придётся удалять вручную.

 

С исправляемыми ошибками сложнее. Собственно они и не ошибки даже, а просто разные нестандартные ситуации. Например получаем объект по имени, а такого не найдено. Быть может это и нормально, но требует дополнительной обработки. Главная мега-сложность в том, что ошибка определяется в одном месте, а реагировать на неё надо в другом. Вот простой пример. Функция деления одного числа на другое. На 0 делить нельзя, поэтому эту ситуацию надо проверять. assert в этом месте можно поставить только тогда, когда можно алгоритмически обеспечить, что в этом месте в знаменатель 0 не попадёт. Если нельзя, то надо как-то реагировать не обрушивая программу. И вот сложность. В самой функции деления нет возможности обеспечить адекватную реакцию. Наивный подход был бы типа такого:

fun_div(a,b)
    if b == 0 then return 0 end
    return a/b
end

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

 

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

 

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

 

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

 

Можно подойти к проблеме расстановки проверок с иной стороны. Проверка - это компенсация нашего незнания о ситуации. Проверять надо там, где мы в чём-то не уверены. Вот например функция alife():create(). после неё можно не проверять наличие созданного объекта, поскольку если он не будет создан, то игра попросту вылетит ещё при выполнениие create. Проверять надо разуеется аргументы перед вызовом. А вот alife():object() - это другое дело, это функция поиска объекта. Может и не найти, поэтому нужно проверить результат.

 

Если есть участок кода, о котором я знаю всё, то внутри можно и не ставить проверки. Например идёт сквозь мой алгоритм некий объект. Зачем проверять его существование, если я точно знаю, что ему деваться некуда? А потом я стыкуюсь с чужим кодом. Я о нём не всё знаю, поэтому ставлю проверки на границе моего и чужого кода. Когда узнаю о чужом коде больше, то можно многие проверки удалить. Поэтому так важно документировать интерфейсы.

 

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

 

Герою, дочитавшему до конца :ny_z_1:

 

 

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

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

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

 

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

malandrinus, Уу,я герой.

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

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

 

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

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

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

AMK-Team.ru

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