• GameFramework:Resource加载,资源加载,依赖加载,任务池,对象池,引用计数


    GF资源加载流程图

    在这里插入图片描述

    GF加载资源简介

    ResourceManager持有某个功能例如ResourceLoader(资源加载),ResourceLoader持有TaskPool,TaskPool持有agent,agent持有helper,真正执行操作的方法是存在helper中。

    ResourceManager:IResourceManager

    缓存资源组和已经加载过的资源。可以获取资源的版本编号和相关的路径地址,还有一些版本更新和资源加载的函数等等。

    如何从asset名索引到对应bundle名

    下面信息从描述文件获得,把GameFrameworkVersion.dat 文件中的 bundle ,asset ,asset的依赖项的关系,从文件中int[]变为string[],保存起来
    调用过程
    GameFramework.Resource.ResourceManager.ResourceChecker.CheckResources
    主要解决问题,只需要记住工程中asset从assets开始的全路径就可以加载资源,而不需要记忆resource名字

    private Dictionary<string, AssetInfo> m_AssetInfos; //用于描述 Unity 中的一个具体资产,如一个预制体、一个材质、一张图片等。
    private Dictionary<ResourceName, ResourceInfo> m_ResourceInfos; //用于描述 Unity 中的一个 AssetBundle(一些 Asset 的集合),或者一个 Game Framework 定义下的二进制文件(策划表)
     private readonly Dictionary<string, ResourceGroup> m_ResourceGroups;//资源组用于将资源分类,是资源的一种特性标签,一个资源可以归属于多个资源组。通过资源组可以游戏中构造出类似于“插件”的更新机制,如高清材质包、游戏语音包等。
    
    • 1
    • 2
    • 3

    ResourceLoader

    Asset,Bundle被依赖引用次数

                private readonly Dictionary<object, int> m_AssetDependencyCount; //asset引用计数,key即资源对象,例如texture等类型资源
                private readonly Dictionary<object, int> m_ResourceDependencyCount; //resource引用计数,object即assetbundle
    
    • 1
    • 2

    每次加载asset,对应所有依赖项asset+1,依赖的bundle根据自己内部的asset被依赖次数 +1
    每次卸载asset,为0,说没被别人依赖,可以卸载,把对应依赖asset-1,依赖bundle-1
    引用计数为0的Asset,即可被释放,Resources.UnloadAsset(object)
    引用计数为0的AssetBundle,即可被释放,AssetBundle.Unload(true)

    使用任务池机制m_TaskPool
    LoadAsset
    LoadBinary
    LoadScene
    每来个加载任务LoadResourceTaskBase,放入到m_TaskPool中,任务可以无限加,s_Serial会自增。但是代理,即执行任务的数量为有限个数
    增加代理,代理总数为UnityGameFramework.Runtime.ResourceComponent.m_LoadResourceAgentHelperCount 控制
    GameFramework.Resource.ResourceManager.ResourceLoader.AddLoadResourceAgentHelper
    最终代理为LoadResourceAgent

                private IObjectPool<AssetObject> m_AssetPool; //缓存的asset对象池
                private IObjectPool<ResourceObject> m_ResourcePool;//缓存的resource对象池
    
    • 1
    • 2

    加载依赖项

    GameFramework.Resource.ResourceManager.ResourceLoader.LoadAsset

    foreach (string dependencyAssetName in dependencyAssetNames)
                    {
                        if (!LoadDependencyAsset(dependencyAssetName, priority, mainTask, userData))
    
    • 1
    • 2
    • 3

    GameFramework.Resource.ResourceManager.ResourceLoader.LoadDependencyAsset

    private bool LoadDependencyAsset(string assetName, int priority, LoadResourceTaskBase mainTask, object userData)
                {
                                ResourceInfo resourceInfo = null;
                    string[] dependencyAssetNames = null;
                    if (!CheckAsset(assetName, out resourceInfo, out dependencyAssetNames))
                    {
                        return false;
                    }
                    LoadDependencyAssetTask dependencyTask = LoadDependencyAssetTask.Create(assetName, priority, resourceInfo, dependencyAssetNames, mainTask, userData);
                    foreach (string dependencyAssetName in dependencyAssetNames)
                    {
                        if (!LoadDependencyAsset(dependencyAssetName, priority, dependencyTask, userData)) //递归调用,加载依赖项,如果有循环依赖如何避免,a引用b,b引用a
                        {
                            return false;
                        }
                    }
    
                    m_TaskPool.AddTask(dependencyTask);
                    if (!resourceInfo.Ready)
                    {
                        m_ResourceManager.UpdateResource(resourceInfo.ResourceName);
                    }
    
                    return true;
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    LoadResourceTaskBase:加载资源任务基类

    创建任务并不是会立即执行任务
    每增加任务,s_Serial会自增

    LoadAssetTask

    加载目标asset任务

    LoadDependencyAssetTask

    加载依赖任务

    TaskPool

    Update中驱动ProcessWaitingTasks
    LoadResourceAgent代理Start

    //等待处理的任务,把所有任务,从free中分配working 代理
            private void ProcessWaitingTasks(float elapseSeconds, float realElapseSeconds)
            {
                LinkedListNode<T> current = m_WaitingTasks.First; //等待任务第一个
                //如果当前有空闲代理
                while (current != null && FreeAgentCount > 0) //当前还有free代理,且会一直循环到wait末尾
                {
                    ITaskAgent<T> agent = m_FreeAgents.Pop();
                    LinkedListNode<ITaskAgent<T>> agentNode = m_WorkingAgents.AddLast(agent);//加入到工作代理
                    T task = current.Value;
                    LinkedListNode<T> next = current.Next;
                    StartTaskStatus status = agent.Start(task);//通过代理控制当前任务执行
    
                    //以下都对出错才处理
                    if (status == StartTaskStatus.Done || status == StartTaskStatus.HasToWait || status == StartTaskStatus.UnknownError)
                    {
                        //当前任务必须等待,归还到空闲代理,工作代理中移除
                        agent.Reset();
                        m_FreeAgents.Push(agent);
                        m_WorkingAgents.Remove(agentNode);
                    }
    
                    if (status == StartTaskStatus.Done || status == StartTaskStatus.CanResume || status == StartTaskStatus.UnknownError)
                    {
                        //可以继续处理
                        m_WaitingTasks.Remove(current);
                    }
    
                    if (status == StartTaskStatus.Done || status == StartTaskStatus.UnknownError)
                    {
                        ReferencePool.Release(task);
                    }
    
                    current = next;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    LoadResourceAgent

    GameFramework.Resource.ResourceManager.ResourceLoader.LoadResourceAgent.Start
    Start执行,任务状态

    1. 可以从m_AssetPool中实例出asset,任务做完了Done
    2. m_ResourcePool中可以实例出resource,任务可以接着做CanResume
    3. 调用辅助器执行加载
      在这里插入图片描述

    在taskPool.update中把空闲代理分配给到工作代理中,Start执行

    //任务不是场景,说明要实例化
                       if (!m_Task.IsScene)
                       {
                           //从对象池里拿一个,已经可以从ab里实例出来asset,任务做完了
                           AssetObject assetObject = m_ResourceLoader.m_AssetPool.Spawn(m_Task.AssetName);
                           if (assetObject != null)
                           {
                               OnAssetObjectReady(assetObject);
                               return StartTaskStatus.Done;
                           }
                       }
                       
                       //遍历依赖
                       foreach (string dependencyAssetName in m_Task.GetDependencyAssetNames())
                       {
                           //如果依赖项目不能实例一个,接着等待
                           if (!m_ResourceLoader.m_AssetPool.CanSpawn(dependencyAssetName))
                           {
                               m_Task.StartTime = default(DateTime);
                               return StartTaskStatus.HasToWait;
                           }
                       }
                       
                        //从resource对象池中取出,说明任务可以接着执行
                       ResourceObject resourceObject = m_ResourceLoader.m_ResourcePool.Spawn(resourceName);
                       if (resourceObject != null)
                       {
                           OnResourceObjectReady(resourceObject);
                           return StartTaskStatus.CanResume;
                       }
                       
                       //以上都不满足
                       m_Helper.ReadFile(fullPath);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    m_Helper.ReadFile(fullPath);这里最终调用DefaultLoadResourceAgentHelper.ReadFile
    m_FileAssetBundleCreateRequest = AssetBundle.LoadFromFileAsync(fullPath);

    DefaultLoadResourceAgentHelper

    会创建GameObject在场景中
    加载代理辅助器,会在场景中创建
    在这里插入图片描述

    ILoadResourceAgentHelper是加载资源代理辅助器接口,加载资源会伴随六大事件(异步加载资源更新事件,异步读取资源文件完成事件,异步读取资源二进制流完成事件,异步将资源二进制流转换为加载对象完成事件,异步加载资源完成事件,错误事件)。资源的文件和二进制异步加载还有重置辅助器的函数。是实际加载资源的接口,加载完成以后会缓存到ResourceManager下ResourceLoader的IObjectPool。

    加载bundle

    1. Update中轮询UpdateFileAssetBundleCreateRequest()里AssetBundleCreateRequest 请求完成后,发送通知,
    2. 最终调用到GameFramework.Resource.ResourceManager.ResourceLoader.LoadResourceAgent.OnLoadResourceAgentHelperReadFileComplete
    3. 创建出ResourceObject,注册到ResourceLoader.m_ResourcePool。ResourceObject主要是增加上次使用时间戳,和一些信息Resource信息
    4. 并且从正在加载Resource表中清除,s_LoadingResourceNames
    5. Resource准备好了,加载 m_Task.LoadMain(this, resourceObject);,即加载目标asset

    加载asset

    UnityGameFramework.Runtime.DefaultLoadResourceAgentHelper.LoadAsset

    1. 如果是场景,开启异步加载:m_AsyncOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    2. 如果是资源,m_AssetBundleRequest = assetBundle.LoadAssetAsync(assetName);
      在Update轮询
      m_AssetBundleRequest.isDone,抛出事件LoadResourceAgentHelperLoadComplete
      加载完成结果
      GameFramework.Resource.ResourceManager.ResourceLoader.LoadResourceAgent.OnLoadResourceAgentHelperLoadComplete
    3. 创建AssetObject,自己+1,依赖的所有asset+1
    4. 加入到m_ResourceLoader.m_AssetPool.Register(assetObject, true);
    5. s_LoadingAssetNames.Remove(m_Task.AssetName);中移除正在加载资源
    6. 所有被依赖的Resource引用+1
      引用计数
      每次加载asset,对应所有依赖项asset+1,依赖的bundle根据自己内部的asset被依赖 +1
      每次卸载asset,为0,说没被别人依赖,可以卸载,把对应依赖asset-1,依赖bundle-1
      引用计数为0的Asset,即可被释放,Resources.UnloadAsset(object)
      引用计数为0的AssetBundle,即可被释放,AssetBundle.Unload(true)

    加载Log流程

    1. 创建加载asset主任务a:Assets/GameMain/UI/UIForms/DialogForm.prefab
      GameFramework.Resource.ResourceManager/ResourceLoader/LoadAssetTask:Create
      初始化加载任务Assets/GameMain/UI/UIForms/DialogForm.prefab–>[“Assets/GameMain/UI/UISprites/Common/dialog-title-background.png”,“Assets/GameMain/UI/UISprites/Common/button-outline.png”,“Assets/GameMain/UI/UISprites/Common/dialog-background.png”,“Assets/GameMain/UI/UISprites/Common/background.png”]
      这里依赖4张PNG
      这里要加载个perfab,对应依赖的资源也找出来
      函数调用
      GameFramework.Resource.ResourceManager.ResourceLoader.LoadAsset
    2. 创建依赖资源任务1:Assets/GameMain/UI/UISprites/Common/dialog-title-background.png
    3. 增加依赖资源任务1到等待列表中GameFramework.Resource.ResourceManager+ResourceLoader+LoadDependencyAssetTask到m_WaitingTasks.count = 1
    4. 创建依赖资源任务2:Assets/GameMain/UI/UISprites/Common/button-outline.png
    5. 增加依赖资源任务2到等待列表中GameFramework.Resource.ResourceManager+ResourceLoader+LoadDependencyAssetTask到m_WaitingTasks.count = 2
    6. 依次增加总共4个依赖任务
    GameFramework.Resource.ResourceManager.ResourceLoader.LoadDependencyAsset 
    private bool LoadDependencyAsset(string assetName, int priority, LoadResourceTaskBase mainTask, object userData)
                {
                   ResourceInfo resourceInfo = null;
                    string[] dependencyAssetNames = null;
                    if (!CheckAsset(assetName, out resourceInfo, out dependencyAssetNames))
                    {
                        return false;
                    }
    
                    if (resourceInfo.IsLoadFromBinary)
                    {
                        return false;
                    }
    
                    LoadDependencyAssetTask dependencyTask = LoadDependencyAssetTask.Create(assetName, priority, resourceInfo, dependencyAssetNames, mainTask, userData);
                    foreach (string dependencyAssetName in dependencyAssetNames)
                    {
                        if (!LoadDependencyAsset(dependencyAssetName, priority, dependencyTask, userData)) //递归调用,加载依赖项,如果有循环依赖如何避免,a引用b,b引用a,因为加载中,asset,resource都有缓存,不会重复创建任务
                        {
                            return false;
                        }
                    }
    
                    m_TaskPool.AddTask(dependencyTask);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    1. 增加主任务到等待中,这样能确保是前面的依赖任务执行完毕,再会执行主任务a
      增加任务:GameFramework.Resource.ResourceManager+ResourceLoader+LoadAssetTask到m_WaitingTasks.count = 5
    2. 从等待任务池里取一个开始,代理执行GameFramework.Resource.ResourceManager.ResourceLoader.LoadResourceAgent.Start
      虽然任务是读取asset,实际上还是先读ab
      异步读取资源文件C:/Users/luoyikun_l/AppData/LocalLow/Game Framework/Star Force/UI/UISprites/Common.dat
    3. UnityGameFramework.Runtime.DefaultLoadResourceAgentHelper.UpdateFileAssetBundleCreateRequest 代理中轮询出来,ab加载完毕
    4. resource加载完成UI/UISprites/Common
    5. AB加载资源代理辅助器ui/uisprites/common (UnityEngine.AssetBundle)里面Assets/GameMain/UI/UISprites/Common/dialog-title-background.png
      task并未释放,还是在加载asset
    6. 其他task 在 TaskPool.update 中轮询时,知道上一个任务共同加载的bundle已加载好
    //从resource对象池中取出,说明任务可以接着执行
                        ResourceObject resourceObject = m_ResourceLoader.m_ResourcePool.Spawn(resourceName);
                        if (resourceObject != null)
                        {
                            OnResourceObjectReady(resourceObject);
                            return StartTaskStatus.CanResume;
                        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    task直接进入 加载asset阶段

    //从resource里加载目标asset
                   public void LoadMain(LoadResourceAgent agent, ResourceObject resourceObject)
                   {
                       m_ResourceObject = resourceObject;
                       agent.Helper.LoadAsset(resourceObject.Target, AssetName, AssetType, IsScene);
                   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. UnityGameFramework.Runtime.DefaultLoadResourceAgentHelper.UpdateAssetBundleRequest 轮询中asset加载完毕
      asset加载完成Assets/GameMain/UI/UISprites/Common/dialog-background.png
      加载完毕的asset都会放入缓冲池中
      asset–>Assets/GameMain/UI/UISprites/Common/button-outline.png加载完成,并且创建assetObject到m_AssetPool缓冲池中
    GameFramework.Resource.ResourceManager.ResourceLoader.LoadResourceAgent.OnLoadResourceAgentHelperLoadComplete 
    m_ResourceLoader.m_AssetPool.Register(assetObject, true);
    
    • 1
    • 2
    1. 因为目标asset会一直等待所有依赖的asset加载完毕,才开始自身bundle加载,然后asset加载
    GameFramework.Resource.ResourceManager.ResourceLoader.LoadResourceAgent.Start 
    //遍历依赖
                        foreach (string dependencyAssetName in m_Task.GetDependencyAssetNames())
                        {
                            //如果依赖asset不能spawn,接着等待
                            if (!m_ResourceLoader.m_AssetPool.CanSpawn(dependencyAssetName))
                            {
                                GameFrameworkLog.Info("{0}依赖项{1}未加载完成", m_Task.AssetName,dependencyAssetName);
                                m_Task.StartTime = default(DateTime);
                                return StartTaskStatus.HasToWait;
                            }
                        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    现在所有依赖asset全部加载完毕,加载目标asset。可能存在依赖项目的asset还会再次依赖别的asset,会导致依赖项的asset再次进入等待状态

    资源卸载时

    GameFramework.Resource.ResourceManager.ResourceLoader.AssetObject.OnUnspawn
    把所有依赖asset都执行遍

                    protected internal override void OnUnspawn()
                    {
                        base.OnUnspawn();
                        //卸载时把相应的资源也处理
                        foreach (object dependencyAsset in m_DependencyAssets)
                        {
                            m_ResourceLoader.m_AssetPool.Unspawn(dependencyAsset);
                        }
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    AssetObject : ObjectBase

     protected internal override void Release(bool isShutdown)
                    {
                        if (!isShutdown)
                        {
                            int targetReferenceCount = 0;
                            if (m_ResourceLoader.m_ResourceDependencyCount.TryGetValue(Target, out targetReferenceCount) && targetReferenceCount > 0)
                            {
                                throw new GameFrameworkException(Utility.Text.Format("Resource target '{0}' reference count is '{1}' larger than 0.", Name, targetReferenceCount));
                            }
    
                            foreach (object dependencyResource in m_DependencyResources)
                            {
                                int referenceCount = 0;
                                if (m_ResourceLoader.m_ResourceDependencyCount.TryGetValue(dependencyResource, out referenceCount))
                                {
                                    m_ResourceLoader.m_ResourceDependencyCount[dependencyResource] = referenceCount - 1;
                                    //只会-1,不会对为 0 的Assetbundle进行卸载
                                }
                                else
                                {
                                    throw new GameFrameworkException(Utility.Text.Format("Resource target '{0}' dependency asset reference count is invalid.", Name));
                                }
                            }
                        }
    
                        m_ResourceLoader.m_ResourceDependencyCount.Remove(Target);
                        m_ResourceHelper.Release(Target); //AssetBundle.Unload(true) ,也可能是asset 执行释放,也可能是scene执行释放
    
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    面向对象--继承 demo
    ORB-SLAM3在保存地图时高频率崩溃问题
    题库API 大学公众号题库API 网课查题题库接口API接口
    salesforce零基础学习(一百一十七)salesforce部署方式及适用场景
    C++ Reference: Standard C++ Library reference: C Library: cwctype: iswalpha
    HBuilder有线调试过程中手机显示"接口执行异常"
    HackTheBox-Starting Point--Tier 2---Archetype
    SkyWalking持久化追踪数据
    【Unity Shader】屏幕后处理1.0:调整亮度/饱和度/对比度
    .Net Web API 005 Controller上传小文件
  • 原文地址:https://blog.csdn.net/luoyikun/article/details/125804373