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

BlobArray with ValueType Generic Parameters fails

Discussion in 'Entity Component System' started by GilCat, Jan 26, 2021.

  1. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    As of Entities 0.17.0-preview.41 I cannot have a BlobArray with a value type containing generic parameters that are constrained to be value type.

    Here is an example of data struct:
    Code (CSharp):
    1.     public struct MyData<T> where T : struct {
    2.       public T Value;
    3.     }
    Here is my method to create the Blob Asset Reference:
    Code (CSharp):
    1.     public static BlobAssetReference<BlobArray<MyData<TData>>> CreateClipBlobReference<TData>(NativeArray<TData> sourceData)
    2.       where TData : struct {
    3.  
    4.       using (var builder = new BlobBuilder(Allocator.Temp)) {
    5.         ref var root = ref builder.ConstructRoot<BlobArray<MyData<TData>>>();
    6.         var clips = builder.Allocate(ref root, sourceData.Length);
    7.         for (var i = 0; i < sourceData.Length; ++i)
    8.           clips[i].Value = sourceData[i];
    9.         return builder.CreateBlobAssetReference<BlobArray<MyData<TData>>>(Allocator.Persistent);
    10.       }
    11.     }
    The error is misleading since there is no reference or pointer type in the data:
    error ConstructBlobWithRefTypeViolation: You may not build a type BlobArray`1 with Construct as BlobArray`1[].Value is a reference or pointer. Only non-reference types are allowed in Blobs.

    The problem lies in the IsValueType() Cecil extension for TypeReference @ Unity.Entities.CodeGen\CecilExtensionMethods.cs

    An easy fix would be to check if any constraint of a generic type is of "System.ValueType" instead of assuming that all generic types are not a value type.
    Code (CSharp):
    1.       if (typeReference is GenericParameter &&
    2.         (typeReference as GenericParameter).Constraints.Any(p => p.FullName == "System.ValueType"))
    3.         return true;
    public static bool IsValueType(this TypeReference typeReference) {
    if (typeReference is GenericParameter &&
    (typeReference as GenericParameter).Constraints.Any(p => p.FullName == "System.ValueType"))
    return true;

    if (typeReference is ArrayType)
    return false;

    if (typeReference is PointerType)
    return false;

    if (typeReference is ByReferenceType)
    return false;

    if (typeReference is GenericParameter)
    return false;

    var pinnedType = typeReference as PinnedType;
    if (pinnedType != null)
    return pinnedType.ElementType.IsValueType();

    var requiredModifierType = typeReference as RequiredModifierType;
    if (requiredModifierType != null)
    return requiredModifierType.ElementType.IsValueType();

    var optionalModifierType = typeReference as OptionalModifierType;
    if (optionalModifierType != null)
    return optionalModifierType.ElementType.IsValueType();

    var typeDefinition = typeReference.Resolve();

    if (typeDefinition == null)
    throw new InvalidOperationException($"Unable to locate the definition for {typeReference.FullName}. Is this assembly compiled against an older version of one of its dependencies?");

    return typeDefinition.IsValueType;
    }

    It would be nice to have this fixed because i use this in some of my generic systems, and right now i have to edit the Entites source code package to have this working as mentioned above
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,652
    bb8_1 and GilCat like this.
  3. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    938
    Sorry to bring that back up but it still happens in 0.50.
    Is that an overseight or is it intened not to allow this ??
     
  4. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    228
    It turns out to be really hard to verify safety in the generic case if you also care about not destroying compile times, which we do.

    In particular, it doesn't work to just constrain to value types or unmanaged types, because those can contain pointers, and pointers in blobassets are bad news, because we have no way of fixing them up properly.

    The two options we thought of before dropping the ball on this were as follows:

    1) Destroy compile times by walking all assemblies everywhere to find every possible generic instantiation of the blob asset, following through unlimited numbers of other generic usages in libraries to find the final concrete type, and check that the final concrete type has no pointers. We don't want to destroy compile times more than we already have with all our crazy magic, so this is out.

    2) Constrain every T that goes in a generic blob anything to implement a new interface, say IBlobType, and then use codegen magic to check that every struct implementing IBlobType doesn't have any pointers. This would break everybody's existing code, and be a bit unintuitive, but it might possibly work? But we never nailed down how well it would work, and then people moved around and got distracted. If I had to guess, we might end up doing something like this in a future version, but I wouldn't bet the farm on it.
     
    Fribur likes this.
  5. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    938
    Ok. I see it's a bit more involved than I thought.
    Thanks for the explanation.
    I'll try to think about a work around for may use case because for now having a local copy of the entities package that check the value type works but it is a pain in the .
     
  6. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    938
    If we consider that is an advanced feature, would it be possible to forbid generic type except if they implement the interface.
    Code (CSharp):
    1.             if (typeReference is GenericParameter &&
    2. if (typeReference is GenericParameter && typeof(IBlobType).IsAssignableFrom(Type.GetType(typeReference.FullName + ", " + typeReference.Module.Assembly.FullName)))
    3.             {
    4. )
    5.             {
    6. return true;
    7. }
    8. if (typeReference is GenericParameter)
    9.             {
    10. return false;
    11. }
    12.  
    13.  
    The IBlobType documentation could specify what it is and what it should ensure.
    You could even put the type under an unsafe namespace to "scare off" less experienced users and emphasize the fact that is may mess things up.
    And have another compiler validator that check that all IBlobType are safe ?

    Or worst case scenario hide that possibility behind a scripting define like DISABLE_GENERIC_BLOB_VALIDATOR or something...

    I get that you want to make sure things are safe to use and that the vast majority of people would certainly mess things up. But I think it's a shame to forbid some features in the name of safety. With that philosophy there should be no unsafe container available to users...
     
    Last edited: Mar 17, 2022
    Elfinnik159, quabug and Mockarutan like this.
  7. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    IMO, just constrain on `unmanaged` type would be enough for blob.
    For those who need pointer in blob (or any context) they must use `unsafe` keyword, which is an announcement of "I know it is **unsafe** here, but I really need to use pointer, and its on me if its break the system".

    And even if you restrict pointer out from blob, you can still use some magic, well, just use `long`, to workaround those restrictions.

    Any other formatter/serializer won't restrict user from serialize pointer to its own format, why would blob do?
     
    Last edited: Mar 18, 2022
  8. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    127
    Can you confirm that NativeLists<T> are broken now as well? The following worked just fine prior to entities 0.50

    upload_2022-3-28_11-21-52.png
     

    Attached Files:

  9. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    127
    Hmm...looks like changing
    Code (CSharp):
    1. where T : struct
    to
    Code (CSharp):
    1. where T : unmanaged
    fixes this: upload_2022-3-28_11-27-5.png
     
    elliotc-unity likes this.
  10. rauiz

    rauiz

    Joined:
    Mar 19, 2013
    Posts:
    40
    @elliotc-unity Any news on the blob asset + generics front? It's becoming rather costly to have to manually disable this, IMO very over-zealous, safety check every time I update the Entities package.

    Either solution is fine by me: add a DISABLE_GENERIC_BLOB_VALIDATOR or an interface to tag possible types that'll go into it. Since the latter is kind of a breaking change and you just released 1.0 without it, I imagine that is out of the conversation. But, the scripting define is a low effort solve for this and opt-in.

    On a side note, I still don't understand why BlobAssets are in Entities and not in Collections btw... it's very weird that the go-to way to access read-only data in bursted jobs can't be used unless you drag all of entities into your project. Seems to go against the "DOTS is a stack. ECS is just a part of it." motto that got thrown around a lot.