05-07-源码-引用计数与资源生命周期

源码-引用计数机制

篇章: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 引用计数的实现

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 引用计数的实现

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 引用计数的应用

// 场景 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 引用计数的实现

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 引用计数的应用

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 依赖资源管理

资源之间的依赖关系也需要引用计数管理:

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 被多个资源引用,需要特殊处理:

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。

配对检查:

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 异步引用计数

异步加载中需要正确管理引用计数:

// 错误:异步加载中提前释放
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:

避免循环引用:

  • 资源之间不应该有循环引用
  • 使用弱引用避免循环引用
  • 定期检查引用计数

循环引用检测:

public class RefCountCycleDetector
{
    public List<ProviderBase> DetectCycles()
    {
        // 使用图算法检测循环引用
        // ...
    }
}

七、总结

本章深入解析了 YooAsset 引用计数机制的源码实现,包括:

  • 引用计数概述:引用计数的概念、YooAsset 中的应用、实现
  • Provider 引用计数:Provider 引用计数的作用、实现、应用
  • BundleLoader 引用计数:BundleLoader 引用计数的作用、实现、应用
  • 引用计数与依赖管理:依赖资源管理、共享 Bundle 管理
  • 引用计数注意事项:配对原则、异步引用、循环引用

引用计数机制是 YooAsset 资源管理的核心,通过深入理解引用计数机制,开发者可以避免内存泄漏,正确管理资源生命周期。


上一篇:异步体系(OperationSystem)
下一篇:资源缓存机制

posted @ 2026-10-24 13:24  SmalBox  阅读(13)  评论(0)    收藏  举报