05-08-源码-缓存系统与内存管理

源码-资源缓存机制

篇章 :05-源码深度-运行时核心模块 阅读时间 :约 25 分钟 前置知识:了解引用计数机制


一、引言

本章将深入解析 YooAsset 资源缓存机制的源码实现。资源缓存是 YooAsset 资源管理的重要组成部分,通过缓存机制可以提高资源加载速度、减少网络请求。YooAsset 提供了多级缓存机制,包括内存缓存、磁盘缓存等。

资源缓存机制的设计目标是提供高效、可靠的缓存能力。本章将从源码层面深入解析 YooAsset 资源缓存机制的实现。


二、缓存机制概述

2.1 缓存的作用

缓存是资源管理的重要组成部分:

缓存的作用:

  • 提高资源加载速度
  • 减少网络请求
  • 减少磁盘 I/O
  • 提升用户体验

缓存的类型:

  • 内存缓存:AssetBundle 缓存
  • 磁盘缓存:文件缓存
  • 预加载缓存:预下载的资源

2.2 YooAsset 中的缓存

YooAsset 提供了多级缓存机制:

markdown 复制代码
YooAsset 缓存体系
├── 内存缓存
│   ├── BundleLoader 缓存
│   ├── Provider 缓存
│   └── AssetBundle 缓存
├── 磁盘缓存
│   ├── 包内资源缓存(构建内)
│   ├── 本地缓存(CacheSystem)
│   └── 临时缓存
└── 预加载缓存
    └── 预下载的资源

内存缓存详解:

内存缓存是 AssetBundle 和 Provider 的内存缓存,提供最快的访问速度。

磁盘缓存详解:

磁盘缓存是已下载资源的本地缓存,提供跨会话的持久化能力。

预加载缓存详解:

预加载缓存是预下载的资源,可以在游戏空闲时预先加载。

2.3 缓存策略

YooAsset 提供了多种缓存策略:

缓存策略 描述 优点 缺点
无缓存 不缓存任何资源 内存占用少 加载慢
LRU 最近最少使用 自动清理 复杂
容量限制 限制缓存大小 可控 需要管理
手动缓存 手动管理缓存 灵活 需要管理

无缓存策略详解:

不缓存任何资源,每次都重新加载。适合内存紧张的小游戏。

LRU 策略详解:

使用 LRU 算法自动清理最近最少使用的资源。

容量限制策略详解:

限制缓存的总大小,避免内存溢出。

手动缓存策略详解:

由开发者手动管理缓存,提供最大的灵活性。


三、BundleLoader 缓存

3.1 BundleLoader 缓存的作用

BundleLoader 缓存用于管理 AssetBundle 的加载:

BundleLoader 缓存的作用:

  • 复用已加载的 AssetBundle
  • 避免重复加载
  • 提高加载速度

3.2 BundleLoader 缓存的实现

csharp 复制代码
public class ResourceManager
{
    private Dictionary<string, BundleLoader> _bundleLoaders = new Dictionary<string, BundleLoader>();
    
    public BundleLoader LoadBundle(BundleInfo bundleInfo)
    {
        string key = bundleInfo.FileName;
        if (_bundleLoaders.TryGetValue(key, out var loader))
        {
            loader.Retain();
            return loader;
        }
        
        loader = new BundleLoader(bundleInfo, Package);
        loader.LoadAsync();
        _bundleLoaders[key] = loader;
        loader.Retain();
        return loader;
    }
    
    public void UnloadBundle(BundleLoader loader)
    {
        loader.Release();
        if (loader.RefCount <= 0)
        {
            _bundleLoaders.Remove(loader.BundleInfo.FileName);
        }
    }
}

缓存查询详解:

通过 Bundle 文件名查询缓存。

缓存复用详解:

缓存命中时复用 BundleLoader,并增加引用计数。

缓存清理详解:

引用为 0 时清理 BundleLoader。

3.3 BundleLoader 缓存策略

csharp 复制代码
public class ResourceManager
{
    public void ClearAllBundles()
    {
        foreach (var loader in _bundleLoaders.Values)
        {
            loader.UnloadForce();
        }
        _bundleLoaders.Clear();
    }
    
    public void ClearUnusedBundles()
    {
        var unused = _bundleLoaders.Where(b => b.Value.RefCount == 0).ToList();
        foreach (var kvp in unused)
        {
            kvp.Value.UnloadForce();
            _bundleLoaders.Remove(kvp.Key);
        }
    }
}

清除所有 Bundle 详解:

ClearAllBundles 清除所有 Bundle,强制卸载所有 AssetBundle。

清除未使用 Bundle 详解:

ClearUnusedBundles 清除未使用的 Bundle,保留正在使用的 Bundle。


四、Provider 缓存

4.1 Provider 缓存的作用

Provider 缓存用于管理已创建的 Provider:

Provider 缓存的作用:

  • 复用已创建的 Provider
  • 减少 Provider 创建开销
  • 跟踪资源状态

4.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 = CreateProvider(location, assetType);
        _providerCache[key] = provider;
        provider.IncreaseRefCount();
        return provider;
    }
    
    public void RemoveProvider(ProviderBase provider)
    {
        string key = $"{provider.Location}_{provider.AssetType.Name}";
        _providerCache.Remove(key);
    }
}

缓存 Key 生成详解:

缓存 Key 由 location 和 assetType 组成。

Provider 复用详解:

缓存命中时复用 Provider,增加引用计数。

Provider 销毁详解:

引用为 0 时销毁 Provider。

4.3 Provider 缓存策略

csharp 复制代码
public class ResourceManager
{
    public void ClearAllAssets()
    {
        var providers = _providerCache.Values.ToList();
        foreach (var provider in providers)
        {
            provider.UnProvide();
        }
        _providerCache.Clear();
    }
    
    public void ClearUnusedAssets()
    {
        var unused = _providerCache.Where(p => p.Value.RefCount == 0).ToList();
        foreach (var kvp in unused)
        {
            kvp.Value.UnProvide();
            _providerCache.Remove(kvp.Key);
        }
    }
}

清除所有资源详解:

ClearAllAssets 清除所有资源,强制销毁所有 Provider。

清除未使用资源详解:

ClearUnusedAssets 清除未使用的资源,保留正在使用的资源。


五、磁盘缓存

5.1 磁盘缓存的作用

磁盘缓存用于持久化存储已下载的资源:

磁盘缓存的作用:

  • 跨会话保存资源
  • 减少重复下载
  • 节省网络流量

5.2 磁盘缓存的位置

csharp 复制代码
public class CacheSystem
{
    private string _cacheRootPath;
    
    public CacheSystem()
    {
        // 缓存根目录
        _cacheRootPath = Path.Combine(Application.persistentDataPath, "YooAsset/Cache");
        if (!Directory.Exists(_cacheRootPath))
            Directory.CreateDirectory(_cacheRootPath);
    }
}

持久化路径详解:

使用 Application.persistentDataPath 作为缓存根目录。

YooAsset/Cache 目录详解:

在 persistentDataPath 下创建 YooAsset/Cache 目录用于缓存。

5.3 磁盘缓存的实现

csharp 复制代码
public class CacheSystem
{
    private Dictionary<string, FileInfo> _cachedFiles = new Dictionary<string, FileInfo>();
    
    public bool IsCached(string fileName)
    {
        return _cachedFiles.ContainsKey(fileName);
    }
    
    public byte[] ReadCacheFile(string fileName)
    {
        string filePath = Path.Combine(_cacheRootPath, fileName);
        if (File.Exists(filePath))
            return File.ReadAllBytes(filePath);
        return null;
    }
    
    public void WriteCacheFile(string fileName, byte[] data)
    {
        string filePath = Path.Combine(_cacheRootPath, fileName);
        File.WriteAllBytes(filePath, data);
        _cachedFiles[fileName] = new FileInfo(filePath);
    }
    
    public void RemoveCacheFile(string fileName)
    {
        string filePath = Path.Combine(_cacheRootPath, fileName);
        if (File.Exists(filePath))
            File.Delete(filePath);
        _cachedFiles.Remove(fileName);
    }
}

缓存查询详解:

通过文件名查询缓存是否存在。

缓存读取详解:

从缓存文件读取数据。

缓存写入详解:

将数据写入缓存文件。

缓存删除详解:

删除缓存文件。

5.4 磁盘缓存的清理

csharp 复制代码
public class CacheSystem
{
    public void ClearExpiredCache()
    {
        // 清理过期缓存
    }
    
    public void ClearBySize(long maxSize)
    {
        // 按大小清理
    }
    
    public void ClearByVersion(string currentVersion)
    {
        // 清理旧版本缓存
    }
}

过期清理详解:

清理超过指定时间的缓存。

容量清理详解:

限制缓存总大小,超出时清理最旧的缓存。

版本清理详解:

清理旧版本的缓存,只保留当前版本的缓存。


六、内存缓存与磁盘缓存的配合

6.1 缓存配合的流程

markdown 复制代码
缓存配合流程
1. 加载资源请求
2. 检查内存缓存(Provider、BundleLoader)
3. 内存缓存命中:直接返回
4. 内存缓存未命中:检查磁盘缓存
5. 磁盘缓存命中:加载到内存缓存
6. 磁盘缓存未命中:下载到磁盘缓存,再加载到内存缓存

内存缓存优先详解:

优先检查内存缓存,提供最快的访问速度。

磁盘缓存次之详解:

内存缓存未命中时,检查磁盘缓存。

下载最后详解:

磁盘缓存未命中时,从远程下载。

6.2 缓存的实现

csharp 复制代码
public class ResourcePackage
{
    public AssetOperationHandle LoadAssetAsync(string location, Type assetType)
    {
        // 1. 检查内存缓存
        var provider = ResourceManager.GetProvider(location, assetType);
        if (provider != null && provider.Status == EProviderStatus.Loaded)
        {
            return provider.GetHandle();
        }
        
        // 2. 内存缓存未命中,加载资源
        provider = ResourceManager.GetOrCreateProvider(location, assetType);
        provider.Provide();
        return provider.GetHandle();
    }
}

内存缓存检查详解:

优先检查内存缓存中是否已有 Provider。

加载资源详解:

内存缓存未命中时,创建 Provider 并加载资源。

6.3 缓存的生命周期

内存缓存的生命周期:

内存缓存在游戏会话内有效,游戏退出后自动释放。

磁盘缓存的生命周期:

磁盘缓存跨会话有效,需要主动清理。

缓存与引用的关系:

内存缓存与引用计数关联,引用为 0 时清理。


七、缓存优化

7.1 预加载

预加载是重要的缓存优化策略:

csharp 复制代码
public class PreloadManager
{
    public void PreloadAssets(List<string> locations)
    {
        foreach (var location in locations)
        {
            // 异步预加载
            var handle = Package.LoadAssetAsync<GameObject>(location);
        }
    }
}

预加载的作用详解:

在游戏空闲时预先加载资源,避免游戏时加载卡顿。

预加载的应用场景详解:

  • 游戏启动时预加载常用资源
  • 关卡切换时预加载下一关资源
  • 网络空闲时预加载未来资源

7.2 缓存预热

csharp 复制代码
public class CacheWarmer
{
    public void WarmCache(List<string> criticalResources)
    {
        foreach (var location in criticalResources)
        {
            var handle = Package.LoadAssetAsync<GameObject>(location);
            handle.Release(); // 预热后释放
        }
    }
}

缓存预热的作用详解:

在游戏开始前预先加载关键资源到内存,避免第一次加载卡顿。

7.3 缓存监控

csharp 复制代码
public class CacheMonitor
{
    public CacheStatistics GetStatistics()
    {
        var stats = new CacheStatistics();
        stats.MemoryCacheCount = ResourceManager.ProviderCache.Count;
        stats.MemoryCacheSize = CalculateMemoryCacheSize();
        stats.DiskCacheCount = CacheSystem.CachedFileCount;
        stats.DiskCacheSize = CalculateDiskCacheSize();
        return stats;
    }
}

缓存监控的作用详解:

监控缓存的使用情况,及时发现问题。


八、总结

本章深入解析了 YooAsset 资源缓存机制的源码实现,包括:

  • 缓存机制概述:缓存的作用、类型、策略
  • BundleLoader 缓存:BundleLoader 缓存的作用、实现、策略
  • Provider 缓存:Provider 缓存的作用、实现、策略
  • 磁盘缓存:磁盘缓存的作用、位置、实现、清理
  • 内存缓存与磁盘缓存的配合:配合流程、实现、生命周期
  • 缓存优化:预加载、缓存预热、缓存监控

通过深入理解资源缓存机制,开发者可以优化资源加载速度,提升游戏性能。


上一篇 :引用计数机制 下一篇 :文件系统体系(IFileSystem)

相关推荐
雪庭1 天前
OCCT OCAF 实战
unity3d
SmalBox1 天前
05-07-源码-引用计数与资源生命周期
unity3d·游戏开发
雪庭1 天前
用 OCCT 造一只啤酒瓶:完整实录
unity3d
SmalBox2 天前
05-06-源码-异步操作体系(OperationSystem)
unity3d·游戏开发
侑虎科技2 天前
深度分析屏幕空间反射算法以及在URP中的实现(一)
unity3d
郝学胜-神的一滴3 天前
游戏设计:原型与开发 08:我们到底在为谁做游戏
程序人生·游戏·游戏引擎·游戏开发·游戏策划
SmalBox3 天前
05-05-源码-资源加载与Provider体系
unity3d·游戏开发
SmalBox4 天前
05-04-源码-资源定位系统
unity3d·游戏开发
Mikko75 天前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing