Hi everyone !
I’ve been stuck with the EntityCommandBuffer for 3 weeks now. I’ve seen this post with a similar issue but the fix was already done on my side. The bug happens not always, making it difficult to resolve.
Here is my code :
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
[UpdateAfter(typeof(SpawnSystem))]
public class CollectDataSystem : SystemBase
{
private EntityQuery m_botPlayerQuery;
private List<SharedBotPlayerData> m_botPlayerFilters;
private EndSimulationEntityCommandBufferSystem m_endSimulationEcbSystem;
protected override void OnCreate()
{
m_botPlayerQuery = GetEntityQuery(ComponentType.ReadOnly<SharedBotPlayerData>());
m_endSimulationEcbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnStartRunning()
{
m_botPlayerFilters = new List<SharedBotPlayerData>(Utils.Parameters.MaxPlayerNumber + 1);
}
protected override void OnUpdate()
{
int botNumber = World.GetExistingSystem<SpawnSystem>().m_botNumber;
//UnityEngine.Debug.Log($"Number of bots: {botNumber}");
EntityManager.GetAllUniqueSharedComponentData(m_botPlayerFilters);
NativeArray<int> playerIdArray = new NativeArray<int>(Utils.Parameters.MaxPlayerNumber + 1, Allocator.TempJob);
NativeArray<int> botPlayerNumberArray = new NativeArray<int>(Utils.Parameters.MaxPlayerNumber + 1, Allocator.TempJob);
NativeArray<int> botPlayerOffsetArray = new NativeArray<int>(Utils.Parameters.MaxPlayerNumber + 1, Allocator.TempJob);
NativeHashMap<int, bool> isLeaderEatenMap = new NativeHashMap<int, bool>(Utils.Parameters.MaxPlayerNumber, Allocator.TempJob);
NativeArray<Entity> entityArray = new NativeArray<Entity>(botNumber, Allocator.TempJob);
NativeArray<bool> isLeaderArray = new NativeArray<bool>(botNumber, Allocator.TempJob);
NativeArray<int> botPlayerIdArray = new NativeArray<int>(botNumber, Allocator.TempJob);
NativeArray<float3> positionArray = new NativeArray<float3>(botNumber, Allocator.TempJob);
NativeArray<quaternion> rotationArray = new NativeArray<quaternion>(botNumber, Allocator.TempJob);
NativeHashMap<int, int> eatenBotToPlayerMap = new NativeHashMap<int, int>(botNumber, Allocator.TempJob);
int indexOffset = 0;
#region Initialize data
for (int playerTypeIndex = 0; playerTypeIndex < m_botPlayerFilters.Count; ++playerTypeIndex)
{
SharedBotPlayerData botPlayerFilter = m_botPlayerFilters[playerTypeIndex];
playerIdArray[playerTypeIndex] = botPlayerFilter.m_playerId;
// Filter bots by playerId
m_botPlayerQuery.AddSharedComponentFilter(botPlayerFilter);
int botPlayerCount = m_botPlayerQuery.CalculateEntityCount();
botPlayerNumberArray[playerTypeIndex] = botPlayerCount;
botPlayerOffsetArray[playerTypeIndex] = indexOffset;
// Player doesn't have any follower
if (botPlayerCount == 0)
{
m_botPlayerQuery.ResetFilter();
continue;
}
JobHandle entityJobHandle = Entities
.WithName("entityJob")
.WithSharedComponentFilter(botPlayerFilter)
.WithNativeDisableParallelForRestriction(entityArray)
.ForEach((int entityInQueryIndex, in Entity _entity) =>
{
entityArray[indexOffset + entityInQueryIndex] = _entity;
})
.ScheduleParallel(Dependency);
JobHandle positionJobHandle = Entities
.WithName("positionJob")
.WithSharedComponentFilter(botPlayerFilter)
.WithNativeDisableParallelForRestriction(positionArray)
.ForEach((int entityInQueryIndex, in Translation _translation) =>
{
positionArray[indexOffset + entityInQueryIndex] = _translation.Value;
})
.ScheduleParallel(Dependency);
JobHandle rotationJobHandle = Entities
.WithName("rotationJob")
.WithSharedComponentFilter(botPlayerFilter)
.WithNativeDisableParallelForRestriction(rotationArray)
.ForEach((int entityInQueryIndex, in Rotation _rotation) =>
{
rotationArray[indexOffset + entityInQueryIndex] = _rotation.Value;
})
.ScheduleParallel(Dependency);
JobHandle botPlayerJobHandle = Entities
.WithName("botPlayerJob")
.WithSharedComponentFilter(botPlayerFilter)
.WithNativeDisableParallelForRestriction(botPlayerIdArray)
.ForEach((int entityInQueryIndex, in BotPlayerData _botPlayerData) =>
{
botPlayerIdArray[indexOffset + entityInQueryIndex] = _botPlayerData.m_playerId;
})
.ScheduleParallel(Dependency);
JobHandle isLeaderJobHandle = Entities
.WithName("isLeaderJob")
.WithSharedComponentFilter(botPlayerFilter)
.WithNativeDisableParallelForRestriction(isLeaderArray)
.ForEach((int entityInQueryIndex, in BotTypeData _botTypeData) =>
{
isLeaderArray[indexOffset + entityInQueryIndex] = _botTypeData.m_botType == BotType.Leader;
})
.ScheduleParallel(Dependency);
JobHandle collectDataJobHandle = Dependency;
collectDataJobHandle = JobHandle.CombineDependencies(collectDataJobHandle, entityJobHandle);
collectDataJobHandle = JobHandle.CombineDependencies(collectDataJobHandle, positionJobHandle);
collectDataJobHandle = JobHandle.CombineDependencies(collectDataJobHandle, rotationJobHandle);
collectDataJobHandle = JobHandle.CombineDependencies(collectDataJobHandle, botPlayerJobHandle);
collectDataJobHandle = JobHandle.CombineDependencies(collectDataJobHandle, isLeaderJobHandle);
Dependency = collectDataJobHandle;
// Reset
m_botPlayerQuery.AddDependency(Dependency);
m_botPlayerQuery.ResetFilter();
indexOffset += botPlayerCount;
}
m_botPlayerFilters.Clear();
#endregion
#region Process data
EntityCommandBuffer entityCommandBuffer = m_endSimulationEcbSystem.CreateCommandBuffer();
// Eat
EatJob eatJob = new EatJob
{
m_botNumber = botNumber,
m_botPlayerNumberArray = botPlayerNumberArray,
m_botPlayerIdArray = botPlayerIdArray,
m_positionArray = positionArray,
m_isLeaderArray = isLeaderArray,
m_eatenBotToPlayerMap = eatenBotToPlayerMap
};
JobHandle eatJobHandle = eatJob.Schedule(botNumber, 1, Dependency);
UpdateBotsJob updateBotsJob = new UpdateBotsJob
{
m_botNumber = botNumber,
m_entityArray = entityArray,
m_botPlayerNumberArray = botPlayerNumberArray,
m_botPlayerOffsetArray = botPlayerOffsetArray,
m_botPlayerIdArray = botPlayerIdArray,
m_isLeaderArray = isLeaderArray,
m_eatenBotToPlayerMap = eatenBotToPlayerMap,
m_commandBuffer = entityCommandBuffer
};
JobHandle updateBotsJobHandler = updateBotsJob.Schedule(botNumber, 64, eatJobHandle);
m_endSimulationEcbSystem.AddJobHandleForProducer(updateBotsJobHandler);
Dependency = updateBotsJobHandler;
#endregion
#region Dispose data
JobHandle disposeJobHandle = playerIdArray.Dispose(Dependency);
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, botPlayerNumberArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, botPlayerOffsetArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, isLeaderEatenMap.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, entityArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, isLeaderArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, botPlayerIdArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, positionArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, rotationArray.Dispose(Dependency));
disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, eatenBotToPlayerMap.Dispose(Dependency));
Dependency = disposeJobHandle;
#endregion
}
struct EatJob : IJobParallelFor
{
[ReadOnly]
public int m_botNumber;
[ReadOnly]
public NativeArray<int> m_botPlayerNumberArray;
[ReadOnly]
public NativeArray<int> m_botPlayerIdArray;
[ReadOnly]
public NativeArray<float3> m_positionArray;
[ReadOnly]
public NativeArray<bool> m_isLeaderArray;
[NativeDisableParallelForRestriction]
public NativeHashMap<int, int> m_eatenBotToPlayerMap;
// The code actually running on the job
public void Execute(int index)
{
// Get current playerId
int botPlayerId = m_botPlayerIdArray[index];
int botPlayerNumber = m_botPlayerNumberArray[botPlayerId];
// If the bot is a civilian
if (botPlayerId == 0)
{
return;
}
// Iterate on each entity
for (int botIndex = 0; botIndex < m_botNumber; ++botIndex)
{
int otherBotPlayerId = m_botPlayerIdArray[botIndex];
// If they are in the same team
if (otherBotPlayerId == botPlayerId)
{
continue;
}
// If the entity is a civilian
if (otherBotPlayerId == SpawnSystem.CIVILIAN_ID)
{
// If the entity is close to the current position
if (AreClose(index, botIndex))
{
ChangeToPlayer(botIndex, botPlayerId);
}
}
// Else the entity belong to another team
else
{
int otherBotPlayerNumber = m_botPlayerNumberArray[otherBotPlayerId];
// If the other team is equal or bigger than the current one, skip it
if (botPlayerNumber <= otherBotPlayerNumber)
{
continue;
}
// If the entity is close to the current position
if (AreClose(index, botIndex))
{
ChangeToPlayer(botIndex, botPlayerId);
}
}
}
}
private bool AreClose(int _left, int _right) => math.distance(m_positionArray[_left], m_positionArray[_right]) < 1.5f;
private void ChangeToPlayer(int _botIndex, int _botPlayerId)
{
m_eatenBotToPlayerMap[_botIndex] = _botPlayerId;
}
}
struct UpdateBotsJob : IJobParallelFor
{
[ReadOnly]
public int m_botNumber;
[ReadOnly]
public NativeArray<Entity> m_entityArray;
[ReadOnly]
public NativeArray<int> m_botPlayerNumberArray;
[ReadOnly]
public NativeArray<int> m_botPlayerOffsetArray;
[ReadOnly]
public NativeArray<int> m_botPlayerIdArray;
[ReadOnly]
public NativeArray<bool> m_isLeaderArray;
[ReadOnly]
public NativeHashMap<int, int> m_eatenBotToPlayerMap;
[NativeDisableParallelForRestriction]
public EntityCommandBuffer m_commandBuffer;
// The code actually running on the job
public void Execute(int index)
{
if (!m_eatenBotToPlayerMap.ContainsKey(index))
{
return;
}
int eatenPlayerId = m_eatenBotToPlayerMap[index];
// Change team
ChangeToPlayer(index, eatenPlayerId);
// If it was a leader
if (m_isLeaderArray[index])
{
SetNewLeader(index);
}
}
private void ChangeToPlayer(int _botIndex, int _playerId)
{
m_commandBuffer.SetComponent(m_entityArray[_botIndex], new BotPlayerData { m_playerId = _playerId });
m_commandBuffer.SetSharedComponent(m_entityArray[_botIndex], new SharedBotPlayerData { m_playerId = _playerId });
m_commandBuffer.SetSharedComponent(m_entityArray[_botIndex], new SharedBotTypeData { m_botType = BotType.Follower });
}
private void SetNewLeader(int _botIndex)
{
int botPlayerId = m_botPlayerIdArray[_botIndex];
int playerOffset = m_botPlayerOffsetArray[botPlayerId];
int playerCount = m_botPlayerNumberArray[botPlayerId];
for (int botAllyIndex = playerOffset; botAllyIndex < playerOffset + playerCount; ++botAllyIndex)
{
int allyPlayerId = m_botPlayerIdArray[botAllyIndex];
// If the ally has been eaten this frame
if (m_eatenBotToPlayerMap.ContainsKey(botAllyIndex))
{
continue;
}
// TODO: Filter on closest ally
m_commandBuffer.SetSharedComponent(m_entityArray[botAllyIndex], new SharedBotTypeData { m_botType = BotType.Leader });
break;
}
}
}
}
Some explanations :
- There are teams, consisted of “bots”, moving with a ‘boids’ algo, by following a leader.
- I collect data from bots
- If a bot is near a bot from another team, I check who has the larger team. If the first team is bigger, the bot eats the other one; this is what EatJob does.
- Thanks to the UpdateBotsJob, I fill up the command buffer to change data
- I notify to do changes in the EndSimulationSystem.
- I dispose data
However, I got this error:
ArgumentException: Handle is not initialized.
EntityCommandBuffer was recorded in CollectDataSystem and disposed in Unity.Entities.EndSimulationEntityCommandBufferSystem.
at System.Runtime.InteropServices.GCHandle.Free () [0x00021] in <9577ac7a62ef43179789031239ba8798>:0
at Unity.Entities.EntityCommandBuffer.FreeChain (Unity.Entities.EntityCommandBufferChain* chain, Unity.Entities.PlaybackPolicy playbackPolicy, System.Boolean didPlayback) [0x00014] in {PROJECT}\Library\PackageCache\com.unity.entities@0.17.0-preview.41\Unity.Entities\EntityCommandBuffer.cs:1230
at Unity.Entities.EntityCommandBuffer.Dispose () [0x00034] in {PROJECT}\Library\PackageCache\com.unity.entities@0.17.0-preview.41\Unity.Entities\EntityCommandBuffer.cs:1203
at Unity.Entities.EntityCommandBufferSystem.FlushPendingBuffers (System.Boolean playBack) [0x0010c] in {PROJECT}\Library\PackageCache\com.unity.entities@0.17.0-preview.41\Unity.Entities\EntityCommandBufferSystem.cs:257
Unity.Entities.EntityCommandBufferSystem.FlushPendingBuffers (System.Boolean playBack) (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/EntityCommandBufferSystem.cs:294)
Unity.Entities.EntityCommandBufferSystem.OnUpdate () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/EntityCommandBufferSystem.cs:192)
Unity.Entities.ComponentSystem.Update () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystem.cs:114)
Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:472)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/Stubs/Unity/Debug.cs:19)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:477)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:417)
Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystem.cs:114)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ScriptBehaviourUpdateOrder.cs:333)
I think I missed something, perhaps something special with SharedComponent… Any ideas/suggestions?
Thanks ![]()