Unity Ecs源码分析

C# Job System

什么是Job System?

  • Job System通过创建Job而不是线程来管理多线程代码。
  • Job System跨多个核心管理一组工作线程。它通常每个逻辑CPU核心有一个工作线程,以避免上下文切换。
  • Job System将Job放入作业队列中用来执行。Job System中的工作线程从作业队列中获取Job并执行它们。Job
    System管理依赖关系并确保作业以适当的顺序执行。

Unity提供了Ijob, IJobParallelFor, and IJobParallelForTransform三种接口,这三种接口分别是为了实现单线程、多线程并行以及针对Transform的多线程并行方式。三种接口本质上都需要用户去实现一个Execute的方法。Ijob的Execute是无参的,IJobParallelFor的Execute是有参的(index),IJobParallelForTransform的Execute是有参的(index,TransformAccess)。
Ijob:
对于实现了Ijob的jobStruct中,通常会在Job反射数据没有创建的时候去创建一个反射数据,这个反射数据是内置在Unity中,且包含了三个参数: C# job type、Job类型(在Ijob中是Single)、当Job需要执行的时候需要调用的delegate。
在这里插入图片描述
当去调度该job的时候(执行Schedule方法),会创建一个参数集,向其中传递指向jobStruct地址的指针、该jobType的反射数据、依赖关系以及调度模式(Ijob是batched),然后通过unity内置的封装好的函数去调度这个参数集(关键的东西就是不开源,2333)。

调用方法:
myJob.Schedule(); 或者myJob.Schedule(myDependency);
还有个方法是run方法,唯一一点和上面的不一样的是,调度模式是run,所以是立即执行且是同步的。

IJobParallelFor:
对于实现了IJobParallelFor的ParallelForJobStruct中,其基本构造和jobStruct一样,不一样的地方有以下几个地方:

  1. 构造反射数据的job类型由single改变为了parallelfor
    在这里插入图片描述

  2. 其execute方法改变为并行执行,先去检测batches里的工作是否完成,完成了就去窃取其他工作队列的工作(目测为了减少竞争,这个job队列是双向队列,窃取者从队尾窃取job)。窃取(或者自身的任务)得到了begin和end,然后执行这些job的execute。
    在这里插入图片描述
    IJobParallelForTransform:
    对于实现了IJobParallelForTransform的TransformParallelForLoopStruct中,其基本构造和IJobParallelFor 的ParallelForJobStruct一样,只是在Execute方法的实现上不一样,所以接下来解释下该方法。

1.这俩句只是对jobData2的拷贝
在这里插入图片描述
2.调用一对unity内置的函数去得到TransformAccess数组
在这里插入图片描述
3. 获取此作业的排序索引范围。这里与IJobParallelFor中的窃取范围不同。
在这里插入图片描述
4. 调用范围中job的Execute,并且将排序索引转换为用户索引,使用用户索引和对应的TransformAccess调用作业的Execute。
在这里插入图片描述

Unity Ecs

UnityEcs主要是做到了数据和行为分离,以及内存的优化管理。
所以,接下来首先分析一下数据是怎么分布的。传统方式都是在class里面去定义数据,这种定义方式造成了其数据离散,在数据量过大的时候在内存中读取数据过于复杂。
比如:

    class PlayerData
    {
    	String name; //8bytes
    	Int[] someValues;//8bytes
    	Quaternion rotation;//16bytes
    	float3 position;//12bytes
    	//总共40bytes
    }

它们的数据其实在内存中是分散的,当去访问或者迭代轮询这些数据的时候,比如这样:
PlayerData[] players;
把它们放在数组里面,虽然说在内存中数组是连续的,但它们的数据在内存中还是离散的,即具体去取某个string值的时候,指针就会从数组跳到离散的需要访问的string数值的地址上去取值。
但Unity为了解决这种问题,把数据放在了struct中,这样也就将离散分布的数据变为一块线性分布的块状数据。

struct PlayerData
{
	string name; //8bytes
	int[] someValues;//8bytes
	Quaternion rotation;//16bytes
	float3 position;//12bytes
	//总共40bytes
}

这时候我们同样定义一个数组存放这个struct的时候,取某数组下标的某个数据的时候,它会自动把下一个距离该数据40bytes的float进行prefetching,然后去轮询数组所有的该数据的时候也就变成了线性的,所以接下来chunk中的内存分布与这种prefetching联合起来可能会更加明白的透彻。
解决了数据在内存中的分布之后,Unity Ecs又对该数据进行了深层次包装,增加了Entity,Component,Chunk的概念。

Chunk:
Ecs引进了Chunk的概念,数据是存储在Chunk中的Archetype中,Chunk中是按照组件流分布的,即一个组件后就是下一个组件,具体的分布会在下面再进行一个详细的分析。对于一个Chunk来说,它是一个具有固定空间大小的内存块,只有16kb,所以目前来说不管在其分多还是分少,Chunk内都是一个平均分布,比如10个Entity分一个Chunk,那么一个Entity就只占有了1.6kb,估计以后Unity会增加动态分布大小的功能。当添加的Entity过多,超过了一个Chunk的容量的时候,那么就又会创建或者复用其他的Chunk。所以如果同类型的Entity过多的时候,我猜测相同的Archetype也会有很多个。

接下来举个例子来展示下具体分布是怎么分布的。

比如我定义一个Entity,其具有position,rotation的IcomponentData,然后呢EntityMananger创建了3个Entity,所以其内存分布就如下所示:

ArchetypeA:
BH[E [0] ,E [1] ,E [2]],BH[p [0], p [1], p [2]],BH[r [0] ,r [1], r [2]]

Entity:
Entity与其说是一个GameObject,更不如说是一个索引int值,在Archetype中其又在本质上可以算是一个“IComponentData”,因为它与其他的IComponentData存放在一起。感觉是一个起到索引链接所有与这个entity相关的IcomponentData的作用。entity这个结构体里面有俩个组成部分,分别是index和version。version就不用多说,主要是用来检查是否有数据变动。而index就是其本质——索引int值,那么这个index如何去索引其在Chunk中的位置呢?所有的Enity是存在一个叫做EntityData的stuct数组中,Entity.index是这个EntityData数组的一个索引,通过Entity.index可以直接拿到其组件的地址。而在Chunk中创建Entity的时候也会在EntityData中同步。
Archetype:
对于Archetype来说,需要注意的是,如果给一个Entity增加一个IComponentData的话,Unity又会创造一个Archetype去存储这个Entity,所以我猜测所有多样性的Archetype都存储在了Chunk中。包括增加SharedComponentData 也是这样的。
SharedComponentData :
这个可能和IComponentData不一样,它在Chunk中存储的只是一个Index值,而不是一个实际的数据,所以可能就占了一个整数字节大小的空间(因为是index值),它的实际数据在内存中的其他地方管理,这个index值估计就是为了用作过滤看看是否有该共享组件数据的一个值。
接下来举个例子来展示下具体分布是怎么分布的。
比如我定义一个Entity,其具有position,rotatio,以及一个SharedComponentData 的IcomponentData,然后呢EntityMananger创建了3个Entity,所以其内存分布就如下所示:

ArchetypeA:
BH[E [0],E [1],E [2]],BH[p[0],p[1],p [2]],BH[r[0],r[1],r[2]], SharedComponentData(仅仅是一个index值) 
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值