如何在连续分配中处理对象删除?

时间:2012-10-06 19:09:29

标签: data-oriented-design

我最近发现了面向数据的设计的好处。看起来很令人印象深刻其中一点是按类型和访问对数据进行分组,而不是在对象中,而是在数组中,以防止缓存未命中和更好的处理。

所以在游戏中我们仍然有实例,用户可以销毁它们中的任何一个(不仅仅是阵列中的最后一个)。我无法弄清楚如何有效地处理数组中间的对象删除。

我有一个想法:拥有isAlive值,但这将对条件数量产生很大影响,因为每个对象在处理,绘图中都会被多次检查......

另一个想法是将整个数组移位以填充必须删除的空间,但这会在删除时消耗大量资源。

国防部怎样才能解决这个问题?

所以提出要求:

  • 必须是数组才能减少在DOD中分配的缓存未命中
  • 必须删除快速随机位置对象,max o(log n)
  • 对象自创建以来无法移动,因为它们可能在未知位置被引用,因此会导致程序错误行为

2 个答案:

答案 0 :(得分:4)

实际上很简单,不要使用直接引用。使用一个间接层,如ID。例如:

假设您有一个管理所有Foo“对象”的FooManager(不是OOP意义上的对象,每个Foo属性的数组集合)。据我所知,你现在所做的只是返回一个索引。假设Foo#42是Foo,其数据位于所有阵列的索引42处。现在你要删除Foo#42,它会在你的数组中打个洞。您可以移动所有其他数组条目,但随后Foo#43不再指向实际索引43。

因此我们添加一个ID表,而不是返回索引,而是返回一个ID。当您创建新的Foo时,将其数据添加到数组中的第一个空闲索引(假设为42)。然后你生成一个新的未使用的ID(比方说1337)。现在你可以更新ID表(一个(哈希)映射很适合这个)说ID 1337指向索引42.现在你可以将ID 1337返回给调用函数。 (注意调用函数如何找不到实际存储数据的位置,这是无关的信息)

下次需要从另一段代码更新Foo时,会使用ID。因此,使用ID 1337调用FooManager的setBar函数,并将新的Bar值作为参数调用。 FooManager在其ID表中查找1337,发现它仍位于索引42处,并使用新的Bar值更新Bar数组索引42。

现在这是这个系统得到它的值的地方,让我们删除Foo 1337.使用ID 1337作为参数调用FooManager的removeFoo。它查找1337,它位于索引42.但是,与此同时,增加了10个新的Foos。所以我们现在可以做的只是将索引42处的值替换为52处的值,实际上将52移动到42.这将导致旧系统中的大问题,但现在,我们只需要更新索引表。因此,我们查找哪些ID指向52(假设它是1401)并将其更新为42.下次有人尝试更新ID为1401的Foo时,它会在索引表中查找并发现它位于索引42处。

因此我们将数据保存在连续内存中,删除只需要非常少量的数据副本操作(Foo的每个属性一个副本),而FooManager的“外部”代码甚至从未意识到发生了什么。它甚至解决了死亡的反驳问题。假设一些代码仍然具有删除的1337 ID(悬空refence,这很糟糕!),当它试图访问/更改它时,FooManager查找它,看到1337不存在(任何更多)并且可以生成一个很好的干净警告/ error和callstack,它允许您直接识别哪些代码仍然具有悬空引用!

只有一个缺点,即ID表中的额外查找。现在哈希表可以非常快,但每次修改Foo对象时它仍然是一个额外的操作。但是,在大多数情况下,从经理外部访问的次数远远少于在经理内部访问。当你有一个BulletManager时,它会每帧更新每个项目符号,但是访问项目符号以更改/请求某些内容并调用创建/删除项目符号的可能性较小。 如果这是另一种方法,您应该更新您的数据结构以针对该情况进行优化。再说一次,在这种情况下,数据位于内存中的情况并不重要,因此您可以使用数组中的“漏洞”,甚至使用完全不同的数据布局。

答案 1 :(得分:0)

它取决于约束和工作量,但一种方法是将已删除的元素与数组中的最后一个元素交换,然后从末尾删除已删除的元素(pop_back)。这假设数组的顺序不是特别重要。另一种方法是稀疏数组,它可能在内存预算固定的环境中工作。

编辑:如果您正在维护数组中的外部指针,则可以使用智能指针管理它们,当移动数组元素时,智能指针的基础地址会更新(shared_ptr::reset)。你最终得到2个相同长度的并行数组:

typedef std::vector<thing> thingVec;
thingVec things;

// smart pointers for each object
std::vector<boost::shared_ptr<thingVec::iterator>> references;

createThing函数中,您将返回一个带有自定义删除器的shared_ptr(一旦所有引用都消失,它将自动执行数组更新):

http://www.boost.org/doc/libs/1_51_0/libs/smart_ptr/sp_techniques.html#static

智能指针可以指向具有多个字段的结构,这些字段最终存储在不同的数组中,只要自定义删除程序知道在删除元素时要压缩哪些数组。