源码-引用计数机制
篇章 :05-源码深度-运行时核心模块 阅读时间 :约 25 分钟 前置知识:了解 Provider 体系
一、引言
本章将深入解析 YooAsset 引用计数机制的源码实现。引用计数是 YooAsset 资源管理的核心机制,用于管理资源的引用关系和生命周期。深入理解引用计数机制是掌握 YooAsset 资源管理的关键。
引用计数机制的设计目标是确保资源在不再使用时及时释放,避免内存泄漏。本章将从源码层面深入解析引用计数机制的实现。
二、引用计数概述
2.1 引用计数的概念
引用计数是一种资源管理技术:
引用计数的定义:
引用计数记录了资源被引用的次数。每次资源被引用时,引用计数加 1;每次引用被释放时,引用计数减 1。
引用计数的作用:
- 跟踪资源的引用关系
- 决定资源的生命周期
- 避免资源过早释放
- 避免内存泄漏
引用计数的特点:
- 实时性:引用计数实时更新
- 准确性:只要遵守规则,引用计数是准确的
- 自动管理:资源生命周期自动管理
2.2 YooAsset 中的引用计数
YooAsset 中的引用计数应用于以下对象:
- Provider:资源提供者
- BundleLoader:Bundle 加载器
- AssetBundle:AssetBundle 实例
Provider 引用计数:
记录资源被引用的次数。引用为 0 时,Provider 销毁。
BundleLoader 引用计数:
记录 Bundle 被引用的次数。引用为 0 时,Bundle 销毁。
AssetBundle 引用计数:
记录 AssetBundle 被 Provider 引用的次数。
2.3 引用计数的实现
csharp
public abstract class RefCountOperation
{
private int _refCount = 0;
public int RefCount => _refCount;
public void IncreaseRefCount()
{
Interlocked.Increment(ref _refCount);
}
public void DecreaseRefCount()
{
Interlocked.Decrement(ref _refCount);
}
}
Interlocked.Increment 详解:
使用 Interlocked.Increment 进行原子操作,确保多线程环境下的引用计数正确性。
Interlocked.Decrement 详解:
使用 Interlocked.Decrement 进行原子操作。
三、Provider 引用计数
3.1 Provider 引用计数的作用
Provider 引用计数记录了资源被引用的次数:
Provider 引用计数的应用场景:
- 同一资源被多个地方引用
- 异步加载时多次调用 LoadAssetAsync
- 长生命周期的对象引用短生命周期的资源
3.2 Provider 引用计数的实现
csharp
public abstract class ProviderBase
{
private int _refCount = 0;
public int RefCount => _refCount;
public void IncreaseRefCount()
{
Interlocked.Increment(ref _refCount);
}
public void DecreaseRefCount()
{
Interlocked.Decrement(ref _refCount);
if (_refCount <= 0)
{
// 引用为 0,销毁 Provider
Destroy();
}
}
}
引用计数增加详解:
LoadAssetAsync 调用时,引用计数加 1。
引用计数减少详解:
UnloadAsset 调用时,引用计数减 1。
销毁触发详解:
引用为 0 时,Provider 自动销毁。
3.3 Provider 引用计数的应用
csharp
// 场景 1:基本用法
var handle = package.LoadAssetAsync<GameObject>("prefab");
Instantiate(handle.AssetObject);
handle.Release(); // 引用 -1,销毁资源
// 场景 2:多次加载同一资源
var handle1 = package.LoadAssetAsync<GameObject>("prefab");
var handle2 = package.LoadAssetAsync<GameObject>("prefab");
// handle1 和 handle2 引用同一 Provider
handle1.Release(); // 引用 -1
handle2.Release(); // 引用 -1,销毁资源
// 场景 3:异步加载中的引用
var handle = package.LoadAssetAsync<GameObject>("prefab");
// 异步加载中,handle 还在等待
await handle.Task;
Instantiate(handle.AssetObject);
handle.Release();
基本用法详解:
LoadAssetAsync 加载资源,Release 释放资源。
多次加载详解:
多次加载同一资源会复用同一 Provider,引用计数累加。
异步加载详解:
异步加载中,引用计数保持,确保资源不会被过早释放。
四、BundleLoader 引用计数
4.1 BundleLoader 引用计数的作用
BundleLoader 引用计数记录了 Bundle 被引用的次数:
BundleLoader 引用计数的应用场景:
- 多个资源引用同一 Bundle
- Provider 加载时引用 Bundle
- Provider 卸载时减少引用
4.2 BundleLoader 引用计数的实现
csharp
public class BundleLoader
{
private int _refCount = 0;
public int RefCount => _refCount;
public void Retain()
{
Interlocked.Increment(ref _refCount);
}
public void Release()
{
Interlocked.Decrement(ref _refCount);
if (_refCount <= 0)
{
UnloadBundle();
}
}
private void UnloadBundle()
{
if (_assetBundle != null)
{
_assetBundle.Unload(true);
_assetBundle = null;
}
}
}
引用计数增加详解:
Provider 加载资源时,会调用 BundleLoader.Retain 增加引用。
引用计数减少详解:
Provider 销毁时,会调用 BundleLoader.Release 减少引用。
Bundle 卸载详解:
引用为 0 时,BundleLoader 自动卸载 AssetBundle。
4.3 BundleLoader 引用计数的应用
csharp
public class ResourceManager
{
public BundleLoader LoadBundle(BundleInfo bundleInfo)
{
string key = bundleInfo.FileName;
if (_bundleLoaders.TryGetValue(key, out var loader))
{
loader.Retain();
return loader;
}
loader = new BundleLoader(bundleInfo);
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);
}
}
}
BundleLoader 获取详解:
LoadBundle 方法获取 BundleLoader,如果已存在则复用,否则创建。
引用计数管理详解:
LoadBundle 增加引用,UnloadBundle 减少引用。
自动卸载详解:
引用为 0 时,BundleLoader 自动从缓存中移除。
五、引用计数与依赖管理
5.1 依赖资源管理
资源之间的依赖关系也需要引用计数管理:
csharp
public class AssetProvider : ProviderBase
{
private List<BundleLoader> _dependLoaders;
public override void Provide()
{
// 加载资源
var assetInfo = Package.GetAssetInfo(Location);
// 加载依赖 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);
}
}
public override void UnProvide()
{
// 释放依赖 Bundle
foreach (var loader in _dependLoaders)
{
Package.ResourceManager.UnloadBundle(loader);
}
_dependLoaders.Clear();
}
}
依赖加载详解:
加载资源时,同时加载所有依赖的 Bundle。
依赖释放详解:
释放资源时,同时释放所有依赖的 Bundle。
引用计数管理详解:
通过 LoadBundle 和 UnloadBundle 管理依赖的引用计数。
5.2 共享 Bundle 管理
共享 Bundle 被多个资源引用,需要特殊处理:
csharp
public class ResourceManager
{
public BundleLoader GetOrLoadBundle(BundleInfo bundleInfo)
{
string key = bundleInfo.FileName;
if (_bundleLoaders.TryGetValue(key, out var loader))
{
// 已存在,复用并增加引用
loader.Retain();
return loader;
}
// 不存在,创建
loader = new BundleLoader(bundleInfo);
loader.LoadAsync();
_bundleLoaders[key] = loader;
loader.Retain();
return loader;
}
}
共享 Bundle 处理详解:
共享 Bundle 通过 BundleLoader 缓存实现共享,避免重复加载。
引用计数作用详解:
引用计数确保共享 Bundle 在所有引用都释放后才卸载。
六、引用计数注意事项
6.1 配对原则
引用计数必须遵守"配对原则":
Load-Release 配对:
每次 Load 必须有对应的 Release。
配对检查:
csharp
public class ProviderBase
{
public void VerifyRefCount()
{
if (RefCount < 0)
{
Debug.LogError($"Provider {Location} ref count is negative!");
}
if (RefCount == 0 && Status != EProviderStatus.Destroyed)
{
Debug.LogWarning($"Provider {Location} has zero ref count but not destroyed!");
}
}
}
调试方法详解:
通过 VerifyRefCount 方法检查引用计数是否正确。
6.2 异步引用计数
异步加载中需要正确管理引用计数:
csharp
// 错误:异步加载中提前释放
var handle = package.LoadAssetAsync<GameObject>("prefab");
handle.Release(); // 错误!异步加载还未完成
Instantiate(handle.AssetObject); // 资源可能已被销毁
// 正确:等待异步加载完成后释放
var handle = package.LoadAssetAsync<GameObject>("prefab");
await handle.Task; // 等待加载完成
Instantiate(handle.AssetObject);
handle.Release(); // 正确释放
异步加载注意事项详解:
异步加载中,引用计数保持,确保资源不会过早释放。
6.3 循环引用
循环引用会导致引用计数永远不为 0:
避免循环引用:
- 资源之间不应该有循环引用
- 使用弱引用避免循环引用
- 定期检查引用计数
循环引用检测:
csharp
public class RefCountCycleDetector
{
public List<ProviderBase> DetectCycles()
{
// 使用图算法检测循环引用
// ...
}
}
七、总结
本章深入解析了 YooAsset 引用计数机制的源码实现,包括:
- 引用计数概述:引用计数的概念、YooAsset 中的应用、实现
- Provider 引用计数:Provider 引用计数的作用、实现、应用
- BundleLoader 引用计数:BundleLoader 引用计数的作用、实现、应用
- 引用计数与依赖管理:依赖资源管理、共享 Bundle 管理
- 引用计数注意事项:配对原则、异步引用、循环引用
引用计数机制是 YooAsset 资源管理的核心,通过深入理解引用计数机制,开发者可以避免内存泄漏,正确管理资源生命周期。
上一篇 :异步体系(OperationSystem) 下一篇 :资源缓存机制