Search Unity

DOTS: How to break away from OOP mindset?

Discussion in 'Data Oriented Technology Stack' started by Jyskal, Sep 13, 2019.

  1. Jyskal

    Jyskal

    Joined:
    Dec 4, 2014
    Posts:
    1
    Hello,

    I have been doing a lot of reading and testing with DOTS. However practically moving from OOP to ECS I run into some minor roadblocks.

    In traditional OOP I have a project where I have Citizens who walk around, perform tasks, have needs etc... I can see how I can move the game logic to ECS systems. However converting my OOP classes to ECS is less clear. For example:

    Code (CSharp):
    1. public class Citizen
    2. {
    3.   public int id;
    4.   public string name;
    5.   public Inventory inventory;
    6.   public Vector3 destination;
    7.   public Needs needs;
    8.   ...
    9. }
    As you know you can't convert this kind of class one-on-one to ECS components because ECS components can only store Blittable types. So how to solve this?

    1. Do you create collections in your systems and link the entity ID to structs or classes within the collections to maintain data that does not fit within ECS components?
    2. Do you keep a Citizen class outside of ECS for traditional OOP and move some parts into ECS (pathing, rendering, logic, etc...)? Would this make it a "Hybrid ECS"?
    3. If opting for option 2, wouldn't it be easier to drop ECS as a whole and only use Jobs without ECS?
    4. Is there some other solution?
    I would like to hear your comments. Thanks
     
  2. Draveler

    Draveler

    Joined:
    Jul 1, 2018
    Posts:
    62
    schaefsky likes this.
  3. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,477
    Richard Fabian if I'm not mistaken, wrote it and has a book out if anyone wants to buy it. I think it's worth a read with the feet up :)
     
  4. swejk

    swejk

    Joined:
    Dec 22, 2013
    Posts:
    10
    There are multiple ways how you can arrange your data, it depends on your needs. For Citizen class you can do this:
    Code (CSharp):
    1. struct Citizen : IComponentData {
    2. public int id;
    3. public NativeString64 name;
    4. public float3 destination;
    5. }
    6.  
    It is quite popular opinion that each aspect of the entity should be its own component. This will help you make your code reusable and modular. So you can also use this arrangement of components to represent Citizen entity.
    Code (CSharp):
    1. struct Citizen : IComponentData {
    2. // empty, serves as a tag
    3. }
    4. struct Id : IComponentData {
    5. public int Value;
    6. }
    7. struct Name : IComponentData {
    8. public NativeString64 Value;
    9. }
    10. struct Destination : IComponentData {
    11. public float3 Value;
    12. }
    "Needs" and "Inventory" fields implies that we want to work with collections of data / entities. In such cases you will want to look at DynamicBuffer and IBufferElementData. In short DynamicBuffer is an component which behaves as an array.
    You can use them to represent needs/inventory like:
    Code (CSharp):
    1. struct NeedElement : IBufferElementData {
    2.   // some data
    3. }
    4.  
    5. struct InventoryElement : IBufferElementData {
    6.    public int ItemId;
    7.   // or
    8.    public Entity ItemEntity;
    9. }
    98% of cases you can get by with simple blittable types in IComponentData and IBufferElementData. Dont be afraid to use Entity references in components and ComponentDataFromEntity<> get their components in jobs. If nothing works, you can still have Dictionary<Entity, SomeClass> in your componentSystem or use EntityManager.AddComponentObject().

    We are spoiled by being able to create convoluted object graphs in OOP, no wonder we struggle to convert them to constrained ECS. But thats why I like ECS, you just have to make it simple.
     
  5. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    4,239
    I think this talk from unity anzsea will help you, it touch on that with great details

     
    snaecoperth and JBR-games like this.
  6. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    184
    the entity itself is an ID so there is no need in duplicating it and introducing an extra variable for identifier
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    158
    I assume the citizen name is not actually used for any computational reason, it's just there as data and it's only there because it happens to be convenient to be "stored" on the citizen.
    Then consider putting it into a ComponentObject. Those are stored like OOP objects but are associated with the entity
    Code (CSharp):
    1. public class CitizenNameHolder: Component {
    2.     public string name;
    3. }
    The entity id/version is not constant between application runs, so if you save/load the id/version changes. it is not suitable to uniquely identifying data across multiple executions. It's better to think of it like a pointer reference, you wouldn't use an object's memory position as an ID
     
    JBR-games likes this.
  8. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    815
    Wait a second. I have always just been using IComponentData, I didn't even know there was something that was just Component. I am assuming that if you can load those up with data, I am guessing it doesn't get used in a job or anytihng?