Search Unity

Question Ordering of imports using AssetImportContext.DependsOnArtifact() is wrong?

Discussion in 'Asset Importing & Exporting' started by Harryh_h, Jun 6, 2022.

  1. Harryh_h

    Harryh_h

    Joined:
    May 12, 2018
    Posts:
    4
    It says in the documentation here, that:
    all Assets which depend on [an asset] will also get reimported (after the dependency has been imported).

    But I've found it to be importing the other way round, and it's causing quite a few issues.

    I did a quick test with two different importers which print something to console upon import, and I've found that when I reimport the dependency, it imports the dependent asset first and then the dependency, which causes there to be missing references upon import of the dependent. Here's an example use two simple importers:

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEditor.AssetImporters;
    3. using UnityEngine;
    4.  
    5. [ScriptedImporter(1, ".dependent")]
    6. public class DependentImporter : ScriptedImporter
    7. {
    8.     [SerializeField] Dependency dependency;
    9.  
    10.     public override void OnImportAsset(AssetImportContext ctx)
    11.     {
    12.         if (dependency != null)
    13.             ctx.DependsOnArtifact(AssetDatabase.GetAssetPath(dependency));
    14.         else
    15.             Debug.LogError("This is bad! I am importing before my dependencies! I will no longer have correct dependencies set up on further imports...");
    16.  
    17.         Debug.Log("Dependent Object");
    18.     }
    19.  
    20.     [MenuItem("Assets/Create/Dependent Object", false, 0)]
    21.     private static void CreateNewAsset()
    22.     {
    23.         ProjectWindowUtil.CreateAssetWithContent("Dependent Object.dependent", string.Empty);
    24.     }
    25. }
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEditor.AssetImporters;
    3. using UnityEngine;
    4.  
    5. [ScriptedImporter(1, ".dependency")]
    6. public class DependencyImporter : ScriptedImporter
    7. {
    8.     public override void OnImportAsset(AssetImportContext ctx)
    9.     {
    10.         Dependency dependency = ScriptableObject.CreateInstance<Dependency>();
    11.  
    12.         ctx.AddObjectToAsset("Dependency", dependency);
    13.         ctx.SetMainObject(dependency);
    14.  
    15.         Debug.Log("Dependency Object");
    16.     }
    17.  
    18.     [MenuItem("Assets/Create/Dependency Object", false, 0)]
    19.     private static void CreateNewAsset()
    20.     {
    21.         ProjectWindowUtil.CreateAssetWithContent("Dependency Object.dependency", string.Empty);
    22.     }
    23. }
    Where a Dependency is a very simple ScriptableObject:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Dependency : ScriptableObject
    4. {
    5.  
    6. }
    Expected:
    * Reimport Dependency -> Import Dependency -> Import Dependent

    Whats occuring:
    * Reimport Dependency -> Import Dependent (References to Dependency are now null (BAD)) -> Import Dependency (References no longer null, but it's too late, import has already occured).

    Is this a bug or the intended method of import? And if intended, how can I go about importing in such a way that there are no null references.
     
    Last edited: Jun 6, 2022
  2. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Hi @Harryh_h !

    References to other assets in a ScriptedImport may fail during the import (because the reference may not exist yet and the c# code cannot deal with it...)
    The correct way to write your DependentImporter is to use a LazyLoadReference instead of a direct reference.
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEditor.AssetImporters;
    3. using UnityEngine;
    4. [ScriptedImporter(1, ".dependent")]
    5. public class DependentImporter : ScriptedImporter
    6. {
    7.     [SerializeField] LazyLoadReference<Dependency> dependency;
    8.     public override void OnImportAsset(AssetImportContext ctx)
    9.     {
    10.         if (dependency.isSet && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(dependency, out var guid, out long _))
    11.             ctx.DependsOnArtifact(guid);
    12.         Debug.Log("Dependent Object depending on " + dependency.asset);
    13.     }
    14.     [MenuItem("Assets/Create/Dependent Object", false, 0)]
    15.     private static void CreateNewAsset()
    16.     {
    17.         ProjectWindowUtil.CreateAssetWithContent("Dependent Object.dependent", string.Empty);
    18.     }
    19. }
    Edit: You may see multiple import of the DependentImporter, because the assetdatabase resolves dependencies on the fly and may try to import the DependentImporter once before importing the Dependency, then re-import it after once the dependency is ready.
    To avoid that, you can specify a priority order in the ScriptedImporter attribute so that DependentImporters are always imported after DependencyImporters.
     
  3. zzmingo

    zzmingo

    Joined:
    Sep 8, 2016
    Posts:
    10
    Hi, @bastien_humeau

    In my case:
    1. I created a tile palette
    2. I added a tile image
    3. I created an importer to importing the image and split it to many sprites in OnImportAsset
    4. In OnImportAsset, with this sprites, also create tiles use `ctx.AddObjectToAsset()` instead of create new asset file.
    5. Drag this tiles to Tile Palette in Tile Palette Window

    In the prev case, export this file to a custom package, and create new Unity project, import this files, the tiles will lost in Tile Palette.

    Like this:
    upload_2022-11-1_9-59-7.png

    How to solve this issue?
     
  4. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Are you moving your imported file along with its .meta file?
    The .meta file contains a unique GUID that is used by other assets and scenes to reference your assets and subasset (in this case the sprites), if the guid changes, then any references to it will be lost in your project.
    Looking specifically at the Palette, it keeps a link to the Tile asset GUID itself, and also to the Sprite used by the Tile, so if you're using a ScriptedImporter that generate both Sprites and Tiles, your Palette prefab should contains that guid twice (or more if you have multiple Tiles/Sprites linked to the same palette).
    It should look like that in the .prefab file (fileID and guid being specific to my local tests):
    Code (CSharp):
    1.   m_TileAssetArray:
    2.   - m_RefCount: 1
    3.     m_Data: {fileID: -4990108416319592044, guid: 4d3a91d68d1e2f246a597041ba24f4d7, type: 3}
    4.   m_TileSpriteArray:
    5.   - m_RefCount: 1
    6.     m_Data: {fileID: 8442006906113355076, guid: 4d3a91d68d1e2f246a597041ba24f4d7, type: 3}
     
  5. zzmingo

    zzmingo

    Joined:
    Sep 8, 2016
    Posts:
    10
    @bastien_humeau It's works when I reimport the palette, while not work on import the tile image and palette from custom package for a new project at first time.

    Everything works if this tiles isn't generated from scripted importer.

    It seems depends on importing order of tile and palette, but I didn't know about queue order of this native types.
     
  6. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Can you report a bug with your project and send me the bug number here? I'll be able to have a look early next week and figure out what's going on.
    From what I can tell, the Palette should only use the tiles and sprites as a reference and the import order should not impact that, but maybe there is an issue with the Prefab system or the Palette itself.
     
  7. richard_harrington

    richard_harrington

    Unity Technologies

    Joined:
    Sep 29, 2020
    Posts:
    22
    Just wanted to clarify - I think there's an issue with this code, in that it uses TryGetGUIDAndLocalFileIdentifier to get a string-form GUID, which it then passes to DependsOnArtifact directly.

    The problem being that when you pass a string to DependsOnArtifact, it expects it to be a path.

    So instead, the code should be:
    Code (CSharp):
    1.         if (dependency.isSet && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(dependency, out var guid, out long _))
    2.             ctx.DependsOnArtifact(new GUID(guid));
     
  8. Waz

    Waz

    Joined:
    May 1, 2010
    Posts:
    287
    Is there a working example of doing anything like this? Tests? We shouldn't have to look for code posted by Unity devs in a forum - if you're going to write code to demonstrate something, write it as a Unit Test so you know it works, works on future versions, and can be read by anyone coming along later.

    When I use the pseudocode posted, I get:

    Import of asset 'Assets/NewThing.myasset' setup artifact dependency to 'Assets/Dependency.jpg' but dependency isn't used and therefore not registered in the asset database.​

    And the dependency does nothing.
     
  9. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    I'd refer you to our documentation:
    https://docs.unity3d.com/2022.3/Doc...ers.AssetImportContext.DependsOnArtifact.html
    Each piece of code displayed on the documentation is run in a test suite to make sure it compiles. While it doesn't make sure the code is doing what it says it does, we have internal unit tests making sure each method is working as expected and our documentation examples are usually very similar to the tests, cleaned from some specifics of our test system that makes the code harder to understand.

    Regarding this specific error, this is probably because you are declaring a dependency on an asset that you are not loading.
    I know this is a bit cumbersome, but it is required to call AssetDatabase.LoadAssetAtPath before or after declaring the dependency to tell the AssetDatabase that you are actually trying to use this asset (even if you already have a direct reference to it, that reference may not be up to date and loading the asset is preferred).
     
  10. richard_harrington

    richard_harrington

    Unity Technologies

    Joined:
    Sep 29, 2020
    Posts:
    22
    Circling back on this code snippet per Bastien's response.

    In order for the artifact dependency to "hang around", you must attempt to load the asset after setting up the dependency, so it'd be more like this, if you encounter that warning message:
    Code (CSharp):
    1.         if (dependency.isSet && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(dependency, out var guid, out long _))
    2.         {
    3.             var actualGuid = new GUID(guid);
    4.             ctx.DependsOnArtifact(actualGuid);
    5.             AssetDatabase.LoadAssetAtPath<TheTypeOfTheArtifactYouWantToLoad>(AssetDatabase.GUIDToAssetPath(actualGuid));
    6.         }
     
  11. Waz

    Waz

    Joined:
    May 1, 2010
    Posts:
    287
    I hope this demonstrates precisely why tests that merely check that it compiles are not particularly useful, nor untested code that's posted here (which shows literally the same problem, as it has no LoadAssetAtPath).

    In any case, thanks - it works once I call LoadAssetAtPath().
     
  12. wilgieseler

    wilgieseler

    Joined:
    Oct 17, 2013
    Posts:
    84
  13. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    You still have to call LoadAssetAtPath to tell the AssetDatabse that this has been loaded explicitly.

    By the way, the Import Activity Window is a very good way to make sure dependencies are properly added to your imported assets and check reasons for re-import whenever it happens and you didn't expect it:
    https://docs.unity3d.com/Manual/ImportActivityWindow.html
     
    wilgieseler likes this.