Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

How do you model this in ECS?

Discussion in 'Entity Component System' started by davenirline, Oct 1, 2018.

  1. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    944
    I have this problem where I need to know if a parent data changed. Changes to its child data affects the parent's changed state. In OOP, this is very easy to implement:

    Code (csharp):
    1. class Child {
    2.     private Parent parent; // Let's just say this class exists
    3.  
    4.     public void ChangeSomething() {
    5.         // some changes, then
    6.         this.parent.Changed = true;
    7.     }
    8. }
    In ECS, it's not so easy. My current solution is to add a changed flag in the child data then run a system that checks this flag and update the associated parent data. However, this feels very costly as I have to check every child data even when there were no changes to them.

    Code (csharp):
    1. struct Parent : IComponentData {
    2.     public ByteBool changed;
    3. }
    4.  
    5. struct Child : IComponentData {
    6.     public Entity parentEntity;
    7.     public ByteBool changed;
    8.  
    9.     // Other attributes
    10. }
    11.  
    12. class CheckParentChangedSystem : ComponentSystem {
    13.     private struct Data {
    14.         public readonly int Length;
    15.         public ComponentDataArray<Child> Child;
    16.     }
    17.  
    18.     [Inject]
    19.     private Data data;
    20.  
    21.     [Inject]
    22.     private ComponentDataFromEntity<Parent> allParents;
    23.  
    24.     protected override void OnUpdate() {
    25.         for(int i = 0; i < this.data.Length; ++i) {
    26.             Child child = this.data.Child[i];
    27.             Parent parent = this.allParents[child.parentEntity];
    28.             parent.changed = parent.changed || child.changed;
    29.             this.allParents[child.parentEntity] = parent; // modify the data
    30.         }
    31.     }
    32. }
    One solution I thought of is to create another entity with a Changed component that stores the parent entity every time a child is changed. I could then create a system that processes these entities. This could work but I'd like to know if there are better ways.
     
  2. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    I think you are on the right track with the Changed component. It seems to be ECS-like to me. Then you don't need to loop over all children but only ones with the Changed component attached.

    We do a similar, almost opposite, thing with systems that have a private struct ProcessedComponent : ISystemStateComponentData . We keep this struct private so other systems won't use the component. When this system has seen the entity, it adds the ProcessedComponent. Using a SubtractiveComponent<ProcessedComponent> will cause those entities to be filtered out on future updates.
     
  3. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    944
    My problem with that approach is that any changes will require an EntityCommandBuffer or EntityManager to create the Changed entity. EntityManager can't be used inside a system while using an ComponentDataArray. On the other hand, non systems like MonoBehaviour classes don't have access to an EntityCommandBuffer.
     
  4. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Why is that a problem?
     
  5. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    944
    I'd like to be able to use in in a non ECS project. I can only use EntityManager in this environment. This means that I have to wrap calls whenever I update something to the Child data.

    Code (csharp):
    1. class ChildWrapper {
    2.     private EntityManager entityManager;
    3.     private Entity ownerEntity;
    4.  
    5.     public void ChangeSomething() {
    6.         Child child = this.entityManager.GetComponent<Child>(this.ownerEntity);
    7.  
    8.         // Do the change
    9.  
    10.         // Prepare the changed entity
    11.         Entity changeEntity = this.entityManager.CreateEntity(...);
    12.         this.entityManager.AddComponent(changeEntity, new Changed(child.parentEntity));
    13.     }
    14. }
    If there's a way to avoid this, that would be better.
     
  6. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
  7. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    944
    I don't understand ComponentGroup. It just gives me zero components.

    Code (csharp):
    1. class SomeSystem : ComponentSystem {
    2.     private ComponentGroup group;
    3.  
    4.     protected override void OnCreateManager() {
    5.         this.group = GetComponentGroup(typeof(MyComponent));
    6.     }
    7.  
    8.     protected override void OnUpdate() {
    9.         // Should listen to added components
    10.         this.group.ResetFilter();
    11.         this.group.SetFilterChanged(ComponentType.Create<MyComponent>());
    12.         Debug.Log($"Group length: {this.group.CalculateLength()}");
    13.     }
    14. }
     
  8. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    944
    Listening just for changed works but it returns all components in the group. I don't get it.

    Code (csharp):
    1. this.group.SetFilterChanged(typeof(MyComponent));
     
  9. PerfidiousLeaf

    PerfidiousLeaf

    Joined:
    Aug 30, 2019
    Posts:
    20
    I know this thread is dead but it's one of the first results on Google. In ECS, the Systems are stateless. They are not attached to any entity or data and simply work on all entities based on a search query (GetEntities). Component groups work as filters, the OnUpdate() function, for example, can use these inside it to only get all entities that contain the defined components in this group.

    The idea is that you put all entity components that you will need for any given behaviour/system in the group and so, everything else is ignored. If you don't need a component for whatever the system is going to do, just omit it from the group and you should design your systems around this philosophy. Your group above makes the assumption that you will need all components in the group to do whatever operation it is you want to do.

    For the listeners, they are event-based but still follow the same philosophy.
     
    MNNoxMortem and skraman like this.