Search Unity

Bug (ADDR-1300) Cannot load addressable sprite if it's included in a sprite atlas

Discussion in 'Addressables' started by dlegare-synapse, Jun 30, 2020.

  1. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    I'm testing out a situation where I have a sprite that is both itself marked as addressable, and is also included in a separate sprite atlas (which may or may not be marked addressable). I'm able to directly load the sprite by its address in the editor when running with the "Use Asset Database" (
    BuildScriptFastMode
    ) build script, but when running with "Simulate Groups" (
    BuildScriptVirtualMode
    ) or "Use Existing Build" (
    BuildScriptPackedMode
    ) I can no longer load the sprite directly by address. This appears to be the case even if the atlas isn't marked as addressable (simply including the sprite in an atlas is enough to break loading).

    I have an example of this hosted up on GitHub if anyone wants to take a look at the specific setup I'm testing. My specific setup is as follows:

    • I have two sprites I'm testing with:
      happy.png
      and
      sad.png
      . Both are marked as addressable, with the addresses "happy_non_atlas" and "sad_non_atlas" respectively.
    • I also have a sprite atlas
      Faces.spriteatlas
      setup, with
      sad.png
      included in the atlas. The atlas is NOT marked as addressable.
    • I have a script that attempts to load both "happy_non_atlas" and "sad_non_atlas" using
      Addressables.LoadAssetAsync<Sprite>()
      .
    You can view the specific test script I'm using in the example project.

    With this setup, both "happy_non_atlas" and "sad_non_atlas" load as expected if I'm running with
    BuildScriptFasMode
    . But if I run with
    BuildScriptVirtualMode
    or
    BuildScriptPackedMode
    , "happy_non_atlas" loads fine but "sad_non_atlas" fails with a
    InvalidKeyException
    :

    Code (text):
    1. Failed to load sprite @ sad_non_atlas: System.Exception: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=sad_non_atlas, Type=UnityEngine.Sprite
    Does anyone know what's going on here? Is this expected behavior? I would expect that I can still access the addressable sprite even if it's included in an atlas. It seems like a bug to me, but it could also be a deliberate behavior to avoid duplicating sprites that are also contained in a sprite atlas when building addressables. When I was reading the docs it sounded like Addressables specifically doesn't automatically de-duplicate sprites that are also included in an atlas (since there's a provided analyzer rule for detecting that kind of duplication), but I could have misread.
     
    Last edited: Jun 30, 2020
  2. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    I've done a bit more testing and confirmed that this issue is happening with Addressables 1.11.2, and that it still happens if I mark the sprite atlas as addressable. I've also inspected the generated asset bundle when doing a packed build and it seems like the non-atlased versions of the sprites are included in the bundle (as I would have expected), so it's not clear to me why I can't load the sprite directly once it's marked as being included in an atlas.
     
  3. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    Digging into this a bit more, it looks like the issue is how types are tracked for sprites that are included in an atlas. For an addressable sprite that's not in an atlas, there end up being 2 locations for the asset's key: One for type
    Texture2D
    and one for type
    Sprite
    . However, when a sprite is included in an atlas, there ends up being only a single location of type
    Texture2D
    corresponding to the asset's key.

    This at least explains why I can't load the asset as a
    Sprite
    . However, when I instead try to load the asset as a
    Texture2D
    I end up getting a different error coming from the attempt to load the asset from the bundle:

    Code (text):
    1. Exception encountered in operation Resource<Texture2D>(sad.png): Unable to load asset of type UnityEngine.Texture2D from location Assets/Sprites/sad.png.
    Looking at the bundle that's actually generated in this case, the asset that's stored in the bundle with the name
    Assets/Sprites/sad.png
    is actually a
    Sprite
    !

    So, this is almost definitely a bug, right? It seems like
    BuildScriptPackedMode
    is including assets in the built bundles that are unreachable due to the type tracked for the asset's location not matching the type tracked in the built asset bundle.
     
  4. TreyK-47

    TreyK-47

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    1,822
    Yeah, seems like it might be a bug. Definitely file a bug report for us. We'll also flag this one for the team to have a look.
     
    dlegare-synapse likes this.
  5. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
  6. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    Ah, I got a response saying that this bug is already tracked as ADDR-1300. Looks like this issue has been around for a while, unfortunately :(
     
  7. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    I think I've tracked down the cause of this issue to
    AddressableAssetEntry.CreateCatalogEntriesInternal()
    , which is where the actual content catalog entries are created when building with
    BuildScriptPackedMode
    . In this function, there are two places where an actual catalog entry will be added:
    • An entry will be added with the type of the main asset for the entry.
    • For any additional objects included in the bundle corresponding to that entry's path, an additional catalog entry will be added for any additional object types that differ from the main asset's type.
    So for example, when you mark a non-atlas sprite as addressable, this logic is why it can be loaded as both a
    Texture2D
    and a
    Sprite
    : The main asset type for the sprite is
    Texture2D
    , but the build process also generates a second object of type
    Sprite
    at the same path in the bundle, so a second catalog location gets added for type
    Sprite
    .

    However, it looks like when a sprite is included in an atlas the asset path in the bundle no longer includes an object of type
    Texture2D
    , only a single object of type
    Sprite
    . Instead, the sprite atlas asset will also be included in the bundle (since it's a dependency of the sprite it will still be included even if it's not explicitly marked for inclusion), and the
    Sprite
    asset will reference a portion of the
    Texture2D
    owned by the atlas.

    This is a perfectly reasonable setup, and seems like the right approach to take in terms of how sprites and sprite atlases are built into asset bundles. However, the logic in
    CreateCatalogEntriesInternal()
    doesn't correctly account for the possibility that, once built, the actual object included in a bundle will no longer match the main asset's type. The actual logic for adding the catalog entries first inserts an entry for the main asset type:

    Code (CSharp):
    1. if (mainType != null)
    2. {
    3.     Type runtimeProvider = GetRuntimeProviderType(providerType, mainType);
    4.     if (runtimeProvider != null)
    5.         providerTypes.Add(runtimeProvider);
    6.  
    7.     entries.Add(new ContentCatalogDataEntry(mainType, assetPath, providerType, keyList, dependencies, extraData));
    8. }
    Then it checks to see if there are any additional objects included in the bundle at the same path:

    Code (CSharp):
    1. ObjectIdentifier[] ids = depInfo != null ? depInfo[new GUID(guid)].includedObjects.ToArray() :
    2.     ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(guid), EditorUserBuildSettings.activeBuildTarget);
    3.  
    4. if (ids.Length > 1)
    5. {
    6.     Type [] typesForObjs = ContentBuildInterface.GetTypeForObjects(ids);
    7.     HashSet<Type> typesSeen = new HashSet<Type>();
    8.     typesSeen.Add(mainType);
    9.     foreach (var objType in typesForObjs)
    10.     {
    11.         if (typeof(Component).IsAssignableFrom(objType))
    12.             continue;
    13.         Type rtType = AddressableAssetUtility.MapEditorTypeToRuntimeType(objType, false);
    14.         if (rtType != null && !typesSeen.Contains(rtType))
    15.         {
    16.             entries.Add(new ContentCatalogDataEntry(rtType, assetPath, providerType, keyList, dependencies, extraData));
    17.             typesSeen.Add(rtType);
    18.         }
    19.     }
    20. }
    The notable part here is that the check for
    ids.Length > 1
    assumes that if there is only one object ID for the asset then it will have the same type as the main asset. However, for a sprite that is included in an atlas the main asset's type is still
    Texture2D
    , but the asset actually stored in the bundle is a
    Sprite
    . This perfectly explains the type mismatch I identified in my original post.

    I think the solution here is to remove the initial logic for adding a catalog entry based on the asset's type, and instead always use the types of the included objects to determine the catalog entries. I'm not sure if this will actually work in all cases (notably the logic for checking the included object IDs is within an
    if (!IsScene) { }
    block, so I assume scene assets at least need to be handled differently), but it's probably a part of the solution.

    @TreyK-47 would you be able to pass this information over to the team working on Addressables? I don't know if someone has already dug into this issue internally, but if not this info might save someone some time spent debugging :p
     
  8. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    As an initial test to see if my analysis is accurate, I modified the problematic logic I mentioned in
    CreateCatalogEntriesInternal()
    to be the following:

    Code (CSharp):
    1. if (!IsScene)
    2. {
    3.     ObjectIdentifier[] ids = depInfo != null ? depInfo[new GUID(guid)].includedObjects.ToArray() :
    4.         ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(guid), EditorUserBuildSettings.activeBuildTarget);
    5.  
    6.     Type [] typesForObjs = ContentBuildInterface.GetTypeForObjects(ids);
    7.     HashSet<Type> typesSeen = new HashSet<Type>();
    8.     foreach (var objType in typesForObjs)
    9.     {
    10.         if (typeof(Component).IsAssignableFrom(objType))
    11.             continue;
    12.         Type rtType = AddressableAssetUtility.MapEditorTypeToRuntimeType(objType, false);
    13.         if (rtType != null && !typesSeen.Contains(rtType))
    14.         {
    15.             entries.Add(new ContentCatalogDataEntry(rtType, assetPath, providerType, keyList, dependencies, extraData));
    16.             typesSeen.Add(rtType);
    17.         }
    18.     }
    19. }
    20. else
    21. {
    22.     Type mainType = AddressableAssetUtility.MapEditorTypeToRuntimeType(MainAssetType, false);
    23.     if (mainType == null && !IsInResources)
    24.     {
    25.         var t = MainAssetType;
    26.         Debug.LogWarningFormat("Type {0} is in editor assembly {1}.  Asset location with internal id {2} will be stripped.", t.Name, t.Assembly.FullName, assetPath);
    27.         return;
    28.     }
    29.  
    30.     if (mainType != null)
    31.     {
    32.         Type runtimeProvider = GetRuntimeProviderType(providerType, mainType);
    33.         if (runtimeProvider != null)
    34.             providerTypes.Add(runtimeProvider);
    35.  
    36.         entries.Add(new ContentCatalogDataEntry(mainType, assetPath, providerType, keyList, dependencies, extraData));
    37.     }
    38. }
    This logic isn't quite right (for example, the check for editor-only assets is now in the wrong place), but it does seem to resolve the issue!
     
  9. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    It was introduced in 1.9. we're working on it now.

    Glad you were able to set up a workaround. Another one is to load as an object (LoadAssetAsync<Object>) then cast the result to a Sprite. As you've pointed out, it's being built as a sprite, but recorded in our catalog as a Texture2D.
     
    dlegare-synapse likes this.
  10. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    AH! Loading as
    Object
    is a good solution, I should have thought of that sooner! That should hold me over until a full fix can be released. Thanks!
     
  11. dlegare-synapse

    dlegare-synapse

    Joined:
    Jan 17, 2018
    Posts:
    54
    After updating to Addressables 1.13.1 it looks like we're now able to load sprites even if they're included in a sprite atlas :) However, it looks like there's still an issue where the build process will incorrectly use cached build data after a sprite has been added to a sprite atlas, resulting in broken sprites until you clear the asset build cache.

    @unity_bill I seem to remember you mentioning in another thread that this was a known issue. Is this something that's expected to be fixed soon(-ish)? It's not a huge blocker for my team since it's relatively easy to fix when it comes up, but I'm wondering if it's worth setting up a workaround in our build system to always clear the build cache for the time being.

    Still, I appreciate the fix here! This is a major usability improvement for Addressables, and has the knock-on effect of making sprite atlases also way more practical to use for my team :D
     
  12. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    Loading sprite via Addressables still appears to be an issue in version 1.19.11. When I use Addressables.LoadAssetAsync<Sprite>(address); I get an error: Unable to load asset of type UnityEngine.Sprite from location

    I found a workaround like so: (await Addressables.LoadAssetAsync<Texture2D>(address)).ToSprite() and it works, but than I can not release that resource because I assign sprite to an Image and I can not retrieve info about Texture2D latter.

    I'm not using sprites from atlas, but I'm using single group with a lot of sprites set to Bundle Mode: Pack Separatley, so I'm not sure if the core of the problem is the same
     
    Last edited: Dec 10, 2021
  13. ambid17

    ambid17

    Joined:
    Jun 28, 2017
    Posts:
    7
    bearcoree likes this.
  14. unity_shane

    unity_shane

    Unity Technologies

    Joined:
    Jun 3, 2021
    Posts:
    106
    @ambid17, Hi there - are you still experiencing this issue on the latest version of Addressables? We don't seem to have this new issue documented in any existing tickets, which would suggest that either we already attempted to fix it or we never received a bug report reproducing the issue. If you're still experiencing this issue and you believe it to be a bug, please submit a bug report reproducing the issue so we can take a look at it!
     
  15. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    I'm on Addressabless 1.19.19 and loading Sprite directly is still an issue. I get Exception: Unable to load asset of type UnityEngine.Sprite from location Armor_Original.png

    I selected Use Existing Build under Play Mode Script

    Here are settings of the group containing sprites:
    upload_2022-3-15_11-2-28.png
     
  16. SaulChow

    SaulChow

    Joined:
    Mar 23, 2021
    Posts:
    10
    It's still on Addressables 1.18.19 and 1.20.0.

    Message is "System.Exception: Unable to load asset of type UnityEngine.Sprite from location Assets/ArtResources/Sprite/UI/Common/CharInfo_icon/com_ui_CharInfo_icon_border_player.png."

    I use the function "Addressables.LoadAssetAsync<T>"
    I have no sprite atlas. It's a single png pic.
    Using UnityEngine.Object or Texture2D type will be fine for now.
     
    rinKhung likes this.
  17. Azurexis

    Azurexis

    Joined:
    Oct 26, 2018
    Posts:
    7
    Heyo, just wanted to confirm I also experienced this bug: I get errors when loading a single sprite.

    I found a "hacky" workaround by setting the Sprite Mode still to "Multiple" and then slicing it in the Sprite Editor but only as one tile. I then assigned the resulting sprite to the asset reference and then it worked (for some obscure reason).

    Maybe this helps someone else!:)
     
  18. SharpAccent

    SharpAccent

    Joined:
    Jul 19, 2018
    Posts:
    39
    still strong in july 23, just use texture2d :D