面向数据的设计——如何解决数据依赖?

Data oriented design - how are data dependencies solved?

我看了"Data-Oriented Design and C++" by Mike Acton,觉得挺有意思的。我不明白数据依赖是如何解决的。

假设我有一个简单的 2d 引擎: *物理数据 - 处理物理 * 图形数据 - 渲染精灵 * 声音数据 - 播放声音

图形数据和声音数据取决于存储在物理数据中的位置。位置可以从物理数据中引用,但在我看来,这扼杀了 DOD 的全部意义——在相同的内存位置拥有所需的数据。

在面向数据的设计中如何处理这种情况?

不确定 DOD 是否完美,但如果您的 ID 或句柄在整个物理和图形子系统中共享,您可以让物理子系统生成一个位置数组和所有更新对象的 IDs/handles 并使用它作为图形子系统的输入。

DOD 更像是一种设计体系结构的通用方法,它侧重于如何首先有效地表示数据。没有单一的方法可以做到这一点。 Linus Torvalds 展示了对 Linux 内核和 Git 等等的心态,但它是一个与游戏截然不同的领域。最主要的是,他首先关注的是如何高效地表示数据。

作为一个基本示例,如果您正在设计图像处理应用程序,那么如果您没有以面向数据的方式思考,而是专注于如何最轻松地支持最广泛的像素格式并提出使用最简单的接口,您可能会想出一个抽象 Pixel 甚至可能是每个像素的堆分配。那时你要支付虚拟指针(通常大于像素本身)、动态调度 per-pixel、可能是另一层间接寻址的成本,并且可能会完全丢失空间局部性。相反,如果您首先考虑如何有效地表示数据,那么您可能会在更粗略的 Image 级别进行抽象(像素的抽象集合,对于给定图像可能有数百万像素),如果您完全抽象的话您无需为每像素级别的此类开销付费。

也就是说,对于游戏来说,处理您所谈论的内容的常用方法通常是使数据可集中访问。这似乎违反了 SE 原则,但通常情况下,如果您使用实体组件系统之类的东西,任何给定类型的组件通常只会被少数系统访问。结果,该数据的范围趋于小到足以有效地保持不变量。

至于游戏中可能发生的事件,例如声音系统可能想要播放声音的物理系统中的两个实体相互碰撞,有许多方法可以解决这个问题,以保持物理和音响系统相互解耦。一种是使用事件队列。

至于一个系统需要的数据也被另一个系统共享,一般来说还是比较实用的。如果您想 运行 这些系统彼此并行,它们仍然必须复制共享数据,可能会更新它,并以某种方式协调它们的结果。也就是说,在我看来,避免摆弄它并只是并行化系统正在做的事情(例如:使用并行 for 循环)会更有效率,因为通常 ECS 中只有少数系统是热点并完成繁重的工作,您可以轻松地跨线程分配这些特定系统的工作,而无需尝试同时 运行 许多系统并打开蠕虫病毒。