所以我在Gamasutra上阅读了John Carmack的采访,其中他谈到了他所谓的“生活在内存映射文件中的实时C ++对象”。以下是一些引用:
JC:是的。我实际上从中获得了多项好处...最后一个iOS Rage项目,我们附带了一些新技术,这些技术使用一些聪明的东西来制作生活在内存映射文件中的实时C ++对象,由闪存文件系统支持在这里,我想要构建我们未来在PC上的所有工作。
...
我在这里向自己发出命令,我希望在我们的PC平台上玩两秒钟的游戏,所以我们可以更快地迭代。而就在现在,即使使用固态驱动器,你也会受到加载时所做的所有事情的支配,因此需要这种不同的规则才能说“一切都将被毁灭并用于相对地址”。所以你只要说,“映射文件,我所有的资源都在那里,它在15毫秒内完成。”
(可以找到完整的访谈here)
有人知道Carmack在谈论什么以及你将如何设置这样的东西?我在网上搜索了一下,但我似乎找不到任何东西。
答案 0 :(得分:7)
这个想法是,通过内存映射访问该文件,您可以随时将全部或部分程序状态序列化到文件中。这将要求您没有通常的指针,因为指针仅在您的过程持续时有效。相反,您必须存储映射开始的偏移量,以便在重新启动程序并重新映射文件时可以继续使用它。这种方案的优点是你没有单独的序列化,这意味着你没有额外的代码,你不需要一次保存所有状态 - 而是你的(全部或大部分)程序状态是始终以文件为后盾。
答案 1 :(得分:1)
您可以直接使用placement new,也可以使用自定义分配器。
查看EASTL(子集)STL的实现,该STL专门适用于自定义分配方案(例如在嵌入式系统或游戏控制台上运行的游戏所需)。
EASTL的免费子集在这里:
答案 2 :(得分:1)
我们使用多年来称为“相对指针”的东西,这是某种智能指针。它本质上是非标准的,但在大多数平台上都能很好地工作。它的结构如下:
template<class T>
class rptr
{
size_t offset;
public:
T* operator->() { return reinterpret_cast<T*>(reinterpret_cast<char*>(this)+offset); }
};
这要求所有对象都存储在同一共享内存中(也可以是文件映射)。它通常还要求我们只存储我们自己的兼容类型,以及编写自己的分配器来管理内存。
为了始终拥有一致的数据,我们通过COW mmap技巧使用快照(在linux上的用户空间中工作,不知道其他操作系统)。
随着向64位的大转移,我们有时也会使用固定映射,因为相对指针会产生一些运行时开销。通常有48位的地址空间,我们为应用程序选择了一个保留的memry区域,我们总是将这样的文件映射到。
答案 3 :(得分:1)
这让我想起了一个文件系统,我提出了在很短的时间内加载了CD的级别文件(它将加载时间从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对象只包装了标准IO函数。
在流文件构建中,IFile对象还包装了标准IO,但具有写入流文件(所有者FileSystem打开流文件)的每个字节的附加功能,并写入任何文件的返回值指针位置查询(因此,如果需要知道文件大小,则将该信息写入流文件)。这可以将各种文件连接成一个大文件,但只将实际读取的数据连接起来。
发布模式会创建一个不打开文件或在文件中搜索的IFile,它只是从流文件中读取(由所有者FileSystem对象打开)。
这意味着在发布模式下,所有数据都在一系列连续的读取中读取(操作系统会很好地缓冲它),而不是大量的搜索和读取。这对寻求时间非常慢的CD来说是理想的选择。毋庸置疑,这是为基于CD的控制台系统开发的。
副作用是数据被剥离了通常会被跳过的不必要的元数据。
它确实有缺点 - 关卡的所有数据都在一个文件中。这些可能会变得非常大并且数据无法在文件之间共享,如果您有一组纹理,比如说,这两个或更多级别的通用,则数据将在每个流文件中重复。此外,每次加载数据时,加载过程必须相同,您不能有条件地跳过或向元素添加元素。
答案 4 :(得分:0)
由于Carmack表示许多游戏(和其他应用程序)加载代码的结构很简单,所以很多小的读取和分配。
而不是这样做,你只需要一个fread
(或等效的)将一个级别文件写入内存,然后再修改指针。