Compare commits

..

10 Commits

Author SHA1 Message Date
何冠峰
5df594f7dc Update CHANGELOG.md 2026-06-02 16:30:21 +08:00
何冠峰
5151bb555f Update package.json 2026-06-02 16:29:36 +08:00
何冠峰
c331dc9b58 perf: guard manifest duplicate-key checks behind UNITY_EDITOR || DEBUG 2026-06-02 16:21:25 +08:00
何冠峰
9d5f1b3aa4 fix: verify cache file against manifest FileCRC/FileSize instead of self-recorded .info values 2026-06-02 16:09:20 +08:00
何冠峰
93a9167100 fix #712
在禁用边玩边下的时候,加载原生文件流程未处理的问题。
2026-06-02 15:53:31 +08:00
何冠峰
54d1593dd2 fix #702
修复解压行为在包体内文件被篡改后,会无限尝试的问题。
2026-06-02 14:37:11 +08:00
何冠峰
a5f4b555e7 fix: resolve UnloadAllAssetsOperation deadlock when releasing zero-ref pending providers 2026-06-02 14:21:18 +08:00
何冠峰
20ae423bbe fix #734 2026-06-02 10:53:35 +08:00
何冠峰
d712a00458 Update .gitignore 2026-05-09 17:06:55 +08:00
何冠峰
c14244b2cf Update copyright year for TuYoo Games 2026-04-21 10:15:21 +08:00
13 changed files with 120 additions and 76 deletions

5
.gitignore vendored
View File

@@ -15,7 +15,9 @@
/Bundles/
/ProjectSettings/
/App/
/yoo/
/yoo/
/Assets/Docs
/Assets/Docs.meta
/Assets/StreamingAssets
/Assets/StreamingAssets.meta
/Assets/Samples
@@ -70,6 +72,7 @@ sysinfo.txt
# Builds
*.apk
*.unitypackage
*.zip
# Crashlytics generated file
crashlytics-build.properties

View File

@@ -2,6 +2,20 @@
All notable changes to this package will be documented in this file.
## [2.3.19] - 2026-06-02
### Fixed
- (#734) 修复了强制回收所有资源与自动卸载Bundle冲突。
- (#712) 修复了在禁用边玩边下的时候,加载原生文件流程未处理的问题。
- (#702) 修复了解压行为在包体内文件被篡改后,会无限尝试的问题。
- 修复了释放零引用待处理 Provider 时 UnloadAllAssetsOperation 死锁的问题。
- 修复了缓存文件校验改用清单的 FileCRC/FileSize而非来自 .info 文件的自记录值。
### Improvements
- 优化了资源清单重名键校验仅在 UNITY_EDITOR || DEBUG 下执行。
## [2.3.18] - 2025-12-04
### Fixed

View File

@@ -73,10 +73,8 @@ namespace YooAsset
/// <summary>
/// 自定义参数:初始化的时候缓存文件校验最大并发数
/// 默认值32推荐范围 1-128
/// 说明:过大的值可能导致线程池任务过多,影响系统稳定性
/// </summary>
public int FileVerifyMaxConcurrency { private set; get; } = 32;
public int FileVerifyMaxConcurrency { private set; get; } = int.MaxValue;
/// <summary>
/// 自定义参数:数据文件追加文件格式
@@ -90,17 +88,13 @@ namespace YooAsset
/// <summary>
/// 自定义参数:最大并发连接数
/// 默认值10推荐范围 1-32
/// 说明:过大的并发数可能被服务器限流,也会增加本地资源消耗
/// </summary>
public int DownloadMaxConcurrency { private set; get; } = 10;
public int DownloadMaxConcurrency { private set; get; } = int.MaxValue;
/// <summary>
/// 自定义参数:每帧发起的最大请求数
/// 默认值5推荐范围 1-10
/// 说明:避免单帧发起过多请求导致卡顿
/// </summary>
public int DownloadMaxRequestPerFrame { private set; get; } = 5;
public int DownloadMaxRequestPerFrame { private set; get; } = int.MaxValue;
/// <summary>
/// 自定义参数:下载任务的看门狗机制监控时间
@@ -248,14 +242,7 @@ namespace YooAsset
else if (name == FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY)
{
int convertValue = Convert.ToInt32(value);
if (convertValue > 256)
{
YooLogger.Warning($"FILE_VERIFY_MAX_CONCURRENCY value {convertValue} is too large, clamped to 256. Recommended range: 1 - 128.");
}
// 限制在合理范围内1-256
// 超过 256 的并发数对于文件验证来说没有意义,反而会增加线程池压力
FileVerifyMaxConcurrency = Mathf.Clamp(convertValue, 1, 256);
FileVerifyMaxConcurrency = Mathf.Clamp(convertValue, 1, int.MaxValue);
}
else if (name == FileSystemParametersDefine.APPEND_FILE_EXTENSION)
{
@@ -268,22 +255,12 @@ namespace YooAsset
else if (name == FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY)
{
int convertValue = Convert.ToInt32(value);
if (convertValue > 64)
{
YooLogger.Warning($"DOWNLOAD_MAX_CONCURRENCY value {convertValue} is too large, clamped to 64. Recommended range: 1 - 32.");
}
DownloadMaxConcurrency = Mathf.Clamp(convertValue, 1, 64);
DownloadMaxConcurrency = Mathf.Clamp(convertValue, 1, int.MaxValue);
}
else if (name == FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME)
{
int convertValue = Convert.ToInt32(value);
if (convertValue > 20)
{
YooLogger.Warning($"DOWNLOAD_MAX_REQUEST_PER_FRAME value {convertValue} is too large, clamped to 20. Recommended range: 1 - 10.");
}
DownloadMaxRequestPerFrame = Mathf.Clamp(convertValue, 1, 20);
DownloadMaxRequestPerFrame = Mathf.Clamp(convertValue, 1, int.MaxValue);
}
else if (name == FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME)
{
@@ -492,7 +469,9 @@ namespace YooAsset
if (_records.TryGetValue(bundle.BundleGUID, out RecordFileElement element) == false)
return EFileVerifyResult.CacheNotFound;
EFileVerifyResult result = FileVerifyHelper.FileVerify(element.DataFilePath, element.DataFileSize, element.DataFileCRC, EFileVerifyLevel.High);
// 注意:使用清单里的权威校验值(与下载/解压时的校验基准一致),而不是缓存记录里来源于本地 .info 文件的自描述值,
// 否则数据文件与 .info 文件被一并篡改时仍会校验通过。
EFileVerifyResult result = FileVerifyHelper.FileVerify(element.DataFilePath, bundle.FileSize, bundle.FileCRC, EFileVerifyLevel.High);
return result;
}
public bool WriteCacheBundleFile(PackageBundle bundle, string copyPath)

View File

@@ -336,7 +336,17 @@ namespace YooAsset
}
else
{
_steps = ESteps.DownloadFile;
if (_fileSystem.DisableOnDemandDownload)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The bundle not cached : {_bundle.BundleName}";
YooLogger.Warning(Error);
}
else
{
_steps = ESteps.DownloadFile;
}
}
}

View File

@@ -95,7 +95,8 @@ namespace YooAsset
}
else
{
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
// 注意:本地文件(解压/导入)校验失败属于确定性失败,重试只会重复读取同一文件,直接判定失败防止无限重试。
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0 && _unityDownloadFileOp.VerifyFailed == false)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {_unityDownloadFileOp.URL} Try again !");

View File

@@ -27,6 +27,12 @@ namespace YooAsset
/// </summary>
public int RefCount { private set; get; }
/// <summary>
/// 文件校验是否失败
/// 注意:本地文件(解压/导入)校验失败属于确定性失败,重试无意义,上层不应重试。
/// </summary>
public bool VerifyFailed { protected set; get; } = false;
internal UnityDownloadFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
{
_fileSystem = fileSystem;

View File

@@ -133,6 +133,8 @@ namespace YooAsset
}
else
{
// 注意:本地内置文件被篡改或损坏时校验会失败,重试只会重复读取同一文件,因此标记为不可重试。
VerifyFailed = true;
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyOperation.Error;

View File

@@ -188,19 +188,11 @@ namespace YooAsset
// 结束记录
DebugEndRecording();
try
{
_callback?.Invoke(this);
}
catch (Exception ex)
{
YooLogger.Error($"Exception in completion callback: {ex}");
}
finally
{
if (_taskCompletionSource != null)
_taskCompletionSource.TrySetResult(null);
}
//注意如果完成回调内发生异常会导致Task无限期等待
_callback?.Invoke(this);
if (_taskCompletionSource != null)
_taskCompletionSource.TrySetResult(null);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace YooAsset
@@ -24,6 +25,7 @@ namespace YooAsset
CheckOptions,
ReleaseAll,
TryAbortLoader,
RequestForceDestroy,
CheckLoading,
DestroyAll,
Done,
@@ -72,7 +74,10 @@ namespace YooAsset
// 释放所有资源句柄
if (_options.ReleaseAllHandles)
{
foreach (var provider in _resManager.ProviderDic.Values)
// 注意创建快照因为释放句柄可能触发自动卸载逻辑AutoUnloadBundleWhenUnused
// 进而修改 ProviderDic 容器,导致遍历时抛出 InvalidOperationException。
var snapshot = new List<ProviderOperation>(_resManager.ProviderDic.Values);
foreach (var provider in snapshot)
{
provider.ReleaseAllHandles();
}
@@ -89,6 +94,17 @@ namespace YooAsset
{
loader.TryAbortLoader();
}
_steps = ESteps.RequestForceDestroy;
}
if (_steps == ESteps.RequestForceDestroy)
{
// 向所有资源提供者下发强制销毁请求
// 注意:防止零引用且尚未进入加载阶段的任务被无限挂起,从而导致 CheckLoading 死锁。
foreach (var provider in _resManager.ProviderDic.Values)
{
provider.RequestForceDestroy();
}
_steps = ESteps.CheckLoading;
}

View File

@@ -67,6 +67,11 @@ namespace YooAsset
/// </summary>
public bool IsDestroyed { private set; get; } = false;
/// <summary>
/// 是否已收到强制销毁请求
/// </summary>
public bool ForceDestroyRequested { private set; get; } = false;
/// <summary>
/// 加载任务是否进行中
/// </summary>
@@ -119,6 +124,18 @@ namespace YooAsset
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 注意:收到强制销毁请求时,未在加载中的任务立即结束,防止零引用任务被无限挂起导致卸载流程死锁!
if (ForceDestroyRequested)
{
if (IsLoading == false)
{
InvokeCompletion("Provider force destroyed during unload all assets !", EOperationStatus.Failed);
return;
}
// 注意:已进入加载阶段则继续等待自然完成
}
// 注意:未在加载中的任务可以挂起!
if (IsLoading == false)
{
@@ -199,11 +216,24 @@ namespace YooAsset
}
protected abstract void ProcessBundleResult();
/// <summary>
/// 请求强制销毁
/// 注意:用于卸载流程,标记后未在加载中的任务会在下一次更新时立即结束。
/// </summary>
public void RequestForceDestroy()
{
ForceDestroyRequested = true;
}
/// <summary>
/// 销毁资源提供者
/// 注意:该方法是幂等的,重复调用不会重复释放资源。
/// </summary>
public void DestroyProvider()
{
if (IsDestroyed)
return;
IsDestroyed = true;
// 检测是否为正常销毁
@@ -269,7 +299,6 @@ namespace YooAsset
if (_resManager.UseWeakReferenceHandle)
{
// TODO 高危风险:如果移除弱引用失败,会导致资源永远无法释放。
if (RemoveWeakReference(handle) == false)
throw new System.Exception("Should never get here !");
}
@@ -336,18 +365,11 @@ namespace YooAsset
List<WeakReference<HandleBase>> tempers = _weakReferences.ToList();
foreach (var weakRef in tempers)
{
if (weakRef.TryGetTarget(out HandleBase handle))
if (weakRef.TryGetTarget(out HandleBase target))
{
if (handle.IsValid)
if (target.IsValid)
{
try
{
handle.InvokeCallback();
}
catch (Exception ex)
{
YooLogger.Error($"Exception in completion callback: {ex}");
}
target.InvokeCallback();
}
}
}
@@ -359,14 +381,7 @@ namespace YooAsset
{
if (handle.IsValid)
{
try
{
handle.InvokeCallback();
}
catch (Exception ex)
{
YooLogger.Error($"Exception in completion callback: {ex}");
}
handle.InvokeCallback();
}
}
}

View File

@@ -264,23 +264,27 @@ namespace YooAsset
manifest.AssetList.Add(packageAsset);
// 注意:我们不允许原始路径存在重名
// 说明:清单是构建期生成的可信数据,重名只会因构建错误产生,仅在开发期校验即可,发行版省去一次哈希查找。
string assetPath = packageAsset.AssetPath;
#if UNITY_EDITOR || DEBUG
if (manifest.AssetDic.ContainsKey(assetPath))
throw new System.Exception($"AssetPath have existed : {assetPath}");
else
manifest.AssetDic.Add(assetPath, packageAsset);
#endif
manifest.AssetDic.Add(assetPath, packageAsset);
// 填充AssetPathMapping1
{
string location = packageAsset.AssetPath;
// 添加原生路径的映射
#if UNITY_EDITOR || DEBUG
if (manifest.AssetPathMapping1.ContainsKey(location))
throw new System.Exception($"Location have existed : {location}");
else
manifest.AssetPathMapping1.Add(location, packageAsset.AssetPath);
#endif
manifest.AssetPathMapping1.Add(location, packageAsset.AssetPath);
// 添加无后缀名路径的映射
// 注意:无后缀名重名属于合法运行时情况(仅警告并跳过),因此该校验必须在发行版也执行。
if (manifest.SupportExtensionless)
{
string locationWithoutExtension = Path.ChangeExtension(location, null);
@@ -297,10 +301,11 @@ namespace YooAsset
// 填充AssetPathMapping2
if (manifest.IncludeAssetGUID)
{
#if UNITY_EDITOR || DEBUG
if (manifest.AssetPathMapping2.ContainsKey(packageAsset.AssetGUID))
throw new System.Exception($"AssetGUID have existed : {packageAsset.AssetGUID}");
else
manifest.AssetPathMapping2.Add(packageAsset.AssetGUID, packageAsset.AssetPath);
#endif
manifest.AssetPathMapping2.Add(packageAsset.AssetGUID, packageAsset.AssetPath);
}
// 添加可寻址地址
@@ -309,10 +314,11 @@ namespace YooAsset
string location = packageAsset.Address;
if (string.IsNullOrEmpty(location) == false)
{
#if UNITY_EDITOR || DEBUG
if (manifest.AssetPathMapping1.ContainsKey(location))
throw new System.Exception($"Location have existed : {location}");
else
manifest.AssetPathMapping1.Add(location, packageAsset.AssetPath);
#endif
manifest.AssetPathMapping1.Add(location, packageAsset.AssetPath);
}
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "com.tuyoogame.yooasset",
"displayName": "YooAsset",
"version": "2.3.18",
"version": "2.3.19",
"unity": "2019.4",
"description": "unity3d resources management system.",
"author": {

View File

@@ -187,7 +187,7 @@
identification within third-party archives.
Copyright 2018-2021 何冠峰
Copyright 2021-2025 TuYoo Games
Copyright 2021-2026 TuYoo Games
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.