"Живые объекты С++, которые живут в картографических файлах с памятью"?

Итак, я прочитал это интервью с Джоном Кармаком в Gamasutra, в котором он рассказывает о том, что он называет "живыми объектами С++, которые живут в картографических файлах с памятью". Вот несколько цитат:

JC: Да. И я на самом деле получаю много преимуществ от этого в этом... Последний проект iOS Rage, мы поставляем с некоторыми новыми технологиями, которые используют некоторые умные вещи для создания живых объектов С++, которые живут в картографических файлах памяти, поддерживаемых файловой системой флэш-памяти на вот как я хочу структурировать всю нашу будущую работу на ПК.

...

Мои марширующие заказы на себя здесь, я хочу, чтобы игровые нагрузки составляли две секунды на нашей платформе для ПК, поэтому мы можем повторять это намного быстрее. И прямо сейчас, даже с твердотельными дисками, во всем мире вы занимаете все, что вы делаете во время загрузки, поэтому требуется, чтобы эта различная дисциплина позволяла сказать: "Все будет уничтожено и использовано в относительных адресах", поэтому вы просто говорите: "Сопоставьте файл, все мои ресурсы находятся прямо там, и это делается за 15 миллисекунд".

(Полное интервью можно найти здесь)

Кто-нибудь знает, о чем говорит Кармак, и как вы настроите что-то подобное? Я немного искал в Интернете, но я не могу найти что-либо на этом.

Ответы

Ответ 1

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

Ответ 2

Вы использовали бы размещение new, либо напрямую, либо через пользовательские распределители.

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

Свободное подмножество EASTL находится здесь:

Ответ 3

Мы используем в течение многих лет то, что мы называем "относительными указателями", который является своего рода умным указателем. Он по своей сути нестандартен, но работает на большинстве платформ. Он структурирован следующим образом:

template<class T>
class rptr
{
    size_t offset;
public:
    T* operator->() { return reinterpret_cast<T*>(reinterpret_cast<char*>(this)+offset); }
};

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

Чтобы всегда иметь согласованные данные, мы используем моментальные снимки с помощью трюков COW mmap (которые работают в пользовательском пространстве на Linux, не имеют представления о других ОС).

С большим переходом на 64-битный мы также иногда просто используем фиксированные сопоставления, так как относительные указатели накладывают определенные накладные расходы. Обычно с 48 бит адресного пространства мы выбрали зарезервированную область memry для наших приложений, которую мы всегда сопоставляем с таким файлом.

Ответ 4

Это напоминает мне файловую систему. Я придумал этот загруженный файл уровня компакт-диска в удивительно короткий промежуток времени (он увеличил время загрузки от 10 секунд до почти мгновенного), и он работает и на не-CD-носителях. Он состоял из трех версий класса для обертывания файлов IO-функций, все с одним и тем же интерфейсом:

class IFile
{
public:
  IFile (class FileSystem &owner);
  virtual Seek (...);
  virtual Read (...);
  virtual GetFilePosition ();
};

и дополнительный класс:

class FileSystem
{
public:
  BeginStreaming (filename);
  EndStreaming ();
  IFile *CreateFile ();
};

и вы напишете код загрузки, например:

void LoadLevel (levelname)
{
  FileSystem fs;
  fs.BeginStreaming (levelname);
  IFile *file = fs.CreateFile (level_map_name);
  ReadLevelMap (fs, file);
  delete file;
  fs.EndStreaming ();
}

void ReadLevelMap (FileSystem &fs, IFile *file)
{
  read some data from fs
  get names of other files to load (like textures, object definitions, etc...)
  for each texture file
  {
    IFile *texture_file = fs.CreateFile (some other file name)
    CreateTexture (texture_file);
    delete texture_file;
  }
}

Затем у вас будет три режима работы: режим отладки, режим создания файла потока и режим выпуска.

В каждом режиме объект FileSystem будет создавать различные объекты IFile.

В режиме отладки объект IFile просто завернул стандартные функции ввода-вывода.

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

В режиме освобождения будет создан IFile, который не открыл файлы или не искал файлы, он просто считывается из потокового файла (как открывается объектом FileSystem владельца).

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

Побочным эффектом является то, что данные лишены ненужных метаданных, которые обычно пропускаются.

У него есть недостатки - все данные для уровня находятся в одном файле. Они могут стать довольно большими, и данные не могут быть разделены между файлами, если бы у вас был набор текстур, скажем, общих для двух или более уровней, данные будут дублироваться в каждом потоковом файле. Кроме того, процесс загрузки должен быть одинаковым при каждом загрузке данных, вы не можете условно пропускать или добавлять элементы на уровень.

Ответ 5

Поскольку Carmack указывает, что многие игры (и другие приложения) загружают код структурированным lika много небольших чтений и распределений.

Вместо этого вы делаете одиночный fread (или эквивалент), например, файл уровня в память, и только после этого исправляете указатели.