Как Factorio умудряется работать без лагов с таким числом элементов на экране

Магия, бережная работа с объектами/компонентами и никакого ECS.

f1e4711f01d7da8843762dad6efae69f.png

Разработчик Factorio поделился некоторыми подробностями работы внутренних систем, в частности, рассказал про ECS.

Большая часть игры не использует никакого подобия entity component system. ECS отлично подходит, когда нужно применить некоторое преобразование к набору данных независимо от каких-либо других переменных. К примеру, добавить вектор движения к текущей позиции. Но если у вас 5–10 переменных связаны со сменой позиции, то толку от ECS не будет.

Примером этого являются логистические и строительные роботы. У них есть много разных условий:

  • Хватит ли энергии у робота, чтобы совершить полное движение?

  • Вышла ли цель за пределы зоны логистической сети, и робот должен отменить задание, которое ему было приказано?

  • Есть ли вообще у него работа, которую он должен выполнять, или он просто ждёт команды?

Общая проблема такова: все эти проверки используют данные, характерные для логистических роботов. Если бы робот использовал «компонент положения/движения», этот компонент не имел бы понятия ни об одном из этих условий. Можно попытаться включить эти условия в сам компонент, но он вряд ли будет очень читабельным и, вероятно, будет не очень по производительности.

Боевая система использует два вида снарядов:  хитсканы и проджектайлы.

eb54f3535d8a4ccf461da8e6c99a8786.png

Для отрисовки логика прогоняет видимую область экрана в несколько потоков, находя, что нужно отобразить, и собирая информацию, которая позже отправляется на GPU.

Основные моменты, благодаря которым в Factorio всё хорошо с производительностью:

  1. Система быстрого сна/пробуждения, когда сущностям не нужно выполнять работу. Когда объект «засыпает», то он полностью исключается из цикла обновлений, пока что-то внешнее снова его не включит. Время сна/пробуждения O (1). Большинство вещей большую часть времени в ожидании изменения состояния. Например: если в сборочной машине заканчиваются ингредиенты, она просто выключается. Как только что-то добавляет ресурсы, действие помещения предметов в инвентарь уведомляет машину о том, что они были добавлены, что «пробуждает» машину.

  2. В худшем случае никакая часть логики обновления не может превышать O (N); если обновление 5 000 машин занимает 1 миллисекунду, то 10 000 должно занять максимум 2 миллисекунды. В идеале менее 2 миллисекунд, но это редко возможно.

  3. Уменьшение работы с кусками оперативки, которые необходимо тыкать каждый тик. Процессоры очень быстрые в наши дни, и основными ограничителями в большинстве игр-симуляторов являются загрузка памяти в CPU и выгрузка обратно в ОЗУ.

Касательно пробуждения/засыпания, используют свою реализацию doubly linked circular intrusive list:

  • Обновляемые объекты сами по себе являются нодами в списке, поэтому ноды не нужно аллоцировать или освобождать при добавлении или удалении объекта из списка.

  • Поскольку это двусвязный список, объект может за константное время проверить, прилинкован ли он в данный момент, и может за константное время отлинковать себя (взять предшествующую ноду и линкануть на следующую, взять следующую и линкануть на предыдущую, а себя в nullptr).

  • Поскольку список является закольцованным, «конец» всегда является самим списком, что означает, что вы можете добавлять и удалять во время итерации и всегда будете знать, когда дошли до конца.

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

© Habrahabr.ru