I’m exploring DOTS for a while and came across an issue I need help to understand.
The following system detects, for each player entity, enemy entites closer than 5 Units. A dynamic buffer this then filled with the reachable targets.
The system is working fine, but the job stays on the Main Thread, even when using ScheduleParallel.
I would explain that by the small number of players (6), but no matter how much player I add, the only impact is my frame rate tanking. Must this kind of system stay on the main thread?
I would be more than happy to have your feedback on this subject.
Thanks a lot for your help.
using System.Collections;
using System.Collections.Generic;
using Components;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
public partial class DetectMultipleNearestEnemySystem_JobChunk_Schedule : SystemBase
{
private EntityQueryDesc m_queryDescription;
private EntityQuery m_entityQuery;
protected override void OnStartRunning()
{
m_queryDescription = new EntityQueryDesc
{
None = new ComponentType[] { typeof(AlreadyTargeted), typeof(SingleTargetComponent) },
All = new ComponentType[]{ typeof(EnemyTagComponent) },
};
m_entityQuery = GetEntityQuery(m_queryDescription);
}
protected override void OnUpdate()
{
var translationTypeHandle = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentTypeHandle<Translation>(true);
var archetypeChunkArray = m_entityQuery.CreateArchetypeChunkArray(Allocator.TempJob);
var entityTypeHandle = GetEntityTypeHandle();
Entities.
WithBurst().
WithAll<PlayerTagComponent>().
WithReadOnly(translationTypeHandle).
WithReadOnly(entityTypeHandle).
WithReadOnly(archetypeChunkArray).
ForEach(
(ref DynamicBuffer<TargetItem> _targets, in Translation _localToWorld) =>
{
_targets.Clear();
for (var i = 0; i < archetypeChunkArray.Length; i++)
{
var chunk = archetypeChunkArray[i];
var nativeArrayOfEntities = chunk.GetNativeArray(entityTypeHandle);
var nativeArrayOfTranslations = chunk.GetNativeArray(translationTypeHandle);
for (var j = 0; j < chunk.Count; j++)
{
var distance = math.distancesq(_localToWorld.Value, nativeArrayOfTranslations[j].Value);
if (distance > 25) continue;
{
_targets.Add(new TargetItem
{
m_entity = nativeArrayOfEntities[j]
});
}
}
}
}).
WithDisposeOnCompletion(translationTypeHandle).
WithDisposeOnCompletion(entityTypeHandle).
WithDisposeOnCompletion(archetypeChunkArray).
ScheduleParallel();
}
}
Based on the profiler, the main thread is being treated as a temporary worker thread to help out because the EntityCommandBufferSystem wants that job done ASAP.
What your picture does not show is what the other worker threads are doing.
Hi.
Here is a more detailed snapshot of the profiler.
In which conditions would the EntityCommandBufferSystem want to complete a job asap?
I have another system instantiating projectiles based on the dynamicbuffer content. Thanks for the time you spend helping me, guys.
using System.Net;
using Components;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace Systems
{
public partial class FireMultipleProjectilesTowardTargetsSystem_Schedule : SystemBase
{
private EndSimulationEntityCommandBufferSystem m_ecbs;
protected override void OnCreate()
{
m_ecbs = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
var ecb = m_ecbs.CreateCommandBuffer().AsParallelWriter();
var deltaTime = Time.DeltaTime;
var localToWorldFromEntity = GetComponentDataFromEntity<LocalToWorld>(true);
var alreadyTargetedList = GetComponentDataFromEntity<AlreadyTargeted>(true);
Entities
.WithReadOnly(localToWorldFromEntity)
.WithReadOnly(alreadyTargetedList)
.WithAll<PlayerTagComponent, MultiTargetTagComponent>()
.ForEach((int entityInQueryIndex,ref FireWeaponComponent _weaponData, ref DynamicBuffer<TargetItem> _targets, in Translation _translation) =>
{
if (_weaponData.m_currentRate > 0)
{
_weaponData.m_currentRate -= deltaTime;
return;
}
_weaponData.m_currentRate = _weaponData.m_fireRate;
for (var i = 0; i < _targets.Length; i++)
{
if (alreadyTargetedList.HasComponent(_targets[i].m_entity)) continue;
var projectile = ecb.Instantiate(entityInQueryIndex, _weaponData.m_projectileEntityPrefab);
ecb.SetComponent(entityInQueryIndex,projectile, new Translation
{
Value = _translation.Value+new float3(0,.5f,0)
});
ecb.AddComponent(entityInQueryIndex,projectile, new TargetComponent
{
m_entity = _targets[i].m_entity
});
var targetPosition = localToWorldFromEntity[_targets[i].m_entity];
var direction = math.normalize(targetPosition.Position - _translation.Value);
ecb.AddComponent(entityInQueryIndex, projectile, new MoveDirectionComponent
{
m_value = direction
});
ecb.AddComponent(entityInQueryIndex, _targets[i].m_entity, new AlreadyTargeted()
{
});
}
}).
WithDisposeOnCompletion(localToWorldFromEntity).
WithDisposeOnCompletion(alreadyTargetedList).
ScheduleParallel();
m_ecbs.AddJobHandleForProducer(Dependency);
}
}
public struct TargetComponent: IComponentData
{
public Entity m_entity;
}
}
Ok, understood. Thanks a lot.
I need this entityCommandBuffer to instantiate my projectiles but it’s irrelevant to wait each frame for the completion of the detection job if I only create projectiles each .5f seconds (fire rate of the player).
How could I then condition the execution of “m_ecbs.AddJobHandleForProducer(Dependency)” for when it’s needed? This line is outside the Entities.ForEach loop, so it will execute each frame, no matter what happens in the loop. I would instead need a way to stop the execution of the System if not needed.
I recall a system would stop running if no entity is found by the EntityQuery hidden in the Entities.ForEach(). Setting a special “AllowsFireTag” on player ready to shoot and checking for that tag in the FireSystem may work.
But setting tags require another EntityCommandBuffer. Hum… I’m puzzled.