Compile-time vs run-time: назад в будущее (2 стр)
Автор: Руслан Абдикеев
“Premature optimization is the root of all evil in programming.”
– Tony Hoare
Мы наблюдаем пропасть между различными представлениями. Представление 3D-модели у игрового программиста в данном случае полностью характеризуется фразой «у самолета есть бокс левого закрылка». Представление 3D-модели у рендера в данном случае – это scene graph, иерархия трансформаций или что-нибудь подобное. Совмещение различных взглядов на один и тот же объект – основная идея многих структурных паттернов.
Проблемы в этом коде вызваны тем, что абстрагирование и позднее связывание вызывают потери информации о структуре связей кода с контентом. Эта информация продолжает присутствовать в игровом контенте, но в неявной форме. Она скрыта и недоступна для внешних (управляемых данными) инструментов.
Мы хотели гибкости, но получили вседозволенность. Плюс ко всему, подобный подход вынуждает проектировщика движка принять массу произвольных решений о структуре взаимодействий игрового объекта с 3D-моделью, поскольку он просто не знает, что нужно программисту. Так рождаются перлы вроде этого: «При копировании 3D-модели мы будем копировать только иерархию трансформаций; если игровому программисту потребуется поменять еще что-нибудь – пусть сам копирует».
Обратите внимание на выделенные слова. Тот, кто использовал Java, C#, Pascal или нешаблонные контейнеры в C++, узнает знакомую проблему, известную как оборотная сторона ОО и как пример архитектурного «abstraction penalty». Рассмотрим реализацию стека на C# (псевдокод):
// это стек, который вроде бы можно повторно использовать class Stack { public void Push([b]object[/b] item ); public [b]object[/b] Pop( ); } // но конкретному клиенту нужен стек целых чисел – ни больше, ни меньше Stack s = new Stack( ); s.Push( ( [b]object[/b])123 ); int n = ( [b]int[/b])s.Pop( );
Те же проблемы. Стек слишком гибок: он может хранить любые объекты. Стек не соответствует задаче – нам нужен стек только целых чисел. Такой стек дорог и уродлив из-за необходимости преобразований в/из object на стороне клиента. Этот стек не дает гарантий целостности и провоцирует вседозволенность: если кто-то запихнет в него строку, мы узнаем о проблеме только в run-time.
Но вот тот же стек на C# with Generics (псевдокод):
// это стек, который действительно можно повторно использовать class Stack<T> { public void Push(T item ); public T Pop( ); } // конкретный клиент получает стек целых чисел – ровно то, что ему нужно Stack<int> s = new Stack<int>( ); s.Push( 123 ); int n = s.Pop( );
Что же произошло? Мы ввели формальную спецификацию. Мы утеряли вседозволенность.
Явная параметризация типов (одна из разновидностей формальной спецификации) в языках программирования переносит время связывания на момент компиляции и дает эффективность, удобство и гарантии целостности, при этом сохраняя гибкость и расширяемость.
Давайте проделаем то же самое в нашем примере с закрылком.
Введем в движок описание, которое послужит формальной спецификацией представления игрового программиста о 3D-модели. Это описание задает как атрибуты 3D-модели, используемые/модифицируемые игровым кодом, так и требования к 3D-моделям:
game FighterAce { scene Plane { instance LaGG3 “ЛаГГ-3”; instance P38L “P-38L”; readonly FlapsLeft.box = Mesh(“FlapsLeft”).Box; } }
Для архитектур, управляемых данными – это вполне естественный шаг. Требования к модели используются при экспорте, оптимизации, сборке ресурсов. Информация о связях 3D модели с игровым кодом позволяет сформировать эффективный memory layout, удобный интерфейс к объекту и исключить копирование ненужных данных, поскольку нам точно известно, что хочет программист.
Вот как выглядит реальный C++ код получения бокса левого закрылка в движке, управляемом спецификациями:
// получение бокса левого закрылка
render::box b = Plane.FlapsLeft.box;
Внутренние атрибуты 3D-модели стали доступны нам в естественной для игрового кода форме. Мы можем спокойно полностью поменять внутреннее представление 3D-модели: на игровом коде это не скажется.
Заметьте, что приведенный выше код – это не синтаксический сахар, скрывающий всю ту же уродливую и неэффективную реализацию. Вот как выглядит часть автоматически сгенерированного C++ интерфейса для игрового программиста (общая идея):
class Plane : public render::u_korpus { public: struct FlapsLeft_t { render::box8 box; }; enum instance_t { LaGG3, P38L } instance; FlapsLeft_t FlapsLeft; static Plane* Load(instance_t inst ); Plane* Copy( ) const; void Destroy( ); };
Мы рассмотрели лишь малую толику преимуществ, которые дает использование формальных спецификаций. Замечу лишь, что даже для простого случая построения моста от игрового представления о 3D-модели к ее рендерному представлению, преимущества использования формальных спецификаций неоспоримы:
- Высокая производительность
- Минимальные требования к ресурсам
- Отсутствие сложного lifetime management
- Отсутствие в необходимости библиотек типов, сериализации и reflection
- Эффективная работа с памятью
- Полностью автоматическая сборка
- 100% гарантии целостности ресурсов
- Ясный, простой и эффективный игровой код
- Сохранены гибкость и расширяемость
Будущее систем разработки игр мне видится именно в этом.
Возможность крупномасштабных изменений структуры системы, не затрагивающих игровой код (просто перерегенерируется все, что надо) плюс возможность создать действительно повторно используемую модель игрового мира, – ради этого стоит один раз помучаться.
В следующих статьях я обязательно рассмотрю практические аспекты реализации подобной системы, структуру игрового объекта и схемы его взаимодействий с «низкоуровневыми» подсистемами. На основе двух вышедших игр (Fighter Ace 3.x и PulseRacer) мы обсудим за и против подобного подхода.
Мне будет очень приятно пообщаться с заинтересовавшимися коллегами как в online, так и в творческой, неформальной обстановке.
Литература:
1. GDC 2003 Game Object Structure: http://www.gamearchitect.net/Articles/GameObjectRoundtable.html
2. Kyle Wilson, on trends in game development: http://www.gamearchitect.net/Articles/Trends.html
3. Scott Bilas, GDC lectures.
4. Spinor Shark 3D, technical articles.
5. Intrinsic Alchemy, сходный подход, но разные идеи.
С уважением,
Руслан Абдикеев.
#архитектура, #движок, #спецификации
29 июля 2003 (Обновление: 23 фев 2011)