[Unity] Thousands of people share the same screen, from getting started to giving up - Entities 1.0.16 performance test

The current version of Entities used in testing is 1.0.16

How to use Unity Entities 1.0.16:

Create a component for the spawner example | Entities | 1.0.16

1. Create SubScene

2. Create a mounting Authoring script under SubScene:

Authoring is a MonoBehaviour script, mainly used to serialize and configure entity prefab resources or parameters that need to be created;

Because Entities currently does not support dynamic loading of resources using resource names! That's right, neither AssetsBundle nor Addressables can be used for Entities; this means that you cannot use Entities to develop DLC or hot-updated games at this stage.

Entities must use SubScene, and SubScene cannot be dynamically loaded from resources, so the road is completely blocked.

public class EntitiesAuthoring : MonoBehaviour
{
    public GameObject m_Prefab;
    public int m_Row;
    public int m_Col;
}

 3. Convert the GameObject with the Authoring script in step 2 to Entity:

The MonoBehaviour mounted in SubScene will not be executed and must be converted to Entity;

This step is mainly to transfer the parameters in Authoring to the ComponentData defined by yourself;


class EntitiesAuthoringBaker : Baker<EntitiesAuthoring>
{
    public override void Bake(EntitiesAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.None);
        AddComponent<EntitiesComponentData>(entity, new EntitiesComponentData
        {
            m_PrefabEntity = GetEntity(authoring.m_Prefab, TransformUsageFlags.Dynamic | TransformUsageFlags.Renderable),
            m_Row = authoring.m_Row,
            m_Col = authoring.m_Col
        });
    }
}

struct EntitiesComponentData : IComponentData
{
    public Entity m_PrefabEntity;
    public int m_Row;
    public int m_Col;
}

 4. Write the System script to create Entity:

Since MonoBehaviour will not be executed in SubScene, System is similar to MonoBehavior and has OnCreate, OnUpdate, and OnDestroy life cycles. The Entities framework will automatically execute all defined Systems. If you do not want to automatically execute the System, you can add [DisableAutoCreation] to the header, and then use state.World.CreateSystem<MoveEntitiesSystem>() to manually create the System.

[BurstCompile]
partial struct SpawnEntitiesSystem : ISystem
{
    void OnCreate(ref SystemState state)
    {
        //Application.targetFrameRate = 120;
        state.RequireForUpdate<EntitiesComponentData>();
    }
    void OnDestroy(ref SystemState state)
    {

    }

    void OnUpdate(ref SystemState state)
    {
        var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
        var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);

        foreach (var data in SystemAPI.Query<EntitiesComponentData>())
        {
            Unity.Mathematics.Random m_Random = new Unity.Mathematics.Random(1);
            var m_RandomRange = new float4(-data.m_Row * 0.5f, data.m_Row * 0.5f, -data.m_Col * 0.5f, data.m_Col * 0.5f);
            var halfSize = new float2(data.m_Col * 0.5f, data.m_Row * 0.5f);
            for (int i = 0; i < data.m_Row; i++)
            {
                for (int j = 0; j < data.m_Col; j++)
                {
                    var entity = state.EntityManager.Instantiate(data.m_PrefabEntity);
                    ecb.SetComponent(entity, LocalTransform.FromPosition(new float3(j - halfSize.x, 0, i - halfSize.y)));
                    ecb.AddComponent<TargetMovePointData>(entity, new TargetMovePointData() { targetPoint = new float3(m_Random.NextFloat(m_RandomRange.x, m_RandomRange.y), 0, m_Random.NextFloat(m_RandomRange.z, m_RandomRange.w)) });
                }
            }
            state.Enabled = false;
        }
    }
}

 5. Define a System to specifically control the movement of all villains:

Use JobSystem to calculate and set the position of the villain in parallel:

[BurstCompile]
    partial struct MoveEntitiesJob : IJobParallelFor
    {
        [ReadOnly]
        public Unity.Mathematics.Random random;
        [ReadOnly]
        public float4 randomRange;

        [ReadOnly]
        public float moveSpeed;

        [ReadOnly]
        public NativeArray<Entity> entities;
        [NativeDisableParallelForRestriction]
        public EntityManager entityManager;
        [WriteOnly]
        public EntityCommandBuffer.ParallelWriter entityWriter;

        [BurstCompile]
        public void Execute(int index)
        {
            var entity = entities[index];
            var tPointData = entityManager.GetComponentData<TargetMovePointData>(entity);
            var tPoint = tPointData.targetPoint;
            var transform = entityManager.GetComponentData<LocalTransform>(entity);

            float3 curPoint = transform.Position;
            var offset = tPoint - curPoint;
            if (math.lengthsq(offset) < 0.4f)
            {
                tPointData.targetPoint = new float3(random.NextFloat(randomRange.x, randomRange.y), 0, random.NextFloat(randomRange.z, randomRange.w));
                entityWriter.SetComponent(index, entity, tPointData);
            }

            float3 moveDir = math.normalize(tPointData.targetPoint - curPoint);
            transform.Rotation = Quaternion.LookRotation(moveDir);
            transform.Position += moveDir * moveSpeed;
            entityWriter.SetComponent(index, entity, transform);
        }
    }

 Entities test results:

100,000 moving little people, more than 140 frames, the same order of magnitude higher than the number of frames of the custom BatchRendererGroup by 20+. That is because Entities Graphics internally implements Culling of BatchRendererGroup, and objects outside the camera viewport will be Culled. Custom BatchRendererGroup can achieve the same performance as long as it implements Culling in the OnPerformCulling callback method:

 In fact, the test is meaningless at this point. The end of Entities Graphics on the GPU side is already doomed. The so-called Entities ECS structure only improves the performance of the CPU, but the GPU is the biggest bottleneck for rendering massive objects.

Using Entities 1.0.16 Android performance:

10,000 villains, 12 frames;

 After checking the rendering implementation of URP, it turns out that it is actually a Batch Renderer Group internally, so this also determines that whether it is a customized Batch Renderer Group or using Entities, the performance will be the same on the mobile terminal.

Extended discussion:

Unity supports configuring the multi-threading strategy on the Android side. There is a huge performance gap between the large and small cores of Android CPUs. The large core has strong performance but consumes a lot of power, while the small core is weak but saves power. If Jobs are assigned to the small core, the execution will be slower than the main thread. , will lead to negative optimization.

Since the delay of thousands of people sharing the same screen is mainly in the rendering thread, can it be configured to only allow the rendering thread to use large cores? (Or Unity’s default policy is like this);

You can refer to: Unity - Manual: Android thread configuration to configure the Android thread strategy.

in conclusion:

So far (Entities 1.0.16) has too many fatal limitations, and it has only optimized the performance of the PC platform, and it has been greatly improved like cheating;

However, it has had little effect on mobile platforms. The number of parallel threads of JobSystem on mobile platforms has been greatly reduced, and there is currently no open interface setting. The graphics library must be OpenGLES3 and above or Vulkan. Compared with usage restrictions and development thresholds, the weak performance improvement of the ECS structure is not worth mentioning.

The attempt to have ten thousand people on the same screen on the mobile platform failed and was abandoned. However, no matter how small a fly is, it is still meat. The single-threaded Unity programming method has long been unable to keep up with today's hardware development. Although HybridCLR does not support Burst acceleration and can only execute JobSystem in interpreted mode, safe multi-threading still has a lot of potential for calculating large amounts of data. Big meaning.

Looking forward to the early release of the Entities resource system...

Finally, the code for testing Entities with thousands of people on the same screen is attached:

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

public class EntitiesAuthoring : MonoBehaviour
{
    public GameObject m_Prefab;
    public int m_Row;
    public int m_Col;
}

class EntitiesAuthoringBaker : Baker<EntitiesAuthoring>
{
    public override void Bake(EntitiesAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.None);
        AddComponent<EntitiesComponentData>(entity, new EntitiesComponentData
        {
            m_PrefabEntity = GetEntity(authoring.m_Prefab, TransformUsageFlags.Dynamic | TransformUsageFlags.Renderable),
            m_Row = authoring.m_Row,
            m_Col = authoring.m_Col
        });
    }
}

struct EntitiesComponentData : IComponentData
{
    public Entity m_PrefabEntity;
    public int m_Row;
    public int m_Col;
}
[BurstCompile]
partial struct SpawnEntitiesSystem : ISystem
{
    void OnCreate(ref SystemState state)
    {
        //Application.targetFrameRate = 120;
        state.RequireForUpdate<EntitiesComponentData>();
    }
    void OnDestroy(ref SystemState state)
    {

    }

    void OnUpdate(ref SystemState state)
    {
        var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
        var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);

        foreach (var data in SystemAPI.Query<EntitiesComponentData>())
        {
            Unity.Mathematics.Random m_Random = new Unity.Mathematics.Random(1);
            var m_RandomRange = new float4(-data.m_Row * 0.5f, data.m_Row * 0.5f, -data.m_Col * 0.5f, data.m_Col * 0.5f);
            var halfSize = new float2(data.m_Col * 0.5f, data.m_Row * 0.5f);
            for (int i = 0; i < data.m_Row; i++)
            {
                for (int j = 0; j < data.m_Col; j++)
                {
                    var entity = state.EntityManager.Instantiate(data.m_PrefabEntity);
                    ecb.SetComponent(entity, LocalTransform.FromPosition(new float3(j - halfSize.x, 0, i - halfSize.y)));
                    ecb.AddComponent<TargetMovePointData>(entity, new TargetMovePointData() { targetPoint = new float3(m_Random.NextFloat(m_RandomRange.x, m_RandomRange.y), 0, m_Random.NextFloat(m_RandomRange.z, m_RandomRange.w)) });
                }
            }
            state.Enabled = false;
            
        }
    }
    [BurstCompile]
    partial struct MoveEntitiesSystem : ISystem
    {
        Unity.Mathematics.Random m_Random;
        float4 m_RandomRange;
        void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<TargetMovePointData>();
        }
        void OnStartRunning(ref SystemState state)
        {
            if (SystemAPI.TryGetSingleton<EntitiesComponentData>(out var dt))
            {
                m_RandomRange = new float4(-dt.m_Row * 0.5f, dt.m_Row * 0.5f, -dt.m_Col * 0.5f, dt.m_Col * 0.5f);
            }
            else
            {
                m_RandomRange = new float4(-50, 50, -50, 50);
            }
        }
        void OnDestroy(ref SystemState state)
        {

        }

        void OnUpdate(ref SystemState state)
        {
            EntityCommandBuffer.ParallelWriter ecb = GetEntityCommandBuffer(ref state);
            m_Random = new Unity.Mathematics.Random((uint)Time.frameCount);
            var entityQuery = SystemAPI.QueryBuilder().WithAll<TargetMovePointData>().Build();
            var tempEntities = entityQuery.ToEntityArray(Allocator.TempJob);

            var moveJob = new MoveEntitiesJob
            {
                random = m_Random,
                moveSpeed = SystemAPI.Time.DeltaTime * 4,
                randomRange = m_RandomRange,
                entities = tempEntities,
                entityManager = state.EntityManager,
                entityWriter = ecb
            };
            var moveJobHandle = moveJob.Schedule(tempEntities.Length, 64);
            moveJobHandle.Complete();
        }
        private EntityCommandBuffer.ParallelWriter GetEntityCommandBuffer(ref SystemState state)
        {
            var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
            return ecb.AsParallelWriter();
        }
    }
    struct TargetMovePointData : IComponentData
    {
        public float3 targetPoint;
    }
    [BurstCompile]
    partial struct MoveEntitiesJob : IJobParallelFor
    {
        [ReadOnly]
        public Unity.Mathematics.Random random;
        [ReadOnly]
        public float4 randomRange;

        [ReadOnly]
        public float moveSpeed;

        [ReadOnly]
        public NativeArray<Entity> entities;
        [NativeDisableParallelForRestriction]
        public EntityManager entityManager;
        [WriteOnly]
        public EntityCommandBuffer.ParallelWriter entityWriter;

        [BurstCompile]
        public void Execute(int index)
        {
            var entity = entities[index];
            var tPointData = entityManager.GetComponentData<TargetMovePointData>(entity);
            var tPoint = tPointData.targetPoint;
            var transform = entityManager.GetComponentData<LocalTransform>(entity);

            float3 curPoint = transform.Position;
            var offset = tPoint - curPoint;
            if (math.lengthsq(offset) < 0.4f)
            {
                tPointData.targetPoint = new float3(random.NextFloat(randomRange.x, randomRange.y), 0, random.NextFloat(randomRange.z, randomRange.w));
                entityWriter.SetComponent(index, entity, tPointData);
            }

            float3 moveDir = math.normalize(tPointData.targetPoint - curPoint);
            transform.Rotation = Quaternion.LookRotation(moveDir);
            transform.Position += moveDir * moveSpeed;
            entityWriter.SetComponent(index, entity, transform);
        }
    }
}

Guess you like

Origin blog.csdn.net/final5788/article/details/132953620