SystemStateComponentData的作用,是跟踪资源在系统内部的状态,以提供机会在适当的时机创建和销毁资源,而不是依靠某个回调函数。
SystemStateComponentData和SystemStateSharedComponentData跟ComponentData以及SharedComponentData一样,各自都有一个重要的概念:
SystemStateComponentData在销毁entity时,不会被销毁。
销毁的简单流程如下:
- 找到引用该entity ID的所有的Component Data
- 删除这些components
- 回收entity ID,以重复使用。
然而,如果entity有SystemStateComponentData,它不会被移除。这给system机会来清理该entity ID的资源以及相关状态。只有当SystemStateComponentData被移除后,entity ID才能被重复使用。
Motivation 目的
- System可能需要维持基于ComponentData的一个内部状态。例如,资源是否分配。
- System需要能管理由其它系统对该值或状态的更新。例如,值改变了,或者相关组件添加或删除。
- “无回调”,时ECS设计准则的重要概念。
Concept
一个用法是镜像一个用户的组件的内部状态。
例如:
- FooComponent(ComponentData,用户创建)
- FooStateComponent(SystemComponentData,系统创建)
Detecting Component Add 监测添加组件
当添加FooComponent时,FooStateComponent还不存在。Foo System查询到添加了FooComponent但是没有FooStateComponent,则可以推断出该FooComponent是新添加的。同时Foo System会添加FooStateComponent及其它需要的内部状态。
Detecting Component Remove 检测删除组件
当删除FooComponent组件时,FooStateComponent依然存在。Foo System更新时发现有FooStateComponent但是没有FooComponent,则可以推断出FooComponent被删除了。这时Foo System会删除FooStateComponent并根据需要恢复其它内部状态。
Detecting Destroy Entity 监测销毁实体
实体的销毁,可以简化为步骤:
- 查找到引用该entity ID的所有的components
- 删除这些components
- 回收 entity ID
然而,调用Destroy Entity时,SystemStateComponentData没有被移除,entity ID也不会被回收,直到最后一个组件被删除。这让系统可以用与删除组件相同的方式,清理内部状态。
SystemStateComponent
SystemStateComponent和ComponentData类似,用法也类似:
struct FooStateComponent : ISystemStateComponent
{
}
对于成员,也可以用public,private,protected来修饰可访问性。但是,我们最好在创建该组件的系统内更新,改变它的值,而在该系统之外,是只读的。
SystemStateSharedComponent
SystemStateSharedComponent与SharedComponentData用法类似:
struct FooStateSharedComponent : ISystemStateSharedComponentData
{
public int Value;
}
Example system using state components
下面的例子,用一个简单的系统,展示了如何利用system state component来管理entities。例子定义了一个普通的IComponentData和它的ISystemStateComponentData实例,还定义了三个对该类entities的query查询:
- m_newEntities 选择有普通component但是没有system state component的entities,这些entities是新创建的。系统执行job,为它们添加system state component。
- m_activeEneities选择同时有component和system state component的entities。在实际应用中,其它系统也可能会处理或者销毁这些entities。
- m_destroyedEntities选择了有system state component但是没有component的entities,这些实体是被本系统,或者其它系统删除的entities。该系统运行一个job将system state component从entities上删除,以便ECS可以回收该entity ID。
注意我们的这个简化的例子,并没有处理任何状态,system state component的一个作用就是跟踪资源的分配和清理。
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using UnityEngine;
public struct GeneralPurposeComponentA : IComponentData
{
public bool IsAlive;
}
public struct StateComponentB : ISystemStateComponentData
{
public int State;
}
public class StatefulSystem : JobComponentSystem
{
private EntityQuery m_newEntities;
private EntityQuery m_activeEntities;
private EntityQuery m_destroyedEntities;
private EntityCommandBufferSystem m_ECBSource;
protected override void OnCreate()
{
// Entities with GeneralPurposeComponentA but not StateComponentB
m_newEntities = GetEntityQuery(new EntityQueryDesc()
{
All = new ComponentType[] {ComponentType.ReadOnly<GeneralPurposeComponentA>()},
None = new ComponentType[] {ComponentType.ReadWrite<StateComponentB>()}
});
// Entities with both GeneralPurposeComponentA and StateComponentB
m_activeEntities = GetEntityQuery(new EntityQueryDesc()
{
All = new ComponentType[]
{
ComponentType.ReadWrite<GeneralPurposeComponentA>(),
ComponentType.ReadOnly<StateComponentB>()
}
});
// Entities with StateComponentB but not GeneralPurposeComponentA
m_destroyedEntities = GetEntityQuery(new EntityQueryDesc()
{
All = new ComponentType[] {ComponentType.ReadWrite<StateComponentB>()},
None = new ComponentType[] {ComponentType.ReadOnly<GeneralPurposeComponentA>()}
});
m_ECBSource = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
struct NewEntityJob : IJobForEachWithEntity<GeneralPurposeComponentA>
{
public EntityCommandBuffer.Concurrent ConcurrentECB;
public void Execute(Entity entity, int index, [ReadOnly] ref GeneralPurposeComponentA gpA)
{
// Add an ISystemStateComponentData instance
ConcurrentECB.AddComponent<StateComponentB>(index, entity, new StateComponentB() {State = 1});
}
}
struct ProcessEntityJob : IJobForEachWithEntity<GeneralPurposeComponentA>
{
public EntityCommandBuffer.Concurrent ConcurrentECB;
public void Execute(Entity entity, int index, ref GeneralPurposeComponentA gpA)
{
// Process entity, possibly setting IsAlive false --
// In which case, destroy the entity
if (!gpA.IsAlive)
{
ConcurrentECB.DestroyEntity(index, entity);
}
}
}
struct CleanupEntityJob : IJobForEachWithEntity<StateComponentB>
{
public EntityCommandBuffer.Concurrent ConcurrentECB;
public void Execute(Entity entity, int index, [ReadOnly] ref StateComponentB state)
{
// This system is responsible for removing any ISystemStateComponentData instances it adds
// Otherwise, the entity is never truly destroyed.
ConcurrentECB.RemoveComponent<StateComponentB>(index, entity);
}
}
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
var newEntityJob = new NewEntityJob()
{
ConcurrentECB = m_ECBSource.CreateCommandBuffer().ToConcurrent()
};
var newJobHandle = newEntityJob.ScheduleSingle(m_newEntities, inputDependencies);
m_ECBSource.AddJobHandleForProducer(newJobHandle);
var processEntityJob = new ProcessEntityJob()
{
ConcurrentECB = m_ECBSource.CreateCommandBuffer().ToConcurrent()
};
var processJobHandle = processEntityJob.Schedule(m_activeEntities, newJobHandle);
m_ECBSource.AddJobHandleForProducer(processJobHandle);
var cleanupEntityJob = new CleanupEntityJob()
{
ConcurrentECB = m_ECBSource.CreateCommandBuffer().ToConcurrent()
};
var cleanupJobHandle = cleanupEntityJob.ScheduleSingle(m_destroyedEntities, processJobHandle);
m_ECBSource.AddJobHandleForProducer(cleanupJobHandle);
return cleanupJobHandle;
}
protected override void OnDestroy()
{
// Implement OnDestroy to cleanup any resources allocated by this system.
// (This simplified example does not allocate any resources.)
}
}