Search Unity

[ECS] Is it possible to check if a Component's data is not null (has been set)?

Discussion in 'Entity Component System' started by adammpolak, Jan 21, 2021.

  1. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    I am in a MonoBehaviour and I want to check if a component's data has been set.

    if(m_ClientWorldSimulationSystemGroup.GetSingleton<ClientDataComponent>().GameName != null)
    {
    }

    results in
    NullReferenceException: Object reference not set to an instance of an object


    Anyway if I can check if this specific value has been updated?
     
  2. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Which part of the given expression isn't an instance? The system itself could be null, but we can't really tell without a traceback. Is GameName a string, or one of the DOTS string types?

    But for a struct component, the members must also be structs, so the values can be compared to the default value for that member. So typically you'd use the "default" operator: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/default-values

    Checking if updated is a different question. In this case it would probably involve storing the previous component. In a SystemBase there is changed filters, but that works on a chunk level.
     
    Last edited: Jan 21, 2021
  3. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Thank you for the response!

    .GameName is a FixedString64 (one of the DOTS string types)

    The world/system are an instance as I am able to run:

    m_ClientWorldSimulationSystemGroup.GetSingleton<ClientDataComponent>().GameName.ToString()

    just fine.

    It doesn't seem like there is any default value of FixedString available, so I guess there no way to tell if a FixedString was set in ECS right now?

    upload_2021-1-21_8-25-29.png
    It seems like the FixedString64 is just allocated memory with a null terminator. So with 0 length it would appear as a string that is == "". Someone can have decided to make GameName == "" and this would appear as the same default value.

    Seems like the only option is to take out the GameName field from ClientDataComponent and make it its own component GameNameComponent, and check if the entire component exists.

    Bummer because that data goes well together but they get updated at different times in the setup flow.
     
  4. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    I don't think you would get that error from a FixedString, could be wrong though. If you set a breakpoint on that statement and attach the debugger it should tell you what the null reference is.
     
  5. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Appreciate the response Sarkahn!

    I messed around with seeing if I can check if it was equal to an unset FixedString64 (by defining a new FixedString64 earlier) but found that this was ALSO equal when I actually set the value to "".

    So it appears that is un-doable...
     
  6. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
  7. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
  8. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Sorry if I missed something but does
    fixedString == default
    not work for what you're trying to do?
     
    adammpolak likes this.
  9. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Sorry do you mean just the keyword
    default
    ?

    I actually had tried everything but that, I didn't know that was a thing, appreciate the tip for next time! (I have implemented a new workflow)
     
  10. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Yeah the keyword default is what I mean. That's typically how you check if a struct is at its default value. Of course there are some cases where a default value is a valid useful state for a struct so it's not a catchall, but for a fixedstring I think it makes sense to treat that as a special/invalid state
     
    adammpolak likes this.
  11. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    super super helpful, really appreciate it!
     
  12. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    FYI, you would never get a

    NullReferenceException: Object reference not set to an instance of an object
    error from a struct. That's a NULL reference from some object (class).
    So I don't think that inspecting the FixedString64 is where you want to look.

    I'm new to Unity but a pretty good C# developer. So assuming that Burst/IL2CPP isn't doing something wacky that I don't know about, I would suggest you break things down into multiple lines:


    Code (CSharp):
    1. var cdp= m_ClientWorldSimulationSystemGroup.GetSingleton<ClientDataComponent>()
    2. if(cdp.GameName != null)
    3. {
    4. }
    honestly I bet that your
    m_ClientWorldSimulationSystemGroup
    reference is null.
     
    Last edited: Jan 22, 2021
  13. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    That seems to make sense, but if that was the case why can I print

    m_ClientWorldSimulationSystemGroup.GetSingleton<ClientDataComponent>().GameName.ToString()

    with no error? Wouldn't that also be an issue?
     
  14. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    Sorry, but I didn't catch the
    GameName != null
    of your original post. That's not valid c# syntax (comparing a struct to null) Did you make it a nullable type? or are you sure it's a FixedString64... If it is a "
    FixedString64
    "
    and not a "
    FixedString64?
    "
    then you should be getting a syntax error / build failure.
     
  15. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    It may be easier to pin down the answer if OP gave a stacktrace of this error. It may be something specific to the equality method that gets run in this situation.
     
  16. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    yep.

    Also, I'm wrong. I forgot you can add things like operator overrides, which, from looking at the FixedString64 source, they do. So yeah you can do

    GameName != null
     
  17. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    Ah hah, in fact, looking at the source, it looks like

    GameName != null
    is what's causing your bug!


    Code (CSharp):
    1.         public unsafe static bool operator ==(in FixedString64 a, in FixedString32 b)
    2.         {
    3.             int aLength = a.utf8LengthInBytes;
    4.             int bLength = b.utf8LengthInBytes;
    5.             byte* aBytes = (byte*)UnsafeUtilityExtensions.AddressOf(in a.bytes);
    6.             byte* bBytes = (byte*)UnsafeUtilityExtensions.AddressOf(in b.bytes);
    7.             return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aBytes, aLength, bBytes, bLength);
    8.         }
    Note Line 4 above. it's trying to call
    NULL.utf8LengthInBytes
    which would throw a NullRefException.

    I wrote a quick test to check
    Code (CSharp):
    1.     [Test]
    2.     [Category("ecs lowlevel")]
    3.     public void FixedString64Null()
    4.     {
    5.         var fStr = new FixedString64("hello");
    6.  
    7.         if(fStr != null)
    8.         {
    9.  
    10.         }
    11.         else
    12.         {
    13.             Assert.IsTrue(false, "fStr is null?");
    14.         }
    15.  
    16.     }

    and it crashes with the same error

    FixedString64Null (0.018s)
    ---
    System.NullReferenceException : Object reference not set to an instance of an object
    ---
    at Unity.Collections.FixedString32..ctor (System.String source) [0x00026] in C:\repos\unity\dotsman\DotsMan\Library\PackageCache\com.unity.collections@0.14.0-preview.16\Unity.Collections\FixedString.gen.cs:386
    at Unity.Collections.FixedString32.op_Implicit (System.String b) [0x00000] in C:\repos\unity\dotsman\DotsMan\Library\PackageCache\com.unity.collections@0.14.0-preview.16\Unity.Collections\FixedString.gen.cs:803
    at NativeCollectionTests.FixedString64Null () [0x0000d] in C:\repos\unity\dotsman\DotsMan\Assets\Scripts\Tests\NativeCollectionTests.cs:79
    at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
    at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <9577ac7a62ef43179789031239ba8798>:0

    :D
     
    Sarkahn and RecursiveEclipse like this.
  18. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Okay so it is NOT the ClientSystem but I assume that leaves us with @Sarkahn 's recommendation of GameName != default ?
     
  19. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    I would personally go with
    GameName.Length !=0
    as operator overrides obfuscate the performance cost. Look at that decompiled
    operator ==(in FixedString64 a, in FixedString32 b)
    I pasted above. Not the cheapest piece of code.

    Edit: that said, don't worry about the performance. I just hate the idea of operator overrides in "performant" code I suppose.
     
    adammpolak and Sarkahn like this.
  20. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
    I think this post will help if you want a "nullable" value type in HPC#.
     
  21. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Sorry am I missing how GameName.Length != 0 if I set the GameName = "" (an empty string?).
     
  22. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    I'm not sure what your question is? This statement evaluates to true:
    FixedString64("").Length == 0
    .

    default and "" are the same. So if you want to check for
    != null
    you can just check for
    .Length != 0


    Here is a test that passes showing it:

    Code (CSharp):
    1.     [Test]
    2.     [Category("ecs lowlevel")]
    3.     public void FixedString64Empty()
    4.     {
    5.         var fStrEmpty = new FixedString64("");
    6.         var fStrDefault = default(FixedString64);
    7.  
    8.         Assert.AreEqual(0, fStrEmpty.Length);
    9.         Assert.AreEqual(0, fStrDefault.Length);
    10.         Assert.IsTrue(fStrEmpty == fStrDefault);
    11.         Assert.IsTrue(fStrEmpty == default(FixedString64));
    12.     }
     
    Last edited: Jan 22, 2021
  23. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Sorry for being unclear @jasons-novaleaf !

    - What I meant to say is that a user is given the option to set the game name
    - They have the option to set the game name to ""
    - I wanted to CHECK, if the GameName had been set (as part of a start up flow)
    - It seems like I wouldn't be able to tell the difference between them NOT setting it ("") or them setting it to ""
     
  24. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    Check and see if you can use nullable types. if not, add an extra .isSet boolean field to your ClientDataComponent
     
    adammpolak likes this.
  25. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    NICE!!!

    .isSet! so obvious but terrific, thank yoU!