Два слова: преждевременная оптимизация.
Вы беспокоитесь о производительности. Но, учитывая, что вы хотите сделать клон Майнкрафта, это означает, что игровой мир очень хорошо может быть представлен трехмерным массивом. Доступ к ним достаточно быстрый на всех упомянутых языках программирования; игровая логика должна занять гораздо больше времени, чем доступ к миллионам записей массива. Так зачем оптимизировать часть, которая в любом случае не займет большую часть вычислительного времени - даже до того, как вы написали минимально работающую версию?
Возможно, вы захотите создать интерфейс Java или черту Scala, представляющую игровой мир. Он предлагает методы для получения и хранения содержимого блоков игрового мира. Позже вы также можете добавить массовые методы для дальнейшей оптимизации производительности; например тот, который проверит, все ли блоки в данном кубе пусты, или посчитает количество деревянных блоков, что-то в этом направлении. Но в начале лучше не использовать эти методы или создавать тривиальные реализации, которые основаны на повторном вызове абстрактных методов. Вы можете оптимизировать их позже.
Затем вы можете предоставить очень простую реализацию этого интерфейса на Java / Scala, которая на самом деле использует трехмерный массив. Альтернативой может быть карта, ключи которой являются координатами, а значения - состояниями блоков. Преимущество состоит в том, что не будет никакого реального ограничения на размер игрового мира, и пустые блоки не будут занимать какую-либо память (для координат с пустыми блоками на карте нет записи). Недостатком, очевидно, может быть производительность.
В этот момент вы можете рассмотреть возможность более плотной упаковки данных, если они занимают слишком много памяти. Вы можете использовать наборы битов. Когда вы достигаете этой стадии, на самом деле имеет смысл использовать JNI для внедрения некоторого кода, написанного на C или C ++, в JVM. Таким образом, вы сохраняете игровую логику в Java / Scala и выполняете упаковку и поиск памяти в C.
Нет никакого реального смысла в создании общего «скриптового» источника, который может создавать Java / Scala и C / C ++ версию нативной части кода; нативные функции C / C ++ сильно зависят от оптимизаций, которые нельзя напрямую перевести на Java. Если вы хотите запустить сервер в «чистом режиме Java / Scala», то есть без функций JNI, просто используйте другие классы, которые вы создали в предыдущем шаге. Они могут быть немного медленнее, но они являются чистым байтовым кодом JVM. И поскольку вы сохранили их простыми, нет никакой опасности, что вам придется расширять их или вносить в них новые ошибки. По крайней мере, накладные расходы на создание или адаптацию генератора кода на разных языках программирования намного, намного больше, чем поддержание двух отдельных баз кода, особенно когда реализация Java / Scala действительно проста.
Конечно, битовая упаковка только уводит вас.Возможно, вы захотите заметить, что некоторые части игрового мира почти полностью пусты (особенно над поверхностью), а другие содержат огромные области, заполненные блоками одного типа (например, подземные области, состоящие почти исключительно из камня).Поддержание огромной структуры памяти с такой избыточностью на самом деле является пустой тратой памяти.Таким образом, вы, вероятно, рассмотрите возможность упаковки игрового мира в дерево, где каждый узел обозначает большую кубическую область игрового мира, и дети делят его дальше, вплоть до листьев, которые описывают содержимое только одной конкретной координаты игрового мира.Когда у одного узла есть только дочерние элементы одного и того же типа контента, вам не нужно хранить дочерние элементы.Просто срежьте дерево в этой точке и попросите узел сказать: «Вам не нужно смотреть дальше. Я полон воды, поэтому каждая координата, которая находится внутри меня, указывает на водную плитку».- Это значительно уменьшит использование памяти.Только части игрового мира, которые на самом деле являются сложными, будут занимать много памяти, и это правильно.Более скучные части мира занимают лишь несколько узлов в дереве.Это хорошо.А поскольку это дерево, то прохождение его от вершины к листу занимает в среднем логарифмическое время.Это очень хорошо!- Конечно, вы должны сохранить дерево изменчивым.Если скучная часть мира, представленная только одним узлом, изменится, вам нужно взломать этот узел и разделить его на двух или более дочерних узлов.Если после этого все снова станет простым, вы можете снова присоединиться к дочерним элементам и вырезать дерево.
Одна вещь, которую вы можете заметить на этом этапе: упаковка памяти и оптимизация доступа больше не являются проблемой.Такое дерево не может быть разумно оптимизировано с помощью встроенных функций для методов хранения и поиска.Если вы сможете получить больше, чем, скажем, 10% от такой оптимизации, это было бы невероятно и невероятно впечатляюще.(Скорее всего, это может означать, что аналоги Java / Scala были плохо оптимизированы.) Такое минимальное увеличение скорости не оправдывает огромных дополнительных усилий, которые необходимо вложить в него.Скорее вставьте лучший процессор в машину и наслаждайтесь временем, которое вы сэкономили, поедая мороженое, наблюдая за доктором Хаусом или продолжая улучшать игру и делать ее более интересной и привлекательной для игроков.Создавая что-то ценное, что действительно улучшит продукт.
Но это все-таки не так.Если я правильно помню, начальное состояние мира Minecraft генерируется процедурно.Используя фрактальные алгоритмы, вы действительно можете создавать бесконечные территории, которые в мгновение ока кажутся сложными и естественными.Поэтому вместо того, чтобы предварительно вычислять содержимое игрового мира и сохранять его в огромной структуре данных, вы можете использовать процедуру генерации мира в качестве метода поиска: вместо поиска содержимого координаты из памяти, просто рассчитайте его, используяалгоритм.Таким образом, начальное состояние мира может быть полностью сохранено в четырех байтах: начальное значение алгоритма.
Конечно, мир не всегда будет оставаться в этом состоянии.Когда игрок (или что-то еще) меняет мир, тогда это то, что вам нужно хранить.Таким образом, вы храните только мировое начальное значение и внесенные в него изменения.Всякий раз, когда вы просматриваете содержимое координаты, попробуйте найти его в хранилище измененных плиток.Когда это там, используйте эту информацию.Когда его нет, по умолчанию используется алгоритм генерации процедурного мира.Это значительно сократит потребление памяти.А поскольку изменения в мире относительно невелики и содержат огромные пустые области, вам будет относительно легко написать структуру данных, которая будет хранить эти изменения быстро и эффективно.Опять же, написание собственного кода для этого не приведет к значительному увеличению производительности и не стоит усилий.
Можно оптимизировать еще кое-что: алгоритм генерации процедурного мира.Это один из ключевых компонентов, который вы можете написать на C или C ++.Это должен быть относительно небольшой и не слишком большой код, но он требует много математики и будет вызываться очень часто.Так что оптимизируйте его хорошо и сделайте из него небольшую библиотеку JNI.Это даст вам огромный прирост производительности, который стоит усилий.(Конечно, вы, возможно, захотите сначала выполнить реализацию Java / Scala. Если это уже достаточно быстро, тогда нет необходимости попадать в проблему JNI.)
Если процедура генерации мира все еще должна быть слишкоммедленно, тогда вы можете реализовать кеш для него.Кэш может даже превентивно генерировать некоторые из окружений игрока, когда у JVM есть немного ленивое время.
Я изложил этот процесс разработки для вас как итерацию нескольких идей, одна лучше, чем предыдущая.Изображение, которое вы бы начали писать библиотеки оптимизированного кода C / C ++ уже на первом этапе.Это было бы пустой тратой времени;Вы могли бы выбросить все это на более поздних этапах.Эффективное хранилище массивов, использующее упаковку битов, написанное на C, - это хорошо, но оно абсолютно бесполезно, когда вы реорганизуете свой мир в дерево с разделенными двоичными пространствами.
Так что не переусердствуйте,Если вы не можете создать минимально работающую (но медленную и неоптимизированную) версию только в Java / Scala, то вы не можете создать оптимизированную версию в C / C ++ или в каком-либо кросс-компилируемом языке сценариев.Сначала делайте простые версии, затем тестируйте производительность и оптимизируйте только тогда, когда это действительно необходимо.Не начинайте свой проект, сначала создавая концепции для оптимизации.Подобные оптимизации должны быть последними вещами, над которыми вы будете работать.