Search Unity

[Proposal] User Accessable Field on Entity

Discussion in 'Data Oriented Technology Stack' started by RandyBuchholz, Feb 12, 2020.

  1. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    Add a public field to the Entity class that can be managed by the developer.

    It would be useful to have a public field on Entity for custom use cases. When working with entities outside of systems and jobs (like in the game UI) sometimes we don't care about what is in the entity, but do want to know something about the entity. For example, I have objects in my scene that are backed by entities. These are things you can "gather", and they realize an IGatherable interface that has an Entity property. When I spawn the items, I create entities and populate the field. When I click on an item I can get the entity. The problem is, I know nothing about the entity, and have no way of finding out about it unless I open it up. To do this I need to know its archetype/components, or I add a "known component" to all entities that might realize IGatherable. Either case is a lot of extra work for a simple need. Having "metadata" capabilities would help.

    Adding a user field to Entity would make it much easier to work with individual entities, especially when you are outside of systems.
    Code (CSharp):
    1.   public struct Entity : IEquatable<Entity>, IComparable<Entity>
    2.   {
    3.     public int Index;
    4.     public int Version;
    5.     public int Key; // new field
    6.   }
    While this is a deep change since it impacts Entity, and essentially increases the size of entity by 50% I think it provides a lot of flexibility for working with entities and groups of entities.

    With this field I would be able to tag my objects with something like 1 for tree and 2 for rock. When I click one and get the entity, I can quickly determine the underlying archetype (rock/tree) of the entity and use that to inspect it or determine actions.
     
    elcionap likes this.
  2. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    401
    Its a struct that is copied everywhere. Intentionally as versioning wouldn't work otherwise.
     
  3. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    I'm missing your point. This field wouldn't be used internally. It's just a way for a developer to tag an entity when they are in control of it.
     
  4. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    401
    Every time you assign entity to a variable or pass it to a function an independent copy is created.
    Code (CSharp):
    1. Entity x;
    2. x.key = 1;
    3. Entity y = x;
    4. x.key = 2;
    5. Debug.Log(y.key); // outputs 1
     
    elcionap likes this.
  5. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    264
    Hm, what's holding you back from "tagging" the entity with an empty ICompontentData called Tree, Rock or something more generic like Gatherable?
     
  6. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    401
    Or you wrap it or otherwise cache your data yourself.
    Code (CSharp):
    1. struct EntityRef
    2. {
    3.     public readonly Entity entity;
    4.     public int key;
    5.  
    6.     public EntityRef(Entity e, int key)
    7.     {
    8.         this.entity = entity;
    9.         this.key = key;
    10.     }
    11. }
     
    Last edited: Feb 12, 2020
    elcionap likes this.
  7. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    99
    Seems like you are just choosing "assign metadata to all entities" over "assign a tag component to all entities". An empty ComponentData is (somewhat) a way to "add metadata" to an entity already.
     
    elcionap likes this.
  8. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    147
    If you don't need this field but have a massive number of entities, it's wasted memory.
     
    xVergilx and elcionap like this.
  9. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,494
    There is no "in" or "opening up" the Entity since Entity is one of the components of the chunk (that doubles as an index to other components + API prevent you from editing them), so you imagine they lined up as a data too. (16kB chunk of empty archetype would take 8 bytes each = ~2000 capacity per chunk) If you are to use e.g. (Entity e) => in ForEach, then that metadata field will always get read in between each in a cache line, slowing down everything that iterates Entity. Even if the library not accessing the field I think they would still get indirectly used in reads.
     
    elcionap likes this.
  10. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    This is the biggest issue. The smaller the net size of an entity, the greater the impact - and smaller entities are better.

    Yes, (I had "open up" quoted originally :)) from an implementation perspective there is no "opening" or "in". I'm working on what you might call Object-Entity Interop, and from that conceptual/external perspective an "entity" represents a container.

    In this scenario, I'm looking from the object (Business) side towards the entity (Technology) side. The ECS side can be seen as a closed steady-steady system. I use CQRS and SOA approaches to interact with it. Basically, I just drop disturbances or "demand entities" into the system, or send messages, and it reaches a different state. I can read the state of the system at any time. So adding components to entities isn't an option because the system internals are closed to me.

    Even if it was though, the tags are business/object side so I would be breaking the cubes/chunks for the wrong reasons. Not only that, but adding components consumes as much or more space (if on entity) as a field on Entity or fragments my chunks (if shared). A question about this would be, if this capability was available, would it find enough use cases to where it was used more often then not. If so, the overall memory use might even decrease.

    One way to look at it is like a coat-check. There is a coat room (the ECS systems). I don't know what happens in there. But, I can drop off a coat and scarf and get two tickets - Entity class instances. My problem is, I don't know which ticket represents the cost and which represents the scarf - they just have an Id. I could write on the tickets (e.g., as @julian-moschuering suggested), but I hit a problem with that when using the physics system.

    I created a case and populated it with items backed by entities. I can interact by clicking and ray trace with
    Code (CSharp):
    1. Entity e = physicsWorldSystem.PhysicsWorld.Bodies[raytracehit.RigidBodyIndex].Entity;
    In essence, I get a coat check ticket. But not one I have "written on". I don't know if it is a coat ticket or a scarf ticket. But, I may not care at that time. I put the ticket in my inventory, move it to a chest, or do other "business things" with it. At some later point I'll care what it represents, and use that information to activate the system.

    There is some middle ground where I sort-of care. When I move the item from my inventory to a chest I may have chest for rocks and chests for trees. In this model, I would need to go to the coat check room, give them the ticket to see what it represents, and then write on the ticket so I know what it represents. But there is a problem. How do I get to the coat check? You see, all tickets (entity instances) look alike, regardless of their source.

    In my inventory I have tickets from coat check restaurant, but also from pawn shop and valet. They all look alike. Coat check is like a component. I need to query my ticket-set for a "coat check" component to see if I can use it at coat check. I must know about coat check components. The core conceptual problem is that I need to know about the technical implementation to operate from the business side. I could add a known "business type" component to inspect, but it couldn't be shared because it is entity specific.

    That is what got me the proposal. When we tag an entity it takes space. If we will be querying on that tag it is justified. But, if we are only classifying by the tag, it fragments archetypes/chunks for no good reason. So while the field would add to the size of Entity, it could replace a classifying tag, possibly resulting in better performance overall. I've looked a several of my archetypes and found a lot of cases where the field could replace a tag or single value component with a field value. ECS isn't great for everything, like very large constructs( though it is pretty great overall ). And some things just don't represent well as entities. Entities won't replace objects they live in different architectural areas that need to interact. If the space issue could net zero, adding the field would provide a powerful tool for object-entity interop and potentially provide lower fragmentation.

    Sorry for the long post, hope it makes sense...
     
    Last edited: Feb 12, 2020
  11. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    For what I'm proposing this is more a benefit than issue.
     
  12. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    99
    It is still achievable by having and IComponentData with the exact field you would want to be added to the Entity itself.

    Code (CSharp):
    1. public struct Metadata : IComponentData
    2. {
    3.      public int Value;
    4. }
    Basically instead of doing
    entity.Metadata
    you would be doing
    EntityManager.GetComponentData<Metadata>(entity).Value
    .

    Is there a real use case of what you are trying to achieve so that we can understand what is the issue with using single value components and/or tag components?
     
  13. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    Those are two drastically different things.

    The first case deals with a decoupled value. Given an entity I can just read its "type". I can sent it over the wire, and someone else can directly know what "type" the entity represents.

    The second case deals with a reference, and is coupled to EntityManager. If I send it over the wire, the receiver must also have access to EntityManager (or EM service) and do a round trip to determine what "type" the entity represents.

    The seconds also implies that there is a distinction between an entity with Metadata and one without. They would exist in different chunks.
     
    Last edited: Feb 12, 2020
  14. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    99
    You have access to the
    EntityManager
    through the
    World
    class, are you just trying to avoid using it? And I don't see how the value is more decoupled if it is directly inside the Entity.

    And no, there is no distinction between an entity with Metadata and one without if you just put the Metadata component in all your entities.

    What I mean is that you are already allowed to have that "custom entity", you can even stop dealing with "Entity" in your code and start using only "Metadata" (rename it to BetterEntity if you want to).

    Following your example:

    Code (CSharp):
    1. public struct BetterEntity : IComponentData
    2. {
    3.     public Entity EntityItBelongsTo;
    4.     public int Key;
    5. }
    However I don't really recommend doing it, feels a bad practice to reference it's own entity just to avoid accessing the World class.
     
  15. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    The value isn't "in" entity (i.e., chunk entry), its "on" Entity class. Maybe this will help show where I'm working.

    upload_2020-2-12_15-55-58.png

    I've moved all of my computes into targeted systems. For example, Digest for digesting food. Most deal with simple archetypes. They focus on what ECS does best - predictable iterative processing of discrete sets. I don't have things like big "Player" entities, or many "collections of children", and few entity references. I also have few one-off or event driven systems. Some of these systems drive visual changes and feed the physics system. Some periodically report their state.

    Game logic and variable workflows live in normal objects. These business objects maintain relationships, and have made it so that few entitles need relationships to other entities. This architecture area doesn't know anything about entities. It doesn't really do much data processing either - the entity systems do that. When it needs to do work it uses the Entity Factory. This produces Control Entities that are consumed by the systems. An over-simplified was to see it is that all method bodies are ECS systems. So I can use Objects for what they are good for and Entities for what they are good for, in an integrated way. It can also do async queries on the systems using the reporting system. Reporting can query and translate entities into business objects.

    The physics system exposes the backing entity for the visual/game objects as an Entity class instance (Id/Version). So when a player does something, like clicking to gather, I know the id/version of the backing entity, but nothing more. Usually, I don't need to know more, because I'm not processing data, just creating control entities. All I usually care about is what type of object was clicked. I created a Taxonomy Service in Queries to get this.

    This is why approaches the using World or EntityManager don't help - I'm in the Business Logic area, which knows nothing about either. But, physics and other processes are providing a "vague" reference to entities that I need to work with, yet know nothing about (directly). The more I played with this, the more I found the majority of my significant entities had at least one value that wasn't or didn't need to be "component data" but could been seen as more "archetype data" (for lack of better terms). By having a field on Entity class to hold this I could save a lot of processing steps, and since I found a lot of cases where data in a component could go there, it wouldn't need to sit empty very often, while simplifying my entities by one component. With a field on Entity class I can eliminate the Taxonomy lookups or provide other Meta into to my business layer "directly".
     
    Last edited: Feb 12, 2020
  16. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    147
    i don't understand everything (my english is not the best) but as far as i can see you mix ECS with OOP very strongly. This way you will never reach the performance was is possible with entities. In pure ECS you need a different thinking and put business logic in systems with Jobs as much as possible.
     
    brunocoimbra likes this.
  17. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    558
    What is the benefit of separating your game logic in "managed" objects? If you're only interested in knowing the "type" of the entity, you could also achieve this by maintaining a dictionary that maps entities to their types.
     
  18. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    There are a few benefits to using a mixed approach, and since you can use burst without entities you can burst parts of your OO layer for performance. Simply put though, Objects are better at dealing with "unstructured" information, and Entities excel at dealing with structured data. Games (and most applications) contain both.

    Long post coming...

    Part of why I'm using this approach is historical. I'm an older guy (I did my first programming on punch cards) and I've seen a few technologies come and go. ECS doesn't seem like a new tech to me but has close parallels to other tech I've used. I see some of the same issues down the road. I'm 100% bought in to ECS, but not (an extreme) view that every piece of code needs to be a job and every piece of data needs to be a component. A simple contrived example is a complex decision graph where all of the data is strings and each decision uses many variables. In cases like this you are dealing mostly with a lot of single nodes of non-blittable data. Another case is when you have a lot of dependent wait states in your logic and you need to "lock" information while you are waiting for downstream events/data. From a more business side, ECS is harder to design and maintain, especially at enterprise scale. This is not as relevant to most games, but something to consider. You can do either of the cases in ECS, but is the overall tradeoff in complexity a benefit? Debugging complex logic in ECS can get tricky. Finally, ECS has a subtle risk to be aware of - a job will process any entity of a specific archetype. How do you ensure you don't accidently create an entity in some unrelated part of your system that will get processed by a job that shouldn't process it? There are ways to avoid things like that at the cost of additional complexity. But if you need to protect against the nature of the environment, it may be the wrong environment.

    The two technologies ECS reminds me of most are COBOL on mainframes that use hierarchal databases, and dimensional data warehouses (in an inverted way). In many ways, a chunk is like a cube in a DDW, "regular" components hold facts, and shared components can parallel dimensions. I say "inverted" because we do a lot of transactional writing into our cubes in ECS. The parallels are a little loose, but if you look at DDW approaches you can learn a lot about ECS. Long story short, warehousing has moved to a hybrid relational/dimensional approach. For many of the same reasons I like a hybrid object/entity approach.

    A closer parallel is COBOL, mainframes, and hierarchal databases. A record layout in COBOL



    is similar to an entity. It's a fixed length record, often aligned around memory boundaries. With ECS it's cache boundaries. The 01 level represents an entity, and the 05's are components. In COBOL you can go deeper (level 10, 15, etc.) - nest components. You can define hierarchal structures. In ECS, you can nest components, but ECS can't find nested components. This limits reuse. This in one reason I use a hybrid approach. I can maintain complex relationships and hierarchies on the OO side, and "render" them into flat structs in my factories.

    Take a simple parent-child for example, where a parent has an aggregation (not composition) of children. And, assume jobs that use parent information when processing children.

    Code (CSharp):
    1. parent {
    2.   pd1;
    3.   pd2;
    4.   childCollection[];
    5. }
    6.  
    7. child {
    8.   cd1;
    9.   cd2;
    10. }
    11. }
    ECS is collection based, but doesn't really support collections ironically. A static ECS representation might invert this.

    Code (CSharp):
    1. ecsParent {
    2.   pd1;
    3.   pd2;
    4. }
    5.  
    6. ecsChild{
    7.   parentEntity;
    8.   cd1;
    9.   cd2
    10. }
    11.  
    This fundamentally changes the relationship. The child is no longer an independent entity in an aggregation relationship (it is "owned" be parent), and navigability of the relationship has been reversed. (This may be one of the biggest mental discontinuities in moving form O to E.) What happens when the child is in multiple collections? A parentEntity field for each? Another approach might be shared tags
    Code (CSharp):
    1.  
    2. <parent1><parent2>
    3. ecsChild {
    4.   cd1;
    5.   cd1;
    6. }
    7.  
    But the problems with this should be evident.

    A hybrid/factory approach simplifies this. When data needs to be processed, the factory produces a block of flat components from the object architecture. It can perform culling. If a job doesn't need pd1, don't include it.
    Code (CSharp):
    1.  
    2. struct parentChild : IComponentData {
    3.   pd2; cd1; cd2;
    4. }
    5.  
    This Just-In-Time approach lets my jobs run much faster by eliminating references and packing data with only what the job needs. Additionally, the jobs become more independent and reusable. (Thought this can get a little tricky, from the "accidental matching" concern from above.) The job is no longer tied to specific structure [Parent && Child], but more to only [pd2, cd1, cd2] that can be jitted from any source. Concepts along the line of delegates or interfaces can be used.

    Returning to mainframes. Mainframes run "self-contained" jobs. Jobs are sequenced by a scheduler. Much like ECS. But ECS schedulers are fairly primitive (in many ways) compared to those on mainframes. And more advanced in others. The complexity of scheduling/schedulers was one of the key drivers for the massive number of mainframe migrations in the 90's. An interesting part of that was that the move to OO from jobs/schedulers reduced complexity and overall costs significantly, but also (most often) noticeably increased the raw computing power and time required to perform the same level of compute. For a period of time it was common to see OO frontends with mainframe processing. (Basically what I'm doing).

    This post is way too long, but hopefully useful. My experience with COBOL, mainframes/schedulers, and dimensional modeling has given me a different perspective on ECS, that may or may not be valid. I would recommend anyone doing ECS take a look at some of those technologies.

    As final notes, another area that influenced my thinking on ECS was my education. I'm an EE with a focus on Control Systems and have a long history in manufacturing. I view ECS architectures from a controls systems and factory perspective. A great tool for exploring ECS from that perspective is MATLAB/Simulink, using the Signal Process and Control Systems toolboxes. And finally (whew), my time in management always has Total Cost of Ownership in the back of my mind. In its current state, the TCO of (pure) ECS is pretty high. This is to be expected since it's just now "leaving the lab". The JIT hybrid approach gives me the flexibility objects (and class vs struct) with the performance of entities.
     
    Last edited: Feb 13, 2020
  19. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    1,007
    You'd be surprised how structured unstructured data can be. Normalization approaches are pretty powerful and the reason someone would choose not to do it is because they don't care enough to understand it with respect to their data.

    But even if you do insist your data is unstructured, the misconception here is that OOP is the best solution to represent it. That's not true. Perhaps reference types might be the tool you need, but by using OOP, you are coupling behavior to your data, and in consequence, attempting to encapsulate your objects as a defensive measure. But that process is actually cutting you off from the tools and data you actually need to get your work done in your business logic. Here are three options:

    1) Use class IComponentData.
    2) Use an array to store your additional metadata that can be indexed by the Index filed of the entity.
    3) If you are absolutely stuck in your ways, embed the Entities package and try to modify it to add the feature you need.
     
  20. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    I've done 2 & 3, and using 3 is why I proposed this. Along the lines of understanding, decades of misuse of OOAD has confused what many people think OO is, and has lead to (weird) OOP ideas and things like "Data Objects". Objects should never represent data. That was one of the foundational ideas of OO. That was one of the motivations for the idea of structures. If you need a classifier for data you use structs, never classes. Objects were designed to encapsulate and hide data, so you only dealt with processes (methods). The idea of a "public setter" is an abomination in classic OOAD. In some ways, ECS gets OOAD right, where OOP gets it wrong. So from that perspective I agree with you - OOP isn't the best tool. But, that doesn't make entities the right one. When working on the pure process side, using a data-centric approach is equally naïve. A business process should be able to accept many forms of a business object and not be tied to a specific technology construct. I want a process that can take a "Loan Application", not a fixed set of structs. Once I have the application I can create the properly normalized structs and hand them to optimized jobs.

    I do take some offence at your assumptions about Normalization :). I've architected enterprise databases with several hundred tables and >TB of data (back when that was a lot lol). There are trade-offs in normalization, and all major databases support denormalization for good reason. And that's why the industry has pretty much standardized around third normal form.
     
  21. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    1,007
    There's two different directions I can argue against this.

    Direction 1
    So why are you using ECS? You don't need ECS to schedule jobs with Burst. Neither the data structures nor the API tools appear to be what you want. ECS is about structuring raw data with handles to linked elements of different types, and then allowing systems to interpret that however they wish. It is perspective-based logic. Whereas when you start talking about encapsulation and data hiding, you are now using model-based logic.

    Direction 2
    Well in that case, Entity is data. And its index and version fields make no sense outside of the internal data structures of ECS that use it. Heck, two World instances can have exactly the same entity values that represent completely different entities. Anyways, if you want to encapsulate your objects to not use EntityManager and friends, then you equally should be encapsulating the entire ECS to never expose an Entity struct as it is not useful to the outside. If your objects require some sort of handle with Metadata to represent an instance of something within the game simulation, then using an Entity by itself is the wrong decision. That's not the authors of ECS's fault for not giving Entity metadata fields. That's your fault for leaking the encapsulation of ECS into your objects partially, and leaking it in a format that isn't useful to you.
     
    brunocoimbra and Brendon_Smuts like this.
  22. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    2,624
    ECS as a pattern has been around a while so understanding what it's based on I think helps because it's adhering to established design.

    The first tenant is entities don't contain data. They group components. Implementations here differ but no data on the entity is core to the design pattern.

    The other tenant is systems operate on components, not entities.

    But then we also have ComponentDataFromEntity. Which seems to go against the whole systems operate on components not entities. But this is really just one way of addressing the sticky problem of dependencies between components. An issue every ECS system has had since the pattern first appeared well over a decade ago. And to my knowledge nobody has come up with a really elegant solution.

    But all of this is to illustrate how wrong putting business logic on entities would be. It just fundamentally breaks the core design. Breaking encapsulation is ok now and then in say feature level stuff. But in core architecture of a framework used by thousands it's more of a cardinal sin. Especially if it's exposed up through all the public api's.

    On normalization, nobody say's you have to use ECS as a hammer. If it's beneficial to normalize for say a UI, by all means. The big difference with game clients vs most other applications is it generally makes sense to start denormalized. So it's just kind of the reverse of say what most backends that scale do.
     
  23. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    147
    Games are not classic programs. In games you need process data in a small time window. In classic programs if the business logic takes 20ms its fast, in game its slow. With 60fps you have only a 16.6ms time window.
     
    brunocoimbra likes this.
  24. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,830
    Why can't you use the version and index when creating or injecting an entity and reference those in arrays of the types of metadata they represent? I use a Vector2Int to pass around entities as a single value query between mono and ECS land and it works fine. I keep a matching indexed List of the value/metadata of those categorical entity I may need reference to.
     
  25. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    Keeping per-entity information would be overkill for me. I try to avoid doing anything with entities themselves in mono land. Usually I don't care about an entity for more than a moment.

    I'm treating ECS as a system plant, and mono as a "control panel". ECS chugs along in the background running the game 99% of the time, and I'm not really aware of entities. The only reason I really care about actual entities at all is because ECS is managing the things in the view that I interact with. When clicked, the physics engine gives me the backing entity. I need to tell the plant about the interaction. I use the entity to create "control signal" entities going back into ECS. I only need to know a little about what was clicked to provide parameters. In some cases I don't even need that. Often I just send the original entity component and an action tag - "Entity 1:1 => Clicked". I can't just "tag" the entity. I only allow reads and creates across the boundary so I don't perturb the system. About as deep as I go into "valued entities" is making clones of what was clicked (with the things like mesh stripped off). Gathering is an example. I might click something and create 4 clones, adding a "new" tag. In that case I don't even care about what was clicked. I think my earlier posts were misleading when I used "game logic". I was think about the top-level workflows for UI/external information used to create control entities. Not the plant logic.

    While digging around I did see that entities do seem have a form of metadata. I'm looking into it, but it's "hidden" (probably for good reason). It may not even be accessible outside of debugging. When I'm at a breakpoint and I look at EntityManager.Entities, there is a collection DebugViewUtility.Components. I haven't looked at the source yet, but I'm guessing it has something to do with Name clauses. The first entry, [0], is a string that appears to have lineage information for the entities created by Unity. It is empty for those I create. If I can read that at runtime it will support 90% of my needs. Being able to write to it (for things I create and when it is empty) would be a plus, and would give me what I was looking for in the first place.
     
  26. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    I wanted to close this with a more concrete explanation of what I'm doing with Object-Entity Interop, since some of this thread got pulled that way. You may still disagree, but based more on information than assumption.

    Given two bodies in motion under influences, we want to evaluate them and their relationship over time.
    Newtonian equations commonly use position, velocity, and acceleration data.
    You could create components for this, like
    Code (CSharp):
    1. public struct Body : IDataComponent {
    2.     public float Position;
    3.     public float Velocity;
    4.     public float Acceleration;
    5. }
    and create an Entity for each body. This is something of an (general, not OO) Object/Database-Normalized perspective. It's "object" because the component represents the an object ("BodyInMotion"), and it's DBNorm because it only contains the information for one object type/aspect.

    There is other data involved. Position represents the current position, but there is also an Initial Condition for position. There is the Time that we are iterating over (handled for us by Unity). Iterating with respect to Time brings in incremental change, and using Initial Position, total change. There are the Forces (Impulse, Stepped, or variant) the cause acceleration, and there are also "environmental" factors like Drag/Resistance to consider. These could all be individual components. We could create systems and jobs to work on individual "object" aspects, cache results, and then relative aspects.

    From a systems perspective, we can model a body in the environment, and evaluate it over time steps.
    upload_2020-2-15_11-38-1.png
    (Not an accurate representation, just for illustration)
    Forces act on the body producing motion, and the environment produces a "resistive force" proportional to that motion.
    Stepping over time, we can plot the response - total change in position and instantaneous velocity.
    upload_2020-2-15_11-38-17.png

    Modeling as "Object Components" requires a lot of fetching, and it's not very compact.

    The bodies problem can be framed as solving simultaneous differential equations in the common form
    Code (CSharp):
    1. ax^2 + by + z
    , where x = acceleration, y = velocity, and z = position, all relative to Time (t). Since we are iterating over time, we can use Laplace Transforms to simplify this. Skipping the math and simplifying a little, s basically represents change with respect to time. The behavior of the body with respect to time can be represented by
    Code (CSharp):
    1. (vs + u) / (as^2 + bs + c)
    . Simplifying more, since this is a standard form, the data that defines the behavior of the body over time is simply
    Code (CSharp):
    1. [v, u]/[a, b, c]
    , or as a single value - float3x2 =
    Code (CSharp):
    1. [[u, v, _],[a, b, c]]
    . Because we are iterating, we often need to persist intermediate results between steps. We also have initial values. All of these are floats. Expanding to a 4x2, we can pack these,
    Code (CSharp):
    1. [[u, v, initial, _ ], [a, b, c, temp]]
    . This single variable represents the Body parameters and state, and we can solve for conditions at any time using "off the shelf" jobs. Environment is just another transform, in a standard feedback model, so we can expand to a 4x4 -

    Code (CSharp):
    1. [[pu, pv, pinitial, _ ], [pa, pb, pc, ptemp], [fu, fv, finitial, _ ], [fa, fb, fc, ftemp]]
    The component representing the data for a body in motion, in an environment, with respect to time is
    Code (CSharp):
    1.  
    2. public component DifferentialWithFeedback : IComponentData {
    3.     public float4x4 TransformCoefficients;
    4. }
    5. // Paraphrased
    6. Entity bodyInMotion = EM.CreateEntity(new DifferentialWithFeedback(...));
    7.  
    A single "canned" solver job like `class ODE23 : JobComponentSystem{ }` implementing a standard Ordinary Differential Equation Solver can solve not only bodies in motion, but any problem that can be framed using coefficients of differential equations. All of these can be placed under a single archetype, so one set of code gets cached, and one set of chunks gets processed. Intermediate results are part of the dataset, so lookup sets, temp storage, or references are not always needed.

    This is what data-oriented design is to me - my entities are the data elements (parameters) used by a job (function). They are organized to be machine readable, not human readable. The jobs are more generic systems, that accept values that could come from any type of "thing".

    The component (leaving out some values)
    Code (CSharp):
    1. [3, 1, _, _],[1, 7, 22, _],[1, 1, _, _],[4, 1, 6, _]
    with this job (that isn't complicated on the inside)
    upload_2020-2-15_11-40-0.png
    defines this response
    upload_2020-2-15_11-40-11.png

    This reduces the overall number of jobs, dependencies between jobs, and cache swaps. While this is great for jobs, it's not so great when we need to build or use the data, or results. This is where "regular" objects come in. I don't think naturally in transform coefficients. I can't tell you how changing acceleration by 2 will change the coefficients. But, I can write a job that can do that and the inverse transform too. I'll do something like this
    Code (CSharp):
    1. // Object Side
    2. partial class Motion {
    3.     public float Position {get; private set; }
    4.     public float Velocity {get; private set; }
    5.     public float Acceleration {get; private set; }
    6.  
    7.     public float EnvironmentP1 {get; private set; }
    8.     public float EnvironmentP2 {get; private set; }
    9.     public float EnvironmentP3 {get; private set; }
    10.  
    11.     // Writers
    12.     void SetBody (...){...}
    13.     void SetEnvironment (...){...}
    14.     ...
    15.     public void ToCoefficients(){...}
    16.  
    17.     public Entity CreateEntity() {...}
    18.     public Facet CreateComponent(){...}
    19.     public void FromComponent(Facet component) {...}
    20. }
    21. // Entity Side
    22. partial class Motion {
    23.     public struct Facet: IComponentData {
    24.        public float4x4 Coefficients;
    25.     }
    26. }
    27.  
    My classes bridge the man-machine representations.

    It also makes design easier and more consistent, since structs can't do things like inherit.
    Code (CSharp):
    1. class GroundMotion : Motion {}
    2. class AirMotion : Motion {}
    3. class WaterMotion : Motion {}
    4. var standardDiffEqEntity = new GroundMotion(...).CreateEntity();
    Also composition.
    Code (CSharp):
    1. partial class Car {
    2.     public float Value { get; private set; }
    3.     public GroundMotion Movement { get; private set;}
    4. }
    5. partial class Car {
    6.     public struct Facet : IComponentData {
    7.         public float Value;
    8.         public Motion.Facet Movement; // Nested or -
    9.         public float4x4 Movement; // Expanded
    10.      
    11.         public Facet(...){
    12.             // Expand
    13.             Movement = Motion.Facet.Coefficients;
    14.         }
    15.     }
    16. }
    To deal with simultaneous diffEq's I group them with a component.
    Code (CSharp):
    1. archetype Simultaneous(typeof(eqSet), typeof(Coefficients));
    When my jobs are less canned and mathematical, I may want to know more about the data. `c0.x.y` isn't real clear.
    For that I add a few properties. Tuples are handy.
    Code (CSharp):
    1. partial class DataSet {
    2.     partial struct Facet {
    3.       public float4x4 Ratios;
    4.       public (first, second) Numerator => (first: Ratios.c0.y.x, second: Ratios.c0.y.y );
    5.     }
    6. }
     
  27. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,830
    I am doing the above making a game base on subatomic physics. Because I create the archetype in mono land I keep a list of them as Vector2Int with x= index and y = version and indexed lists matching that have any data about them I need stowed to play with them back in mono land when the time comes to do so.. I tag each with the necessary component so that in mono land if I have need of it I can fetch it which I can for inventory and crafting systems where I simply look at tag and know I can now handle it like I was handling it prior as far as keeping track of entities collided with., hooked into the UI which ECS in its present form would need huge code swaths to handle. I was in the habit of atomizing my data and behaviours anyways before having to switch to mono due to physx crashes and poor performance with many rigibodies in play.
     
  28. Brendon_Smuts

    Brendon_Smuts

    Joined:
    Jun 12, 2017
    Posts:
    67
    I’m just going to reply to the topic because after reading all your posts I can’t make sense of 90% of what you’re talking about.

    If the architectural decision here is to change a fundamental ECS building block and embed some random value into every single entity whose only purpose is to POSSIBLY store some undefined user data that may never be used and can never be reasoned about by anyone outside the project owners OR simply add a component with the data you want to every entity that needs it....

    This is a no brainer.
     
  29. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    99
    In the end, you just have a specific use case not related to Unity ECS itself (and Unity DOTS in general, as you are dealing with OOP stuff, which goes totally against the Unity DOTS idea), which doesn't justify your feature proposal (someone at Unity may think differently, not speaking by them).

    Then your options are to either to adapt your code with one of the many options we suggested for you or modify the package yourself or to hope for someone at Unity to agree with your point of view and implement that for you.
     
unityunity