Неделя 32-37 (май 5 — июль 1): Сервер.

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

Если кто вдруг обратил внимание, да, у меня 31 неделя закончилась в начале мая, а 32 началась в конце мая. Это я так съездил на Кипр. На Кипре жарко, туда можно ехать только работать в прохладном офисе…

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

Май 5 — Загрузка конфигов

Было это скорее так: я возвращаюсь из отпуска и понимаю что вообще не помню, до чего я дошел в процессе изучения GameSparks. Сажусь резко читать документацию с самого начала, быстро ее пролистываю, потому что все кажется каким-то банальным и снова обретаю дзен. Или чего там обретают.

Наконец, я обрел некоторую уверенность в том, как пользоваться этим дурацким сервером и начал непосредственно экспериментировать. Выяснил, например, что загружать конфиги с клиента на сервер не получится… Приходится выгружать конфиги ручками. Могло быть и хуже.

Я уже не уверен, что решил свои проблемы, а не создал новые.

Или что система, которая рекомендуется разработчиками для хранения изменяемой информации об игроке, предназначена… не для меня. У GameSparks есть примеры хранения, например, списка друзей этим способом. Идея заключается в том, чтобы не грузить в память сервера лишние данные об игроке и обращаться к ним только по мере надобности. Действительно, к списку друзей игрок обращается не слишком часто и его можно хранить отдельно. А вот список построек игрока или список его персонажей?

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

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

***

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

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

Июнь 1 — Покупка и перемещение построек и персонажей

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

    • Восстановил магазин — заставил его работать с новыми конфигами. Благо новый механизм конфигов позволяет добраться до любой нужной информации намного быстрее и удобнее. Просто в конфиге магазина уже есть ссылка на постройку, у которой есть ссылка на иконку. С ресурсом покупки все так же просто, разве что только самих ресурсов еще нет, а значит нет и отображения количеств.

 

    • Следующей идет отправка на сервер запроса о покупке новой постройки. Всей отправкой и приемом сообщений между клиентом и сервером естественно занимается один единственный класс.

 

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

 

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

Если на стороне клиента все известно и проблем не вызывает, то на стороне сервера все оказалось не просто из-за особенностей JS. Я не знаю, кто это придумал, что можно кодить серверную логику Java Script‘ом и очень надеюсь, что в аду для него есть теплое место. В JS нет возможности создать какой-то класс, создавать его экземпляры и заполнять его поля. Есть у тебя JSON, ему и радуйся. Плюс он не позволяет передавать значения переменных ссылками (это когда ты делаешь а = 9, в = а, в = 5 и оказывается что а теперь тоже = 5). Вернее, позволяет, но только для известных ему классов. Мои же классы-джейсоны ему неизвестны. В результате клиентскую логику, написанную на c#, можно переносить на сервер только условно, а каждая функция обработки клиентских запросов превращается в монстроузного и нечитаемого монстра нечитаемости.

Но дальше все немного проще. Обманчивое чувство, будто освоился в новой среде.

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

 

    • Самое смешное что работу этой функции невозможно проверить, не сделав загрузки всего сохраненного состояния игрока при старте приложения. Так что я сделал и его.
    • Дальше идет создание базового состояния игрока. А то у меня, например, склад ресурсов находится в Холле, а лота для его покупки нет — он должен появляться при создании игрока.
      • Кстати, сейчас есть бага, что если попытаться два раза создать игрока, то система создает его один раз, а вот всякие постройки выдаст ему два раза.
    • Раз уж есть покупка построек, то не должно возникнуть сложностей и с покупкой персонажей…

 

    • …и с восстановлением отображения персонажей…

 

  • … и перемещением персонажа… А, нет, тут уже сложно. При перемещении персонаж может оказаться в постройке, а значит, в результате работы какой-то серверной логики могут измениться свойства персонажа (и даже не одного) и постройки. И эти изменения надо как-то вернуть с сервера и обработать.

И вроде бы все уже написано на клиенте, но так просто переноситься на сервер отказывается. Нужно обязательно нахлобучить пару циклов чтобы найти постройку, найти всех персонажей в постройке, понять куда можно вставить персонажа… Меня это бесит. Ненавижу JS.

Июнь 2 — Магазин ресурсов и начисление и списание айтемов

Не помню точно, когда именно (вряд ли у меня на это всего 3 дня ушло — вторая неделя июня была коротенькой, если кто не вкурсе) но:

    • Восстановил магазин ресурсов.

 

    • Сделал запрос на добавление ресурсов в инвентарь.

 

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

 

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

 

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

       

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

 

  • Ну и под конец восстановил систему ивентов, которая обновляла счетчики айтемв и список айтемов. Для списков у меня образовался новый ивент, обновляющий вообще любые списки. Очень удобно для вызова события новой постройки, например.

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

Июнь 3 — Интерфейс построек (рабочие и крафт)

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

    • Делать нечего, надо восстанавливать интерфейс постройки. Он слегка раздолбалася при переходе на новые конфиги. Слегка больше, чем все остальное.

 

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

 

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

 

    • Чтобы не трогать действительно страшное, решил восстановить механизм рестрикшенов.

 

    • А заодно исправил показ по рестрикшенам лотов построек в магазине — рестрикшены на клиенте являются одним классом так что многие вещи происходят уже сами собой.

 

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

 

  • Случайно восстановил работу меню рабочих — забыл на работе закомитить изменения и пришлось дома заняться чем-то не связанным с рецептами. Выпилил оттуда механизм драг-н-дроп. Установка персонажей через интерфейс теперь работает так же как и в мире: если свободных слотов нет, то выгоняет случайного. Когда будет время добавлю функцию освобождения слота.

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

Июнь 4 — Логика крафта

Отступать уже некуда, больше прокрастинировать нечем. Занялся переносом логики приготовления крафта на сервер. И тут пришлось серьезно поломать голову. Логика рецептов подразумевает наличие определенной рекурсии в происходящем: если игрок оставляет в очереди несколько рецептов и возвращается через некоторое время все, то все рецепты могут быть завершены, просто потому что прошло достаточно много времени. Но чтобы это узнать, надо рекурсивно пробежаться по всем элементам в очереди. Не просто циклом, а циклом с проверкой “следующего” — ведь если текущий рецепт готов, то следующий надо изменить. JS вообще не облегчает работу.

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

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

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

    • Добавление нового рецепта сделал довольно быстро. Но только для “нормального” рецепта. А есть еще дефолтный, и левелапы постройки и персонажа. Но к ним нельзя даже подобраться пока не выполнен полный цикл функций для нормального. Там же находится проверка на свободные слоты и имеющиеся в инвентаре ингредиенты.

 

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

 

  • А потом сделал и взятие крафта… вот и завершен полный цикл.

Я перестал понимать, как и почему это все работает, но оно работает.

Июль 1 — Дефолтные рецепты

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

Например, для проверки выполненности рецепта надо рассчитать два значения: время окончания крафта и оставшееся время (разница между текущим временем и временем окончания). Для любых манипуляций со временем у JS, что уж там, есть набор довольно удобных функций. Например, позволяющих прибавить минуты. Правда, этот фокус работает максимум до часов: JS не знает о том, что дни бывают не днями месяца, а просто абстрактными сутками, но это не важно. Важно то, что проводя эти манипуляции JS превращает значение времени в класс и перестает считать его просто числом. Хотя если попросить JS просто дать текущее время, то он даст именно число, а не класс. GameSparks смотрит на это дело и сохраняет будто так и должно быть, а на клиент уходит не число, а какая-то какаха, оставшаяся после сохранения класса времени.

https://youtu.be/BrB0oaSz8Nk

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

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

Например, мы вернулись в игру где у нас был один крафт. Система запускает проверку на его завершенность. Если в результате крафт завершается, так как он был один (нет другого крафта, который можно было бы запустить), система должна создать дефолтный. Дефолтный рецепт создает новый крафт — запускает уже имеющуюся функцию создания крафта — и возвращает его в механизм проверки, где перезапускает весь цикл. При этом и в функции проверки, и в фукнции создания нового крафта есть этапы чтения и сохранения измененных инстансов крафта. И фукнция проверки благополучно перезатирает собой действие функции создания… Афигеть, как все надежно устроено. Я уже писал об этом, да.

Вот как-то так выглядит процесс отладки. Да, в Блокноте.

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

Оглавление