05-05-源码-资源加载与Provider体系

源码-资源提供者(Provider 体系)

篇章 :05-源码深度-运行时核心模块 阅读时间 :约 30 分钟 前置知识:了解寻址系统


一、引言

本章将深入解析 YooAsset 资源提供者(Provider 体系)的源码实现。Provider 是 YooAsset 资源加载的核心,封装了不同类型资源的加载逻辑。YooAsset 提供了多种 Provider 类型,包括 AssetProvider、SceneProvider、ComponentProvider 等。

Provider 体系的设计目标是提供灵活、可扩展的资源加载能力。本章将从源码层面深入解析 Provider 体系的设计和实现。


二、Provider 概述

2.1 Provider 的概念

Provider 是 YooAsset 中负责加载单个资源的核心组件:

Provider 的定义:

Provider 是一个抽象类,封装了资源加载的所有逻辑。每个 Provider 对应一个资源的加载。

Provider 的特点:

  • 单一职责:每个 Provider 只负责一个资源
  • 状态管理:Provider 维护资源加载的状态
  • 引用计数:Provider 内置引用计数管理
  • 异步加载:Provider 支持异步加载资源

Provider 的作用:

  • 接收资源加载请求
  • 创建和加载 Bundle
  • 从 Bundle 中加载资源
  • 管理资源生命周期

2.2 Provider 状态机

Provider 内部维护了一个状态机:

css 复制代码
Provider 状态机
├── None:初始状态
├── Loading:加载中
├── Loaded:已加载
├── Releasing:释放中
└── Destroyed:已销毁

None 详解:

None 是 Provider 的初始状态,Provider 创建后即进入 None 状态。

Loading 详解:

Loading 是 Provider 的加载中状态。Provide 方法被调用后进入 Loading 状态。

Loaded 详解:

Loaded 是 Provider 的已加载状态。资源加载完成后进入 Loaded 状态。

Releasing 详解:

Releasing 是 Provider 的释放中状态。UnProvide 方法被调用后进入 Releasing 状态。

Destroyed 详解:

Destroyed 是 Provider 的已销毁状态。资源销毁后进入 Destroyed 状态。

2.3 Provider 状态详解

csharp 复制代码
public enum EProviderStatus
{
    None = 0,       // 初始状态
    Loading = 1,    // 加载中
    Loaded = 2,     // 已加载
    Failed = 3,     // 加载失败
    Destroyed = 4,  // 已销毁
}

状态转换详解:

  • None → Loading:调用 Provide 方法
  • Loading → Loaded:资源加载成功
  • Loading → Failed:资源加载失败
  • Loaded/Failed → Destroyed:调用 Destroy 方法

2.4 Provider 创建

csharp 复制代码
public abstract class ProviderBase
{
    public string Location { get; }
    public EProviderStatus Status { get; protected set; }
    public float Progress { get; protected set; }
    public object AssetObject { get; protected set; }
    public int RefCount { get; protected set; }
    
    public abstract void Provide();
    public abstract void UnProvide();
    public abstract void Update();
}

Location 详解:

Location 是 Provider 加载的资源地址。

Status 详解:

Status 是 Provider 的当前状态。

Progress 详解:

Progress 是 Provider 的加载进度。

AssetObject 详解:

AssetObject 是 Provider 加载的资源对象。

RefCount 详解:

RefCount 是 Provider 的引用计数。


三、Provider 创建/获取

3.1 Provider 的创建

Provider 的创建由 ResourceManager 负责:

csharp 复制代码
public class ResourceManager
{
    public AssetProvider CreateAssetProvider(string location, Type assetType)
    {
        AssetProvider provider = new AssetProvider(this, location, assetType);
        return provider;
    }
}

Provider 实例化详解:

ResourceManager 根据资源的类型创建对应的 Provider。

Provider 参数详解:

创建 Provider 时需要传入 ResourceManager、location、assetType 等参数。

3.2 Provider 的获取

csharp 复制代码
public class ResourceManager
{
    private Dictionary<string, ProviderBase> _providerCache = new Dictionary<string, ProviderBase>();
    
    public ProviderBase GetOrCreateProvider(string location, Type assetType)
    {
        string key = $"{location}_{assetType.Name}";
        if (_providerCache.TryGetValue(key, out var provider))
        {
            provider.IncreaseRefCount();
            return provider;
        }
        
        provider = CreateAssetProvider(location, assetType);
        _providerCache[key] = provider;
        provider.IncreaseRefCount();
        return provider;
    }
}

Provider 复用详解:

GetOrCreateProvider 方法会先在缓存中查找已存在的 Provider。如果存在则复用,否则创建新的。

引用计数增加详解:

每次获取 Provider 时,引用计数加 1。

3.3 Provider 的销毁

csharp 复制代码
public class ResourceManager
{
    public void DestroyProvider(ProviderBase provider)
    {
        string key = provider.Location;
        _providerCache.Remove(key);
        provider.Destroy();
    }
}

Provider 销毁详解:

当 Provider 的引用计数为 0 时,会从缓存中移除并销毁。


四、Provider 加载/卸载

4.1 加载流程

Provider 的加载流程如下:

markdown 复制代码
Provider 加载流程
1. 接收加载请求
2. 增加引用计数
3. 启动加载流程
   a. 创建 BundleLoader
   b. 加载 Bundle
   c. 加载依赖的 Bundle
   d. 从 Bundle 中加载资源
4. 加载完成
5. 触发回调

接收加载请求详解:

用户调用 LoadAssetAsync 时,ResourceManager 会创建或复用 Provider。

增加引用计数详解:

每次加载请求会增加 Provider 的引用计数。

启动加载流程详解:

Provide 方法启动加载流程。

Bundle 加载详解:

Provider 创建 BundleLoader 加载 Bundle。

资源加载详解:

从 Bundle 中加载资源。

4.2 加载实现

csharp 复制代码
public class AssetProvider : ProviderBase
{
    private BundleLoader _bundleLoader;
    private List<BundleLoader> _dependLoaders;
    private AssetBundleRequest _request;
    
    public override void Provide()
    {
        Status = EProviderStatus.Loading;
        Progress = 0;
        
        // 1. 获取 AssetInfo
        var assetInfo = Package.GetAssetInfo(Location);
        if (assetInfo == null)
        {
            Status = EProviderStatus.Failed;
            Error = "AssetInfo not found";
            return;
        }
        
        // 2. 加载依赖 Bundle
        _dependLoaders = new List<BundleLoader>();
        foreach (var depBundleID in assetInfo.DependIDs)
        {
            var depBundle = Package.GetBundleInfo(depBundleID);
            var loader = Package.ResourceManager.LoadBundle(depBundle);
            _dependLoaders.Add(loader);
        }
        
        // 3. 加载主 Bundle
        var mainBundle = Package.GetBundleInfo(assetInfo.BundleID);
        _bundleLoader = Package.ResourceManager.LoadBundle(mainBundle);
    }
    
    public override void Update()
    {
        if (Status != EProviderStatus.Loading) return;
        
        // 计算进度
        float total = _dependLoaders.Count + 1;
        float current = 0;
        foreach (var loader in _dependLoaders)
        {
            if (loader.IsDone) current++;
        }
        if (_bundleLoader.IsDone) current++;
        Progress = current / total;
        
        // 检查是否完成
        if (Progress < 1) return;
        
        // 加载资源
        _request = _bundleLoader.AssetBundle.LoadAssetAsync(AssetInfo.AssetPath, AssetType);
    }
}

加载依赖 Bundle 详解:

加载资源前,先加载所有依赖的 Bundle。

加载主 Bundle 详解:

加载资源所属的主 Bundle。

进度计算详解:

进度 = 已加载的 Bundle 数 / 总 Bundle 数。

资源加载详解:

所有 Bundle 加载完成后,从主 Bundle 中加载资源。

4.3 卸载流程

Provider 的卸载流程:

markdown 复制代码
Provider 卸载流程
1. 接收卸载请求
2. 减少引用计数
3. 引用计数为 0 时启动销毁
4. 释放资源
5. 销毁 Bundle 引用
6. 销毁 Provider

接收卸载请求详解:

用户调用 UnloadAsset 时,ResourceManager 减少 Provider 的引用计数。

引用计数减少详解:

每次卸载请求会减少 Provider 的引用计数。

销毁触发详解:

引用计数为 0 时,Provider 启动销毁流程。

4.4 卸载实现

csharp 复制代码
public class AssetProvider : ProviderBase
{
    public override void UnProvide()
    {
        DecreaseRefCount();
        if (RefCount > 0) return;
        
        Status = EProviderStatus.Destroyed;
        
        // 释放资源
        if (AssetObject != null)
        {
            if (AssetObject is GameObject)
            {
                // 释放 GameObject 资源
            }
            Resources.UnloadAsset(AssetObject);
        }
        
        // 减少 Bundle 引用
        Package.ResourceManager.UnloadBundle(_bundleLoader);
        foreach (var loader in _dependLoaders)
        {
            Package.ResourceManager.UnloadBundle(loader);
        }
    }
}

资源释放详解:

使用 Resources.UnloadAsset 释放资源。

Bundle 引用减少详解:

减少 Bundle 的引用计数,引用为 0 时销毁 Bundle。


五、Provider 类型

5.1 AssetProvider

AssetProvider 是最常用的 Provider,用于加载普通资源:

csharp 复制代码
public class AssetProvider : ProviderBase
{
    public override void Provide();
    public override void UnProvide();
    public override void Update();
}

AssetProvider 的用途详解:

加载 Prefab、Texture、Material、AudioClip 等普通资源。

AssetProvider 的实现详解:

AssetProvider 继承自 ProviderBase,实现了 Provide、UnProvide、Update 方法。

5.2 SceneProvider

SceneProvider 用于加载场景资源:

csharp 复制代码
public class SceneProvider : ProviderBase
{
    public Scene Scene { get; }
    public AsyncOperation LoadSceneAsync();
    public AsyncOperation UnloadSceneAsync();
}

SceneProvider 的用途详解:

加载 Unity 场景。

SceneProvider 的特殊处理详解:

SceneProvider 使用 Unity 的 SceneManager 加载场景,并管理场景的激活和卸载。

5.3 ComponentProvider

ComponentProvider 用于加载 Component 类型的资源:

csharp 复制代码
public class ComponentProvider : ProviderBase
{
    public GameObject Instance { get; }
    public T GetComponent<T>() where T : Component;
}

ComponentProvider 的用途详解:

加载 Prefab 并获取其中的 Component。

ComponentProvider 的实现详解:

ComponentProvider 在 AssetProvider 的基础上,提供了 Component 获取的能力。

5.4 SubAssetsProvider

SubAssetsProvider 用于加载子资源:

csharp 复制代码
public class SubAssetsProvider : ProviderBase
{
    public Object[] SubAssets { get; }
}

SubAssetsProvider 的用途详解:

加载包含多个资源的 AssetBundle 中的特定子资源。

SubAssetsProvider 的应用场景详解:

加载 Sprite Atlas 中的特定 Sprite、加载 AnimationClip 等。

5.5 RawFileProvider

RawFileProvider 用于加载原生文件:

csharp 复制代码
public class RawFileProvider : ProviderBase
{
    public byte[] RawData { get; }
    public string RawText { get; }
}

RawFileProvider 的用途详解:

加载原生格式的文件,如图片、文本等。

RawFileProvider 的实现详解:

RawFileProvider 不会将资源转换为 Unity 对象,而是返回原始的字节数据。

5.6 BundleProvider

BundleProvider 用于加载整个 Bundle:

csharp 复制代码
public class BundleProvider : ProviderBase
{
    public AssetBundle AssetBundle { get; }
}

BundleProvider 的用途详解:

加载整个 AssetBundle,不加载具体的资源。

BundleProvider 的应用场景详解:

需要手动管理 AssetBundle 内容的场景。


六、Provider 缓存机制

6.1 缓存的作用

Provider 缓存的作用:

避免重复创建:

相同资源只创建一次 Provider,避免重复创建的开销。

复用 Provider:

相同资源的加载请求复用同一个 Provider,避免重复加载。

提高性能:

通过缓存减少内存分配和资源加载。

6.2 缓存的实现

csharp 复制代码
public class ResourceManager
{
    private Dictionary<string, ProviderBase> _providerCache = new Dictionary<string, ProviderBase>();
    
    public ProviderBase GetProvider(string location, Type assetType)
    {
        string key = GenerateProviderKey(location, assetType);
        if (_providerCache.TryGetValue(key, out var provider))
        {
            return provider;
        }
        return null;
    }
    
    public void CacheProvider(ProviderBase provider)
    {
        string key = GenerateProviderKey(provider.Location, provider.AssetType);
        _providerCache[key] = provider;
    }
}

缓存 Key 生成详解:

缓存 Key 由 location 和 assetType 组成,确保相同资源使用相同的 Key。

缓存查询详解:

加载资源时先查询缓存,命中则复用,未命中则创建。

6.3 缓存的清理

csharp 复制代码
public class ResourceManager
{
    public void ClearUnusedProviders()
    {
        var unused = _providerCache.Where(p => p.Value.RefCount == 0).ToList();
        foreach (var kvp in unused)
        {
            kvp.Value.Destroy();
            _providerCache.Remove(kvp.Key);
        }
    }
}

清理条件详解:

引用计数为 0 的 Provider 是未使用的 Provider。

清理时机详解:

可以在资源切换、关卡切换等时机调用清理方法。


七、总结

本章深入解析了 YooAsset 资源提供者(Provider 体系)的源码实现,包括:

  • Provider 概述:Provider 的概念、状态机、状态详解
  • Provider 创建/获取:Provider 的创建、获取、销毁
  • Provider 加载/卸载:加载流程、加载实现、卸载流程、卸载实现
  • Provider 类型:AssetProvider、SceneProvider、ComponentProvider、SubAssetsProvider、RawFileProvider、BundleProvider
  • Provider 缓存机制:缓存的作用、实现、清理

Provider 体系是 YooAsset 资源加载的核心,通过深入理解 Provider 体系,开发者可以更好地使用 YooAsset 的资源加载功能,并实现自定义的 Provider。


上一篇 :寻址系统 下一篇 :异步体系(OperationSystem)

相关推荐
SmalBox1 天前
05-04-源码-资源定位系统
unity3d·游戏开发
Mikko72 天前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
郝学胜_神的一滴2 天前
游戏设计:原型与开发 06:读懂游戏设计最容易被忽略的文化层
游戏·游戏开发
SmalBox2 天前
05-03-源码-运行模式体系(PlayMode)
unity3d·游戏开发
努力小陈13 天前
Redis ZSet 排行榜实战:SLG 后端排行榜的 10 个坑
游戏开发
努力小陈13 天前
SLG 后端最终选了 Actor
游戏开发
努力小陈13 天前
玩家 Actor 的生命周期与阻塞 IO:全局协程池分流实战
游戏开发
SmalBox3 天前
05-02-源码-全局入口与包管理(YooAssets & ResourcePackage)
unity3d·游戏开发
SmalBox4 天前
05-01-源码-运行时总览
unity3d·游戏开发