ECS 简略版说明一:Entities and components

目录

Entities and components

Worlds and EntityManagers

Archetypes

Chunks

Queries

Entity ID's

IComponentData

Managed IComponentData components

DynamicBuffer components

Aspects

Allocator overview

Allocator.Temp

Allocator.TempJob

Allocator.Persistent

Deallocating an allocator

IsCreated property

NOTE


 

Entities and components

entity 是一个轻量级、未托管的 GameObject. Entities 和 GameObjects 相似但又有一些不同:

  • GameObject是托管的, entity 不是一个托管对象,而是一个唯一标识符号(id)
  • entity上依附的components 通常是值类型 struct values.
  • 一个 entity 只能有一个给定类型的组件. gameobject 上面可挂载多个同一个component
  • 尽管entity  component 可以包含方法,但通常不鼓励这样做
  • 一个实体没有parent概念。相反,标准的Parent组件包含对另一个实体的引用

Entity 组件是通过实现下面的接口实现的:

Kind of componentDescription
IComponentDataDefines the most common, basic kind of component type.
IBufferElementDataDefines a dynamic buffer (growable array) component type.
ISharedComponentDefines a shared component type, whose values can be shared by multiple entities.
ICleanupComponentDefines a cleanup component type, which facilitates proper setup and teardown of resources.

除此之外还有 (ICleanupSharedComponent and ICleanupBufferElementData) and chunk components (which are defined with IComponentData but added and removed from entities by a different set of methods).

A component type defined with IComponentData or IBufferElementData can be made 'enableable' by also implementing IEnableableComponent.

Worlds and EntityManagers

 World 是实体的集合. 一个 entity's ID 在它所在的world中是唯一的,两个world中拥有相同id的实体,没有任何关联。

world 拥有自己的 systems 集合, 是在main thread 上运行的,通常一帧一次,实体通常被所在world中的system来访问, (and the jobs scheduled by those systems), 但是这不是强制

The entities in a world are created, destroyed, and modified through the world's EntityManager. 方法有:

MethodDescription
CreateEntity()Creates a new entity.
Instantiate()Creates a new entity with a copy of all the components of an existing entity.
DestroyEntity()Destroys an existing entity.
AddComponent<T>()Adds a component of type T to an existing entity.
RemoveComponent<T>()Removes a component of type T from an existing entity.
HasComponent<T>()Returns true if an entity currently has a component of type T.
GetComponent<T>()Retrieves the value of an entity's component of type T.
SetComponent<T>()Overwrites the value of an entity's component of type T.
📝 NOTE
CreateEntityInstantiateDestroyEntityAddComponent, and RemoveComponent are structural change operations.

Archetypes

archetype 表示world中的一个特殊的组件的组合,拥有相同组件类型的实体,被认为archetypes是一致的。

实际上,添加或删除实体的组件会改变实体所属的原型,这就需要entitymanager将实体及其组件从旧原型移动到新原型,如果新的archetypes 不存在,EntityManager会创建一个

⚠ IMPORTANT
Moving too many entities between archetypes too frequently can add up to significant costs.

the archetype is only destroyed when its world is destroyed.

Chunks

相同archetypes 的实体,存储的地方称之为chunk, 每个chunk 16kb大小,每一个chunk 至多存储 128 entities (精确的数量取决于原型中组件类型的数量和大小).

在chunk 中 ,entity ID's以及每种类型的component,是分别存储在数组中的

For example, in the archetype for entities which have component types A and B, each chunk will store three arrays:

  • one array for the entity ID's
  • ...a second array for the A components
  • ...and a third array for the B components.

EntityManager来处理chunk 的创建和销毁:

  • EntityManager 只当一个实体被添加到一个已经存在的块都满了的情况下,才会创建一个新的块.
  •  EntityManager only destroys a chunk when the chunk's last entity is removed.和archetypes 的销毁不一样

Queries

EntityQuery 表示一个查询

📝 NOTE
The archetypes matching a query will get cached until the next time a new archetype is added to the world. Because the set of existing archetypes in a world tends to stabilize early in the lifetime of a program, this caching usually helps make the queries much cheaper.

Entity ID's

entity ID表示一个 Entity.

为了根据ID查询 entities , world 的EntityManager 维持了一个 entity metadata 的数组. 每个实体ID都对应该数组中的一个索引值,metadata中存储了一个指向存储实体所在块的指针,以及块中实体的索引. When no entity exists for a particular index, the chunk pointer at that index is null. Here, for example, no entities with indexes 1, 2, and 5 currently exist, so the chunk pointers in those slots are all null:

 

为了允许实体索引在实体被销毁后被重用, version number 在销毁后 incremented, 所以如果一个ID的version number 和当前存储的不一致,那么该 ID 指向的实体一定是被销毁了或者不存在。

IComponentData

IComponentData struct 期望是 unmanaged,所以不能包含 managed 字段类型. Specifically, the allowed field types are:

  • Blittable types (可移位类型,即 在托管和非托管类型上不需要做额外的操作)
  • bool
  • char
  • BlobAssetReference<T>, a reference to a Blob data structure
  • Collections.FixedString, a fixed-sized character buffer
  • Collections.FixedList
  • Fixed array (only allowed in an unsafe context)
  • Struct types that conform to these same restrictions.

有一个没有任何数据的组件叫 tag component. 它在做查询标记上很有用, For example, if all of our entities representing monsters have a Monster tag component, a query for the Monster component type will match all the monster entities.

Managed IComponentData components

一个类实现了 IComponentData 接口,表示它是一个 managed component type. 不像非托管的IComponentData structs一样, 这些 managed components 可以存储任何 managed objects.

一般来说,托管组件类型应该只在真正需要时使用,因为与非托管组件相比,它们会产生一些沉重的成本:

  • Like all managed objects, managed components cannot be used in Burst-compiled code.
  • Managed objects cannot normally be used safely in jobs.
  • 托管组件不是直接存储在chunk中,而是存储在一个大的数组里面,chunk里面只存储它在数组中的索引
  • 与所有托管对象一样,创建托管组件会产生垃圾收集开销

ICloneable, 当复制实例本身时,它所包含的任何资源都可以被正确地复制。  

IDisposable, 从实体中移除实例或销毁实体.

DynamicBuffer components

DynamicBuffer 是一个可变大小的数组组件类型

继承自IBufferElementData,即可声明一个Dynamic buffer.

The buffer of each entity stores a Length, a Capacity, and a pointer:

  • The Length 包含元素的真实数量,从0开始
  • The Capacity 数组的容量. It starts out matching the internal buffer capacity (which defaults to 128 / sizeof(Waypoint) but can be specified by the InternalBufferCapacity attribute on the IBufferElementData struct). Setting Capacity resizes the buffer.
  • The pointer indicates the location of the buffer's contents. Initially it is null, signifying that the contents are stored directly in the chunk. If the capacity is set to 超过了 the internal buffer capacity, a new larger array is allocated outside of the chunk, the contents are 复制到 external array, and the pointer is set to point to this new array. If the length of the buffer 超过了 the capacity of the external array, then the contents of the buffer are copied to another new, larger array outside the chunk, and the old array is disposed. The buffer can also be shrunk.

The internal buffer capacity and the external capacity (if present) are deallocated when the EntityManager destroys the chunk itself.

📝 NOTE
当dynamic buffer存储在chunk外时,chunk内部的空间就浪费掉了,访问元素通过额外的指针来访问,可以通过让元素的数量不超过 internal capacity避免,但是通常数量都是非常大的,这时可以设置internal capacity to 0, 表示所有的元素都存储在chunk外, 这样的只有访问时用额外的指针的代价,而避免了chunk内部空间的浪费

The EntityManager has these key methods for using dynamic buffers:

MethodDescription
AddComponent<T>()Adds a component of type T to an entity, where T can be a dynamic buffer component type.
AddBuffer<T>()Adds a dynamic buffer component of type T to an entity; returns the new buffer as a DynamicBuffer<T>.
RemoveComponent<T>()Removes the component of type T from an entity, where T can be a dynamic buffer component type.
HasBuffer<T>()Returns true if an entity currently has a dynamic buffer component of type T.
GetBuffer<T>()Returns an entity's dynamic buffer component of type T as a DynamicBuffer<T>.

DynamicBuffer<T> represents the dynamic buffer component of type T of an individual entity. Its key properties and methods include:

Property or MethodDescription
LengthGets or sets the length of the buffer.
CapacityGets or sets the capacity of the buffer.
Item[Int32]Gets or sets the element at a specified index.
Add()Adds an element to the end of the buffer, resizing it if necessary.
Insert()Inserts an element at a specified index, resizing if necessary.
RemoveAt()Removes the element at a specified index.

为了确保 job 安全,DynamicBuffer 的内容在scheduled jobs 不能访问,只能访问 只读的 buffer component type,  main thread 可以读取buffer。

structural change之后,需要重新获取该buffer.

DynamicBuffer<T> can be 'reinterpreted'. The target reinterpretation element type must have the same size as T.

Aspects

aspect 类似于对象封装器,封装了多个component,Aspects 可用于简化查询和与组件相关的代码,比如,The TransformAspect, groups together the standard transform components (LocalTransformParentTransform, and WorldTransform).

query中包含 aspect 和包含相同组件类型是一样的。

aspect is defined as a readonly partial struct implementing IAspect. The struct can contain fields of these types:

Field typeDescription
EntityThe wrapped entity's entity ID.
RefRW<T> or RefRO<T>A reference to the wrapped entity's T component.
EnabledRefRW<T> and EnabledRefRO<T>A reference to the enabled state of the wrapped entity's T component.
DynamicBuffer<T>The wrapped entity's dynamic buffer T component.
Another aspect typeThe containing aspect will encompass all the fields of the 'embedded' aspect.

These EntityManager methods create instances of an aspect:

MethodDescription
GetAspect<T>Returns an aspect of type T wrapping an entity.
GetAspectRO<T>Returns a readonly aspect of type T wrapping an entity. A read-only aspect throws an exception if you use any method or property that attempts to modify the underlying components.

Aspect instances can also be retrieved by SystemAPI.GetAspectRW<T> or SystemAPI.GetAspectRO<T> and accessed in an IJobEntityor a SystemAPI.Query loop.

⚠ IMPORTANT
You should generally get aspect instances via SystemAPI rather than the EntityManager: unlike the EntityManager methods, the SystemAPI methods register the underlying component types of the aspect with the system, which is necessary for the systems to properly schedule jobs with every dependency they need.

Allocator overview

allocator 负责分配 unmanaged memory.  Collections package 包含三种allocator:

  • Allocator.Temp: The fastest allocator, for short-lived allocations. You can't pass this allocator to a job.
  • Allocator.TempJob: A short-lived allocator that you can pass into jobs.
  • Allocator.Persistent: The slowest allocator for indefinite lifetime allocations. You can pass this allocator to a job.

Allocator.Temp

每一帧,main thread 都会创建一个 Temp allocator ,在帧结束时释放掉,每一个 job 也为每个线程创建一个Temp allocator,在job 结束时释放掉,Temp allocations 最小是64 bytes.

Temp allocations are only safe to use in the thread and the scope where they were allocated. While you can make Temp allocations within a job, you can't pass main thread Temp allocations into a job. For example, you can't pass a native array that's Temp allocated in the main thread into a job.

Allocator.TempJob

必须在4帧内释放掉 TempJob allocations . TempJob allocations 最小是 16 bytes.

For Native- collection types, the disposal safety checks throw an exception if a TempJob allocation lasts longer than 4 frames. For Unsafe- collection types, you must deallocate them within 4 frames, but Unity doesn't perform any safety checks to make sure that you do so.

Allocator.Persistent

Because Persistent allocations can remain indefinitely, safety checks can't detect if a Persistent allocation has outlived its intended lifetime. As such, you must deallocate a Persistent allocation when you no longer need it. Persistent allocations 最小是 16 bytes.

Deallocating an allocator

Each collection retains a reference to the allocator that allocated its memory. 释放的方法:

  • An Unsafe- collection's Dispose().
  • Native- collection's Dispose( ).
  • An enumerator's Dispose method does nothing. The method exists only to fulfill the IEnumerator<T> interface.

To dispose a collection after the jobs which need it have run, you can use the Dispose(JobHandle) method. This creates and schedules a job which disposes of the collection, and this new job takes the input handle as its dependency. Effectively, the method defers disposal until after the dependency runs:

NativeArray<int> nums = new NativeArray<int>(10, Allocator.TempJob);

// Create and schedule a job that uses the array.
ExampleJob job = new ExampleJob { Nums = nums };
JobHandle handle = job.Schedule();

// Create and schedule a job that will dispose the array after the ExampleJob has run.
// Returns the handle of the new job.
handle = nums.Dispose(handle);

IsCreated property

The IsCreated 在下面的情况返回false:

  • Immediately after creating a collection with its default constructor.
  • After Dispose has been called on the collection.
NOTE

不需要使用默认的 collection's 构造器,The constructor is only available because C# requires all structs have a public default constructor.

调用 Dispose 只设置调用的struct  IsCreated 为false, 对拷贝的 struct 并不生效. 下面情况IsCreated仍为true

  • Dispose was called on a different copy of the struct.
  • The underlying memory was deallocated via an alias.


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TO_ZRG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值