I have several systems that add components to some Entites via the CommandBuffer. Now I have the problem that I don’t know if this component already exists, because the CommandBuffer only adds the components at the end of the frame (EndFrameBarrier).
Is there a way to tell the CommandBuffer that it should only add components if they are not yet existing?
One possibility I would come up with is: After each system that adds or deletes the tag components, build its own barrier to change the entites directly after the system. But I think this can be very slow.
There’s a lot of issues around this for command buffers. If you have 3 systems that add the same component in the same frame/barrier. Which system takes precedence?
The first one. The rest can just be ignored. (Hence the naming scheme “Add Only If Not Exists” or “Remove Only If Exists”). This is for cases where you just want to make sure the component to exist. I ran into needing this use case quite a lot actually.
Ultimately I mean It’d be great if Unity can shed some thoughts on the possibility of this API.
Multiple systems or jobs might make changes to a mesh and will require the mesh to be updated. Instead of using a ‘DirtyTag’ component on the mesh that needs to be rebuilt and running into this issue, you create a DirtyMesh entity. Then you can simply check before regenerating the mesh if it’s already been done this frame.
/// <inheritdoc/>
protected override void OnUpdate()
{
this.entitySet.Clear();
var meshDirtyType = this.GetArchetypeChunkComponentType<MeshDirty>(true);
var chunks = this.meshDirtyQuery.CreateArchetypeChunkArray(Allocator.TempJob);
for (var chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++)
{
var chunk = chunks[chunkIndex];
var meshDirty = chunk.GetNativeArray(meshDirtyType);
for (var index = 0; index < chunk.Count; index++)
{
var entity = meshDirty[index].Entity;
// Already updated this entity this frame
if (!this.entitySet.Add(entity))
{
continue;
}
// ... create mesh
Yes that’s a good concern. But isn’t it still deterministic though? The system/job that does the first “dirtying” will always be the first in any environment. Maybe I’m missing something here.
But anyway, I’m actually only advocating this API for PostUpdateCommands in non-jobified code. This is more to fill some gaps than anything else (I used to have the same kind of workaround as you do). To me, ECS is great for networking and jobs. I find it more natural to only use jobified code for things that are great in numbers and share the same logic/calculations. These types of code are very performance-conscious, and therefore are usually very low-level, messy and convoluted. And, I don’t mind that at all.
But in most games, there are a lot of standalone entities that have their own unique logic. And these are usually not performance-conscious and do not need to be jobified. So I think the code for them should be clean and concise, and the APIs should be convenient and thorough.
No it’s not deterministic if for next system you don’t know exists component which can be added in previous step or not. This mean you thinking by logic, not by data as DOD required. ECB adds not in end of frame, ECB playbacks in place where you want - manually or in barrier. If you need change data between systems, you must declare barriers between this systems and use ECS from this brriers. For example systems A B C and barriers bD and bE, and you must declare order by UpdateBefore\After:
A - UpdateBefore bD
B - UpdateAfter bD UpdateBefore bE
C - UpdateAfter bE
bD - UpdateBefore bE
In result you’ll have chain:
A - bD - B - bE - C
And if you use ECB form bD in A your component changes will applied in bD place, and B will use updated data e.t.c
Very basic sample, all deterministic, all predictable, same result always:
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
[AlwaysUpdateSystem, UpdateBefore(typeof(ChainBarrierD))]
public class ChainSystemH: ComponentSystem
{
[Inject] private ChainBarrierD barrier;
protected override void OnUpdate()
{
Debug.Log("--------------SYSTEMS CHAIN START-----------");
var b = barrier.CreateCommandBuffer();
b.CreateEntity();
b.AddComponent(new Position());
b.AddComponent(new Rotation());
Debug.Log("Add 1 Pos and 1 Rot");
}
}
[UpdateBefore(typeof(ChainBarrierE))]
public class ChainBarrierD: BarrierSystem {}
[UpdateBefore(typeof(ChainBarrierE)), UpdateAfter(typeof(ChainBarrierD))]
public class ChainSystemB: ComponentSystem
{
[Inject] private ChainBarrierE barrier;
private ComponentGroup g;
protected override void OnCreateManager()
{
g = GetComponentGroup(typeof(Position));
}
protected override void OnUpdate()
{
var posE = g.GetEntityArray();
if (posE.Length > 0)
{
var b = barrier.CreateCommandBuffer();
b.RemoveComponent<Position>(posE[0]);
Debug.Log("Remove 1 Pos");
}
}
}
public class ChainBarrierE: BarrierSystem {}
[UpdateAfter(typeof(ChainBarrierE))]
public class ChainSystemA: ComponentSystem
{
private ComponentGroup g1;
private ComponentGroup g2;
protected override void OnCreateManager()
{
g1 = GetComponentGroup(typeof(Position));
g2 = GetComponentGroup(typeof(Rotation));
}
protected override void OnUpdate()
{
Debug.Log("Count Pos - " + g1.CalculateLength());
Debug.Log("Count Rot - " + g2.CalculateLength());
var rotE = g2.GetEntityArray();
if (rotE.Length > 0)
{
PostUpdateCommands.RemoveComponent<Rotation>(rotE[0]);
Debug.Log("Remove 1 Rot");
}
Debug.Log("--------------SYSTEMS CHAIN END-----------");
}
}
Other way, not use ECB and do changes at end of system update, but also chains systems (without barriers)
Right, I’m aware of that. So your example showed deterministic code. Can you show an example with non-deterministic code (with the addition of the proposed API “AddComponentIfNotExists”)? I’m sorry, but I’m still failing to see one myself right now.
You don’t need that, you must target to determinism in ECS. You must chain systems, or not use ECB from default barrier and anyway adds components in main thread manually at system end (just same as ECB does, you add components only on main threal always, ECB just fill queue on multithread, but applies changes in main thread)
Hi. Just asking for some clarification on the OP topic.
Am I to understand correctly that the following isn’t currently possible:
System A adds a ‘Foo’ Component to Entity.
System B runs immediately after System A. System B filters for Foo components, and therefore picks up the new Foo Component that was just added by System A.
Is it true that the new Foo Component would be missed by System B, because it wouldn’t actually be added until the end of the frame?
Is that true both with or without the use of jobs?
Assuming System A is a ComponentSystem, and you are using the PostCommandUpdates, then the Foo component is added to the entity at the end of System A’s Update method (before System B is updated).
Assuming System A is a JobComponentSystem, and you are using the MyBarrierSystem.CreateCommandBuffer, then the Foo component is added after the MyBarrierSystem is updated. In this case, you will need to ensure System B has the UpdateAfter(typeof(MyBarrierSystem)) attribute.
First of all, that is exactly what I’m targeting here. I said “I’m only advocating this API for PostUpdateCommands in non-jobified code”. (Main thread, system’s end)
We probably have different notions of “determinism” here. “Deterministic”, to me, means same code with same input will yield the same result on any execution of the code on any environment. And as far as I can tell, adding that API does not violate that.
I think this is not required, ECB is only queue and nothing more, for checking existance we already have good API’s ComponentDataFromEntity.Exists\EntityManager.HasComponent.