最近在研究Chaos系统时偶然间在UE中发现了一种巧妙的SOA数据结构,目前来看似乎没有文章讲过,所以特地分享一下,目前版本UE5.2preview1
SOA和AOS
全称是Array of structures (AOS)和Structure of arrays (SOA),简单来说就是两种数据的组织方式,一种把整个结构体数据组织成数组,一种把结构体数据的每一项单独抽成数组组织成结构体,鉴于知乎上已经有大佬简单清晰的说明了他俩的区别与优劣,所以在这里贴个链接,更加详细的解释看大佬说的就可以了
在这里我们只需要简单粗暴的下个结论,SOA在顺序访存上相较于AOS有着不小的优势,还可以通过SIMD等操作提高计算效率,所以在很多应用领域中都可以通过这种方式来对运行效率进行优化,但是SOA写起来会有一点点,怪怪的
// Structure of arrays
struct Vector{
float x[1000];
float y[1000];
float z[1000];
};
显然在工程中直接这么写既不优雅也不方便维护,所以在UE中,FManagedArrayCollection出现了,概括一点说FManagedArrayCollection就是为了更方便维护SOA做的一个轻量化抽象,而在这里对应的数组,就是TManagedArray,相关源码在Engine/Source/Runtime/Experimental/Chaos/Public/GeometryCollection目录下,目前主要在Chaos下的GeometryCollection中有用到,十分有趣
关于FManagedArrayCollection简单使用在声明的上方的注释中Epic已经非常热心的写明了

简单来说,FManagedArrayCollection有数个Group,而在Group中,又包含有数个Attribute,每个Attribute都是一个数组,而FManagedArrayCollection为你提供的功能,就是保证单个Group中所有Attribute数组的元素数量,都是相同的.甚至还可以runtime添加Group和Attribute,看起来就像这样
ManagedArrayCollection{
Vertex{
Vector Position[90];
Vector VertexColor[90];
}
Face{
IntVector Index[30];
}
}
//执行AddElement(10,"Vertex")后
ManagedArrayCollection{
Vertex{
Vector Position[100];
Vector VertexColor[100];
}
Face{
IntVector Index[30];
}
}
//再执行AddAttribute<Vector>("Normal", "Vertex")后
ManagedArrayCollection{
Vertex{
Vector Position[100];
Vector VertexColor[100];
Vector Normal[100];
}
Face{
IntVector Index[30];
}
}
看起来是不是大大减少了SOA的维护成本和理解成本,接下来我们再浅析一下具体实现,如有谬误,还请大佬指正
实现
首先目光转到FManagedArrayCollection包含的成员变量,暂不考虑序列化的情况下,有且只有三个成员变量分别是下图这仨:

可以看到在这里是用FName来标识group的,FManagedArrayCollection本身存储了两个Map,一个用于存储GroupInfo,GroupInfo中目前就一个size信息,而另一个Map则存储了真正的SOA抽象数据,FKeyType直接采用的TTuple<FName, FName>,第一个FName为Attribute,第二个为Group(其实我想吐槽一下为什么不是反过来的,理论上来讲不应该Group是Attribute的上级么),至于FValueType则放一些相关数据,如下图

其中关键数据就在于用了一个枚举来存储类型和一个指向真正存储数据的数组的指针,枚举存储类型信息的操作也很简单,点进去就会发现是一堆宏,然后声明的时候把类型列表inline进去
声明的地方
内联的一堆数据结构,如果后面需要扩展支持的类型的话直接在这里加就可以
至于剩下的crud,dirty和序列化什么的,就都是老生常谈的内容了,随便打开一个看看就可以举一反三了,本质上都是根据上面那两个map来检索数据,在此不表
用法
直接继承FManagedArrayCollection类便可,内部存储的数组可以直接写成成员变量,然后在构造函数里把他们全都塞进上述的两个map里,就像FGeometryCollection里写的那样
塞进map里
各种各样的声明
至于TManagedArray,点进去我们可以看到其基类FManagedArrayBase注释中说明了这个数组的特点,即不允许随意缩放数组大小,仅相关联的Managers可以这么做,而Managers,仅有一个上面啰里啰嗦了这么多的FManagedArrayCollection,从而在防止了误操作带来的debug地狱

一点点总结
其实这个东西自己写一个也不难,不过好在UE自己提供了这么个玩意,以后想要一个类似需求就可以参考这个实现,而且很大的一个优势就是轻量简单易懂,整套东西加起来也不过寥寥两三千行,大部分还都是喜闻乐见的内容,在如此简单轻量的前提的同时还是ue高效的破碎系统的基石,就很强
谨以此文,分享这两天挖源码的收获,如有谬误,还请大佬在评论区指出
原文发表于知乎。