游戏蛮牛学习群(纯技术交流,不闲聊):539178957
游戏蛮牛 手机端
开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:2257

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 5567|回复: 35

[士郎] 关于Unity2018的新版ECS框架

[复制链接]  [移动端链接]
排名
3
昨日变化

6569

主题

7087

帖子

2万

积分

Rank: 16

UID
1231
好友
185
蛮牛币
8329
威望
30
注册时间
2013-7-29
在线时间
3365 小时
最后登录
2018-7-16

社区QQ达人活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

发表于 2018-4-3 10:44:24 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
1.jpg



先说说和它一同推出的,和ECS没直接关系的新特性:NativeArray<T>
至于ECS的定义咱们先跳过,也可以看看我之前的一篇文章: 一个无框架的ECS实现(Entity-Component-System)
按照官方的说法,以后还会有NativeList,NativeHashMap,NativeQueue之类(这些在C#端就能实现)。
NativeArray内部只能容纳值对象。而且在创建的时候除了指定length外,还需要指定allocator模式:
  • Temp(临时)
  • TempJob(Job内临时)
  • Persistent(持久)





按文档的说法,这种Native的数组更加有助于数据的连续性,对cache友好。但仅仅如此的话,一个普通的只包含struct的Array也可以达到同样的效果。
我个人是相当怀疑这玩意儿其实是在非托管堆上申请的内存,也就是和Mono的内存管理没啥关系,毕竟这东西是Jobs系统必须的东西,而Jobs看上去和C++部分走的很近。从名字上,也比较像。


先不管这个。至少从表面上看,它就是个普通的struct数组。



使用struct数组其实并不是为了减少GC(虽然实际上也大幅减少了),目的是为了更快的内存访问速度,在访问速度上通常能提高>100%的效率。这个和GPU里对纹理的cache是一样的道理,数据会先从内存到达二级缓存,在二级缓存内的读取速度远大于内存,而连续的内存会很大概率被提前放进二级缓存中。
在CPU的工作过程中,数据的读取成为瓶颈的情况其实并不罕见,只是我们平时使用的情况下,很多数据本来就是连续的,所以连续性相当不友好的“链表”才较少被使用。这种整块的内存结构也能减轻内存管理器整理内存的成本。


即使,数据读取确确实实没有成为瓶颈,但CPU内的晶体管可不是光用来计算的,也有很大一部分用于数据存取,减少它们的工作量会很明显的降低功耗和发热。


这点在GPU上提得比较多。但其实,只要是芯片,都是一样的。



另一个就是提供了SIMD的可能性……只是可能性而已。

不过C#里struct的使用有一个“复制”的大坑。因为C#不提倡使用指针,在存取数组里struct数据的时候只能多次复制,语言特性注定这个问题现在没法解决,所以虽然struct大家都知道好处,真去用它却是不现实的。


而最新版的C#7则适时提供了local/return ref进行了支持。


但目前的Unity最多支持到C#6。

2.jpg


所以这个框架的存在确实有可能会督促Unity将Mono编译环境升级到C#7,那样struct就能真正开始用了。没这玩意儿struct很容易搞成负优化的。

Jobs System
ECS的一个重要特性就是并发优势,甚至可以说,不支持多并发的ECS几乎没有价值。


(多线程可以支持多核执行,在单核天花板已经到达的现在,多核逻辑的时代也差不多该到了。而ECS是少数有可能自动实现多核的逻辑架构,因为提供了数据隔离的依据。)


Jobs是Unity自己的多线程框架,在这里就对ECS提供了支持。


[AppleScript] 纯文本查看 复制代码
public class RotationSpeedSystem : JobComponentSystem
{
    [ComputeJobOptimization]
    struct RotationSpeedRotation : IJobProcessComponentData<Rotation, RotationSpeed>
    {
        public float dt;
        public void Execute(ref Rotation rotation, [ReadOnly]ref RotationSpeed speed)
        {
            rotation.Value = math.mul(math.normalize(rotation.Value), math.axisAngle(math.up(), speed.Value * dt));
        }
    }
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var job = new RotationSpeedRotation() { dt = Time.deltaTime };
        return job.Schedule(this, 64, inputDeps); //64指单个时间片内至少循环64次
    } 
}

整套东西使用起来还是很简单的,用实现IJobProcessComponentData<T1, T2...>的类标记要读写的ComponentData,Execute是逻辑,然后用Schedule推入Jobs系统。按这个模板书写就行了。


[ReadOnly][WriteOnly]元标签可以指定Component的读写模式,将数据进一步分离。

剩下就是归系统内部调配了,会在不产生线程冲突的情况下分配逻辑线程的时间片,实现高效率的并发。


上面这个IJobProcessComponentData是ECS框架实现的一个比较简便的用法(不需要写循环,只写其中一个循环节就可以了),本身是IJob的一个扩展,可以点进去看看稍微原始一点的写法。如果在循环前后还有处理,或者需要对数组做筛选就必须用这个了。



ISharedComponentData
这个不是个底层相关特性,却是个很有趣的东西。
与普通的IComponentData不同,虽然都是struct,如果你先初始化它的值再给每个Entity用AddSharedComponent附加上它的话,并不会产生复制,只会占用一块内存(估计传过去的是地址)


只是读取很正常,但当你修改某个Entity上它的副本的时候,它会把自己立即复制一份,然后应用这个修改。
这玩意儿很类似PSS或者WindowDLL共享内存的做法,充分体现了“这是一个性能框架”的特征。



不过认真一想……

这东西不就是shareMaterial和material的关系么……



至于这个ECS框架本身……

其实还是很传统的。Entitiy实际上是一个int,ComponentData也就是普通的struct,System则是各种系统事件的接受者,类似MonoBehaviour。他们都只要实现对应的接口就能实现功能。在用EnitityManager创建Entity,添加Component之后(和原来的GameObject一样),System逻辑就会自动生效。
System的逻辑实现,是在特定的事件上(如OnUpdate)用自定义的代码拉取一些Component数组来执行自己的逻辑,主要用的就是GetEntities<Group>方法,这会按Group的内容,拉取指定的Component数组出来,然后遍历就可以了。


(注意拉取的是整个场景上同类型的所有Component,而不是一个)


拉取的时候会进行筛选,没有添加指定Component的Entitiy并不会被拉取(除此之外,还可以用ComponentGroup设置筛选条件)


总之就是每个System手动遍历一遍指定的Component执行逻辑了——但也可以不遍历,或者遍历多次,两两交叉遍历都是可以的,因为全都是自定义代码,就算是JobComponentSystem也一样。不要被上面的Job的简单例子误导,用IJob就可以在Jobs里写任意逻辑了,并不需要一定按循环走。


所以,System其实就是MonoBehaviour(删除数据后)的集合体,MonoBehaviour执行一个Entity的逻辑,而System执行全部的。


[AppleScript] 纯文本查看 复制代码
class RotatorSystem : ComponentSystem
{
    struct Group
    {
        RotateComponentData rotation;
        SpeedComponentData speed;
    }
    
    override protected OnUpdate()
    {
        float deltaTime = Time.deltaTime;
        foreach (var e in GetEntities<Group>())
        {
            e.rotation *= Quaternion.AxisAngle(e.speed * deltaTime, Vector3.up);
        }
    }
}

执行顺序





然后有心人就会意识到,MonoBehaviour执行顺序不确定的问题在这里依然存在……


虽然并发的系统本来就希望允许乱序执行嘛,系统还是给了方法确定顺序的,只要在System上加元标签就行了:
[UpdateBefore(typeof(RotationSpeedSystem))]


觉得可以乱序执行的部分不加就是了,在使用Jobs的时候不限制顺序才能增加性能。

对GameObject系统的支持
ECS和原有的GameObject+MonoBehaviour功能其实并没有完全重合。至少在现在,Transfrom,Renderer和物理组件依然必须挂接在GameObject上,并且不挂在GameObject上也无法在场景里编辑。


Entity本来是用EntityManager.CreateEntity()来创建,然后这样进行复制的(非必须)

EntityManager.Instantiate(entity, instances);



但是如果你把上面参数里的entity换成一个Prefab对象的话,它也能正常执行,并和GameObject.Instantiate一样生成一个可显示的GameObject对象,并且照常生成对应的entity。这些生成的entity就是和GameObject绑定的存在了,也就把GameObject引入了ECS系统。


对于引入ECS的GameObject,不管是GameObject还是上面的组件都可以和ComponentData一样管理。


虽然Renderer之类必须得是Behaviour组件,但是一些挂载在GameObject用来填写数据的脚本,我们显然还是希望它们进入系统用的是ComponentData,而不是旧版的MonoBehaviour。


另外,我们的ComponentData也是需要能够在编辑器环境显示以及编辑的,而ComponentData并不能挂在GameObject上。


这个框架提供了一个桥接类来处理这个问题:


[AppleScript] 纯文本查看 复制代码
   [Serializable]
    public struct Radius : IComponentData
    {
        public float radius;
    }

    public class RadiusComponent : ComponentDataWrapper<Radius> { } 


下面那个RadiusComponent 是可以正常挂到脚本上的。而在ECS系统内,它则会被处理隐式地处理成Radius。

依赖注入


使用[Inject]标签,可以根据字段类型自动注入一些数据。

[AppleScript] 纯文本查看 复制代码
public struct Group
{
        //获得某个类型的ComponentData
        public ComponentDataArray<Position> Position;
        
        //获得某个类型的Component(这里的Component指的是原本的Unity组件)              
        public ComponentArray<Rigidbody> Rigidbodies;

        //获得Entity列表
        public EntityArray Entities;

        //获得GameObject列表
        public GameObjectArray GameObjects;

        //标识排除所有包含MeshCollider的对象
        public SubtractiveComponent<MeshCollider> MeshColliders;

        //数据数量
        public int Length;
}
[Inject]Group m_Group;


和之前GetEntities<Group>不同,这次的Group的不会返回迭代器,所以Group里的字段本身就必须是数组。但依然会进行筛选,并且按Entity顺序排列。


[AppleScript] 纯文本查看 复制代码
[Inject]OtherSystem m_SomeOtherSystem;


用这个方法可以直接和另一个System通信。这个框架并没有提供任何和解耦相关的东西,所以System间通信都是直接调用。

[AppleScript] 纯文本查看 复制代码
[Inject]ComponentDataFromEntity<LocalPosition> m_LocalPositions;

Entity myEntity = ...;[/color][/size][/font][/align][align=left][font=微软雅黑][size=3][color=rgb(26, 26, 26)]
var position = m_LocalPositions[myEntity];

这个东西看上去和GetEntities<Group>差不多(除了只能获得一个Component外),但其实功能完全不同。GetEntities会做筛选,而它不会。
但为啥不直接用EntityManager.GetComponentData<LocalPosition>(myEntity)就不了解了,可能是性能问题吧。



EntityCommandBuffer
写过物体管理系统的都会遇到“遍历中增删对象”这个麻烦的问题。虽然确实可以用倒序遍历处理,但其实这样很不稳定,而且很可能出现删除未遍历到的对象这个问题,然后产生非必现错误。


通常的做法就是将所有这些操作延后执行。


这个框架里虽然没有这个问题,但是在多线程的时候,增删对象依然会产生较大的数据变动,这容易对同时运行的其他线程产生阻塞,就依然需要进行延后处理。


可以在一个调用次数靠后的System(SystemA)调用CreateCommandBuffer()创建EntityCommandBuffer对象,然后把它的实例传给其他需要增删对象的System(SystemB),调用这个实例的函数来负责增删。


这样增删操作会延迟到SystemA时才执行。


由于系统会特地将增删操作向后延迟,所以SystemA不做任何处理就会是最后一个执行的System,像示例里那样,用一个空System当这个SystemA也是可以的。


知乎@flashyiyi


评分

参与人数 2鲜花 +3 收起 理由
天涯沦落人 + 2 很给力!
liu18255603500 + 1

查看全部评分


跟我念“站长妹纸萌萌哒!”我说站长,你说YO!爱你们么么哒~
回复

使用道具 举报

3偶尔光临
284/300
排名
8607
昨日变化
3

0

主题

84

帖子

284

积分

Rank: 3Rank: 3Rank: 3

UID
214958
好友
0
蛮牛币
28
威望
0
注册时间
2017-3-28
在线时间
76 小时
最后登录
2018-7-15
发表于 2018-4-3 10:47:35 | 显示全部楼层
我来刷币

回复

使用道具 举报

5熟悉之中
901/1000
排名
2355
昨日变化
1

0

主题

53

帖子

901

积分

Rank: 5Rank: 5

UID
192573
好友
0
蛮牛币
1099
威望
0
注册时间
2016-12-15
在线时间
304 小时
最后登录
2018-6-25
发表于 2018-4-3 11:22:54 | 显示全部楼层
哇  看不懂

回复 支持 反对

使用道具 举报

5熟悉之中
823/1000
排名
5379
昨日变化
6

0

主题

426

帖子

823

积分

Rank: 5Rank: 5

UID
146677
好友
9
蛮牛币
2690
威望
0
注册时间
2016-4-25
在线时间
161 小时
最后登录
2018-7-13
QQ
发表于 2018-4-3 11:36:46 | 显示全部楼层
马上看看,貌似有点意思

回复 支持 反对

使用道具 举报

7日久生情
2032/5000
排名
1035
昨日变化
3

0

主题

211

帖子

2032

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
147766
好友
0
蛮牛币
469
威望
0
注册时间
2016-5-6
在线时间
847 小时
最后登录
2018-7-16
QQ
发表于 2018-4-3 11:37:40 | 显示全部楼层
感谢分享

回复

使用道具 举报

4四处流浪
305/500
排名
7187
昨日变化
3

0

主题

75

帖子

305

积分

Rank: 4

UID
267103
好友
0
蛮牛币
632
威望
0
注册时间
2018-1-31
在线时间
70 小时
最后登录
2018-7-15
发表于 2018-4-3 13:04:55 | 显示全部楼层

回复

使用道具 举报

6蛮牛粉丝
1033/1500
排名
2451
昨日变化
13

1

主题

252

帖子

1033

积分

Rank: 6Rank: 6Rank: 6

UID
147717
好友
0
蛮牛币
1271
威望
0
注册时间
2016-5-5
在线时间
254 小时
最后登录
2018-7-16
发表于 2018-4-3 13:18:55 | 显示全部楼层
哇  看不懂

回复 支持 反对

使用道具 举报

4四处流浪
464/500
排名
7000
昨日变化
59

0

主题

160

帖子

464

积分

Rank: 4

UID
243107
好友
0
蛮牛币
602
威望
0
注册时间
2017-9-13
在线时间
138 小时
最后登录
2018-7-16
发表于 2018-4-3 13:21:00 | 显示全部楼层

回复

使用道具 举报

3偶尔光临
156/300
排名
16957
昨日变化
8

0

主题

32

帖子

156

积分

Rank: 3Rank: 3Rank: 3

UID
172616
好友
0
蛮牛币
63
威望
0
注册时间
2016-9-28
在线时间
90 小时
最后登录
2018-7-16
发表于 2018-4-3 14:42:33 | 显示全部楼层
这个框架里虽然没有这个问题,但是在多线程的时候,增删对象依然会产生较大的数据变动,这容易对同时运行的其他线程产生阻塞,就依然需要进行延后处理。

回复 支持 反对

使用道具 举报

7日久生情
3048/5000
排名
2427
昨日变化
9

0

主题

2080

帖子

3048

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
219676
好友
1
蛮牛币
2479
威望
0
注册时间
2017-7-12
在线时间
436 小时
最后登录
2018-7-16

活力之星

发表于 2018-4-3 16:11:42 | 显示全部楼层
谢谢分享

回复

使用道具 举报

6蛮牛粉丝
1442/1500
排名
3108
昨日变化
10

5

主题

625

帖子

1442

积分

Rank: 6Rank: 6Rank: 6

UID
239879
好友
1
蛮牛币
2567
威望
0
注册时间
2017-8-26
在线时间
386 小时
最后登录
2018-7-16

迈向小康

发表于 2018-4-3 16:21:46 | 显示全部楼层
站长妹纸萌萌哒!

回复

使用道具 举报

3偶尔光临
171/300
排名
10169
昨日变化
4

0

主题

25

帖子

171

积分

Rank: 3Rank: 3Rank: 3

UID
267703
好友
2
蛮牛币
168
威望
0
注册时间
2018-2-4
在线时间
54 小时
最后登录
2018-6-23
发表于 2018-4-3 17:24:05 | 显示全部楼层
哇,感觉好厉害但是看不懂
[发帖际遇]: wx_jShISTms 乐于助人,奖励 3 蛮牛币. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

4四处流浪
338/500
排名
7128
昨日变化
56

0

主题

73

帖子

338

积分

Rank: 4

UID
253671
好友
1
蛮牛币
462
威望
0
注册时间
2017-11-10
在线时间
105 小时
最后登录
2018-7-16
发表于 2018-4-3 17:24:41 | 显示全部楼层
厉害厉害

回复

使用道具 举报

排名
32366
昨日变化
38

0

主题

5

帖子

17

积分

Rank: 1

UID
266970
好友
0
蛮牛币
40
威望
0
注册时间
2018-1-30
在线时间
4 小时
最后登录
2018-4-3
发表于 2018-4-3 17:48:07 | 显示全部楼层
2018.1已经抛弃了monobehavior 了

回复 支持 反对

使用道具 举报

7日久生情
2982/5000
排名
279
昨日变化

1

主题

242

帖子

2982

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
50601
好友
2
蛮牛币
3053
威望
0
注册时间
2014-10-23
在线时间
968 小时
最后登录
2018-7-16
QQ
发表于 2018-4-3 22:00:12 | 显示全部楼层
清风无敌。太棒了

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

关闭

站长推荐 上一条 /1 下一条

快速回复 返回顶部 返回列表