Душа поэта не вытерпела безвестности, и он щедро делится своими высокими идеями. (с) анонимус
Некоторое время назад я написал три статьи «Архитектурные решения для мобильной игры» посвящённые архитектуре моей мечты:
• Часть 1: Model
• Часть 2: Command и их очереди
• Часть 3: View на реактивной тяге
Я даже думал сделать из этого продукт на ассетсторе, но в ходе голосования выяснилось, что людям идеи и обсуждения гораздо важнее готового кода. С тех пор я поработал в двух игровых конторах, одна занимались и одна до сих пор занимается десктопными играми, но идеи организации User Interface через реактивности в обоих случаях пришлись очень кстати, в разы ускорили некоторую часть работы над сложными интерфейсами, и сделали возможным реализацию интерфейсов, которые до того казались слишком уж сложными. Всё это показало, что даже стадия первых закрытых бетта-тестов ещё не слишком поздно, чтобы сделать проекту сильно лучше.
Я смотрел на разные youtube-доклады по программированию, и заметил у них некоторую общую черту: Идеи, пусть даже и совершенно правильные, плохо заходят в мозг тех кому они нужнее всего, и иногда неправильно понимаются, потому что в рассказах мало кода. Программист вышедший с такого доклада может садиться и начинать писать только если то, о чём там рассказывалось для него уже почти и так понятно, он только не до всего успел сам догадаться. На другом полюсе туторилы, по hello world-у. Печалька, в общем.
Есть такое понятие «Зона ближайшего развития»: Она определяется содержанием тех задач, которые человек ещё не может решить самостоятельно, но способен решить в совместной деятельности с тем, кто уже взял этот барьер. То, что изначально доступно для человека при участии других, становится затем его собственным достоянием (навыками, умениями). Поэтому полезнее всего, конечно, совместно с сильным лидом работать над интересным проектом. Но где ж на всех проектов найти, а уж лидов тем более.
В общем, подумав во всё вот это я захотел попробовать делиться с людьми в другом формате: Я устрою стрим, посвящённый Code Review, буду показывать свой код, посвящённый реактивности и объяснять те идеи, которые в нём заложены и почему написано именно так, а не иначе. Стрим продлится ровно час. Здесь в статье я бегло опишу о чём я хочу поговорить, а потом постфактум добавлю хронометраж и какие вопросы удалось обсудить и с какой минуты.
В процессе можно и нужно задавать вопросы, потому что говорят качество кода измеряется количеством WTF за единицу времени ревьюва. Ещё какие-нибудь развёрнутые вопросы можно будет задать прямо тут в комментариях, и я постараюсь ответить на них в ходе стрима, если подходящий для ответа кусок кода подвернётся.
В процессе также можно и нужно меня поправлять и указывать на ошибки. Потому что наверняка встретится кто-то кто умнее в частностях или в общем, а ещё потому что, когда человек смотрит на свой собственный код, то часть его он словно не видит, мозг уже имеет мнение что написано в таких «слепых пятнах» и не перечитывает их. Многим, как и мне знакомо это чувство, когда зовёшь ещё два глаза к себе в код, и вдруг посторонний человек замечает очевидный ляп на самом видном месте, которого ты не видел. У разных людей «слепые пятна» ложатся по-разному, на одном и том же коде.
Стрим начнётся в среду в 22:30 (вот такое у меня свободное время, увы. :) и будет доступен вот тут:
Теперь собственно о содержании стрима.
Вообще ничёшная реализация реактивности есть в библиотеке UniRX с которой я всячески рекомендую знакомиться. Возможно вы её буквально возьмёте себе, а может быть просто натырите оттуда идей. Но я буду показывать свой собственный велосипед, написанный с нуля. Это не только потому что я люблю велосипеды. В UniRX, реализует стандартные интерфейсы System. IObserver<in T> и System.IObservable<out T>. Причём во многих местах делает это ThreadSafe (не всегда правильно) и internal. То есть библиотека имеет много лишнего кода, и её неудобно расширять. А расширять и приспосабливать под местные условия в реальности мне понадобилось в трех случаях из трёх.
Кроме того, как говорил наш техдиректор Assetstore бустит время, но не бустит мозги. Если ты берёшь оттуда что-то, что не смог бы написать сам, то рано или поздно хлебнёшь с этим кодом.
Правда показывать я буду не код приложения, который реально работает в игре, а свою домашнюю его версию. Во-первых, как бы нельзя, а во-вторых, что более важно, дома у меня более функциональная версия.
Многопоточность, в этом месте совсем лишняя если мы используем реактивность для интерфейса. Все что мы захотим сделать должно будет в конечном счёте оказаться в UnityThred, чтобы пошевелить экранными компонентами. Во-вторых, потоки для того же самого, ещё и в более тяжёлом случае я для писал на работе, и это занимает раз в пять больше времени, даже при том, что кое где я ловко использую особенности нашего чрезвычайно асинхронного серверного движка. Делать так чтобы код реализовал весь контракт без поблажек заняло бы ещё больше.
Кроме того, проблему из себя представляет сам IObserver. Во-первых, в нем есть на мой вкус совершенно лишний метод OnError(Exception e). Когда у вас многпоточность в этом есть глубокий смысл, заключающийся в том, чтобы вывалить этот эксепшен в UnityThread и он не остался незамеченным. А ещё изначально этот интерфейс был придуман для работы с файлами, которые сплошь и рядом падают с ошибками. Но в однопоточности и когда вы работаете с моделью приложения это лишний гемор на ровном месте, я предпочитаю, чтобы код поднял тревогу именно в том месте, где он сдох.
Вторая проблема IObserver состоит в том, что я хочу транзактность изменений. Просто представьте, что из одного источника вам приходит List, а из другого потока мы получаем индекс элемента, который мы должны из листа вынуть и передать дальше. И вот индекс указывает на последний элемент, и вот один элемент удаляется, а индекс уменьшается на 1. В конечном счёте всё будет прекрасно и результат операции не изменится, но если к нам придёт сообщение об изменении List и только потом сообщение о смене индекса, наш код поймает IndexOutOfRangeException. На самом деле та же проблема с порядком применения изменений может проявляться ещё десятками способов, этот просто самый очевидный.
Поэтому я хочу свой интерфейс, с одной стороны без протаскивания OnError но зато с другой стороны содержащий .OnTransaction(ITransaction t)
Что-то я чувствую, что слишком углубляюсь. Проговорить об этом во время стрима с кодом на экране будет явно быстрее, и на много понятнее. Дальше совсем о верхам:
- Мои интерфейсы IActive и IReactive. Чем они прекраснее всяких event-ов и как выглядит конечный результат их использования.
- ActiveProperty<T>. Чем отличается от Active.Proxy<T>, как передаётся и как обрабатывается начальное значение переменной.
- Как я всё это проверяю тестами и почему это очень удобно. На самом деле нормально написать такую штуку без тестов вообще не получилось бы.
- Стратегия уборки за собой IDisposible. Двойной механизм, поддерживающий и OnComplete и ICollection<IDisposible>
- Как легко делать расширения потокового инструментария и чтение самых полезных примеров.
- Инструменты отладки всего этого безобразия. В первую очередь .Log(), а до CalculationTree дойдём как-нибудь в слекдующий раз.
- Внимательное чтение кода Active.Proxy<T>, почему там под капотом не какие-нибудь эвенты, а двусвязный список.
- IActiveList и IActiveDictionary, сначала самые общие идеи.
- Как производится разделение на ViewModel, Controller и View. Как не зациклить свои стримы.
- Процесс проваливания переменных из реактивности в модель.
- ActiveList и ActiveDictionary с минимальной дельтой. Отличие от лобовой реализации DeltaDictionary вообще и в UniRX в частности.
- Общая стратегия исправления ошибок в коде, потому что нет такого предмета в вузах, а должен быть.
На самом деле тут уже на несколько часов рассказов и показов кода, так что давайте начнём с первого часа, а дальше как попрёт.
P.S. Это будет мой первый стрим, так что не судите строго если там какие-нибудь технические накладки случатся.