Войти
ПрограммированиеСтатьиОбщее

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, сходный подход, но разные идеи.

С уважением,
Руслан Абдикеев.

Страницы: 1 2

#архитектура, #движок, #спецификации

29 июля 2003 (Обновление: 23 фев 2011)

Комментарии [364]