AddressableAsset源码学习:组成与工作原理

阅前提示

该系列为Unity AddressableAssetSystem 学习笔记,记录源码阅读的理解。
适合人群:All
阅读方式:浏览
如描述有误请指教,如果对你有所帮助,点赞收藏吧:)


官方文档

简介

Addressable Asset system 是Unity推出的新的资产管理加载打包的插件。它主打的特点在于:便捷

相较于传统的资产加载方式(Resources/AssetBundle),AddressableAssest拥有了完备的可视化编辑窗口以及内存管理。

  • VS Resources:不需要对资产路径要求的那么严格,做到了任何地方都可能加载。
  • VS AssetBundle:不需要复杂的准备过程,开发即用,并且不需要单独的进行内存管理,总结下来就是比较无脑。

其他关于Addressable的基础介绍这里就不再赘述了,作者在写这篇文章之前浏览了网上其他有关AddressableAsset的文章,发现存在很多关于此的基础介绍和简单使用,所以这里就不提了。接下来我们就从源码出发,好好看看AddressableAsset。


组成

Addressable Asset System,我把它的组成分为以下几个部分:

  • Manager
    • AddressablesImpl :AddressableAsset使用的接口本口
    • ResourceManager:管理资源
  • Operation
    • AsyncOperation :Addressable 核心异步操作流,一切的加载都源于其异步链式操作(AsyncOperation + ChainOperation)
    • ProviderOperation:资源加载操作,异步操作真正执行者,实现资源的异步加载。
  • Locator
    • ResourceLocationBase:资产定位的组成部分

这三个部分就包含了整个系统的所有脚本了,这里只列举了具有代表性的部分脚本,以便大家找到阅读源码的位置。

简单的来说,Addressable Asset System 的优势在于它统一了资源加载的入口与资源加载的方式。

可想而知,在使用Resources或者AssetBundle时我们关心的地方在哪?我们希望加载资源的Key是一致的,不希望它在情况A的时候要使用KeyA来加载,情况B的时候使用KeyB来加载,这很烦不利于资源的管理。

我们还要考虑加载方式,是同步加载还是异步加载Addressable 全都给你省了~


生命周期

初始化:

public AsyncOperationHandle<IResourceLocator> InitializeAsync(string runtimeDataPath, string providerSuffix = null, bool autoReleaseHandle = true)

m_InitializationOperation:初始化的异步操作处理
​ 添加各类Provider:ResourceManager.ResourceProviders.Add(...)

Update:

internal void Update(float unscaledDeltaTime)


资源加载的本质

 private AsyncOperationHandle ProvideResource(IResourceLocation location, Type desiredType = null)
 {
     if (location == null)
         throw new ArgumentNullException("location");
     IResourceProvider provider = null;
     if (desiredType == null)
     {
         provider = GetResourceProvider(desiredType, location);
         if (provider == null)
             return CreateCompletedOperation<object>(null, new UnknownResourceProviderException(location).Message);
         desiredType = provider.GetDefaultType(location);
     }

     IAsyncOperation op;
     int hash = location.Hash(desiredType);
     if (m_AssetOperationCache.TryGetValue(hash, out op))
     {
         op.IncrementReferenceCount();
         return new AsyncOperationHandle(op);
     }

     Type provType;
     if (!m_ProviderOperationTypeCache.TryGetValue(desiredType, out provType))
         m_ProviderOperationTypeCache.Add(desiredType, provType = typeof(ProviderOperation<>).MakeGenericType(new Type[] { desiredType }));
     op = CreateOperation<IAsyncOperation>(provType, provType.GetHashCode(), hash, m_ReleaseOpCached);

     // Calculate the hash of the dependencies
     int depHash = location.DependencyHashCode;
     var depOp = location.HasDependencies ? ProvideResourceGroupCached(location.Dependencies, depHash, null, null) : default(AsyncOperationHandle<IList<AsyncOperationHandle>>);
     if (provider == null)
         provider = GetResourceProvider(desiredType, location);

     ((IGenericProviderOperation)op).Init(this, provider, location, depOp);

     var handle = StartOperation(op, depOp);

     if (depOp.IsValid())
         depOp.Release();

     return handle;
 }

核心就是这一部分,返回 AsyncOperationHandle 用于做异步逻辑的节点触发。资源由ResourceProviderBase来完成加载,AsyncOperationBase 为 Provider 与 Handle的桥梁


工作流程

1.key -> IResourceProvider : 唯一路径 对应 IResourceProvider

2. ProvideResource :IResourceProvider 生成 AsyncOperationHandle

private AsyncOperationHandle ProvideResource(IResourceLocation location, Type desiredType = null)

细节:

  • provider 是执行资源加载的真正位置(继承ResourceProviderBase):根据location 数据获取对应的provider public IResourceProvider GetResourceProvider(Type t, IResourceLocation location)

  • op 是存放provider的操作流(ProviderOperation), 以 location.Hash为key -> m_AssetOperationCache

  • depOp 是依赖资源的加载操作

  • 根据 location 获取等到 provider

    provider = GetResourceProvider(desiredType, location);

  • op init 时绑定 provider

    ((IGenericProviderOperation)op).Init(this, provider, location, depOp);

  • 链式操作 执行 op.Execute

    var handle = StartOperation(op, depOp);

  • op.Execute 时进行provider的资源加载,并将自身绑定在ProvideHandle中

    m_Provider.Provide(new ProvideHandle(m_ResourceManager, this));

  • provider 资源加载完毕之后执行 ProvideHandle.Complete -> 即 op.ProviderCompleted :

    InternalOp.ProviderCompleted<T>(result, status, exception); result 就是加载的资源

  • 最终执行 AsyncOperationBase.Complete

3. StartOperation(operation,dependency) : 链式操作dependency若没完成则等待dependency结束

var handle = StartOperation(op, depOp);

        internal void Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList<float> updateCallbacks)
        {
			...
            if (dependency.IsValid() && !dependency.IsDone)
                dependency.Completed += m_dependencyCompleteAction;
            else
                InvokeExecute();
        }

**4. InvokeExecute : 执行Execute() 并在ResourceManager帧更新中添加回调 **

ResourceManager.m_UpdateCallbacks.Add(AsyncOperationBase.UpdateCallback)

ResourceManager.m_UpdateCallbacks 基于 MonoBehaviourCallbackHooks

5.ProviderOperation.Execute

        protected override void Execute()
        {
			...
                try
                {
                    m_Provider.Provide(new ProvideHandle(m_ResourceManager, this));
                }
                catch (Exception e)
                {
                    ProviderCompleted(default(TObject), false, e);
                }
        }

执行各类Provider的Provide 进行资源加载,例如 AssetBundleProvider.Provide

m_RequestOperation = AssetBundle.LoadFromFileAsync(path, m_Options == null ? 0 : m_Options.Crc);
m_RequestOperation.completed += LocalRequestOperationCompleted;


private void LocalRequestOperationCompleted(AsyncOperation op)
{
    m_AssetBundle = (op as AssetBundleCreateRequest).assetBundle;
    m_ProvideHandle.Complete(this, m_AssetBundle != null, null);
}


InternalOp.ProviderCompleted<T>(result, status, exception);



.
.
.
.
.


嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值