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. Dismiss Notice

Tricks with C#8

Discussion in 'Entity Component System' started by tertle, Sep 13, 2020.

  1. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,627
    If you're into writing low level code, C#8 (2020.2) opens a few cool new patterns that you can take advantage of so I thought I'd start a topic to share an ideas and to see if anyone else has some interesting patterns they utilize.

    So here is mine, change in behaviour of a "Pointer of a managed type". Take these structs

    Code (CSharp):
    1. private struct RootStruct
    2. {
    3.    public void SetData(SystemBase system)
    4.    {
    5.        fixed (RootStruct* root = &this)
    6.        {
    7.            ((TestStruct*)root)->SetData(system);
    8.        }
    9.    }
    10.  
    11. }
    12.  
    13. private struct TestStruct
    14. {
    15.    [ReadOnly]
    16.    private ComponentDataFromEntity<Translation> translations;
    17.  
    18.    public void SetData(SystemBase system)
    19.    {
    20.        translations = system.GetComponentDataFromEntity<Translation>(true);
    21.    }
    22. }
    in 2020.1 because of the ComponentDataFromEntity<Translation> this throws a
    error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type ('TestSystem.TestStruct')


    But in 2020.2 this is completely legal code and works as you'd expect.

    Here's a full working example

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Collections.LowLevel.Unsafe;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6. using UnityEngine;
    7.  
    8. public unsafe class TestSystem : SystemBase
    9. {
    10.     private BlobAssetReference<RootStruct> reference;
    11.  
    12.     protected override void OnCreate()
    13.     {
    14.         var testStruct = new TestStruct { TargetPosition = new float3(1, 2, 3) };
    15.         ref var root = ref UnsafeUtility.As<TestStruct, RootStruct>(ref testStruct);
    16.         reference = BlobAssetReference<RootStruct>.Create(UnsafeUtility.AddressOf(ref root), UnsafeUtility.SizeOf<TestStruct>());
    17.  
    18.         // Create 1 entity, default translation is fine
    19.         this.EntityManager.CreateEntity(typeof(Translation));
    20.     }
    21.  
    22.     protected override void OnDestroy()
    23.     {
    24.         reference.Dispose();
    25.     }
    26.  
    27.     protected override void OnUpdate()
    28.     {
    29.         var localRef = reference;
    30.         localRef.Value.SetData(this);
    31.  
    32.         this.Entities.ForEach((Entity entity) =>
    33.             {
    34.                 if (localRef.Value.TryGetDistance(entity, out var distance))
    35.                 {
    36.                     Debug.Log($"Distance {distance}");
    37.                 }
    38.             })
    39.             .ScheduleParallel();
    40.     }
    41.  
    42.     private struct RootStruct
    43.     {
    44.         public void SetData(SystemBase system)
    45.         {
    46.             fixed (RootStruct* root = &this)
    47.             {
    48.                 ((TestStruct*)root)->SetData(system);
    49.             }
    50.         }
    51.  
    52.         public bool TryGetDistance(Entity entity, out float distance)
    53.         {
    54.             fixed (RootStruct* root = &this)
    55.             {
    56.                 return ((TestStruct*)root)->TryGetDistance(entity, out distance);
    57.             }
    58.         }
    59.     }
    60.  
    61.     private struct TestStruct
    62.     {
    63.         public float3 TargetPosition;
    64.  
    65.         [ReadOnly]
    66.         private ComponentDataFromEntity<Translation> translations;
    67.  
    68.         public void SetData(SystemBase system)
    69.         {
    70.             translations = system.GetComponentDataFromEntity<Translation>(true);
    71.         }
    72.  
    73.         public bool TryGetDistance(Entity entity, out float distance)
    74.         {
    75.             if (!translations.HasComponent(entity))
    76.             {
    77.                 distance = default;
    78.                 return false;
    79.             }
    80.  
    81.             var translation = translations[entity];
    82.             distance = math.distance(TargetPosition, translation.Value);
    83.             return true;
    84.         }
    85.     }
    86. }
    Returns the result, Distance 3.741657, as expected.

    Please note you should probably not use BlobAssetReference this way, it's used like this as a simple example.

    Why would you need to do this? I'm considering the concept for dynamic graphs but maybe you have some other use, or it could just be a terrible idea.



    Have you discovered any other interesting patterns?
     
    Last edited: Sep 13, 2020
    Grizmu, bb8_1, WAYNGames and 5 others like this.
  2. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    Does this mean generic types do not count as managed anymore?
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,627
  4. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    If you want to take the address/pointer to a generic type, you need to add
    where T : unmanaged
    constraint. Note that you still can't use it in
    fixed()
    expression, AFAICT, which doesn't matter much for Burst/ECS, but is still a strange limitation.
     
  5. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    944
    I don't quite get it yet. Can you enlighten us? I read it as some kind of hacked polymorphism or am I wrong?
     
  6. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    So... ECS Octrees that are as easy to use and understand as OOP octrees?
     
  7. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Does this still work as written in 2020.2.0b13 or a different version and latest entities? I copy pasted your second example and get

    "The ComponentDataFromEntity<Unity.Transforms.Translation> has been declared as [WriteOnly] in the job, but you are reading from it."
     
    Last edited: Dec 1, 2020