Search Unity

  1. Curious about what's going to be in 2020.1? Have a look at the 2020.1 beta blog post.
    Dismiss Notice
  2. Want to see 2020.1b in action? Sign up for our Beta 2020.1 Overview Webinar on April 20th for a live presentation from our evangelists and a Q&A session with guests from R&D.
    Dismiss Notice
  3. Interested in giving us feedback? Join our online research interviews on a broad range of topics and share your insights with us.
    Dismiss Notice
  4. New Unity Live Help updates. Check them out here!

    Dismiss Notice

ECS: How to reuse logic in multiple Systems

Discussion in 'Data Oriented Technology Stack' started by thomasvt, Feb 4, 2020.

  1. thomasvt

    thomasvt

    Joined:
    Jun 23, 2016
    Posts:
    1
    Hi,

    I have a question about Unity's ECS architecture: where does the reusable "business" logic go? In OO, objects would be the central place of reusabe logic that naturally applies to them, but in ECS there are only Systems. Systems are grouping logic by feature (give or take), not by object. So where do we put reusable algorithms/calculations?

    Example: if my 2D game has a PlanetSpawnSystem generating Planet entities, that system converts planet-coordinate to Vector2 for putting items on the surface. This uses a specific algorithm and the data defining that planet.

    But my TeleportCharacterSystem, or SpawnNewCharacterSystem, or LandShipAutopilotSystem will be asking a lot of the same questions. What is the surface height at planet coordinate x?

    Do you put that in static classes, like: Vector2 PlanetLogic.GetSurface(float angularCoord, PlanetDefinition planetDefinition) or am I missing something?

    thanks
     
  2. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    147
    I think putting in static classes is a good approach, i do it myself and make sure the methods are bust compatible.
     
    thomasvt likes this.
  3. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,121
    If it's all about mathematical operations (give an input and get an ouput) then static classes are ok, sure. Static classes are not bad as long as they are stateless.

    If you instead are talking about abstract behaviours applied to different specialized entities (is not your case it seems) Generally in ECS you abstract code using abstract systems. Abstract Systems operates on abstract components. You must find what data an abstract system (shared logic like you say) operates on and encapsulate this data in an abstract component. Then your specialized entities will use the common data along the specialized one, but the abstract systems will never know about the specialized entities, they will operate only on the abstract portion of data.
     
    Last edited: Feb 4, 2020
    siggigg and thomasvt like this.
  4. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    286
    yes static utility classes are fine to stay DRY (don't repeat yourself ;)

    side note, do not use Vector2 but int2 of float2 - and if you can figure a way to do it, use int4 and float4 for more perfs!
     
    thomasvt likes this.
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    2,058
  6. hellholegames

    hellholegames

    Joined:
    Dec 19, 2019
    Posts:
    3
    (OP here)

    Interesting... I understand why you make structs to represent a set of data+logic, but I see you also make structs for something equivalent to a single method-call with some arguments? Does that have a benefit (instead of just haveing a static method and calling that?) I'm a business developer, so maybe your approach has benefits in Unity/ECS context that I don't understand yet.

    And you use structs vs class because they get allocated on the stack instead of the heap, which prevents GC needs?
     
    Last edited: Feb 5, 2020
  7. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    160
    I use float4s as xyz + distance/radius/angle/etc. for my jobs, however when used in structs they take up equal amounts of space as two float2s, and four floats, but are a bit laborious to work around if you need only one or two values, so it all depends on how you use them.
     
  8. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    147
    Unity using for they new math library "static class" and add the attribute "[MethodImpl(MethodImplOptions.AggressiveInlining)]" on the methods.
     
  9. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    2,058
    Because I can store data in the struct. This one method call might end up calling 20 other methods and the algorithm might be 500-1000 lines of code.

    If I used a static the method it might end up looking like this, calling a bunch of other static methods

    Code (CSharp):
    1. public static void ParticleSimulation(Entity entity,
    2.     int index,
    3.    EntityCommandBuffer.Concurrent commandBuffer,
    4.    ComponentDataFromEntity<Translation> translations,
    5.    ComponentDataFromEntity<Rotation> rotations,
    6.    ComponentDataFromEntity<Particle> particle,
    7.    BufferFromEntity<Links> links,
    8.    BufferFromEntity<Vertices> vertices,
    9.    BufferFromEntity<Tris> tris,
    10.    BufferFromEntity<Normal> normals,
    11.    BufferFromEntity<Uvs> uvs,
    12.    NativeArray<CollisionMap> collisions,
    13.    PhysicsWorld physics)
    14. {
    15. GetCollisions(entity, index, commandBuffer, translations, rotations, particle, links, collisions, physics);
    16. Simulate(entity, index, commandBuffer, translations, rotations, particle, links, vertices, tris, normals, uvs);
    17. // etc etc
    18. }
    19.  
    20. public static void GetCollisions(Entity entity,
    21.    int index,
    22.    EntityCommandBuffer.Concurrent commandBuffer,
    23.    ComponentDataFromEntity<Translation> translations,
    24.    ComponentDataFromEntity<Rotation> rotations,
    25.    ComponentDataFromEntity<Particle> particle,
    26.    NativeArray<CollisionMap> collisions,
    27.    PhysicsWorld physics)
    28. {
    29.  
    30. }
    31.  
    32. public static void Simulate(Entity entity,
    33.    int index,
    34.    EntityCommandBuffer.Concurrent commandBuffer,
    35.    ComponentDataFromEntity<Translation> translations,
    36.    ComponentDataFromEntity<Rotation> rotations,
    37.    ComponentDataFromEntity<Particle> particle,
    38.    BufferFromEntity<Links> links,
    39.    BufferFromEntity<Vertices> vertices,
    40.    BufferFromEntity<Tris> tris,
    41.    BufferFromEntity<Normal> normals,
    42.    BufferFromEntity<Uvs> uvs)
    43. {
    44.  
    45. }
    46.  
    And I use struct because classes can't be used in burst jobs.
     
    Last edited: Feb 5, 2020
    hellholegames likes this.
  10. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    147
    Static methods in classes can used in burst. Static classes cannot be instantiated and with aggressive inlining i think burst use it to reduce function call overhead.
     
  11. hellholegames

    hellholegames

    Joined:
    Dec 19, 2019
    Posts:
    3
    Tertle was talking about encapsulating data in classes, so instances, not static classes. But, i can imagine that static method calls are allowed in Burst, yes.

    A note: aggressive inlining is a standard .NET feature (not Unity specific) and I know from experience (non Unity .NET) that Aggressive Inlining is very often declined by the compiler: the criteria to get a method inlined are quite high (eg. a very limited instruction count in the method body). Perhaps Burst is more pro-inlining, but, I wouldn't trust it blindly.
     
  12. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    286
    Burst takes Aggressive inlining into account, I did see it first hand with this method of mine that I call in a for loop inside an IJobParallelFor job.
    Code (CSharp):
    1.   public static void IdxToSpherePoint(in int idx, in CubemapFace face, ref int4 tv, ref double3 p, in float3 offset, in int seed)
    2.      
    In my case it was a 10 times speedup.

    I did read in the doc that burst is inlining a lot of things by default, but I assume if the method has a certain threshold count of instructions it doesn't inline it.

    PS: I know that passing ints by ref/in is normally silly but in the case of making sure the code is inlined in a core for loop in another loop( the foreachjob) it makes sense :)
     
    Last edited: Feb 5, 2020
    Nyanpas likes this.
unityunity