Pull to refresh

Comments 6

Привет, спасибо за статью.

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

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

«На самом деле большинству игр нужна только одна команда, ожидающая ответ — GetInitialGameState»
Не соглашусь, таких команд может быть много — зависит от того, как много логики скрывается от пользователя.

«Как команды делятся между серверами»
Первый вариант имеет право быть, так как Вы не можете запросить часть данных — неизвестно, что нужно команде для работы,
что она меняет — соответственно нужен полный стейт.
С другой стороны, это не реал тайм — тут нет необходимости молотизировать 20 раз в секунду логику, тут 1) запрос/ответ
2) не так просто отследить, что клиент покинул игру и нужно закрыть его процесс.
В итоге и стейт нужен полный (кто знает, какие данные нужны команде) + трудно определить, когда клиент покинул игру + процесс по большому счету будет простаивать.
Поэтому открывается N процессов с игровой логикой, они загружают статические правила игры (один раз) и далее получают разных игроков и исполняют над ними разные команды
через общение с кластером, а кластер уже общается с клиентом (в реал тайме игровая логика общается напрямую).
Хитрость тут в том, что на самом деле мы можем узнать какие поля потребуются для команды. По целым двум причинам. У нас есть экранированный Геттер, ничего не мешает в нём спрятать фиксацию информации о том, что к нему обратились. Клиент сможет сообщить какие ему модели понадобились когда он команду выполнял. Второй вариант ещё интереснее: На сервере непосредственно выполнение команды обычно занимает в десятки раз меньше времени, чем подгрузка данных из реляционной базы. Так что мы сначала грузим большую часть стейта из большого блоба, после чего можем запускать выполнение команды, если натыкаемся на отсутствие данных в каком-нибудь коллекшене, хранящемся не в блобе, а в отдельной таблице, команда стопорится эксепшеном, состояние дерева откатывается, и мы грузим из базы этот коллекшен, после чего повторно пытаемся выполнить команду. Идея чисто теоретическая, надо будет её, при случае, попробовать.
Привет, не могу промолчать)) За глубину приверженности идее — респект! Но вдруг кто-нибудь решит проникнуться, а я против, это сплошная идеологическая ошибка!)))))
1) Борьба за мелко-гранулярную реактивность изнутри модели — лишняя писанина (очень много писанины) обреченная на провал. Провал наступит когда кнопке надо будет подписаться на результат функции вычисляющей валидна ли операция тригерящаяся к кнопке. В том и сила Rx, что отображение может из единственного Observable изменений модели в целом Select'ить себе то что нужно. Поуправлять Throttling'ом для «отложенности» и все что хочешь… Вообще, реактивность не должно быть свойством модели — это бессмыслица, например с т.з приложения которое реализует точно такую же бизнес логику но не имеет UI вообще (именно таким приложением является сервер ;) ). Реактивность — это свойство способа, которым отображение получает данные из модели.
2) Команда — хороший паттерн для реализации (на самом деле нет, потому что правильное место данным описывающим изменения — в DTO без кода) но плохой способ предоставлять API. Хороший способ предоставлять API это интерфейс с функциями с понятной сигнатурой и контрактом. Полезность команд для реализации такого интерфейса в этом кейсе — ну, дело вкуса, мне кажется и это баловство.
3) Запихивать стейт и прочие подробности отображения в модель бизнес-логики — странно, неудобно и неправильно. Описание танцев с бубнами которые нужны для того чтобы потом отделить одно от другого — лучшее тому подтверждение. Я в принципе могу согласиться с понятием «модель интерфейса» как стейта отображения, но мухи отдельно. Но если честно, ее отдельное выделение — довольно бесполезная трата времени имо. Полезная трата времени — выделение «нормального» слоя для логики отображения — вью-контроллера, презентера, вью-модели — как угодно, но нельзя его не выделять, для него объективно существует область ответственности (отдельная от модели и вью).
Митя из Glu, это ты что-ли? )))

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

1) На самом деле писанины очень не много. Это шаблонные две строчки кода, в которых меняется по сути только название переменной. Они делаются из соседней такой же модели простым копированием. Более того я просто добавлял готовый шаблон в настройки редактора, чтобы он по трём буквам добавлялся. Всё остальное спрятано под капотом движка и на написание конкретного функционала игры никак не влияет.
Самый просто способ проверки на сколько много или мало лишней писанины я выработал для себя давным давно. Берётся движок, и на нём делается модель в которой есть одно простое поле, показывающаяся в одном текстовом поле в одном новом вьюве, а рядом кнопка, изменяющая это поле. И засекаем сколько времени в минутах это займёт у человека делающего это хотя бы второй-третий раз. За счёт того, что вся машинерия спрятана под капотом и программисту её писать не надо на моём движке это занимает не больше 10 минут вместе с отправкой команды на сервер и отработкой серверного ответа, про опыте ещё и меньше. Все виденные мной альтернативы требовали в разы большего времени.
2) DTO плюс правила — тоже хороший вариант, но он требует дополнительных телодвижений чтобы сопоставлять DТО с правилами. Учитывая что сериализация всё равно кастомная, потому что хочется иметь в данных не только простые типы, но и ссылки на модели, варианты получаются фактически эквивалентными.
а) Откзываться от ссылок на модели значит требовать от программиста каждый раз руками по параметрам искать объект в дереве. Лишняя писанина, которая может быть спрятана под капотом команды.
б) Ну или иметь сквозные PersistentId для объектов и всегда писать их. Но это во-первых, очень плохо влияет на читаемость лога команды и, если таковой есть, лога произведённых в модели изменений. А кроме того ты всё равно не обойдёшься одним PersistentId, потому что нужно иметь один способ для объектов унимкальных в рамках сервера, один для объектов уникальных для игрока, и ещё один уникальный для игрока, но не затрагивающий игровую логику и его NextFreeId, чтобы детерминированность операций не нарушать.
в) Если ты вызываешь функции потребуются дополнительные телодвижения чтобы вызов функции превратить в отправку команды на сервер. Соответственно усложнение кода функции. При моём подходе он чище, понятнее, и лишён каких либо лишних телодвижений и места для ошибки.
3) Разделение модели для бизнеслогики и модели для View, что-то типа MVVM в одно движение приводит к тому, что эти две модели должны обслуживаться одним и тем же общим предком Model и иметь общую точку доступа к ним GameState. Сериализация-десериализация GameState как одной сущности проще сделать если он, в свою очередь, тоже наследник Model-а. И вот мы получаем, что по сути это единое целое, только одна веточка поддерева для буковки M а другая VM. А дальше возникает ситуация, что если уж всё равно это одно целое, а у тебя половина полей объекта нужны только для отображения, то вместо того чтобы для одной сущности заводить две модели, одна для бизнес-логики, а другая для View мы этот очень простой, на самом деле функционал, распространяем не только на первое деление на два дерева, но и на любое поле.
А танцы с бубном чтобы красиво и доходчиво показать ошибку это делается только один раз при создании движка, а для программистов фигачащих логику это всё просто движок, дающий понятные сообщения об ощибках и просто он это делает или сложно и им и приложению в проде когда ошибок нет — совершенно пофигу.

А вот правильная идеология или неправильная — это вопрос религиозного свойства, особого внимания на прагматическим аргументам тут уделять не нужно. :))
Придумал новое мероприятие по этому же коду — кодревьюв во время стрима с объяснением что в коде конкретно написано и почему. Показывать буду как раз ту самую реактивность, которая описана в третьей части статьи и немного здесь в комментариях.
habr.com/ru/post/480668
Sign up to leave a comment.

Articles

Change theme settings