1 Create Entity with ComponentA and ComponentB
2 Create system with group (EntityQuery)
SetFilterChanged(ComponentType.ReadOnly()
3 Add or Remove ComponentC to this entity.
4 System will trigger ComponentA is changed??? Why?
Example code:
public class AddRemoveComponentC : MonoBehaviour
{
private EntityQuery compGroup;
void Start()
{
compGroup = World.Active.EntityManager.CreateEntityQuery(ComponentType.ReadOnly<ComponentB>());
}
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 200, 200), "Add/Remove C"))
{
var es = compGroup.ToEntityArray(Allocator.TempJob);
foreach (var entity in es)
{
if (World.Active.EntityManager.HasComponent<ComponentC>(entity))
{
World.Active.EntityManager.RemoveComponent<ComponentC>(entity);
}
else
{
World.Active.EntityManager.AddComponentData(entity, new ComponentC());
}
}
es.Dispose();
}
}
}
public struct ComponentA : IComponentData
{
}
public struct ComponentB : IComponentData
{
}
public struct ComponentC : IComponentData
{
}
[UpdateInGroup(typeof(PresentationSystemGroup))]
public class WatchChangeComponentSystem : ComponentSystem
{
private EntityQuery _notifyGroup;
protected override void OnCreateManager()
{
_notifyGroup = GetEntityQuery(ComponentType.ReadOnly<ComponentA>());
_notifyGroup.SetFilterChanged(ComponentType.ReadOnly<ComponentA>());
var e = EntityManager.CreateEntity();
EntityManager.AddComponentData(e, new ComponentA());
EntityManager.AddComponentData(e, new ComponentB());
}
protected override void OnUpdate()
{
if (_notifyGroup.CalculateLength() == 0) return;
Debug.Log("NotificationSpellCreate "+_notifyGroup.CalculateLength());
}
}
Its normal? I dont rewrite ComponentA but system will trigger.
If its normal how use SetFilterChanged? Any person anywhere in the project can add or remove any component from entity. And this will trigger all change filters on this entity…
Unsure how to answer that to be honest. I do know the “Active world” is not likely your target per system here though. You’d be better off accessing the System’s EntityManager.
But as far as I know, if you make a component group, if that group ever changes, then the system with said component group will also update as it doesn’t know what you are doing with it, just that you were interested with it at start.
For example, the simplest task when creating an Entity (from SystemA) is to add an additional componentB to it (from SystemB)
If SetFilterChanged is broken and useless I can not create a systemB like:
Because any other system changes(add\remove component) this Entity, it will come to double call this logic.
And the only safe option to implement such logic is to use SystemStateComponents.
SetFilterChanged is per chunk, not per entity.
and a structural change (e.g. adding a component) will change which chunk the entity is in, therefore the filter will trigger.
if you also want a per-entity filter, you need to track them with state components (or in your case, ComponentType.Exclude)
var query = new EntityQueryDesc()
{
None = new ComponentType[]
{
typeof(ComponentB),
},
All = new ComponentType[]
{
ComponentType.ReadOnly<ComponentA>(),
}
};
In this case when I add ComponentB all ok.
But when I remove ComponentB → this will _notifyGroup.SetFilterChanged(ComponentType.ReadOnly()); trigger again…
And in real world with 5+ ppl on project I dont know about components B and C add|remove. I create logic with component A, and some code other ppl completely broken my code without any reference to A component…
SystemStateComponents yes I may create somthing like
public class CreateDestroyComponantA: JobComponentSystem
{
[ExcludeComponent(typeof(ComponantAState))]
struct ProcessAddedEntities : IJobProcessComponentDataWithEntity<ComponentA>
{
[ReadOnly] public EntityCommandBuffer.Concurrent ecb;
public void Execute(Entity entity, int index, [ChangedFilter] [ReadOnly] ref ComponentA data)
{
ecb.AddComponent(index, entity, new ComponantAState());
// Only one time on creation logic
}
}
[ExcludeComponent(typeof(ComponentA))]
struct ProcessRemovedEntities : IJobProcessComponentDataWithEntity<ComponantAState>
{
public void Execute(Entity entity, int index, [ReadOnly] ref ComponantAStatedata)
{
ecb.RemoveComponent<ComponantAState>(index, entity);
// Only one time on destroy logic
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var commandBuffer = _commandBufferSystem.CreateCommandBuffer().ToConcurrent();
var job1 = new ProcessAddedEntities
{
ecb = commandBuffer
};
var handle = job1.Schedule(this, inputDeps);
handle.Complete();
var job2 = new ProcessRemovedEntities
{
ecb = commandBuffer,
}.Schedule(this, handle);
job2.Complete();
return job2;
}
}
}
But this pattern more complicated. And its work only for create\remove componentA.
What if I need to call logic only when the value of the component changes?
The only thing that comes to my mind is to make a SystemStateComponentALastValue and on every execute check SystemStateComponentALastValue != ComponentA. But it ugly and have many performance issue…
filter will exclude unchanged chunks, then you search which entity actually changed inside the chunk.
or you centralize all the modifications of A then you can react to it as you want (e.g. create an additional entity that tracks the change, then process and destroy it)