"Живые объекты С++, которые живут в картографических файлах с памятью"?
Итак, я прочитал это интервью с Джоном Кармаком в 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
(или эквивалент), например, файл уровня в память, и только после этого исправляете указатели.