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

Язык Lua. Общие вопросы программирования


Malandrinus

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

Andrey07071977,

Как быть в ситуациях когда в классе несколько методов которые необходимо подписать на разные события? Например, есть класс "actor_hit_effect", в нем два метода-подписчика - один на actor_hit, другой на item_use.

Как раз для этого есть вариант с указанием self

local hit_effect = actor_hit_effect()
sm:subscribe({signal = "on_actor_hit", self = hit_effect, fun = hit_effect.on_hit})
sm:subscribe({signal = "on_item_use", self = hit_effect, fun = hit_effect.on_use})

или даже так

class "actor_hit_effect"
function actor_hit_effect:__init()
    self.sm = ogse_signals.get_mgr()
    self.desc_on_hit = {signal = "on_actor_hit", self = self, fun = self.on_hit}
    self.sm:subscribe(self.desc_on_hit)
    self.desc_on_use = {signal = "on_item_use", self = self, fun = self.on_use}
    self.sm:subscribe(self.desc_on_use)
end
function actor_hit_effect:unsubscribe_itself()
    self.sm:unsubscribe(self.desc_on_hit)
    self.sm:unsubscribe(self.desc_on_use)
end

 

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

 

xStream,

Я не знаю, зачем такие сложности Саша придумал

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

xStream,

Саш, дополнительные параметры в инетрфейсе, использование метатаблиц - это не сложности? По сравнению с одним единственным замыканием - еще какие.

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

 

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

 

Andrey07071977,

потом приведенный пример сбил с толку

Там же был пример с обычным методом. Разве нет?

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Andrey07071977,

имеет смысл перенести пример, который ты привел позже, в первый пост.

И тем не менее, пример там есть. Последний по счёту. Впрочем, ты прав, заменю на более поздний. Только уже завтра...

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Слегка поменял описание под спойлером для системы событий. Примеры выделены, пример для методов класса поставлен вторым, для функционального объекта спрятан под спойлер и снабжён соответствующим комментарием.

Ссылка на пост

 

xStream,

Саш, peace :)

Мир, дружба, жвачка =)

 

Просто ты привел такие примеры.

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

 

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

 

Да куда там проще? Один дополнительный и вообще опциональный параметр. Агрегация свойств слота в таблицу-дескриптор на мой взгляд получилась удачным решением, поскольку упрощает отписывание. И потом, возможность подписать произвольный метод без дополнительных затрат упрощает жизнь программисту. К примеру, можно подписать метод биндера одного объекта на событие другого. Или вот у меня в таймерах это используется именно таким образом, как я выше показывал в примере.

-- Запуск таймера, аргумент задаёт использование проверки с низким приоритетом
function quick_timer:start(low_priority)
    if self.timeout then -- если при создании задавали таймаут
        self.termination_time = time_global() + self.timeout -- то запоминаем время останова
    end
    self.slot_desc = {signal = "on_update", self = self, fun = self.aux_cond, queued = low_priority}
    ogse_signals.get_mgr():subscribe(self.slot_desc)
end
-- останов таймера
function quick_timer:stop()
    ogse_signals.get_mgr():unsubscribe(self.slot_desc)
end

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

 

"Быстрые" таймеры я сделал почти год назад, тогда это была просто обёртка над движковыми же вызовами set_call и классом client_spawn_manager. Если кому интересно, можете взглянуть на эту старую реализацию.

http://rghost.net/35857622

С использованием сигналов реализация сильно упростилась.

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

xStream,

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

event("event_name"):register(funct)

sm:subscribe({signal = "signal_name", fun = funct}) -- если не используем self

сложнее?

 

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

self.object:set_callback(callback.on_item_take, self.on_take, self)

Видим все те-же самые элементы: имя сигнала, вызываемый метод, ссылка на сам объект, которая будет передана в виде аргумента self. Никого же это не смущало до сих пор. И по поводу того, что это С++ подобный стиль. Не соглашусь. Как раз в С++ для реализации подобного надо городить весьма брутальные решения с оберткой вокруг класса на шаблонах и прочими радостями. Все потому, что this там - внутреннее дело компилятора. А здесь self - всего лишь часть синтаксического сахара и позволяет простые манипуляции с собой. Так что это ООП, но не в стиле С++, а просто ООП. А интенсивное использование замыканий - это как раз не ООП, а из области функционального программирования. Дело хорошее не спорю, но это альтернативный подход, к ООП не имеющий отношения.

 

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

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

 

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

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

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

xStream,

Что касается поворота - мб. Там всего лишь написано, что мне подсказали.

Поворот задается. Это не 4 вектора, а матрица на самом деле. Матрица трансформации. Берется кубик размером 1 метр с центром в точке 0,0,0 и с помощью этой матрицы превращается в параллелепипед. Матрица трансформации является в свою очередь произведением матриц поворота, масштабирования и сдвига. Сдвиг в SDK не задать (как мне кажется) поэтому центральная часть 3 на 3 обычно представляет собой произведение матриц поворота и вращения. При отсутствии вращения остается только матрица масштабирования, которая в данном случае будет выглядеть как размеры бокса, расположенные по диагонали. Матрица сдвига тривиальна и при умножении просто добавляет три числа в 4-ю строку. Ну и там еще порядок умножения выбран таким, чтобы это было тривиально.

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Добавил к своим таймерам простую обёртку для создания таймеров в процедурно-ориентированном стиле.

ogse_st_mgr.start_timer(<строка с именем таймера>, <задержка в секундах>, <строка с полным именем запускаемой функции>, <список аргументов для передачи в функцию произвольной длины>)

пример

ogse_st_mgr.start_timer("test_timer", 30.5, "some_module.some_function", 1, 2.3, "qwerty", false, true)

Будет создан таймер с именем "test_timer", который сработает через 30 с половиной секунд, при этом вызовет функцию some_function из модуля some_module и передаст её при этом аргументы (1, 2.3, "qwerty", false, true)

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

Для вызова таймера в игровом времени есть аналогичный вызов

ogse_st_mgr.start_gtimer()

Вообще, этих обёрток можно наплодить сколько угодно на все случаи жизни: задание в часах, днях, точного времени срабатывания и т.д. Загляните в конец ogse_st_mgr.script, там всё очень просто.

Ссылка на обновлённый пакет скриптов

http://rghost.net/35880388

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

xStream,

Все эти функции, если им подсунуть что-то "левое", вернут так называемый net_dummy. Пакет, который не делает ничего. Это сделано для избавления от разных проверок на то, правильный ли объект передали или нет. Так же все функции принимают как серверный, так и клиентский объект.

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

 

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

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

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

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

 

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

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

xStream,

Если нужен контроль, то есть метод isOk() у всех видов пакетов.

Это не контроль, поскольку isOk() ничего не контролирует, у него не спросишь - ответа не получишь. Здесь человеческий фактор работает на полную силу, поскольку в программисткой практике никто и никогда не проверяет коды возврата. Осознание этого простого факта явилось одной из причин развития идеи исключений. Их попросту невозможно игнорировать. Ассерты выполняют ту же роль, но несколько более ограниченно. Если исключения позволяют что-то сделать во время выполнения, делегируя решение проблемы туда, где знают, что с ней делать, то ассерт работает в том случае, когда решать в процессе работы нечего, а надо остановиться и исправлять логику работы программы.

Если кто не знает:

ASSERT(<условие>, "сообщение оь ошибке")

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

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

 

        local pk = xs_netpk.monster(obj)
        if not pk:isOk() then
            pk = xs_netpk.stalker(obj)
        end

Я бы сказал, что где-то в предыдущей логике немерянная кривизна. С какой стати obj вдруг может ссылаться либо на монстра либо на сталкера? Такое может случиться в 99% случаев только если где-то накосячили: создаём не того, ищем криво, косяки в конфигах и т.д. Это надо исправлять, а не ставить затычки в подобном стиле. Если же так задумано специально, то надо "задумщику" руки обрывать.

 

 

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

Artos,

По нюансам нет-пакетов в ЗП/ЧН:

Есть подозрение, что в этих версиях ограниченно число копий объектов, выдаваемых функцией net_packet(). Т.о. в процессе игры возникают ситуации, когда по такому запросу пакета возвращается 'уже использованный' ранее объект, т.е. с не нулевыми позициями чтения/записи.

Нет, ограничения нет, просто не инициализируется позиция записи. Смотри здесь

 

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

xStream,

Прошу смотреть amk_offline_alife.script. Не надо вне контекста оценивать такой пример, особенно, когда заявляют что оно таки работает.

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

Вот обсуждаемый фрагмент.

local pk = xs_netpk.monster(obj)
if not pk:isOk() then
    pk = xs_netpk.stalker(obj)
end

если уж так делать, то надо дополнить следующим образом

local pk = xs_netpk.monster(obj)
if not pk:isOk() then
    pk = xs_netpk.stalker(obj)
    if not pk:isOk() then
        abort("error!")
    end
end

Теперь скажи, чем хуже так?

local pk
if IsStalker(obj) then
    pk = xs_netpk.stalker(obj)
if IsMonster(obj) then
    pk = xs_netpk.monster(obj)
else
    abort("wrong object type!")
end

Ровно тот-же объём кода. Зато как минимум ясно, что здесь происходит:

Это сталкер? Получаем пакет для сталкера

Это монстр? получаем для монстра

Ни то, ни другое? Косяк, вылетаем и разбираемся, откуда взялся левый объект.

 

Вообще, идея с методом класса в стиле isOK() годится только в достаточно специальных случаях. Например, когда состояние класса зависит от внешних по отношению к системе факторов. Типичным примером являются всякие обёртки для файлового ввода/вывода. В тех-же потоках stl есть возможность генерировать исключения при ошибках, но по умолчанию отключена, и состояние потока проверяется кодом ошибки. Но там это реально обосновано, поскольку получится или нет открыть и прочитать файл реально не зависит от программиста. И решение о таком дизайне было принято далеко не с потолка, умные люди это как минимум обсудили и оставили альтернативную возможность. В данном же случае, оставляя втихую состояние объекта "не ок" - это совсем другое. Это практически всегда означает, что мы попросту прощаем ошибки программиста.

 

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Багфикс к таймерам.

http://rghost.net/35897685

 

Monnoroch,

почему ASSERT, зачем такая жестокость? Обычно в такой ситуации бросают исключение, потому на мой взгляд логичнее было бы просто ничего не сделать и как-то сообщить пользователю, что он не совсем прав. А то сразу assert делать слишком сурово.

В добавок мне видится ситуация, когда я хочу подписаться на эвент, если еще не подписан, и ничего не делать если подписан.

Понимаешь, убрать эту проверку можно. В принципе можно даже не ограничивать попытки подписать один и тот же слот два раза. По большому счёту системе всё равно, что в очереди два одинаковых вызова. Вызовет оба, а при отписывании отпишет первый найденный, а при втором отписывании - второй. Но на мой взгляд это лишено всяческого смысла и практического применения я этому не вижу. Предположим, с этим решили. Два раза не подписываем. Теперь вопрос, что делать, вылетать/предупреждать/тихо ничего не делать? Вот ты говоришь, что представляешь себе ситуацию, когда можно захотеть подписаться на сигнал и при этом неизвестно, подписаны мы уже или нет. Но дело в том, что я то как раз такой ситуации из моего собственного опыта не вижу. Вот два примера подписывания. Первый, для глобальной функции при подписывании модуля. Но каждый модуль подписывается только один раз и никогда не будет подписан второй раз. Или скажем в таймерах, где подписывание методов происходит в конструкторе класса. Конструктор не может быть вызван более одного раза для объекта, так что даже теоретически подписывание не произойдёт дважды.

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

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

Я надеюсь, что смог объяснить, почему у меня там стоит ассерт. Это вообще то не столь уж значительно дело. Честно. Если ты действительно найдёшь практическую ситуацию (не придуманную "на всякий случай"), когда этот ассерт будет мешать, ну так и убери его. Делов то на пару секунд. Это ведь не мой персональный способ усложнить себе и другим жизнь. Я пытался сказать, что эта техника помогает избегать ошибок. Она на самом деле экономит мне время.

 

Замена ассерта на более мягкую обработку ошибки позволит тут избавиться от ветвлений типа

if not obj:is_already_subscribed(slot) then
obj:subscribe(slot)
end

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

насчёт этого

Либо добавить signals_mgr:subscribe_silently() :)

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

 

К тому же с ветвлением obj:is_already_subscribed вызывается дважды.

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

 

xStream,

Опять разговор о фломастерах начинается. Только вот твои фломастеры у тебя никто не отбирает :wub:

Ох, Аня, ну где я что-то у тебя отнимаю? Если ты опасаешься, что я полезу в твой код всё там менять, то не переживай, не полезу =)

 

Andrey07071977,

- выкладывай :). Перепробовал уже с пяток разных методов, ни один не нравится, последнее на чем остановился это то с чего начал (1/uninitialized).

один из методов - использование функции fail, добавленной в рамках проекта x-ray extensions. Это собственно движковый вылет. Второй метод, использование консольной команды "quit", которая незатейливо закрывает игру.

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Monnoroch,

Хранить счетчик подписываний, а не подписывать каждый раз.

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

 

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

 

А пример я приводил: вот у меня класс хелпер какой-то. Мне присылают обьект и список эвентов на которые подписать.

Извини, так в общем нельзя. Что за хелпер, что за объект и кто может так присылать, что вы не в состоянии договориться, кто и что подписывает? Для объектов я выше приводил стратегию работы с ними. Помести подписывание в их конструктор, он с гарантией отработает один раз за всё время жизни объекта.

 

Допустим код одной схемы вызывает:

helper.subscribe_all(mgr, {s1,s2,s3})

А второй:

helper.subscribe_all(mgr, {s2,s3,s4})

Вон как все красиво. Да, можно всунуть if, но как-то уже не то.

Хотя вот я тут подумал - если просто в сам класс подписывателя инкапсулировать этот subscribe_all с if-ом, то будет все хорошо в плане использования.

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

 

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

Тогда мирись с дизайном автора =)

 

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

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

 

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

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

 

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

 

Во, кстати, я еще досмотрел и такой вопрос: почему сигнал встраивается в слот? Можно же было бы сделать как-то так:

obj:subscribe(signal,slot), тогда один слот мог бы быть подпинан на множество сигналов в разное время разным кодом.

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

 

Andrey07071977,

В релизе нет необходимости вывода в лог и уж тем более крэшить игру

Это не совсем так. Логи игры при вылетах - результат работы именно таких ассертов. Без них было бы совсем кисло =)

(в разработке, кстати тоже нет смысла крэшить)

Именно крэшить, в этом вся суть! Ассерт проверяет условие, нарушение которого приводит систему в неопределённое состояние. После того, как это условие нарушено, продолжать работу нельзя, надо остановиться и разбираться.

 

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

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Monnoroch,

Ситуация простая: некий мододел захотел на непися повесить поведение при неком скриптовом событии - например при выбросе. А выброс, так как наша система работает вокруг слот-сигнальной модели, генерирует сигнал. Ну что делает мододел - подписывает слот на сигнал.

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

...

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

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

 

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

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Monnoroch,

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

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Monnoroch,

анонимная функция пойдет для решения проблемы?

навроде:

slt = {fun = function() return f() end}

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

 

 

В общем случае сработает ли такое решение:

function duplicate(f)
  return function(...) return f(...) end
end

Как-то слишком сложно. Дубликат можно сделать так.

function fun1(a,b,c)
end

function fun1_dublicat(...)
    return fun1(...)
end

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Dennis_Chikin,

Вот интересно, почему local sim = alife() и далее по тексту - можно писать, а с vector() такое не прокатывает ?

Не совсем понятно, что ты имеешь в виду. Что ты хочешь от сохранённого на потом вектора?

Вообще же, когда ты пишешь alife() ты вызываешь функцию получения глобального объекта класса alife_simulation. Этот объект существует постоянно (по крайней мере в пределах одной загруженной игры). Если же пишешь vector(), то это вызов конструктора класса vector с созданием нового экземпляра объекта. Не знаю, отвечает ли это на вопрос...

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Andrey07071977,

вопрос по проверке зависаний. Как я понимаю, колбэк заданный в level.add_call(...) вызывается с частотой апдэйта актера

Вызывается с частотой fast_call, который, как я понимаю, вызывается с частотой пересчета физики. По идее даже быстрее апдейта, хотя на последних патчах у меня стабильно работает с частотой апдейта (или апдейт с частотой fast_call, уж не знаю). Это может также от машины зависеть, я на самом деле так и не понял от чего.

 

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

Этот вызов никак не может сработать во время работы другого вызова. Lua однопоточный, все вызовы или следуют один за другим или вызывают друг друга. Для проверки специально используется отдельный механизм чтобы исключить второй вариант. Поэтому эта проверка срабатывает строго в моменты между вызовами. Значит, если счетчик не нулевой, то было зависание.

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Andrey07071977,

подвисание одного колбека никак не влияет на работу других.

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Andrey07071977,

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Andrey07071977,

почему проверка зависаний не сработает до тех пор пока не закончится данный вызов - ведь колбэки разные?

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
  • Недавно просматривали   0 пользователей

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