当前位置: 首页 > news >正文

MyFramework:ResourceRef 资源引用凭证设计

ResourceManager加载资源时,没有直接把UnityEngine.Object返回给业务层,而是返回一个ResourceRef<T>

这个类很小,但它承担了资源引用生命周期管理的核心逻辑。


一、代码

ResourceRef<T>的实现如下:

public class ResourceRef<T> : ClassObject where T : UObject { protected T mResource; // 引用的资源 protected long mToken; // 引用凭证,一般不允许外部直接访问 public override void resetProperty() { base.resetProperty(); mResource = null; mToken = 0; } public void setResource(T res) { mResource = res; if (mResource == null) { logError("resource is null"); return; } mToken = mResourceManager.addReference(mResource); } public bool isValid() { return mResource != null; } public T getResource() { return mResource; } public long getToken() { return mToken; } // 在UN_CLASS时自动被调用 public override void destroy() { base.destroy(); if (mResource == null) { logError("resource is null"); return; } mResourceManager.removeReference(mResource, ref mToken); } // 对当前资源新创建一个引用对象出来,用于使多个地方对同一个资源拥有生命周期所有权 public ResourceRef<T> copyRef() { CLASS(out ResourceRef<T> newObjRef).setResource(mResource); return newObjRef; } }

业务层拿到的不是裸资源,而是:

ResourceRef<Texture> refTex;

真正使用时再取资源:

Texture tex = refTex.getResource();

释放时也不是直接卸载资源,而是释放引用对象:

mResourceManager.unload(ref refTex);

二、加载时加引用

同步加载资源时,ResourceManager会先加载出真实资源:

T res = mAssetBundleLoader.loadAsset<T>(name);

资源不为空时,再创建ResourceRef<T>

CLASS(out ResourceRef<T> resRef).setResource(res); return resRef;

setResource()里会调用:

mToken = mResourceManager.addReference(mResource);

也就是说,只要业务层拿到一个ResourceRef<T>,资源系统内部就会增加一份引用凭证。


三、token 不是简单计数

ResourceManager中不是只保存一个整数引用计数,而是保存 token 集合:

protected Dictionary<int, HashSet<long>> mReferenceTokenList = new(); protected Dictionary<int, UObject> mInstanceIDToUObject = new();

增加引用时:

public long addReference(UObject res) { long token = ++mTokenSeed; int instanceID = res.GetInstanceID(); mInstanceIDToUObject.TryAdd(instanceID, res); if (!mReferenceTokenList.getOrAddNew(instanceID).Add(token)) { logError("添加资源引用凭证失败:" + token); } return token; }

移除引用时:

public void removeReference(UObject res, ref long token) { if (!mReferenceTokenList.TryGetValue(res.GetInstanceID(), out var list) || !list.Remove(token)) { logError("移除资源引用凭证失败,可能是重复移除一个资源:" + token); } token = 0; }

这里的关键是:

每个 ResourceRef 都有自己的 token 同一个资源可以有多个 token 释放时只移除当前 ResourceRef 的 token token 清空后可以检测重复释放

如果只用一个整数引用计数,重复释放很难定位。

使用 token 后,如果同一个ResourceRef被重复释放,第二次移除 token 就会失败,并打印错误。


四、tokenSeed 放在 ResourceManager

mTokenSeed放在ResourceManager中:

protected static long mTokenSeed;

代码注释里写得很清楚:

不能放在 ResourceRef<T> 中, 因为每个模板类型都有一个静态变量, 这样就不能保证同一个资源的引用凭证在不同模板类型中是唯一的。

这是一个容易忽略的细节。

如果写成:

public class ResourceRef<T> { private static long mTokenSeed; }

那么这些类型会各自有一份静态变量:

ResourceRef<Texture>.mTokenSeed ResourceRef<Sprite>.mTokenSeed ResourceRef<UObject>.mTokenSeed

同一个资源可能通过不同泛型类型包装。

如果 token 分散在不同泛型类里生成,就可能出现重复 token。

所以 token 生成必须放在统一的ResourceManager中。


五、为什么用 InstanceID

引用表的 Key 使用的是:

res.GetInstanceID()

不是直接用UnityEngine.Object

代码注释说明了原因:

UObject 重载了 ==, 外部卸载 UObject 后可能出现 GetHashCode 不变, 但引用资源为空的问题, 所以使用 GetInstanceID 作为 Key。

Unity 的Object和普通 C# 对象不完全一样。

它有自己的生命周期。

资源被 Unity 销毁后,C# 引用还可能存在,但== null的行为已经被 Unity 重载。

如果直接拿UObject当 Dictionary Key,后续判断会变得不稳定。

GetInstanceID()做索引,逻辑更明确。


六、释放时不立刻卸载

释放ResourceRef<T>时,只是移除 token。

mResourceManager.removeReference(mResource, ref mToken);

真正卸载资源不是在这里立即完成。

ResourceManager.update()会定时检查引用表:

protected const float CHECK_REF_INTERVAL = 3.0f;

检查逻辑是:

foreach (var item in mReferenceTokenList) { if (item.Value.isEmpty()) { if (willRemoveList == null) { LIST(out willRemoveList); } willRemoveList.add(item.Key); } }

发现某个资源的 token 集合为空后,再统一卸载:

foreach (int id in willRemoveList) { mInstanceIDToUObject.Remove(id, out UObject item); mReferenceTokenList.Remove(id); unloadInternal(item); }

这样做有两个好处:

资源释放和真实卸载解耦 避免同一帧频繁加载和卸载

业务层只负责释放引用。

资源系统决定什么时候真正卸载资源。


七、copyRef 的意义

ResourceRef<T>提供了:

public ResourceRef<T> copyRef() { CLASS(out ResourceRef<T> newObjRef).setResource(mResource); return newObjRef; }

它不是简单复制对象引用。

它会创建一个新的ResourceRef<T>,并重新调用setResource()

这意味着:

同一个资源 新的 ResourceRef 新的 token 独立生命周期

适合这种情况:

一个资源加载后,需要交给多个模块使用 每个模块都应该独立释放自己的引用 最后一个引用释放后,资源才允许卸载

如果只是把同一个ResourceRef<T>传给多个地方,就会出现所有权不清楚的问题。

一个模块释放后,其他模块可能还在使用。

copyRef()让多个持有者拥有独立引用。


八、不是裸资源所有权

如果业务层直接拿TextureSpritePrefab,资源系统无法知道谁还在使用它。

ResourceRef<T>的作用是把资源使用权显式化。

拿到 ResourceRef 表示持有一份资源引用 释放 ResourceRef 表示归还这份资源引用

资源本体可以被多个地方共享。

引用凭证属于每个持有者。

这个设计比裸传资源更适合框架统一管理资源生命周期。


九、和对象池配合

ResourceRef<T>继承自ClassObject

它本身也是池化对象。

创建时:

CLASS(out ResourceRef<T> resRef)

释放时:

UN_CLASS(ref res);

释放过程会走:

destroy ↓ removeReference ↓ resetProperty ↓ 回收到 ClassPool

destroy()负责移除资源引用。

resetProperty()负责清空自身字段。

这和 MyFramework 的对象池规则保持一致。


十、精巧点

ResourceRef<T>精巧的地方主要有四个。

1. 引用不是 int,而是 token 集合

可以检测重复释放,也能让每个持有者有独立凭证。

2. tokenSeed 不放在泛型类中

避免不同ResourceRef<T>类型各自产生重复 token。

3. 使用 InstanceID 追踪 Unity Object

避免 UnityObject重载==后带来的 Dictionary Key 问题。

4. copyRef 创建独立引用

同一个资源可以交给多个模块使用,每个模块释放自己的引用。


总结

ResourceRef<T>的设计不是简单包一层资源对象。

它解决的是资源所有权问题。

核心流程是:

加载资源 ↓ 创建 ResourceRef ↓ ResourceManager 生成 token ↓ 业务层持有 ResourceRef ↓ 释放 ResourceRef ↓ 移除 token ↓ 所有 token 清空后资源进入卸载流程

这个设计让资源生命周期从“谁拿着裸对象”变成“谁持有引用凭证”。

在 MyFramework 这种同时支持 AssetDatabase、AssetBundle、异步加载、子资源、下载和卸载的资源系统里,这层引用凭证非常关键。

http://www.gsyq.cn/news/1568624.html

相关文章:

  • 2026年6月最新浪琴中国官方售后服务电话客服网点地址一览 - 浪琴服务中心
  • SQL注入攻防实战:从“明小子”到现代检测与防御体系
  • i.MX35平台WinCE 6.0 NAND Flash驱动移植实战指南
  • CentOS 6下WordPress稳定部署指南:nginx+PHP-FPM+SELinux深度适配
  • i.MX 6SoloX数据手册修订解析:工业硬件设计的避坑指南
  • Python+Pytest+Selenium+Allure:构建企业级Web自动化测试框架实战
  • Motorola蓝牙开发套件实战:从环境搭建到协议栈移植全解析
  • Rails后台任务实战:Sidekiq+Redis高可用部署与压测调优
  • 分享一些在 AI 解析中常见的问题,以及工具区别
  • 终极指南:3分钟彻底修复Visual C++运行库缺失问题
  • 南京宠物店打卡,梦宠山庄现场看宠记录 - 园友3800037
  • Windows热键侦探:揭秘快捷键冲突的终极解决方案
  • MC68HC908JW32 USB开发实战:从控制传输到HID/CDC设备实现
  • 2026家用车换电瓶避坑指南,慈溪换汽车电瓶别再花冤枉钱!开发大道西路骆驼蓄电池批发门店,全品牌正品平价更换 - 速递信息
  • AI知识图谱实战:让AI真正理解FAB的工艺流程,异常根因分析准确率提升3倍
  • 实战赋能 + 技术自研双硬核|2026上海本地 GEO 优化公司 TOP5 甄选与实力评测 - 936品牌测评网
  • 2026年国内Ozon选品工具赛道观察:巽迈网络科技爆单AI选品助手给出跨境电商工具+陪跑一体化标准答案 - 速递信息
  • 2026广安装修公司哪家靠谱 5家本土正规企业实力梳理 - 速递信息
  • 2026上海变速箱维修|正规专修厂权威推荐,激速变速箱维修稳居行业第一 - 速递信息
  • Linux动态壁纸引擎完整指南:在Linux上运行Steam创意工坊壁纸的终极方案
  • 女生入门吉他2026精选|4款低弦距好琴推荐,手小腕弱照样学得快
  • 连续时间系统信号时序逻辑韧性量化:从STL规范到最大可容忍扰动计算
  • 2026北京房产律师推荐:专业律所助您守护房产权益 - 产业观察网
  • 黄山学院交通方便吗?离市区远不远?周边有没有地铁、公交站? - 寻茫精选
  • 云吞连锁靠谱的公司 - 速递信息
  • 寄包裹省钱,快递折扣平台对比实测:选哪个好? - 快递物流资讯
  • 天津廊坊保定彩钢瓦防水优选!东莞宝绿榕三城驻点施工,免费上门勘测报价 - 速递信息
  • 如何用biliTickerBuy抢票神器轻松搞定B站会员购抢票:面向新手的终极指南
  • 嵌入式Linux中的按键中断控制
  • 义乌青阳路西福变速箱专修,19 年连锁老店,全车型变速箱一站式维修 联系电话:13735634594 地址:义乌青口工业区青阳路179号 - 速递信息