Search Unity

Question Comparing all serialized values with custom attribute in the Asset Database

Discussion in 'Asset Database' started by sbsmith, Jan 11, 2023.

  1. sbsmith

    sbsmith

    Joined:
    Feb 7, 2013
    Posts:
    126
    I made a UniqueId attribute for string fields that just provides a nice way to visualize IDs in the editor. Now, I want to guarantee that all those IDs are actually unique by scanning every asset in the project that has a string serialized with that attribute. This seems like it could get tricky. It's easy enough to find all the classes that use the attribute and then retrieve all the assets that match those classes, but that doesn't work for assets with serialized members that contain the attribute.

    Is there an easy way to do this, or does this just require brute force through reflection and recursion?

    I can get all the types that directly use my attribute here

    Code (CSharp):
    1. string definedInAssemblyName = typeof( UniqueIdAttribute ).Assembly.GetName().Name;
    2. BindingFlags binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
    3.  
    4. List<Type> typesWithUniqueIdAttribute =
    5.    ( from assembly in AppDomain.CurrentDomain.GetAssemblies()
    6.      where ( ( !assembly.GlobalAssemblyCache )
    7.              && ( ( assembly.GetName().Name == definedInAssemblyName ) || assembly.GetReferencedAssemblies().Any( a => a.Name == definedInAssemblyName ) ) )
    8.      from type in assembly.GetTypes()
    9.      from field in type.GetFields( binding )
    10.      let attributes = field.GetCustomAttributes( typeof( UniqueIdAttribute ), true )
    11.      where attributes != null && attributes.Length > 0
    12.      select type ).Distinct().ToList<Type>();
    and I can get all the assets that directly use these types this way

    Code (CSharp):
    1. foreach( var type in typesWithUniqueIdAttribute )
    2. {
    3.     var objs = FindAssetsByType( type );
    4.     foreach( var obj in objs )
    5.     {
    6.         Debug.Log( obj );
    7.     }
    8. }
    9.  
    10. public static List<UnityEngine.Object> FindAssetsByType( Type type )
    11. {
    12.     List<UnityEngine.Object> retVal = new List<UnityEngine.Object>();
    13.     string[] assetIds = AssetDatabase.FindAssets( $"t:{type}" );
    14.     for( int iAssetId = 0; iAssetId < assetIds.Length; ++iAssetId )
    15.     {
    16.         string assetPath = AssetDatabase.GUIDToAssetPath( assetIds[ iAssetId ] );
    17.         UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath( assetPath, type );
    18.         if( asset != null )
    19.         {
    20.             retVal.Add( asset );
    21.         }
    22.     }
    23.     return retVal;
    24. }
    Should I be taking a different approach entirely?
     
  2. sbsmith

    sbsmith

    Joined:
    Feb 7, 2013
    Posts:
    126
    I wrote code to build the reference chains that lead to strings with the UniqueId attribute and now it's just a matter of checking all the serialized data in the project... which I'm going to put off until later. I thought the UniqueId attribute was a nifty little idea, but it turns out to be a larger undertaking that will require a test framework to guarantee that it works properly on all ScriptableObjects, Prefabs, and Scenes. This may even not even be the best way to go about creating an easy-to-use Unique ID system. I have followed my curiosity far enough for now. If anyone has a drop-in solution, or a better idea, I am still interested and will keep this thread open.