среда, 19 августа 2015 г.

Про Unit-тестирование


Так уж сложилось, что я до этого времени писал довольно-таки много кода в разных мелких и не очень проектах, кусках проектов и прочего и нигде по-настоящему не применял такую штуку как юнит-тестирование. А уж тем более Test-Driven Development. Но для своего воксельного движка я решил попробовать хоть и не TDD, но хотя-бы тестирование разных методов классов. И даже некоторых классов целиком.
Сказать, что это не понравилось, я не могу. По-своему, это, конечно, требует иногда и дополнительных абстракций, которые кажутся совсем ненужными для нормалного, работающего кода, но зато это экономит время. Не так, чтобы прямо экономит время вообще, потому что оно также тратится и на написание и отладку тестов, но зато это позволяет потом в достаточной мере быть уверенным, что "баг где-то в другом месте". Сейчас я применяю тесты, в основном, для того, чтобы убедиться, что у меня правильно посылаются и парсятся отдельные сообщения между клиентом и сервером, или, например, что основные классы с объектами игрового мира правильно определяют координаты и не вылазят за пределы массивов при этом. Удобно то, что это можно сделать без компиляции и запуска всего проекта, включая отдельно сервер и клиент, что занимает, конечно, намного больше времени, чем нажатие Ctrl-F6 в IDE. Также это позволяет убедиться, что ничего не сломалось, после рефакторинга кода.
Конечно, мне даже хотелось сделать какое-то более тщательное покрытие тестами, хотя-бы половины кода, но я пока остановился на наиболее сложных частях кода и тех частях, где уже находились баги (или были сомнения. что они работают правильно). Но пока я пытался прикрутить анализ покрытия тестами, я узнал, что нормально работающий плагин для Maven, который работает с Java7/Java8 сейчас трудно. Emma не умеет 7-ю версию и выше, JaCoCo глючит и не работает, как мне показалось из списка рассылки, с проектами из множества модулей (а мой именно такой). Однако, я все-таки нашел, что Cobertura, во-первых, работает, во-вторых, все-таки встраивается в Netbeans, хотя добиться общего отчета по всем модулям я пока не смог. А еще оно иногда глючит и не работает.
В общем, штука полезная, хотя я к модным мейнстримным вещам всегда отношусь как-то с прохладой. Хотя автоматизация процесса тестирования вещь очевидная :).

вторник, 18 августа 2015 г.

Немного накопленного опыта в работе с трехмерной графикой.


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

Во-первых, оказалось, что рисовать огромную кучу отдельных кубиков - неподъемная ноша для графического движка JMonkeyEngine. Да и, думаю, для любого другого. Поэтому все нормальные люди превращают кубики в большие наборы, которые называются в Майнкрафте "чанками". Это позволяет еще до рендеринга преобразовать массив кубиков в массив вершин, решить, какие стороны кубиков, в принципе, можно увидеть, преобразовать это в некий объект и загрузить для дальнейшей обработки шейдерами. Поэтому пришлось от "отдельных блоков" отказаться на этапе загрузки всего, что увидит игрок и вместо этого реализовать некоторый "менеджер чанков" и "чанки". Первый нужен для того, чтобы хранить и находить чанки вокруг игрока, решать, что их нет, запрашивать у сервера и удалять из памяти, когда они стали не нужны (игрок ушел за пределы "видимости", скажем). Второй, собственно, занимается хранением отдельных блоков и преобразованием их набора в "mesh", то есть некоторый объемный объект, который содержит несколько VBO, то есть массивов вершин, координат текстур и других данных для обработки в видеокарте.
Вот после их полной загрузки их, в принципе, можно и обновлять отдельными событиями вроде "удалить блок", "добавить блок", "изменить блок". Но тут тоже не все так гладко. В выбранном мной для простоты начинаний JMonkeyEngine3, да и, я думаю, где угодно еще, один Mesh не может содержать множества разных материалов. То есть, на него, в принципе, можно натягивать много разных текстур, используя атлас и натягивая на разные треугольники разные куски этого атласа, но сделать его часть прозрачной, а часть непрозрачной, например, уже сложнее. Все дело, видимо, в том что он целиком обрабатывается одним и тем же набором шейдерных программ. Чтобы это решить, нужно еще крепко подумать.
Первый вариант - выделять из чанка отдельно прозрачные и непрозрачные блоки, например и делать из них два разных "меша", Увеличивает количество VBO, потенциально снижая производительность рендера, зато делать с ними можно что угодно. Второй - считать весь чанк прозрачным и управлять прозрачностью отдельных блоков с помощью альфа-канала текстуры. До этого я еще не дошел и предстоит еще разобраться, какой позволит добиться правильного эффекта.

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

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

  • Как я уже говорил, мир состоит не из тупо блоков, а из каких-то веществ в каком-то состоянии. Значит, нужно думать, где и как сохранить этот набор веществ и правила, по которым они отображаются. Прозрачные они или нет, какие у них текстуры и все такое прочее. Потом нужно научиться это рендерить. Также важно, может ли игрок двигаться внутри блоков, или должен "сталкиваться" с их границей. Сейчас он просто пролетает сквозь них, это по-своему удобно для отладки одних вещей. но неудобно для отладки других. Значит, и на сервере и на клиенте нужно обнаруживать столкновения и запрещать дальнейшее движение.
  • Следующая проблема, это, конечно, тики сервера. Сейчас я их отключил совсем. Нужно их возродить и что-то обрабатывать. Так как блоки пока стали статичными, обрабатывать их нет смысла. Одним из важных параметров мира, как правило, является энергия, например, тепловая энергия. Ее хранение и перенос. Это непростая проблема и интересно она начинает работать только если блоки разные, в том числе, по поведению. Например, нагревающийся газ расширяется. Нагревающаяся вода испаряется.
  • Это значит, что без какой-то физической модели, кроме тепловой, обойтись нельзя. Нужно вводить какие-то понятия, во-первых, силы и давления (расширяющися газ), гравитации, импульса, массы, наконец. Появление таких вещей, как гравитации, заставляет задуматься над тем, могут ли двигаться блоки и какие правила при этом действуют. Очевидно, как минимум нетвердые блоки должны двигаться. Или, скажем, не сами блоки, а их содержимое. Пока я не столкнулся еще с проблемами производительности, я бы все-таки попробовал подумать, можно ли реализовать конечную воду, которая течет в соответствии с действием гравитации, пусть даже без "просачивания" в пористые/проницаемые вещества.
  • Но все эти вещи трудно смотреть и отлаживать без возможности для игрока ставить и убирать любые блоки. То есть, нужен какой-то "админский" или "божественный" режим, похожий на креатив в Майнкрафте. Для этого нужно, конечно же, реализовать не только такие мелочи как "узнать, в какой блок сейчас смотрит игрок и в какую его плоскость", но и некоторый GUI для выбора, какой вид блока поставить. Возможно, еще какой-то UI для того, чтобы понять, что перед тобой есть.
  • Для дальнейшей отладки очень удобно было бы все-таки заиметь текстовую консоль с командами на сервере и на клиенте. И какой-то инструмент для "замера параметров" блока с помощью наведения на него, так как нет смысла передавать все параметры каждого блока и некоторые стоит все-таки передавать по запросу.
  • Также ясно, что наличие движения требует синхронизации того, что игрок видит на экране и того, что сервер посчитал у себя. Игрок должен видеть это плавно, сервер должен делать это быстро (потому что игроков может быть более одного), так что это интересная задача, даже если думать только о движении самого игрока. Сейчас, для простоты, игрок просто передает вектор своего относительного движения на каждый апдейт (по сути - фрейм) внутри клиента, что накладно, так как это сотни-тысячи сообщений в секунду. С учетом того, что двигает игрока, на самом деле, сервер, отвечая ему новыми координатами. можно прикинуть, насколько это неоптимально. Конечно, когда FPS начнет проседать, автоматически решится и эта проблема, но зачем надеяться, что ты сделаешь слайд-шоу? :)
  • Ну и конечно, за всем этим следует работа над первым этапом генерации мира, на основе самого простого трехмерного шума. Почему так? Да потому что для всех остальных фич все намного сложнее и оно требует хотя-бы их наличия. Не хотелось бы иметь мир, который после генерации и загрузки в память вдруг оказывается нестабильным по действующим законам и требует еще кучу времени, чтобы устаканиться (такое можно иногда в Майнкрафте заметить, когда вдруг в новозагруженных чанках все осыпается и начинает течь с потолка поток лавы).

четверг, 6 августа 2015 г.

Штатуш апдейт

Пока выдалась по-странному свободная минутка, хочу рассказать про большой фейл с управлением приоритетами. Дело в том, что эта фигня с кубическим изменяющимя миром, похоже, вызвала у меня страшную зависимость, и мне хочется без остановки писать код. В связи с чем появились, похоже, проблемы со всем остальным, отклонения от привычных распорядков и это, в свою очередь, вызывает стресс. Не делайте так. Следуйте планам, ибо ущерб всей другой деятельности рано или поздно начинает сказываться в виде давления на психику, раздражения, депрессии.
Хотя, конечно, как некоторые говорят, это приносит дополнительный опыт, связанный с программированием и геймдевом, это не повод бросать другие проекты. Да и, к тому же, если бы у меня действительно получалось все писать в том темпе, в котором хочется, наверняка все было иначе. Но пока что у меня все перешло, сначала, к безудержному рефакторингу всего, разбиванию на кучу модулей, написание "инфраструктуры" для загрузки и запуска всего и, конечно, в этом всем немало багов появляется, которые не всегда удается быстро и легко вылавливать.
Чтобы решить проблему забрасывания всего остального, пришлось для этого проекта выделить отдельный контекст, чтобы не было лишнего соблазна вместо других вещей заниматься только этой.
В общем, на прошлой неделе я забросил почти все кроме работы и всякой самоучебы и до поздней ночи писал, отлаживал и опять писал. Чем все кончилось, я уже немного открыл в предыдущем посте. С тех пор изменилось мало, единственное, что я начал делать - все-таки добавил блокам понятия "фазового состояния", то есть "пустой, газ, жидкость или твердый", перешел в три измерения, удивился, как все стало намного медленнее из-за 3D. То есть, я уже по-настоящему перешел, со всеми вытекающими в виде увеличения числа итераций по блокам на глубину мира. Здесь, конечно, возник вопрос "что считать глубиной" и вообще, как сделать из такого параметра как "радиус" эту глубину, да так, чтобы мир оставался в виде куба из кубов, а не из параллелепипедов (для удобства). Лучшим решением, как мне показалось, будет вообще забить на "радиус" на этапе выделения блоков и просто считать все за пределами твердой части и атмосферы "пустым". Тем более что радиус, конечно же, намного меньше, чем половина длины окружности, и кроме него в нее укладывается и высота условного верхнего слоя "атмосферы", что лежит в районе линии Кармана, которая находится очень близко над поверхностью, если сравнить с радиусом самой планеты. А чтобы по пустым местам серверу не тикать, просто знать, что там ничего нет и не делать этого.
Конечно, в таком представлении сферического тела как параллелепипеда есть недостатки, но ими можно просто пренебречь для простоты (все-таки это не симулятор). Первый недостаток в том, что нарезка после развертки делает блоки растянутыми у центра шара и "сжатыми" у его краев. То есть, кубометр блока вмещает только долю кубометра реального шара, если говорить о чем-то возле центра планеты, и наоборот, если посмотреть где-то далеко от него.

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

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

В общем, похоже, нельзя считать блоки только твердыми или только жидкими. В твердых блоках может быть жидкость и она оттуда может вытечь или испариться. В атмосфере может быть водяной пар. В воде может растворяться воздух. Так что, в будущем, похоже придется вместо твердости или жидкости пойти в сторону просто "состава блока", имея в виду наличие нескольких компонентов в разном фазовом состоянии. А рендерить и извлекать основную часть поведения уже от того компонента, который преобладает. Попутно, конечно, подумав, как эти данные хранить компактно, не выделяя под каждый компонент кучу байт.
Да, еще "твердость", она же может быть разной. Это может быть цельный плотный кусок чего-то, а ведь еще "твердыми" свойствами может обладать какая-нибудь щебенка или песок. У такого рода вещей, конечно, свои особенности поведения должны быть. Не так как в Майнкрафте "должно падать вниз", а несколько более продвинуто, потому что, все-таки, мне бы хотелось подумать, как сделать так, чтобы падать вниз должно было все что угодно (и тут должны поныть любители парящих в воздухе островов). В общем, в будущем предстоит раздумье, как эффективно впихнуть в один блок все его свойства и особенности поведения. А потом - как быть и с рукотворными блоками тоже.

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

суббота, 1 августа 2015 г.

Не, не выйдет ничего

Итак, я окончательно закопался со своим "невероятно изменчивым миром".
Напомню, что изначальная идея состоит в том, чтобы сделать что-то похожее на Майнкрафт, но отличающееся от него тем, что мир не является чем-то статичным, а изменяется так, что создается ощущение, будто ты находишься в таком месте, где все имеет свои последствия и изменения в одном месте так или иначе немного, но влияют на все остальное. Как - вопрос количества "законов" в этой природе.
Начал я, как было рассказано ранее, с двухмерной плоскости, которая представляла собой некую не очень точную (в силу растянутости у полюсов) развертку поверхности круглой планеты. Плоскость была разбита на квадраты 16х8 с очень хитрой целью. Дело в том, что для меня показалось важным смоделировать так же поток энергии от ближайшей звезды, но, конечно, не абсолютно точно, так как это точно не успевало бы обрабатываться с той скоростью, которая нужна. А вот 16х8 показалось идеальным размером. Дело в том, что одно полушарие, которое освещено сейчас, таким образом сотоит из 8х8 блоков, и они точно делятся на две части в любом направлении. И здесь можно найти зону, в которую лучи падают почти перпендикулярно, то есть, экватор, в котором сейчас полдень и приходит больше всего энергии, места, где солнце уже не в зените или, если смотреть с экватора в сторону полюса, более "тропический" климат. И, конечно, полярные шапки, куда ее поступает меньше всего. Далее я считаю, что блоки, в которых никого нет в данный момент, можно не "уточнять". То есть, достаточно обработать 16х8 блоков, но.
На планете кто-то есть, кто может ее наблюдать и трогать своими грязными руками. Это игроки. Изначально предполагается, что можно уметь в мультиплеер. То есть, точек, в которых может кто-то быть, много.
Чтобы зоны вокруг игроков "уточнить", применялся простой алгоритм пересечения круга (некоего радиуса восприятия игроком) и квадрата, то есть "блока". В случае пересечения считалось, что игрок "чувствует" блок и алгоритм рекурсивно спускался глубже, на уровень своей структуры 8х8, проверяя каждый внутренний блок на предмет пересечения уже с кругом радиусом в 8 раз меньше. Так достигалось постепенное увеличение точности при приближении к игроку. В минимальном радиусе обрабатываются уже конкретные единичные блоки. Все выглядит офигенно просто, но. Рост количества вычислений с ростом числа игроков, которые находятся далеко друг от друга - линейный. Однако, есть плюс - алгоритм обхода "тиками" сервера, как мне кажется, довольно легко рассыпается по отдельным потокам, лишь с учетом блокировок, которые неизбежны при влиянии соседнего блока на текущий обрабатываемый.
Для тестирования производительности применялся довольно-таки простой, но, в то же время, очень нужный изначально алгоритм постепенного "сглаживания" некоего значения между блоками. Нужный он потому, что он неплохо изображает из себя, например, растекание кубометра воды по плоскости, хотя он и предельно упрощен. Также на нем я заткнулся в одну задачу, которая также принципиально важна вообще для идеи.
Задача следующая, состоит из двух частей:
1) Игрок удаляется от блока, из-за чего точность в этом месте должна снизиться. Необходимо "свернуть" всю информацию, которая обрабатывалась перед этим с высоким разрешением в виде, скажем 8х8 блков, в одно значение, которое сохранится теперь в одном "верхнем" блоке, так, чтобы после ее обработки можно было выполнить вторую часть.
2) Игрок возвращается. Нужно снова увеличить разрешение, сделав из одного значения снова 8х8. При этом должно быть максимально достигнуто ощущение, что все обрабатывалось как раньше.
Скажу сразу, наскоком изобрести "правдоподобное" не удалось.
Далее попытался перейти в 3D, для более удобного рассматривания результатов. То что делалалось на плоскости, стало сложнее. Хотя я не сделал все структуры трехмерными (и рисовал просто "пластинку" верхнего словя), и даже не перешел к поиску способа впихнуть все вышеописанное в трехмерность, у меня уже возникла проблема, как передать клиенту только то, что он и правда может увидеть. То есть, нужен рейтрейсинг/рейкастинг или какой угодно еще алгоритм. Причем, видимо, он должен работать на сервере, чтобы не гонять лишние данные по сети. Либо, второй вариант, нужно найти все, что обрабатывалось из-за него сервером и передать всю эту кучу блоков-подблоков, а он уже пусть там сам разбирается. Тут, кстати, несколько решений пришло в голову, начиная с простого обхода некоторой зоны вокруг игрока с загрузкой только того, что "обработано", до запихивания ссылок на каждый обрабатываемый блок прямо в объект игрока для передачи прямо сейчас (он кажется проще и делает меньше дополнительных "обходов"). Оба варианта, скажем так, будут "тестироваться". Пока что тестируется второй. Однако, все равно не ясно пока, как просто и дешево потом в клиенте избавляться от "невидимого", а также более "крупных" блоков при получении мелких.

Но, в целом, создается впечатление, что со всем таким не справится даже топовое железо.