目录
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 component | Description |
---|---|
IComponentData | Defines the most common, basic kind of component type. |
IBufferElementData | Defines a dynamic buffer (growable array) component type. |
ISharedComponent | Defines a shared component type, whose values can be shared by multiple entities. |
ICleanupComponent | Defines 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. 方法有:
Method | Description |
---|---|
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 |
---|
CreateEntity , Instantiate , DestroyEntity , AddComponent , 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 structureCollections.FixedString
, a fixed-sized character bufferCollections.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 to128 / sizeof(Waypoint)
but can be specified by the InternalBufferCapacity attribute on theIBufferElementData
struct). SettingCapacity
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:
Method | Description |
---|---|
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> . |
A DynamicBuffer<T>
represents the dynamic buffer component of type T of an individual entity. Its key properties and methods include:
Property or Method | Description |
---|---|
Length | Gets or sets the length of the buffer. |
Capacity | Gets 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.
A 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 (LocalTransform, ParentTransform, and WorldTransform).
query中包含 aspect 和包含相同组件类型是一样的。
aspect is defined as a readonly partial struct implementing IAspect. The struct can contain fields of these types:
Field type | Description |
---|---|
Entity | The 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 type | The containing aspect will encompass all the fields of the 'embedded' aspect. |
These EntityManager
methods create instances of an aspect:
Method | Description |
---|---|
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'sDispose()
. - A
Native-
collection'sDispose
( ). - An enumerator's
Dispose
method does nothing. The method exists only to fulfill theIEnumerator<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.