浅谈Addressables中的ResourceLocation和ResourceLocator


我们在研究Addressables运行时加载资源过程的这篇文章里,提到过几个术语,像ResourceLocationResourceLocator等等,限于那篇文章的篇幅,我们没有深入的来分析这几个类的代码,那么今天就让我们来分别看一下这几个类。

Addressables在Build资源的时候,会生成一个ContentCatalog文件,里头带有各个资源的Addressables地址和物理地址。而用户在使用Addressables加载资源的时候传入的是Addressables地址,这里就涉及到了查找与转换Addressables地址到实际物理地址的操作。并且在加载实际需要的资源之前还得加载它的所有依赖项(这里的依赖项不只是所依赖的AssetBundle,还包括Catalog文件的Hash文件等)。

先来看一下[color=red]IResourceLocation[/color]接口,这个接口包含了一个资源所有的信息。来看一下它的定义:

public interface IResourceLocation
{
    /// 官方说明是用来给Provider加载该资源的内部名称
    /// 看代码其实就是物理路径
    string InternalId
    {
        get;
    }

    /// 对应的Provider
    string ProviderId
    {
        get;
    }

    /// 该资源所有的依赖项地址
    IList<IResourceLocation> Dependencies
    {
        get;
    }

    /// 传入你所要加载的类型,返回该地址与该类型合并计算的哈希值
    /// 为什么要跟类型合并计算呢,这是为了防止同一个资源你需要返回不同类型的情况
    int Hash(Type resultType);

    /// 预计算好的依赖项的哈希值,在Hash(Type resultType)函数中会被用到
    int DependencyHashCode
    {
        get;
    }

    /// 该资源是否有依赖项
    bool HasDependencies
    {
        get;
    }

    /// 临时存储的中间数据
    object Data
    {
        get;
    }

    /// 该资源对应的Addressables地址
    string PrimaryKey
    {
        get;
    }

    /// 所请求返回的资源类型
    Type ResourceType
    {
        get;
    }
}

看完了[color=red]IResourceLocation[/color],来看一下Addressables中最常用的它的实现—-[color=red]ResourceLocationBase[/color]类

public class ResourceLocationBase : IResourceLocation
{
    string m_Name;
    string m_Id;
    string m_ProviderId;
    object m_Data;
    int m_DependencyHashCode;
    int m_HashCode;
    Type m_Type;
    List<IResourceLocation> m_Dependencies;
    string m_PrimaryKey;

    public string InternalId { get { return m_Id; } }

    public string ProviderId { get { return m_ProviderId; } }

    public IList<IResourceLocation> Dependencies { get { return m_Dependencies; } }

    public bool HasDependencies { get { return m_Dependencies != null && m_Dependencies.Count > 0; } }

    public object Data { get { return m_Data; } set { m_Data = value; } }

    public string PrimaryKey
    {
        get { return m_PrimaryKey; }
        set { m_PrimaryKey = value; }
    }

    public int DependencyHashCode { get { return m_DependencyHashCode; } }

    public Type ResourceType { get { return m_Type; } }

    /// 计算该地址哈希值的函数
    public int Hash(Type t)
    {
        return (m_HashCode * 31 + t.GetHashCode()) * 31 + DependencyHashCode;
    }

    public override string ToString()
    {
        return m_Id;
    }

    /// ResourceLocationBase的构造函数
    /// <param name="name">该资源的Addressables地址</param>
    /// <param name="id">InternalID,目前一般是物理路径</param>
    /// <param name="providerId">Provider的ID,其实就是Provider类的类全名</param>
    /// <param name="t">需要返回的类型(GameObject、Sprite之类)</param>
    /// <param name="dependencies">依赖项</param>
    public ResourceLocationBase(string name, string id, string providerId, Type t, params IResourceLocation[] dependencies)
    {
        if (string.IsNullOrEmpty(id))
            throw new ArgumentNullException(nameof(id));
        if (string.IsNullOrEmpty(providerId))
            throw new ArgumentNullException(nameof(providerId));
        m_PrimaryKey = name;
        m_HashCode = (name.GetHashCode() * 31 + id.GetHashCode()) * 31 + providerId.GetHashCode();
        m_Name = name;
        m_Id = id;
        m_ProviderId = providerId;
        m_Dependencies = new List<IResourceLocation>(dependencies);
        m_Type = t == null ? typeof(object) : t;
        ComputeDependencyHash();
    }

    /// 预计算依赖项的哈希值并保存
    public void ComputeDependencyHash() // TODO: dependency hash is no longer just objects
    {
        m_DependencyHashCode = m_Dependencies.Count > 0 ? 17 : 0;
        foreach (var d in m_Dependencies)
            m_DependencyHashCode = m_DependencyHashCode * 31 + d.Hash(typeof(object));
    }
}

Addressables加载时那些[color=purple]ResourceLocator[/color]都是返回的地址数据一般都是[color=red]ResourceLocationBase[/color]。看到这里,你应该也猜到了,[color=purple]ResourceLocator[/color]就是负责返回资源地址数据的。Addressables在你传入一个key的时候,会遍历当前系统里所有的ResourceLocator([color=blue]AddressablesImpl.m_ResourceLocators[/color]),依次调用Locate接口来查询对应的地址。我们先来看看接口类[color=red]IResourceLocator[/color]:

public interface IResourceLocator
{
    /// 该Locator的ID,即名字
    string LocatorId { get; }

    /// 该Locator能解析的所有Key(有的实现会返回null,例如)
    IEnumerable<object> Keys { get; }

    /// 定位key所对应的资源地址,并返回
    /// <param name="key">资源所对应的key,一般为Addressables地址或者AssetReference</param>
    /// <param name="type">所请求的类型</param>
    /// <param name="locations">返回的资源地址</param>
    /// <returns>如果找到key所对应的资源地址,则返回true,否则为false</returns>
    bool Locate(object key, Type type, out IList<IResourceLocation> locations);
}

Addressables自带的Locator共有4个:[color=red]ResourceLocationMap[/color]、[color=red]DynamicResourceLocator[/color]、[color=red]AddressableAssetSettingsLocator[/color]、[color=red]LegacyResourceLocator[/color]。其中[color=red]LegacyResourceLocator[/color]已经不再使用,没有任何代码使用到这个类。
当你在[color=purple]PlayModeScript[/color]选择[B]Use Asset Database[/B]时,[color=blue]AddressablesImpl.m_ResourceLocators[/color]里带有两个Locator,分别是[color=red]AddressableAssetSettingsLocator[/color]和[color=red]DynamicResourceLocator[/color]。其中[color=red]DynamicResourceLocator[/color]比较简单,它仅仅是负责处理想要读取资源子资产(Sub Asset)的情况。来看一下它的代码:

public bool Locate(object key, Type type, out IList<IResourceLocation> locations)
{
    locations = null;
    /// ResourceManagerConfig.ExtractKeyAndSubKey是一个负责提取出key中mainKey与subKey的函数
    /// 格式为mainKey[subKey],即主资源的key在前,然后跟着用[]包裹的子资源名字
    if (ResourceManagerConfig.ExtractKeyAndSubKey(key, out string mainKey, out string subKey))
    {
        /// 取得主资源key之后,调用AddressablesImpl.GetResourceLocations来获取主资源的ResourceLocation
        if (!m_Addressables.GetResourceLocations(mainKey, type, out IList<IResourceLocation> locs))
        {
            // 获取主资源失败,这里做了个处理,如果当前请求类型是Sprite的话,尝试使用SpriteAtlas再获取一次
            if (type == typeof(Sprite))
                m_Addressables.GetResourceLocations(mainKey, typeof(SpriteAtlas), out locs);
        }

        if (locs != null && locs.Count > 0)
        {
            /// 调用CreateDynamicLocations创建ResourceLocationBase
            locations = new List<IResourceLocation>(locs.Count);
            foreach (var l in locs)
                CreateDynamicLocations(type, locations, key as string, subKey, l);
            return true;
        }
    }
    return false;
}

internal void CreateDynamicLocations(Type type, IList<IResourceLocation> locations, string locName, string subKey, IResourceLocation mainLoc)
{
    if (type == typeof(Sprite) && mainLoc.ResourceType == typeof(U2D.SpriteAtlas))
    {
        /// 如果当前请求类型是Sprite而实际资源类型是SpriteAtlas的话,则手动写入AtlasSpriteProvider的Id
        locations.Add(new ResourceLocationBase(locName, $"{mainLoc.InternalId}[{subKey}]", AtlasSpriteProviderId, type, new IResourceLocation[] { mainLoc }));
    }
    else
    {
        /// 区分是否有依赖项来创建ResourceLocationBase
        if (mainLoc.HasDependencies)
            locations.Add(new ResourceLocationBase(locName, $"{mainLoc.InternalId}[{subKey}]", mainLoc.ProviderId, mainLoc.ResourceType, mainLoc.Dependencies.ToArray()));
        else
            locations.Add(new ResourceLocationBase(locName, $"{mainLoc.InternalId}[{subKey}]", mainLoc.ProviderId, mainLoc.ResourceType));
    }
}

可以看到,[color=red]DynamicResourceLocator[/color]其实就是做了个预处理,解析出mainKey和subKey之后,创建响应的[color=red]ResourceLocationBase[/color]对象。
而[color=red]AddressableAssetSettingsLocator[/color],它的主要任务是从AddressableAssetSettings文件即Group配置中定位资源,让我们来看看它的主要函数:

internal class AddressableAssetSettingsLocator : IResourceLocator
{
    private static Type m_SpriteType = typeof(Sprite);
    private static Type m_SpriteAtlasType = typeof(SpriteAtlas);

    public string LocatorId
    {
        get;
        private set;
    }
    public Dictionary<object, HashSet<AddressableAssetEntry>> m_keyToEntries;
    public Dictionary<CacheKey, IList<IResourceLocation>> m_Cache;
    public AddressableAssetTree m_AddressableAssetTree;
    HashSet<object> m_Keys = null;
    AddressableAssetSettings m_Settings;
    bool m_includeResourcesFolders = false;
    bool m_dirty = true;

    public IEnumerable<object> Keys
    {
        get
        {
            if (m_dirty)
                RebuildInternalData();
            if (m_Keys == null)
            {
                var visitedFolders = new HashSet<string>();
                using (new AddressablesFileEnumerationScope(m_AddressableAssetTree))
                {
                    m_Keys = new HashSet<object>();
                    foreach (var kvp in m_keyToEntries)
                    {
                        var hasNonFolder = false;
                        foreach (var e in kvp.Value)
                        {
                            if (AssetDatabase.IsValidFolder(e.AssetPath))
                            {
                                if (!visitedFolders.Contains(e.AssetPath))
                                {
                                    foreach (var f in EnumerateAddressableFolder(e.AssetPath, m_Settings, true))
                                    {
                                        m_Keys.Add(f.Replace(e.AssetPath, e.address));
                                        m_Keys.Add(AssetDatabase.AssetPathToGUID(f));
                                    }
                                    visitedFolders.Add(e.AssetPath);
                                }
                                foreach (var l in e.labels)
                                    m_Keys.Add(l);
                            }
                            else
                            {
                                hasNonFolder = true;
                            }
                        }
                        if (hasNonFolder)
                            m_Keys.Add(kvp.Key);
                    }
                    if (m_includeResourcesFolders)
                    {
                        var resourcesEntry = m_Settings.FindAssetEntry(AddressableAssetEntry.ResourcesName);
                        resourcesEntry.GatherResourcesEntries(null, true, entry =>
                        {
                            m_Keys.Add(entry.address);
                            m_Keys.Add(entry.guid);
                            return false;
                        });
                    }
                }
            }
            return m_Keys;
        }
    }

    public AddressableAssetSettingsLocator(AddressableAssetSettings settings)
    {
        m_Settings = settings;
        LocatorId = m_Settings.name;
        m_dirty = true;
        m_Settings.OnModification += Settings_OnModification;	/// Settings_OnModification所做的就是m_dirty=true
    }

    /// 重建内部数据,当m_dirty==true时会被调用
    void RebuildInternalData()
    {
        m_Keys = null;
        m_AddressableAssetTree = BuildAddressableTree(m_Settings);
        m_Cache = new Dictionary<CacheKey, IList<IResourceLocation>>();
        m_keyToEntries = new Dictionary<object, HashSet<AddressableAssetEntry>>(m_Settings.labelTable.labelNames.Count);
        using (new AddressablesFileEnumerationScope(m_AddressableAssetTree))
        {
            /// 遍历AddressablesAssetSetting中的Group,将下面的资源添加到m_keyToEntries中
            foreach (AddressableAssetGroup g in m_Settings.groups)
            {
                if (g == null)
                    continue;

                foreach (AddressableAssetEntry e in g.entries)
                {
                    if (e.guid == AddressableAssetEntry.EditorSceneListName)
                    {
                        if (e.parentGroup.GetSchema<GroupSchemas.PlayerDataGroupSchema>().IncludeBuildSettingsScenes)
                        {
                            /// 如果设置了IncludeBuildSettingsScenes,则添加场景到m_keyToEntries中
                            e.GatherAllAssets(null, false, false, false, s =>
                            {
                                AddEntriesToTables(m_keyToEntries, s);
                                return false;
                            });
                        }
                    }
                    else if (e.guid == AddressableAssetEntry.ResourcesName)
                    {
                        m_includeResourcesFolders = e.parentGroup.GetSchema<GroupSchemas.PlayerDataGroupSchema>().IncludeResourcesFolders;
                    }
                    else
                    {
                        /// 说明非BuildInData组,添加资源
                        AddEntriesToTables(m_keyToEntries, e);
                    }
                }
            }
        }
        m_dirty = false;
    }

    static void AddEntry(AddressableAssetEntry e, object k, Dictionary<object, HashSet<AddressableAssetEntry>> keyToEntries)
    {
        if (!keyToEntries.TryGetValue(k, out HashSet<AddressableAssetEntry> entries))
            keyToEntries.Add(k, entries = new HashSet<AddressableAssetEntry>());
        entries.Add(e);
    }

    static void AddEntriesToTables(Dictionary<object, HashSet<AddressableAssetEntry>> keyToEntries, AddressableAssetEntry e)
    {
        /// 添加对应的条目
        AddEntry(e, e.address, keyToEntries);
        AddEntry(e, e.guid, keyToEntries);
        if (e.IsScene && e.IsInSceneList)
        {
            /// 取出场景索引添加到资源table中
            int index = BuiltinSceneCache.GetSceneIndex(new GUID(e.guid));
            if (index != -1)
                AddEntry(e, index, keyToEntries);
        }
        if (e.labels != null)
        {
            /// 添加资源label对资源的绑定
            foreach (string l in e.labels)
            {
                AddEntry(e, l, keyToEntries);
            }
        }
    }

    static void GatherEntryLocations(AddressableAssetEntry entry, Type type, IList<IResourceLocation> locations, AddressableAssetTree assetTree)
    {
        /// 资源路径是不能带有[]的,因为这表示子资源(SubAsset)
        if (!string.IsNullOrEmpty(entry.address) && entry.address.Contains("[") && entry.address.Contains("]"))
        {
            Debug.LogErrorFormat("Address '{0}' cannot contain '[ ]'.", entry.address);
            return;
        }
        /// AddressablesFileEnumerationScope是类似LockGuard一样的结构,保存当前的tree,在Dispose的时候恢复
        using (new AddressablesFileEnumerationScope(assetTree))
        {
            /// AddressableAssetEntry.GatherAllAssets是一个遍历当前AssetEntry,并调用回调的函数
            entry.GatherAllAssets(null, true, true, false, e =>
            {
                // 检查遍历的entry,如果符合条件,则创建ResourceLocationBase
                if (e.IsScene)
                {
                    /// 是场景,则检查类型是否符合
                    if (type == null || type == typeof(object) || type == typeof(SceneInstance) || AddressableAssetUtility.MapEditorTypeToRuntimeType(e.MainAssetType, false) == type)
                        locations.Add(new ResourceLocationBase(e.address, e.AssetPath, typeof(SceneProvider).FullName, typeof(SceneInstance)));
                }
                else if (type == null || (type.IsAssignableFrom(e.MainAssetType) && type != typeof(object)))
                {
                    /// 非场景,且类型符合
                    locations.Add(new ResourceLocationBase(e.address, e.AssetPath, typeof(AssetDatabaseProvider).FullName, e.MainAssetType));
                    return true;
                }
                else
                {
                    /// 到这里,没有可以直接赋值的资源,检查是否有包含的符合类型可以赋值
                    ObjectIdentifier[] ids = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(e.guid), EditorUserBuildSettings.activeBuildTarget);
                    if (ids.Length > 0)
                    {
                        foreach (var t in AddressableAssetEntry.GatherMainAndReferencedSerializedTypes(ids))
                        {
                            if (type.IsAssignableFrom(t))
                                locations.Add(new ResourceLocationBase(e.address, e.AssetPath, typeof(AssetDatabaseProvider).FullName, AddressableAssetUtility.MapEditorTypeToRuntimeType(t, false)));
                        }
                        return true;
                    }
                }
                return false;
            });
        }
    }

    /// 具体的Locate函数
    public bool Locate(object key, Type type, out IList<IResourceLocation> locations)
    {
        /// 检查是否需要重建数据
        if (m_dirty)
            RebuildInternalData();
        /// CacheKey是一个将key和type合并生成hash的结构体,方便生成缓存m_Cache的key
        CacheKey cacheKey = new CacheKey()
        {
            m_key = key, m_type = type
        };
        if (m_Cache.TryGetValue(cacheKey, out locations))
            return locations != null;

        locations = new List<IResourceLocation>();
        /// 先检查m_keyToEntries里是否有对应的资源记录(这些都是Group里明确记录的资源)
        if (m_keyToEntries.TryGetValue(key, out HashSet<AddressableAssetEntry> entries))
        {
            /// 将对应的条目添加到locations中
            foreach (AddressableAssetEntry e in entries)
            {
                if (AssetDatabase.IsValidFolder(e.AssetPath) && !e.labels.Contains(key as string))
                    continue;

                if (type == null)
                {
                    if (e.MainAssetType != typeof(SceneAsset))
                    {
                        ObjectIdentifier[] ids =
                            ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(e.guid),
                                    EditorUserBuildSettings.activeBuildTarget);
                        List<Type> mainObjectTypes = AddressableAssetEntry.GatherMainObjectTypes(ids);

                        if (mainObjectTypes.Count > 0)
                        {
                            foreach (Type t in mainObjectTypes)
                                GatherEntryLocations(e, t, locations, m_AddressableAssetTree);
                        }
                        else
                        {
                            GatherEntryLocations(e, null, locations, m_AddressableAssetTree);
                        }
                    }
                    else
                    {
                        GatherEntryLocations(e, null, locations, m_AddressableAssetTree);
                    }
                }
                else
                {
                    GatherEntryLocations(e, type, locations, m_AddressableAssetTree);
                }
            }
        }

        if (type == null)
            type = typeof(UnityEngine.Object);

        string keyStr = key as string;
        if (!string.IsNullOrEmpty(keyStr))
        {
            /// 先检查key是否是个GUID字符串,若是则通过AssetDatabase.GUIDToAssetPath查找对应的文件
            var keyPath = AssetDatabase.GUIDToAssetPath(keyStr);
            if (!string.IsNullOrEmpty(keyPath))
            {
                /// 如果目前还没找到任何资源,则判断该GUID是否指向目录
                if (locations.Count == 0)
                {
                    var slash = keyPath.LastIndexOf('/');
                    while (slash > 0)
                    {
                        keyPath = keyPath.Substring(0, slash);
                        var parentFolderKey = AssetDatabase.AssetPathToGUID(keyPath);
                        if (string.IsNullOrEmpty(parentFolderKey))
                            break;

                        if (m_keyToEntries.ContainsKey(parentFolderKey))
                        {
                            AddLocations(locations, type, keyPath, AssetDatabase.GUIDToAssetPath(keyStr));
                            break;
                        }
                        slash = keyPath.LastIndexOf('/');
                    }
                }
            }
            else
            {
                /// 非GUID,判断它是否是目录,且是否某个目录名包含在entry中
                keyPath = keyStr;
                int slash = keyPath.LastIndexOf('/');
                while (slash > 0)
                {
                    keyPath = keyPath.Substring(0, slash);
                    if (m_keyToEntries.TryGetValue(keyPath, out var entry))
                    {
                        foreach (var e in entry)
                            AddLocations(locations, type, keyStr, GetInternalIdFromFolderEntry(keyStr, e));
                        break;
                    }
                    slash = keyPath.LastIndexOf('/');
                }
            }

            /// 判断是否需要检查Resources目录,且key在Resources目录中
            if (m_includeResourcesFolders)
            {
                string resPath = keyStr;
                var ext = System.IO.Path.GetExtension(resPath);
                if (!string.IsNullOrEmpty(ext))
                    resPath = resPath.Substring(0, resPath.Length - ext.Length);
                UnityEngine.Object obj = Resources.Load(resPath, type);
                if (obj == null && keyStr.Length == 32)
                {
                    /// 检查是否是个GUID,且对应文件在Resources目录下
                    resPath = AssetDatabase.GUIDToAssetPath(keyStr);
                    if (!string.IsNullOrEmpty(resPath))
                    {
                        int index = resPath.IndexOf("Resources/", StringComparison.Ordinal);
                        if (index >= 0)
                        {
                            int start = index + 10;
                            int length = resPath.Length - (start + System.IO.Path.GetExtension(resPath).Length);
                            resPath = resPath.Substring(index + 10, length);
                            obj = Resources.Load(resPath, type);
                        }
                    }
                }
                if (obj != null)
                    /// 添加到locations
                    locations.Add(new ResourceLocationBase(keyStr, resPath, typeof(LegacyResourcesProvider).FullName, type));
            }
        }

        if (locations.Count == 0)
        {
            locations = null;
            m_Cache.Add(cacheKey, locations);
            return false;
        }

        m_Cache.Add(cacheKey, locations);
        return true;
    }

    internal static void AddLocations(IList<IResourceLocation> locations, Type type, string keyStr, string internalId)
    {
        /// 创建ResourceLocationBase并指定AssetDatabaseProvider来提供具体的资源
        if (!string.IsNullOrEmpty(internalId) && !string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(internalId)))
        {
            if (type == m_SpriteType && AssetDatabase.GetMainAssetTypeAtPath(internalId) == m_SpriteAtlasType)
                locations.Add(new ResourceLocationBase(keyStr, internalId, typeof(AssetDatabaseProvider).FullName, m_SpriteAtlasType));
            else
            {
                foreach (var obj in AssetDatabaseProvider.LoadAssetsWithSubAssets(internalId))
                {
                    var rtt = AddressableAssetUtility.MapEditorTypeToRuntimeType(obj.GetType(), false);
                    if (type.IsAssignableFrom(rtt))
                        locations.Add(new ResourceLocationBase(keyStr, internalId, typeof(AssetDatabaseProvider).FullName, rtt));
                }
            }
        }
    }
}

它的主要功能就是在需要的时候从AddressableAssetSettings读取数据并重建自己内部数据,然后检查资源是在Group配置,还是在Resources目录中。

当你在[color=purple]PlayModeScript[/color]选择[B]Simulate Groups[/B]或者[B]Use Existing Build[/B]时,[color=blue]AddressablesImpl.m_ResourceLocators[/color]里也是默认带有两个Locator,分别是[color=red]ResourceLocationMap[/color]和[color=red]DynamicResourceLocator[/color]。刚才已经说过[color=red]DynamicResourceLocator[/color]了,现在来说说[color=red]ResourceLocationMap[/color]。[color=red]ResourceLocationMap[/color]是通过调用[color=blue]ContentCatalogData.CreateLocator[/color]来生成的。那么我们就来看一下这个函数和[color=red]ResourceLocationMap[/color]类的代码:

public class ContentCatalogData
{
    public ResourceLocationMap CreateLocator(string providerSuffix = null)
    {
        /// 从m_BucketDataString解析数据
        /// m_BucketDataString储存了有多少个Key,以及它们对应entry的索引
        var bucketData = Convert.FromBase64String(m_BucketDataString);
        int bucketCount = BitConverter.ToInt32(bucketData, 0);
        var buckets = new Bucket[bucketCount];
        int bi = 4;
        for (int i = 0; i < bucketCount; i++)
        {
            var index = SerializationUtilities.ReadInt32FromByteArray(bucketData, bi);
            bi += 4;
            var entryCount = SerializationUtilities.ReadInt32FromByteArray(bucketData, bi);
            bi += 4;
            var entryArray = new int[entryCount];
            for (int c = 0; c < entryCount; c++)
            {
                entryArray[c] = SerializationUtilities.ReadInt32FromByteArray(bucketData, bi);
                bi += 4;
            }
            buckets[i] = new Bucket { entries = entryArray, dataOffset = index };
        }
        /// providerSuffix是用来当有多个ContentCatalogData时,防止冲突用的
        if (!string.IsNullOrEmpty(providerSuffix))
        {
            for (int i = 0; i < m_ProviderIds.Length; i++)
            {
                if (!m_ProviderIds[i].EndsWith(providerSuffix, StringComparison.Ordinal))
                    m_ProviderIds[i] = m_ProviderIds[i] + providerSuffix;
            }
        }
        /// 从m_ExtraDataString解析数据
        /// m_ExtraDataString储存的是entry附带的额外参数
        /// 目前看下来只有当这个entry是AssetBundle时会带有
        /// 依据当前模式的不同分别是
        /// VirtualAssetBundleRequestOptions --- Simulate Groups
        /// AssetBundleRequestOptions --- Use Existing Build
        /// 这里头记录了AssetBundle的一些信息
        var extraData = Convert.FromBase64String(m_ExtraDataString);

        /// 从m_KeyDataString解析数据
        /// m_KeyDataString储存了具体的Key值,跟上面的bucketData一一对应
        var keyData = Convert.FromBase64String(m_KeyDataString);
        var keyCount = BitConverter.ToInt32(keyData, 0);
        var keys = new object[keyCount];
        for (int i = 0; i < buckets.Length; i++)
            keys[i] = SerializationUtilities.ReadObjectFromByteArray(keyData, buckets[i].dataOffset);

        var locator = new ResourceLocationMap(m_LocatorId, buckets.Length);

        /// 从m_EntryDataString解析所有的entry数据
        var entryData = Convert.FromBase64String(m_EntryDataString);
        int count = SerializationUtilities.ReadInt32FromByteArray(entryData, 0);
        var locations = new IResourceLocation[count];
        for (int i = 0; i < count; i++)
        {
            var index = kBytesPerInt32 + i * (kBytesPerInt32 * k_EntryDataItemPerEntry);
            var internalId = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            index += kBytesPerInt32;
            var providerIndex = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            index += kBytesPerInt32;
            var dependencyKeyIndex = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            index += kBytesPerInt32;
            var depHash = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            index += kBytesPerInt32;
            var dataIndex = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            index += kBytesPerInt32;
            var primaryKey = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            index += kBytesPerInt32;
            var resourceType = SerializationUtilities.ReadInt32FromByteArray(entryData, index);
            object data = dataIndex < 0 ? null : SerializationUtilities.ReadObjectFromByteArray(extraData, dataIndex);
            locations[i] = new CompactLocation(locator, Addressables.ResolveInternalId(ExpandInternalId(m_InternalIdPrefixes, m_InternalIds[internalId])),
                                               m_ProviderIds[providerIndex], dependencyKeyIndex < 0 ? null : keys[dependencyKeyIndex], data, depHash, keys[primaryKey].ToString(), m_resourceTypes[resourceType].Value);
        }

        /// 将上面的entry数据组装成Key-Entrys的结构
        for (int i = 0; i < buckets.Length; i++)
        {
            var bucket = buckets[i];
            var key = keys[i];
            var locs = new IResourceLocation[bucket.entries.Length];
            for (int b = 0; b < bucket.entries.Length; b++)
                locs[b] = locations[bucket.entries[b]];
            locator.Add(key, locs);
        }

        return locator;
    }
}

public class ResourceLocationMap : IResourceLocator
{
    public ResourceLocationMap(string id, int capacity = 0)
    {
        LocatorId = id;
        Locations = new Dictionary<object, IList<IResourceLocation>>(capacity == 0 ? 100 : capacity);
    }

    /// 这个接口是在初始化时创建CatalogLocator使用
    public ResourceLocationMap(string id, IList<ResourceLocationData> locations)
    {
        LocatorId = id;
        if (locations == null)
            return;
        Locations = new Dictionary<object, IList<IResourceLocation>>(locations.Count * 2);
        var locMap = new Dictionary<string, ResourceLocationBase>();
        var dataMap = new Dictionary<string, ResourceLocationData>();
        //create and collect locations
        for (int i = 0; i < locations.Count; i++)
        {
            var rlData = locations[i];
            if (rlData.Keys == null || rlData.Keys.Length < 1)
            {
                Addressables.LogErrorFormat("Address with id '{0}' does not have any valid keys, skipping...", rlData.InternalId);
                continue;
            }
            if (locMap.ContainsKey(rlData.Keys[0]))
            {
                Addressables.LogErrorFormat("Duplicate address '{0}' with id '{1}' found, skipping...", rlData.Keys[0], rlData.InternalId);
                continue;
            }
            var loc = new ResourceLocationBase(rlData.Keys[0], Addressables.ResolveInternalId(rlData.InternalId), rlData.Provider, rlData.ResourceType);
            loc.Data = rlData.Data;
            locMap.Add(rlData.Keys[0], loc);
            dataMap.Add(rlData.Keys[0], rlData);
        }

        //fix up dependencies between them
        foreach (var kvp in locMap)
        {
            var data = dataMap[kvp.Key];
            if (data.Dependencies != null)
            {
                foreach (var d in data.Dependencies)
                    kvp.Value.Dependencies.Add(locMap[d]);
                kvp.Value.ComputeDependencyHash();
            }
        }
        foreach (KeyValuePair<string, ResourceLocationBase> kvp in locMap)
        {
            ResourceLocationData rlData = dataMap[kvp.Key];
            foreach (var k in rlData.Keys)
                Add(k, kvp.Value);
        }
    }

    /// 定位函数
    /// </summary>
    /// <param name="key">Addressables地址</param>
    /// <param name="type">请求的资源类型</param>
    /// <param name="locations">所找到的对应资源地址。这个List有可能是保存在Dictionary中的,所以调用方不能对它进行修改</param>
    /// <returns>Returns true if a location was found. Returns false otherwise.</returns>
    public bool Locate(object key, Type type, out IList<IResourceLocation> locations)
    {
        /// 在Locations中查找对应key的地址
        IList<IResourceLocation> locs = null;
        if (!Locations.TryGetValue(key, out locs))
        {
            locations = null;
            return false;
        }

        /// 若type为null,那就不需要检查类型,直接返回
        if (type == null)
        {
            locations = locs;
            return true;
        }

        /// 检查符合类型的资源
        var validTypeCount = 0;
        foreach (var l in locs)
            if (type.IsAssignableFrom(l.ResourceType))
                validTypeCount++;

        /// 没有符合类型的,直接返回null
        if (validTypeCount == 0)
        {
            locations = null;
            return false;
        }

        /// 全部都符合,直接返回locs
        if (validTypeCount == locs.Count)
        {
            locations = locs;
            return true;
        }

        /// 创建个新的List,只返回类型符合的
        locations = new List<IResourceLocation>();
        foreach (var l in locs)
        {
            if (type.IsAssignableFrom(l.ResourceType))
                locations.Add(l);
        }
        return true;
    }

    /// ResourceLocationMap(string id, IList<ResourceLocationData> locations)里调用的
    public void Add(object key, IResourceLocation location)
    {
        IList<IResourceLocation> locations;
        if (!Locations.TryGetValue(key, out locations))
            Locations.Add(key, locations = new List<IResourceLocation>());
        locations.Add(location);
    }

    /// ContentCatalogData.CreateLocator里组装Key-Entrys结构时调用
    public void Add(object key, IList<IResourceLocation> locations)
    {
        Locations.Add(key, locations);
    }
}

这套代码也比较简单,核心逻辑就是生成一个资源的映射表。
至此,Addressables自带的三个ResourceLocator我们都看过了,其中最复杂的大概要数[color=red]AddressableAssetSettingsLocator[/color]了。接下来的文章,我们要分析一下Provider和Operation。

,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注