UnityECS学习日记十三:Job中动态创建实体(实体命令缓冲系统)

在以前的案例中,通常实体都是在主线程进行动态创建的,而想在Job中进行动态创建,Unity提供了EntityCommandBuffer(实体命令缓冲)概念,它解决了两个问题:

1.在Job中无法访问EntityManager。

2.当访问EntityManager时(例如,创建实体),将使所有注入的数组和EntityQuery对象无效。


案例十一:Job中创建实体模拟细胞分裂(Scne12)

脚本:Spawner_FromEntity

using Unity.Entities;

public struct Spawner_FromEntity : IComponentData
{
    public float time;
    public Entity Prefab;
}

脚本:SpawnerAuthoring_FromEntity

using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
// 将自己转换成实体,再由预设生成新的实体
[RequiresEntityConversion]                                             // IDeclareReferencedPrefabs接口的实现,声明引用的预设,好让转化系统提前知道它们的存在
public class SpawnerAuthoring_FromEntity : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
    public GameObject prefab;

    public void DeclareReferencedPrefabs(List referencedPrefabs)
    {
        referencedPrefabs.Add(prefab);
    }

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var spawnerData = new Spawner_FromEntity
        {   
            //被引用的预设因为做了声明将被转化成实体
            //所以我们这里只是将游戏对象标记到一个引用该预设的实体上
            Prefab = conversionSystem.GetPrimaryEntity(prefab),
            time = 5
        };
        dstManager.AddComponentData(entity, spawnerData);
    }

脚本:SpawnerSystem_FromEntity

using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Collections;

[UpdateInGroup(typeof(SimulationSystemGroup))]      // 标记更新组为模拟系统组
public class SpawnerSystem_FromEntity : JobComponentSystem
{

    /// 开始初始化实体命令缓存系统(BeginInitializationEntityCommandBufferSystem)被用来创建一个命令缓存,
    /// 这个命令缓存将在阻塞系统执行时被回放。虽然初始化命令在生成任务(SpawnJob)中被记录下来,
    /// 它并非真正地被执行(或“回放”)直到相应的实体命令缓存系统(EntityCommandBufferSystem)被更新。
    /// 为了确保transform系统有机会在新生的实体初次被渲染之前运行,SpawnerSystem_FromEntity将使用
    /// 开始模拟实体命令缓存系统(BeginSimulationEntityCommandBufferSystem)来回放其命令。
    /// 这就导致了在记录命令和初始化实体之间一帧的延迟,但是该延迟实际通常被忽略掉。
    /// 

    BeginInitializationEntityCommandBufferSystem m_BeginInitializationEntityCommandBufferSystem;

    protected override void OnCreate()
    {
        ////在一个字段中缓存BeginInitializationEntityCommandBufferSystem,这样我们就不必在每一帧都创建它
        m_BeginInitializationEntityCommandBufferSystem = World.GetOrCreateSystem();

    }

    struct SpawnJob : IJobForEachWithEntity
    {
        public EntityCommandBuffer.Concurrent commandBuffer;
        public float delatime;
        public float3 position;

        public void Execute(Entity entity, int index, ref Spawner_FromEntity spawnerData, ref LocalToWorld location)
        {

            if (spawnerData.time <= 0)
            {
                spawnerData.time = 5;
                Debug.Log("创建一个cube");
                var instance = commandBuffer.Instantiate(index, spawnerData.Prefab);
                commandBuffer.SetComponent(index, instance, new Translation { Value = position });
                commandBuffer.SetComponent(index, instance, new Spawner_FromEntity { time = 5, Prefab = spawnerData.Prefab });
            }
            else
            {
                spawnerData.time -= delatime;
            }

            //commandBuffer.DestroyEntity(index, entity);
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        //取代直接执行结构的改变,一个任务可以添加一个命令到EntityCommandBuffer(实体命令缓存),从而在主线程上完成其任务后执行这些改变
        //命令缓存允许在工作线程上执行任何潜在消耗大的计算,同时把实际的增删排到之后把将要添加实例化命令到EntityCommandBuffer的任务加入计划
        var job = new SpawnJob
        {
            commandBuffer = m_BeginInitializationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent(),
            delatime = Time.deltaTime,
            position = UnityEngine.Random.insideUnitSphere * 50
        };
        JobHandle jobHandle = job.Schedule(this, inputDeps);
        //Debug.Log("场景实体个数===" + EntityManager.EntityCapacity);   错误
        ///生成任务并行且没有同步机会直到阻塞系统执行
        ///当阻塞系统执行时,我们想完成生成任务,然后再执行那些命令(创建实体并放置到指定位置)
        /// 我们需要告诉阻塞系统哪个任务需要在它能回放命令之前完成
        m_BeginInitializationEntityCommandBufferSystem.AddJobHandleForProducer(jobHandle);

        return jobHandle;
    }


}

UnityECS学习日记十三:Job中动态创建实体(实体命令缓冲系统)_第1张图片

如上图,将自身设定为预制体,并拖拽到Inspector面板里作为动态创建的对象。 


涉及到的线程知识比较多:

1.任务系统Jobs是C#为了让我们安全地使用多线程而封装的;

2.不能滥用任务系统,否则会引起线程之间的竞争,例如你有四个线程,但是现在有三个被占用,却有五个任务要完成,这时就会五个任务去争夺一个线程,从而造成线程安全问题;

3.任务组件系统可以在工作线程上运行,但是创建和移除实体只能在主线程上做,从而防止线程之间的竞争;

4.为了确保任务可以完成,这里引入了命令缓存机制,就在先把任务缓存起来,等待主线程完成工作后,再进行增删实体的操作。

5.关于阻塞系统,是为了确保安全而生,当线程在执行任务的时候,将其阻塞起来,避免其他任务误入,等任务完成之后,再执行下一个任务,从而有序进行。

6.有的任务由于等待太久而错过时机怎么办,Play Back回放,大概是把没有执行的任务重新添加到队列(我猜的)
这些多线程的东西,知道一些常识即可,然后按照常识操作。


ECS规定,以下行为都不能在Job中处理:(上面的线程知识也就是在复述下面的四个问题)

1.创建实体(Create Entities)

2.销毁实体(Destroy Entities)

3.给实体添加组件(Add Components)

4.删除实体的组件(Remove Components)

很多情况下,只有在Job中才能决定要不要创建实体、添加组件等,这种时候应该怎么办?于是,就有了EntityCommandBufferSystem(实体命令缓存系统)。简单地说,EntityCommandBufferSystem(实体命令缓存系统)可以让我们在Job里添加一些任务队列,然后在主线程中执行这些任务。
 

读到这里一定要看这篇文章(注意一定要看):http://www.benmutou.com/archives/2829  ,并结合下面图中的红框指示内容,了解ECS的运行流程。

UnityECS学习日记十三:Job中动态创建实体(实体命令缓冲系统)_第2张图片

 

本文参考:https://blog.csdn.net/qq_30137245/article/details/99083411

远程项目仓库(github):https://github.com/h1031464180/UnityECS 

你可能感兴趣的:(UnityECS)