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