Обновить
4
Viktor Pti@Qbit

Пользователь

4
Подписчики
Отправить сообщение
Чтобы увидеть, как безграмотно пишет Qbit.
Ещё забыл упомянуть: при возбуждении исключения в одном из обработчиков стандартных событий C#, последующие обработчики не будут выполнены. Ну нелепо же.

как эти проблемы решены в упомянутом Rx.

Они не то чтобы решены, они просто изначально не заложены в дизайн.
А что кривого в событиях C#? Опишите чем конкретно недовольно прогрессивное человечество


События в .NET являются синтаксическим сахаром для паттерна Observer. Причём сахаром довольно никчемным, потому что упрощения от них чуть, а ограничения весьма существенные.

Как правило среди наблюдателей (observers) и наблюдаемого (observable) большим сроком жизни (областью видимости) обладает последний. Это значит, что в их взаимодействии критически важной становится отписка от событий. В противном случае мы сталкиваемся с утечками памяти — подвисанием короткоживущих объектов, которые после своего использования недоступны сборщику мусора, даже если отсутствуют прямые ссылки на них в пользовательском коде (поскольку продолжают оставаться неявные ссылки из источника событий).

В нормальных реализациях паттерна Observer для отписки используются токены подписки (subscriptions), желательно совместимые с идиомой RAII (отписка через деструкторы в C++ или через IDisposable в C#). Именно так это сделано в Boost.Signals2 в C++ или в Rx в C#. Правильный синтаксический сахар в C# должен был выглядеть так (псевдокод):

    private IDisposable _subscription;
    ...
    _subscription = publisher.SomeEvents += subscriber.HandleSomeEvent;
    ...
    _subscription.Dispose();


Токен подписки не требует тащить всю дорогу явную ссылку на обработчик, чтобы отписаться. Но в C# последнее сделано иначе:

    publisher.SomeEvents -= subscriber.HandleSomeEvent;


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

    publisher.SomeEvents += (sender, e) => { Debug.Log(“Some message.”); subscriber.HandleSomeEvent(sender, e); }


потому что:

    publisher.SomeEvents -= ???


Обычно observers и observable друг о друге ничего не знают, а подписку инициирует какая-то третья сторона (назовём её «менеджер»).

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

    _subscription = observable.SomeEvents.Subscribe(observer.HandleSomeEvent);


Для того, чтобы отписаться, ему не нужны ни подписчик, ни источник, достаточно безликого поля IDisposable:

    _subscription.Dispose();


В случае отписки от событий C# всё сильно хуже, зачем-то нужны и подписчик, и издатель:

    observable.SomeEvent -= observer.HandleSomeEvent;


То есть безосновательно увеличена связность.

Выше уже писали про кривой синтаксис возбуждения событий (raise events). В отсутствие подписчиков (зауряднейшая ситуация) вместо простого «ничего» получаем на ровном месте `NullReferenceException`. Причём нельзя просто так взять, и проверить на `null`, как в статье, потому что между проверкой и возбуждением события может отписаться последний подписчик, и вместо ожидаемого пустого invokation list получаем `null`; выше уже указывали на это.

Далее, события в C# не могут сигнализировать об окончании потока `OnComplete()` или об ошибке `OnError(exception)`. Не могут на лету комбинироваться и преобразовываться Linq-методами. Не предусмотрено библиотечного механизма для разделения «холодных» и «горячих» потоков событий, буферизующих, и т.д.
Автор, ты какой-то мнительный.
Так как события в .NET реализованы криво, прогрессивное человечество использует Rx.
Приложение “Диетограф”: 329 рублей.

Самого главного-то и не написал: сколько тянешь, приседаешь, жмёшь, и какой собственный вес?
• А что там с ProBuilder? Никогда не пользовался; он позволяет настраивать уровни в редакторе? Но они же должны случайно генерироваться? При генерации используется детерминированный PRNG, или при каждом запуске результат может быть разным? Что именно случайного генерируется на уровнях, каковы пределы вариации интерьера?
• Тестировал на всех трёх платформах? Были ли проблемы с отличающимся поведением на разных платформах?
• Планируешь ли реализовывать мультиплеер?
• Какие шаги предпринимались для увеличения виральности; игроки как-то поощряются за популяризацию и привлечение новых игроков? Прикручены ли к игре соцсети?

Вообще, я перечитываю свои сообщения, вопросы какие-то «жадные» получились :) Просто потому, что интересная тема, прекрасная игра и хороший пост. Буду ждать соедующих выпусков! Спасибо за поддержку общения в комментариях.
• На какие игры ты ориентировался, когда делал свою игру? В какие игры переиграл, как их анализировал, из каких перенимал те или иные гейм-дизайнерские решения? От каких решений сознательно отказывался?
• Какие шаги принимал по оптимизации игры? Я так понимаю, производительность на ПК в таком сеттинге не особенно ограничивала, но тем не менее: считал ли draw calls, настраивал ли как-то батчинг, что там с тенями и освещением?
• Какие это вообще целевые платформы в Unity — Windows и GNU/Linux?
• Не очень понял комментарий про YouTube: что такое «монетизация видео»?
Собираешься ли портировать на мобильные платформы, и почему сразу на них не ориентировался?
Почему NGUI, а не Daikon Forge?
Где и как покупать/лицензировать арт и музыку?
Как бороться с рвотными позывами, глядя на код или API любого Unity-плагина из Asset Store?
они обладают опытом и пониманием мельчайших механизмов [монетизации].

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


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

Посетитель — это и идея инъекции алгоритма в алгоритм (полиморфизм со стороны посетителя), и идея взаимодействия с невидимыми извне разнотипными объектами (полиморфизм с принимающей стороны).
Педалировать какую-то одну грань и упускать другую — было бы странно.


Я не упускаю ни одну из этих твоих граней — двойная диспетчеризация (читай, «полиморфизм») по типу-объекта и по типу-операции — но по прежнему считаю, что к каким-либо контейнерам или деревьям паттерн не имеет никакого отношения. Единственный пример, который GoF придумали для своей книжки, был связан с обходом синтаксического дерева; вот они и назвали паттерн «Visitor».

это и идея инъекции алгоритма в алгоритм (полиморфизм со стороны посетителя), и идея взаимодействия с невидимыми извне разнотипными объектами (полиморфизм с принимающей стороны).


Это твоя идея. Моя идея, например, в том, что Посетитель транспонирует иерархию из n исходных классов с m операциями над этими классами в параллельную иерархию из m классов-операций (визиторы), каждый с n методами посещения (по одному на каждый класс исходной иерархии). И всё это как вариант решения т.н. expression problem: в ООП добавление метода в иерархию является ломающим изменением, а добавление класса — нет. Поэтому операцию добавления функции в иерархию классов сводят к добавлению класса в навешенную иерархию визиторов. Ситуация дуальна — теперь сложно добавлять классы в исходную иерархию, так как требуется добавлять функции посещения (ломающее изменение) в иерархию визиторов.
Итератор и Посетитель, конечно, ходят рука об руку.


Visitor вполне может применятся даже к единственному объекту, «контейнеры» и «обходы» вообще к нему никаким боком. На эту тему есть хорошая статья (или глава из книжки) Вендевурда (Vandevoorde), но сходу не могу нагуглить. Тогда приведу цитату другого известного плюсиста, из «My Most Important C++ Aha! Moments… Ever» by Scott Meyers: «Then, one day, I made a fundamental realization: the Visitor Pattern has nothing to do with visitation... Because I was so fixated on the pattern’s name, I couldn’t get past the idea that Visitor had something to do with visitation or iteration or something like that.»
Тут стоит подчеркнуть, что у паттерна Посетитель есть две независимые стороны
— разнести алгоритм работы с контейнером и алгоритм работы с элементом


Разнести алгоритм работы с контейнером и элементом призван паттерн Итератор. Паттерну Посетитель вообще безразличны какие-то контейнеры, графы, деревья, etc. Просто множественная диспетчеризация.
Я не понял, почему не надо класть положительность эпсилон в импликацию слева под квантором и почему утверждение про связанную переменную «внешнее», только и всего.


В последней формуле куски «m > N» и «ε > 0» имеют разную природу. В первом случае есть переменная N. Она не может быть вынесена вне формулы как
...∃m     ...     (...; m ∈ Z>N)
потому что на этом уровне уже никакой N нет, она связана квантором, не является свободной. Поэтому соответствующий bounded quantifier существования должен раскрываться в конъюнкцию (соответственно, для квантора всеобщности в импликацию).

Можно рассматривать кванторы более общо, чисто синтаксически, как «штуки, уменьшающие арность предиката». По аналогии: абстракция, записываемая через λ — тоже «штука, уменьшающая арность выражения», например: «λx  .  x + 1». Т.е. в результате получается предикат (в случае кванторов) или выражение (в случае лямбды) с меньшим количеством плейсхолдеров. Если свяжем или зафиксируем все переменные, то такой предикат арности 0 можно называть высказыванием.
Вам нужны типы, чтобы" более строго" записывать ε ∈ R+, при том что это утверждение по схеме выделения равносильно ε ∈ R ∧ ε > 0.


Почему вместо «ε ∈ R+» должны писать «ε ∈ R ∧ ε > 0»; но при этом не пишем «m ∈ Z ∧ m > 0» вместо «m ∈ N»?
Вам нужны типы

Мне не нужны типы.

при том что это утверждение по схеме выделения равносильно ε ∈ R ∧ ε > 0

А почему тогда не писать «ε ∈ C ∧ (ε ∈ R ∧ ε > 0)»? Это тоже равносильно «ε ∈ R+».

Вообще, не понимаю, что может быть неинтуитивного в разворачивании всеобщности в импликацию и существования — в конъюнкцию

Так я её так и развернул. Неинтуитивность неразвёрнутых bounded quantifiers проявляется, например, при построении отрицания.
Смотрите, вот исходная (простая и правильная на мой взгляд) формула (функция x : N → R введена где-то выше):

∀ε ∃N ∀n ∀m     n > N ∧ m > N → |xn − xm| < ε     (ε ∈ R+; N, n, m ∈ N)

Длинными горизонтальными интервалами она разбита на три куска. Отрицание строится обращением всех кванторов в левом куске; в среднем куске для удобства отрицание спускается внутрь предиката по законам де Моргана и формуле отрицания импликации ¬(ab) ⇔ a ∧ ¬b. Правый же кусок, который играет роль «типов», или «области определения», you name it — не претерпевает никаких именений:

∃ε ∀N ∃n ∃m     (n > N ∧ m > N) ∧ |xn − xm| ≥ ε     (ε ∈ R+, N, n, m ∈ N)

А теперь постройте отрицание для формулы, где правый кусок вшит внутрь в bounded quantifiers или в импликации/коньюнкции. Эти коньюнкции при построении отрицания превратятся в кадавров типа «… ∀m   m ∉ N ∨ ¬...»
Бог с ним с Хомский/Чомски. Но почему Елена называет Ноама Наомом?
Схема выделения тут, очевидно, ни при чём — тут даже нет «переменных-множеств». И даже ZF ни при чём — в курсе матана традиционно используется наивная теория множеств, ну и пусть. Речь идёт о том, как от матанистических «ограниченных кванторов» (на которые даже отрицания трудно навешивать) перейти к формуле с обычными логическими кванторами; это ортогонально теориям множеств.
По упомянутой ссылке в последнем разделе говорится про кванторы, которые помимо переменной содержат некий терм t, который может содержать вхождение свободных (несвязанных) переменных. В случае же «ε > 0» подобного терма нет — нужно смотреть, что в упомянутой статье в начале. В этом случае, если мы перепишем более строго «ε ∈ R+», то здесь «∈» понимается как в некотором смысле типε : R+»). В отличие от высказывания вроде «εUδ», которое может быть как истинным, так и ложным, в зависимости от того, попадает ли ε в Uδ при тех или иных условиях (не можем написать «ε : Uδ»).

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность