Compare commits

...

9 Commits

Author SHA1 Message Date
何冠峰
c8c74b8c20 refactor : 重构资源包裹模块 2026-01-13 19:11:49 +08:00
何冠峰
b3d024743c update space shooter 2026-01-13 17:09:18 +08:00
何冠峰
246a62a675 refactor : 重构异步操作模块 2026-01-13 17:01:15 +08:00
何冠峰
354ca5197f refactor : 重构异步操作模块 2026-01-13 14:55:31 +08:00
何冠峰
ce4d6911db refactor : 重构异步操作模块 2026-01-13 12:08:02 +08:00
何冠峰
b796b1a44e refactor : 重构异步操作模块 2026-01-13 11:23:50 +08:00
何冠峰
7198e639d9 refactor : 重构异步操作模块 2026-01-12 16:10:24 +08:00
何冠峰
294fa18fec refactor : 重构异步操作模块 2026-01-12 15:35:00 +08:00
何冠峰
a3f689d815 refactor : 重构异步操作模块 2026-01-12 11:09:27 +08:00
54 changed files with 864 additions and 804 deletions

View File

@@ -255,12 +255,7 @@ namespace YooAsset
if (_watchdogAborted)
return;
#if UNITY_2020_3_OR_NEWER
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartupAsDouble;
#else
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartup;
#endif
double realtimeSinceStartup = TimeUtility.RealtimeSinceStartup;
if (DownloadedBytes != _lastDownloadBytes)
{
_lastDownloadBytes = DownloadedBytes;

View File

@@ -87,7 +87,7 @@ namespace YooAsset
if (Status == EDownloadRequestStatus.None)
{
Status = EDownloadRequestStatus.Running;
_lastUpdateTime = GetUnityEngineRealtime();
_lastUpdateTime = TimeUtility.RealtimeSinceStartup;
}
}
@@ -99,7 +99,7 @@ namespace YooAsset
if (Status != EDownloadRequestStatus.Running)
return;
double currentTime = GetUnityEngineRealtime();
double currentTime = TimeUtility.RealtimeSinceStartup;
double deltaTime = currentTime - _lastUpdateTime;
_lastUpdateTime = currentTime;
@@ -137,14 +137,5 @@ namespace YooAsset
public void Dispose()
{
}
private double GetUnityEngineRealtime()
{
#if UNITY_2020_3_OR_NEWER
return UnityEngine.Time.realtimeSinceStartupAsDouble;
#else
return UnityEngine.Time.realtimeSinceStartup;
#endif
}
}
}

View File

@@ -2,100 +2,6 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 下载器结束
/// </summary>
public struct DownloaderFinishData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 是否成功
/// </summary>
public bool Succeed;
}
/// <summary>
/// 下载器相关的更新数据
/// </summary>
public struct DownloadUpdateData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载进度 (0-1f)
/// </summary>
public float Progress;
/// <summary>
/// 下载文件总数
/// </summary>
public int TotalDownloadCount;
/// <summary>
/// 当前完成的下载文件数量
/// </summary>
public int CurrentDownloadCount;
/// <summary>
/// 下载数据总大小(单位:字节)
/// </summary>
public long TotalDownloadBytes;
/// <summary>
/// 当前完成的下载数据大小(单位:字节)
/// </summary>
public long CurrentDownloadBytes;
}
/// <summary>
/// 下载器相关的错误数据
/// </summary>
public struct DownloadErrorData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载失败的文件名称
/// </summary>
public string FileName;
/// <summary>
/// 错误信息
/// </summary>
public string ErrorInfo;
}
/// <summary>
/// 下载器相关的文件数据
/// </summary>
public struct DownloadFileData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载的文件名称
/// </summary>
public string FileName;
/// <summary>
/// 下载的文件大小
/// </summary>
public long FileSize;
}
/// <summary>
/// 导入文件的信息
/// </summary>

View File

@@ -41,20 +41,20 @@ DownloadSystem 的职责是提供“可替换后端 + 统一请求接口 + 轮
```
┌─────────────────────────────────────────────────────────┐
│ 上层调用者 │
│ (FileSystem / ResourceManager)
│ 上层调用者
│ (FileSystem / ResourceManager) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ IDownloadBackend
│ IDownloadBackend │
│ (后端接口) │
│ 定义网络库合约,工厂模式创建请求 │
│ 定义网络库合约,工厂模式创建请求
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ IDownloadRequest
│ IDownloadRequest │
│ (请求接口) │
│ 轮询式生命周期管理,状态机驱动 │
│ 轮询式生命周期管理,状态机驱动
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
@@ -244,16 +244,6 @@ public struct DownloadSimulateRequestArgs
}
```
### 回调数据结构体
| 结构体 | 用途 | 关键字段 |
|--------|------|----------|
| `DownloaderFinishData` | 下载完成回调 | `PackageName`, `Succeed` |
| `DownloadUpdateData` | 进度更新回调 | `Progress`, `TotalDownloadBytes`, `CurrentDownloadBytes` |
| `DownloadErrorData` | 下载错误回调 | `FileName`, `ErrorInfo` |
| `DownloadFileData` | 文件完成回调 | `FileName`, `FileSize` |
| `ImportFileInfo` | 导入文件元数据 | `FilePath`, `BundleName`, `BundleGUID` |
---
## 核心类说明

View File

@@ -108,14 +108,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -108,14 +108,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -107,7 +107,7 @@ namespace YooAsset
internal override void InternalWaitForAsyncComplete()
{
//注意:场景加载不支持异步转同步,为了支持同步加载方法需要实现该方法!
InternalUpdate();
RunOnceExecution();
}
public override void UnSuspendLoad()
{

View File

@@ -108,14 +108,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -109,14 +109,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -88,14 +88,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -113,7 +113,7 @@ namespace YooAsset
internal override void InternalWaitForAsyncComplete()
{
//注意:场景加载不支持异步转同步,为了支持同步加载方法需要实现该方法!
InternalUpdate();
RunOnceExecution();
}
public override void UnSuspendLoad()
{

View File

@@ -100,14 +100,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -131,14 +131,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
@@ -204,14 +197,141 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
RunBatchExecution();
}
}
#if TUANJIE_1_7_OR_NEWER
/// <summary>
/// 加载团结文件
/// </summary>
internal class DBFSLoadInstantBundleOperation : FSLoadBundleOperation
{
private enum ESteps
{
None,
LoadInstantBundle,
CheckResult,
Done,
}
private readonly DefaultBuildinFileSystem _fileSystem;
private readonly PackageBundle _bundle;
private AssetBundleCreateRequest _createRequest;
private AssetBundle _assetBundle;
private Stream _managedStream;
private ESteps _steps = ESteps.None;
internal DBFSLoadInstantBundleOperation(DefaultBuildinFileSystem fileSystem, PackageBundle bundle)
{
_fileSystem = fileSystem;
_bundle = bundle;
}
internal override void InternalStart()
{
DownloadProgress = 1f;
DownloadedBytes = _bundle.FileSize;
_steps = ESteps.LoadInstantBundle;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.LoadInstantBundle)
{
if (ExecuteWhileDone())
if (_bundle.Encrypted)
{
if (_fileSystem.DecryptionServices == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The {nameof(IDecryptionServices)} is null !";
YooLogger.Error(Error);
return;
}
}
if (IsWaitForAsyncComplete)
{
if (_bundle.Encrypted)
{
var decryptResult = _fileSystem.LoadEncryptedAssetBundle(_bundle);
_assetBundle = decryptResult.Result;
_managedStream = decryptResult.ManagedStream;
}
else
{
string filePath = _fileSystem.GetBuildinFileLoadPath(_bundle);
_assetBundle = AssetBundle.LoadFromFile(filePath);
}
}
else
{
if (_bundle.Encrypted)
{
var decryptResult = _fileSystem.LoadEncryptedAssetBundleAsync(_bundle);
_createRequest = decryptResult.CreateRequest;
_managedStream = decryptResult.ManagedStream;
}
else
{
string filePath = _fileSystem.GetBuildinFileLoadPath(_bundle);
_createRequest = AssetBundle.LoadFromFileAsync(filePath);
}
}
_steps = ESteps.CheckResult;
}
if (_steps == ESteps.CheckResult)
{
if (_createRequest != null)
{
if (IsWaitForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
_assetBundle = _createRequest.assetBundle;
}
else
{
if (_createRequest.isDone == false)
return;
_assetBundle = _createRequest.assetBundle;
}
}
if (_assetBundle == null)
{
if (_bundle.Encrypted)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load encrypted buildin asset bundle file : {_bundle.BundleName}";
YooLogger.Error(Error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load buildin asset bundle file : {_bundle.BundleName}";
YooLogger.Error(Error);
}
}
else
{
_steps = ESteps.Done;
break;
Result = new AssetBundleResult(_fileSystem, _bundle, _assetBundle, _managedStream);
Status = EOperationStatus.Succeed;
}
}
}
internal override void InternalWaitForAsyncComplete()
{
RunBatchExecution();
}
}
#endif
}

View File

@@ -262,14 +262,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
@@ -419,14 +412,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -124,14 +124,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
internal override void InternalAbort()
{

View File

@@ -98,6 +98,10 @@ namespace YooAsset
// 检测下载结果
if (_steps == ESteps.CheckRequest)
{
//TODO 更新下载后台,防止无限挂起
if (IsWaitForAsyncComplete)
_fileSystem.DownloadBackend.Update();
DownloadProgress = _request.DownloadProgress;
DownloadedBytes = _request.DownloadedBytes;
Progress = DownloadProgress;
@@ -177,23 +181,12 @@ namespace YooAsset
internal override void InternalAbort()
{
if (_request != null)
_request.AbortRequest();
_request.Dispose();
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
//TODO 更新下载后台,防止无限挂起
_fileSystem.DownloadBackend.Update();
//TODO 等待导入或解压本地文件完毕,该操作会挂起主线程!
InternalUpdate();
if (IsDone)
break;
//TODO 短暂休眠避免完全卡死
System.Threading.Thread.Sleep(1);
}
//TODO 等待导入或解压本地文件完毕,该操作会挂起主线程!
RunUntilCompletion();
}
}
}

View File

@@ -144,7 +144,7 @@ namespace YooAsset
internal override void InternalAbort()
{
if (_request != null)
_request.AbortRequest();
_request.Dispose();
}
internal override void InternalWaitForAsyncComplete()
{

View File

@@ -119,23 +119,16 @@ namespace YooAsset
}
/// <summary>
/// 中止所有下载任务
/// </summary>
public void AbortAll()
{
foreach (var valuePair in _downloaders)
{
valuePair.Value.AbortOperation();
}
_downloaders.Clear();
}
/// <summary>
/// 释放资源
/// 释放下载资源
/// </summary>
public void Dispose()
{
AbortAll();
foreach (var valuePair in _downloaders)
{
var operation = valuePair.Value;
operation.AbortOperation();
}
_downloaders.Clear();
}
/// <summary>

View File

@@ -17,7 +17,7 @@ namespace YooAsset
private readonly DefaultCacheFileSystem _fileSystem;
private IEnumerator<string> _filesEnumerator = null;
private float _verifyStartTime;
private double _verifyStartTime;
private ESteps _steps = ESteps.None;
/// <summary>
@@ -33,7 +33,7 @@ namespace YooAsset
internal override void InternalStart()
{
_steps = ESteps.Prepare;
_verifyStartTime = UnityEngine.Time.realtimeSinceStartup;
_verifyStartTime = TimeUtility.RealtimeSinceStartup;
}
internal override void InternalUpdate()
{
@@ -58,7 +58,7 @@ namespace YooAsset
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
float costTime = UnityEngine.Time.realtimeSinceStartup - _verifyStartTime;
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Search cache files elapsed time {costTime:f1} seconds");
}
}

View File

@@ -25,7 +25,7 @@ namespace YooAsset
private List<VerifyFileElement> _verifyingList;
private int _verifyMaxNum;
private int _verifyTotalCount;
private float _verifyStartTime;
private double _verifyStartTime;
private int _succeedCount;
private int _failedCount;
private ESteps _steps = ESteps.None;
@@ -40,7 +40,7 @@ namespace YooAsset
internal override void InternalStart()
{
_steps = ESteps.InitVerify;
_verifyStartTime = UnityEngine.Time.realtimeSinceStartup;
_verifyStartTime = TimeUtility.RealtimeSinceStartup;
}
internal override void InternalUpdate()
{
@@ -84,7 +84,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
float costTime = UnityEngine.Time.realtimeSinceStartup - _verifyStartTime;
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Verify cache files elapsed time {costTime:f1} seconds");
}

View File

@@ -69,16 +69,8 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
//TODO 等待子线程验证文件完毕,该操作会挂起主线程!
InternalUpdate();
if (IsDone)
break;
//TODO 短暂休眠避免完全卡死
System.Threading.Thread.Sleep(1);
}
//TODO 等待子线程验证文件完毕,该操作会挂起主线程!
RunUntilCompletion();
}
private void VerifyInThread(object obj)

View File

@@ -142,14 +142,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -91,7 +91,7 @@ namespace YooAsset
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "WebGL platform not support sync load method !";
UnityEngine.Debug.LogError(Error);
YooLogger.Error(Error);
}
}
}

View File

@@ -91,7 +91,7 @@ namespace YooAsset
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "WebGL platform not support sync load method !";
UnityEngine.Debug.LogError(Error);
YooLogger.Error(Error);
}
}
}

View File

@@ -10,7 +10,7 @@ namespace YooAsset
{
private List<AsyncOperationBase> _childs;
private Action<AsyncOperationBase> _callback;
private int _whileFrame = 1000;
private uint _priority = 0;
/// <summary>
/// 等待异步执行完成
@@ -35,10 +35,28 @@ namespace YooAsset
}
}
/// <summary>
/// 标记脏(用于调度器检测并重排)
/// </summary>
internal bool IsDirty { set; get; } = false;
/// <summary>
/// 任务优先级
/// </summary>
public uint Priority { set; get; } = 0;
public uint Priority
{
set
{
if (_priority == value)
return;
_priority = value;
IsDirty = true;
}
get
{
return _priority;
}
}
/// <summary>
/// 任务状态
@@ -122,7 +140,7 @@ namespace YooAsset
}
internal virtual void InternalWaitForAsyncComplete()
{
throw new System.NotImplementedException(this.GetType().Name);
throw new YooInternalException($"InternalWaitForAsyncComplete() not implemented : {this.GetType().Name}");
}
internal virtual string InternalGetDesc()
{
@@ -137,9 +155,19 @@ namespace YooAsset
if (_childs == null)
_childs = new List<AsyncOperationBase>(10);
#if UNITY_EDITOR
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("The child node is null !");
if (ReferenceEquals(child, this))
throw new YooInternalException("The child node cannot be itself !");
if (_childs.Contains(child))
throw new YooInternalException($"The child node {child.GetType().Name} already exists !");
// 禁止形成环依赖
if (WouldCreateCycle(child))
throw new YooInternalException($"AddChildOperation would create a cycle : {this.GetType().Name} -> {child.GetType().Name}");
#endif
_childs.Add(child);
@@ -153,7 +181,10 @@ namespace YooAsset
if (_childs == null)
return;
#if UNITY_EDITOR
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("The child node is null !");
if (_childs.Contains(child) == false)
throw new YooInternalException($"The child node {child.GetType().Name} not exists !");
#endif
@@ -182,7 +213,16 @@ namespace YooAsset
DebugBeginRecording();
// 开始任务
InternalStart();
try
{
InternalStart();
}
catch (Exception ex)
{
Status = EOperationStatus.Failed;
Error = ex.ToString();
YooLogger.Error($"Exception in {this.GetType().Name}.InternalStart : {ex}");
}
}
}
@@ -197,7 +237,18 @@ namespace YooAsset
DebugUpdateRecording();
// 更新任务
InternalUpdate();
// 注意:兜底隔离机制
// 说明检测的异常源包含I/O解压/读写权限/磁盘满),平台差异等
try
{
InternalUpdate();
}
catch (Exception ex)
{
Status = EOperationStatus.Failed;
Error = ex.ToString();
YooLogger.Error($"Exception in {this.GetType().Name}.InternalUpdate : {ex}");
}
}
if (IsDone && IsFinish == false)
@@ -226,12 +277,15 @@ namespace YooAsset
Error = "user abort";
YooLogger.Warning($"Async operation {this.GetType().Name} has been aborted !");
}
//注意强制收尾确保Task能完成
FinishOperation();
}
/// <summary>
/// 强制结束异步任务
/// </summary>
internal void FinishOperation()
private void FinishOperation()
{
if (IsFinish == false)
{
@@ -243,6 +297,7 @@ namespace YooAsset
try
{
//TODO 单个回调异常会阻断后续订阅者
_callback?.Invoke(this);
}
catch (Exception ex)
@@ -259,25 +314,59 @@ namespace YooAsset
}
/// <summary>
/// 执行While循环
/// 执行一次更新逻辑
/// </summary>
protected bool ExecuteWhileDone()
protected void RunOnceExecution()
{
if (IsDone == false)
if (IsDone)
return;
UpdateOperation();
}
/// <summary>
/// 批量执行一定次数的更新逻辑
/// </summary>
/// <param name="count">次数</param>
protected void RunBatchExecution(int count = 1000)
{
if (IsDone)
return;
int runCount = count;
while (true)
{
// 执行更新逻辑
InternalUpdate();
UpdateOperation();
if (IsDone)
break;
// 当执行次数用完时
_whileFrame--;
if (_whileFrame <= 0)
{
Status = EOperationStatus.Failed;
Error = $"Operation {this.GetType().Name} failed to wait for async complete !";
YooLogger.Error(Error);
}
runCount--;
if (runCount <= 0)
break;
}
}
/// <summary>
/// 无限次数的执行更新逻辑,直到任务完成
/// 注意:该方法会阻塞主线程
/// </summary>
/// <param name="sleepMS">休眠时长</param>
protected void RunUntilCompletion(int sleepMS = 1)
{
if (IsDone)
return;
while (true)
{
UpdateOperation();
if (IsDone)
break;
// 注意: 短暂休眠避免完全占用CPU资源
System.Threading.Thread.Sleep(sleepMS);
}
return IsDone;
}
/// <summary>
@@ -285,11 +374,7 @@ namespace YooAsset
/// </summary>
public void WaitForAsyncComplete()
{
if (IsDone)
return;
//TODO 防止异步操作被挂起陷入无限死循环!
// 例如:文件解压任务或者文件导入任务!
if (Status == EOperationStatus.None)
{
StartOperation();
@@ -298,12 +383,19 @@ namespace YooAsset
if (IsWaitForAsyncComplete == false)
{
IsWaitForAsyncComplete = true;
InternalWaitForAsyncComplete();
#if UNITY_EDITOR
if (IsDone == false)
throw new YooInternalException($"WaitForAsyncComplete() must complete operation: {this.GetType().Name}");
#endif
InternalWaitForAsyncComplete();
if (IsDone == false)
{
Status = EOperationStatus.Failed;
Error = $"Operation {this.GetType().Name} failed to wait for async complete !";
YooLogger.Error(Error);
}
//注意强制收尾确保Task能完成
FinishOperation();
}
}
@@ -311,7 +403,7 @@ namespace YooAsset
/// <summary>
/// 开始的时间
/// </summary>
public string BeginTime = string.Empty;
public string BeginTime { protected set; get; }
/// <summary>
/// 处理耗时(单位:毫秒)
@@ -326,7 +418,7 @@ namespace YooAsset
{
if (_watch == null)
{
BeginTime = SpawnTimeToString(UnityEngine.Time.realtimeSinceStartup);
BeginTime = SpawnTimeToString(TimeUtility.RealtimeSinceStartup);
_watch = Stopwatch.StartNew();
}
}
@@ -350,14 +442,51 @@ namespace YooAsset
}
}
private string SpawnTimeToString(float spawnTime)
private string SpawnTimeToString(double spawnTime)
{
float h = UnityEngine.Mathf.FloorToInt(spawnTime / 3600f);
float m = UnityEngine.Mathf.FloorToInt(spawnTime / 60f - h * 60f);
float s = UnityEngine.Mathf.FloorToInt(spawnTime - m * 60f - h * 3600f);
double h = System.Math.Floor(spawnTime / 3600);
double m = System.Math.Floor(spawnTime / 60 - h * 60);
double s = System.Math.Floor(spawnTime - m * 60 - h * 3600);
return h.ToString("00") + ":" + m.ToString("00") + ":" + s.ToString("00");
}
private bool WouldCreateCycle(AsyncOperationBase child)
{
const int maxVisited = 4096;
var stack = new Stack<AsyncOperationBase>();
var visited = new HashSet<AsyncOperationBase>();
stack.Push(child);
while (stack.Count > 0)
{
var node = stack.Pop();
if (node == null)
continue;
if (visited.Add(node) == false)
continue;
if (visited.Count > maxVisited)
throw new YooInternalException("Child operation graph is too large, cycle check aborted !");
if (ReferenceEquals(node, this))
return true;
if (node._childs == null)
continue;
for (int i = 0; i < node._childs.Count; i++)
{
stack.Push(node._childs[i]);
}
}
return false;
}
/// <summary>
/// 获取调试信息
/// 注意:递归构建子树存在深度风险
/// </summary>
internal DebugOperationInfo GetDebugOperationInfo()
{
var operationInfo = new DebugOperationInfo();

View File

@@ -1,25 +0,0 @@

namespace YooAsset
{
public abstract class GameAsyncOperation : AsyncOperationBase
{
internal override void InternalStart()
{
OnStart();
}
internal override void InternalUpdate()
{
OnUpdate();
}
/// <summary>
/// 异步操作开始
/// </summary>
protected abstract void OnStart();
/// <summary>
/// 异步操作更新
/// </summary>
protected abstract void OnUpdate();
}
}

View File

@@ -10,6 +10,7 @@ namespace YooAsset
{
private readonly List<AsyncOperationBase> _operations = new List<AsyncOperationBase>(100);
private readonly List<AsyncOperationBase> _newList = new List<AsyncOperationBase>(100);
private uint _priority = 0;
/// <summary>
/// 所属包裹名称
@@ -19,7 +20,23 @@ namespace YooAsset
/// <summary>
/// 调度器优先级(值越大越优先)
/// </summary>
public int Priority { private set; get; }
public uint Priority
{
get { return _priority; }
set
{
if (_priority != value)
{
_priority = value;
IsDirty = true;
}
}
}
/// <summary>
/// 优先级是否已变更(需要重新排序)
/// </summary>
public bool IsDirty { set; get; } = false;
/// <summary>
/// 创建顺序(用于同优先级稳定排序)
@@ -27,10 +44,9 @@ namespace YooAsset
public int CreateIndex { private set; get; }
public OperationScheduler(string packageName, int priority, int createIndex)
public OperationScheduler(string packageName, int createIndex)
{
PackageName = packageName;
Priority = priority;
CreateIndex = createIndex;
}
@@ -41,9 +57,6 @@ namespace YooAsset
{
_newList.Add(operation);
operation.StartOperation();
// 通知开始回调
OperationSystem.InvokeStartCallback(PackageName, operation);
}
/// <summary>
@@ -58,31 +71,29 @@ namespace YooAsset
if (operation.IsFinish)
{
_operations.RemoveAt(i);
// 通知完成回调
OperationSystem.InvokeFinishCallback(PackageName, operation);
}
}
// 添加新增的异步操作
if (_newList.Count > 0)
{
bool sorting = false;
foreach (var operation in _newList)
{
if (operation.Priority > 0)
{
sorting = true;
break;
}
}
_operations.AddRange(_newList);
_newList.Clear();
}
// 重新排序优先级
if (sorting)
_operations.Sort();
// 检测是否需要执行排序
bool isDirty = false;
foreach (var operation in _operations)
{
if (operation.IsDirty)
{
operation.IsDirty = false;
isDirty = true;
}
}
if (isDirty)
{
_operations.Sort();
}
// 更新进行中的异步操作
@@ -109,7 +120,6 @@ namespace YooAsset
foreach (var operation in _newList)
{
operation.AbortOperation();
operation.FinishOperation(); //注意强制收尾确保Task能完成
}
_newList.Clear();
@@ -117,7 +127,6 @@ namespace YooAsset
foreach (var operation in _operations)
{
operation.AbortOperation();
operation.FinishOperation(); //注意强制收尾确保Task能完成
}
_operations.Clear();
}

View File

@@ -5,7 +5,7 @@ using System.Diagnostics;
namespace YooAsset
{
internal class OperationSystem
internal static class OperationSystem
{
#if UNITY_EDITOR
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
@@ -16,24 +16,36 @@ namespace YooAsset
#endif
// 全局调度器名称
public const string GLOBAL_SCHEDULER_NAME = "";
public const string GLOBAL_SCHEDULER_NAME = "YOOASSET_GLOBAL_SCHEDULER";
private static readonly Dictionary<string, OperationScheduler> _schedulerDic = new Dictionary<string, OperationScheduler>(100);
private static readonly List<OperationScheduler> _schedulerList = new List<OperationScheduler>(100);
private static bool _schedulerListDirty = false;
private static bool _isInitialize = false;
private static int _createIndex = 0;
private static Action<string, AsyncOperationBase> _startCallback = null;
private static Action<string, AsyncOperationBase> _finishCallback = null;
// 计时器相关
private static Stopwatch _watch;
private static long _frameTime;
private static long _maxTimeSlice = long.MaxValue;
/// <summary>
/// 异步操作系统的每帧最大执行预算(毫秒)
/// </summary>
public static long MaxTimeSlice { set; get; } = long.MaxValue;
public static long MaxTimeSlice
{
set
{
if (value < 10)
{
_maxTimeSlice = 10;
YooLogger.Warning($"MaxTimeSlice minimum value is 10 milliseconds.");
}
else
{
_maxTimeSlice = value;
}
}
}
/// <summary>
/// 异步操作系统是否繁忙
@@ -45,11 +57,11 @@ namespace YooAsset
if (_watch == null)
return false;
if (MaxTimeSlice == long.MaxValue)
if (_maxTimeSlice == long.MaxValue)
return false;
// 注意 : 单次调用开销约1微秒
return _watch.ElapsedMilliseconds - _frameTime >= MaxTimeSlice;
return _watch.ElapsedMilliseconds - _frameTime >= _maxTimeSlice;
}
}
@@ -59,10 +71,14 @@ namespace YooAsset
/// </summary>
public static void Initialize()
{
_watch = Stopwatch.StartNew();
if (_isInitialize == false)
{
_isInitialize = true;
_watch = Stopwatch.StartNew();
// 创建全局调度器
CreatePackageScheduler(GLOBAL_SCHEDULER_NAME, 0);
// 创建全局调度器
CreatePackageScheduler(GLOBAL_SCHEDULER_NAME, uint.MaxValue);
}
}
/// <summary>
@@ -70,10 +86,21 @@ namespace YooAsset
/// </summary>
public static void Update()
{
// 重新排序调度器
if (_schedulerListDirty)
if (_isInitialize == false)
return;
// 检测是否需要执行排序
bool isDirty = false;
foreach (var scheduler in _schedulerList)
{
if (scheduler.IsDirty)
{
scheduler.IsDirty = false;
isDirty = true;
}
}
if (isDirty)
{
_schedulerListDirty = false;
_schedulerList.Sort();
}
@@ -95,6 +122,9 @@ namespace YooAsset
/// </summary>
public static void DestroyAll()
{
_isInitialize = false;
YooLogger.Log("Operation system destroy all !");
// 清空所有调度器
foreach (var scheduler in _schedulerList)
{
@@ -102,11 +132,8 @@ namespace YooAsset
}
_schedulerDic.Clear();
_schedulerList.Clear();
_schedulerListDirty = false;
_createIndex = 0;
_startCallback = null;
_finishCallback = null;
_watch = null;
_frameTime = 0;
MaxTimeSlice = long.MaxValue;
@@ -115,24 +142,29 @@ namespace YooAsset
/// <summary>
/// 创建包裹调度器
/// </summary>
internal static void CreatePackageScheduler(string packageName, int priority)
public static OperationScheduler CreatePackageScheduler(string packageName, uint priority)
{
DebugCheckInitialize(packageName);
if (_schedulerDic.ContainsKey(packageName))
{
throw new YooInternalException($"Package scheduler already exists: {packageName}");
}
var scheduler = new OperationScheduler(packageName, priority, _createIndex++);
var scheduler = new OperationScheduler(packageName, _createIndex++);
_schedulerDic.Add(packageName, scheduler);
_schedulerList.Add(scheduler);
_schedulerListDirty = true;
scheduler.Priority = priority;
return scheduler;
}
/// <summary>
/// 销毁包裹调度器
/// </summary>
internal static void DestroyPackageScheduler(string packageName)
public static void DestroyPackageScheduler(string packageName)
{
DebugCheckInitialize(packageName);
// 不允许销毁默认调度器
if (packageName == GLOBAL_SCHEDULER_NAME)
{
@@ -152,6 +184,8 @@ namespace YooAsset
/// </summary>
public static void ClearPackageOperation(string packageName)
{
DebugCheckInitialize(packageName);
var scheduler = GetScheduler(packageName);
scheduler.ClearAll();
}
@@ -161,40 +195,32 @@ namespace YooAsset
/// </summary>
public static void StartOperation(string packageName, AsyncOperationBase operation)
{
DebugCheckInitialize(packageName);
var scheduler = GetScheduler(packageName);
scheduler.StartOperation(operation);
}
/// <summary>
/// 监听任务开始
/// 设置调度器优先级
/// </summary>
public static void RegisterStartCallback(Action<string, AsyncOperationBase> callback)
public static void SetSchedulerPriority(string packageName, uint priority)
{
_startCallback = callback;
DebugCheckInitialize(packageName);
var scheduler = GetScheduler(packageName);
scheduler.Priority = priority;
}
/// <summary>
/// 监听任务结束
/// 获取调度器优先级
/// </summary>
public static void RegisterFinishCallback(Action<string, AsyncOperationBase> callback)
public static uint GetSchedulerPriority(string packageName)
{
_finishCallback = callback;
}
DebugCheckInitialize(packageName);
/// <summary>
/// 触发任务开始回调
/// </summary>
internal static void InvokeStartCallback(string packageName, AsyncOperationBase operation)
{
_startCallback?.Invoke(packageName, operation);
}
/// <summary>
/// 触发任务完成回调
/// </summary>
internal static void InvokeFinishCallback(string packageName, AsyncOperationBase operation)
{
_finishCallback?.Invoke(packageName, operation);
var scheduler = GetScheduler(packageName);
return scheduler.Priority;
}
/// <summary>
@@ -202,32 +228,34 @@ namespace YooAsset
/// </summary>
private static OperationScheduler GetScheduler(string packageName)
{
// 空包名路由到默认调度器
if (string.IsNullOrEmpty(packageName))
packageName = GLOBAL_SCHEDULER_NAME;
if (_schedulerDic.TryGetValue(packageName, out var scheduler))
{
return scheduler;
}
// 严格模式:非默认包裹必须先创建调度器
throw new YooInternalException($"Package scheduler not found: {packageName}. Please call YooAssets.CreatePackage() first!");
throw new YooInternalException($"Operation scheduler not found: {packageName}.");
}
#region
internal static List<DebugOperationInfo> GetDebugOperationInfos(string packageName)
{
// 空包名路由到默认调度器
if (string.IsNullOrEmpty(packageName))
packageName = GLOBAL_SCHEDULER_NAME;
DebugCheckInitialize(packageName);
if (_schedulerDic.TryGetValue(packageName, out var scheduler))
{
return scheduler.GetDebugOperationInfos();
}
var scheduler = GetScheduler(packageName);
return scheduler.GetDebugOperationInfos();
}
#endregion
return new List<DebugOperationInfo>();
#region
[Conditional("DEBUG")]
private static void DebugCheckInitialize(string packageName)
{
if (string.IsNullOrWhiteSpace(packageName))
throw new YooInternalException("Package name is null or empty.");
if (_isInitialize == false)
throw new YooInternalException($"{nameof(OperationSystem)} not initialized !");
}
#endregion
}

View File

@@ -32,23 +32,23 @@ OperationSystem 是 YooAsset 资源管理系统的**异步操作调度核心**
```
┌─────────────────────────────────────────────────────────┐
│ 上层调用者 │
│ 上层调用者
│ (ResourceManager / FileSystem / 业务层) │
└─────────────────────────┬───────────────────────────────┘
│ StartOperation()
┌─────────────────────────▼───────────────────────────────┐
│ OperationSystem
│ OperationSystem │
│ (调度器) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 优先级队列 │ │ 时间切片 │ │ 回调通知 │ │
│ │ 优先级队列 │ │ 时间切片 │ │ 回调通知 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────┘
│ UpdateOperation()
┌─────────────────────────▼───────────────────────────────┐
│ AsyncOperationBase
│ AsyncOperationBase │
│ (操作基类) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 状态机 │ │ 子任务管理 │ │ 异步模式 │ │
│ │ 状态机 │ │ 子任务管理 │ │ 异步模式 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
```
@@ -56,8 +56,8 @@ OperationSystem 是 YooAsset 资源管理系统的**异步操作调度核心**
### 核心组件
- **OperationSystem**: 静态调度器,管理所有操作的执行
- **OperationScheduler**: 包裹级调度器,维护操作队列并负责更新调度
- **AsyncOperationBase**: 异步操作基类,定义生命周期和状态
- **GameAsyncOperation**: 游戏层操作基类,提供更友好的 API
- **EOperationStatus**: 操作状态枚举
---
@@ -68,8 +68,8 @@ OperationSystem 是 YooAsset 资源管理系统的**异步操作调度核心**
OperationSystem/
├── EOperationStatus.cs # 操作状态枚举
├── AsyncOperationBase.cs # 异步操作基类
├── OperationScheduler.cs # 包裹级调度器
├── OperationSystem.cs # 异步操作调度器
└── GameAsyncOperation.cs # 游戏层操作基类
```
---
@@ -113,12 +113,21 @@ None ─────────────────► Processing ───
| `Status` | `EOperationStatus` | 当前状态 |
| `Error` | `string` | 错误信息(失败时) |
| `Progress` | `float` | 处理进度0-1 |
| `PackageName` | `string` | 所属包裹名称 |
| `IsDone` | `bool` | 是否已完成Succeed 或 Failed |
| `Task` | `Task` | 用于 async/await |
| `BeginTime` | `string` | 开始时间(调试用) |
| `ProcessTime` | `long` | 处理耗时毫秒(调试用) |
> 说明:`AsyncOperationBase` 本身不保存包裹名称;包裹名称由 `OperationSystem.StartOperation(packageName, operation)` 传入,并由 `OperationScheduler` 维护。
#### 内部协作:时间切片(`IsBusy`
为配合 `OperationSystem.MaxTimeSlice` 的时间切片预算,`AsyncOperationBase` 提供了内部属性 `IsBusy``internal`)用于任务在 `InternalUpdate()` 内主动让出本帧预算。
- 推荐用法:在 `InternalUpdate()` 内部(或内部子步骤)在执行重逻辑前判断 `IsBusy`,若繁忙则 `return`,把工作拆到下一帧继续执行。
- 同步等待特殊处理:当调用了 `WaitForAsyncComplete()` 进入同步等待阶段时,`IsBusy` 会强制返回 `false`,避免因时间切片判断导致同步等待无法推进。
- 注意:`WaitForAsyncComplete()` 会阻塞主线程,应谨慎使用;同步等待阶段不受时间切片保护,可能带来卡顿。
#### 公共事件
```csharp
@@ -150,14 +159,16 @@ public void WaitForAsyncComplete();
#### 子任务管理
```csharp
// 子任务列表
internal readonly List<AsyncOperationBase> Childs;
// 添加/移除子任务
// 添加/移除子任务(内部使用)
internal void AddChildOperation(AsyncOperationBase child);
internal void RemoveChildOperation(AsyncOperationBase child);
```
**调用约束(重要):**
- 仅允许在 Unity 主线程调用(与 `OperationSystem.Update()` 的调度线程一致)。
- 不要在 `InternalUpdate()` 正在遍历/处理中途频繁增删子任务;推荐在任务启动阶段完成子任务挂接,或在确保无并发修改风险的安全点调整。
- `AbortOperation()` 会递归中止子任务,子任务的生命周期由父任务统一管理;避免在 `Completed` 回调里再去修改子任务关系,防止时序混乱。
---
### OperationSystem调度器
@@ -206,61 +217,36 @@ public static void ClearPackageOperation(string packageName);
/// 启动异步操作
/// </summary>
public static void StartOperation(string packageName, AsyncOperationBase operation);
/// <summary>
/// 设置调度器优先级
/// </summary>
public static void SetSchedulerPriority(string packageName, uint priority);
/// <summary>
/// 获取调度器优先级
/// </summary>
public static uint GetSchedulerPriority(string packageName);
```
#### 包裹调度说明
- `packageName` 不允许为空(`null` / `""`),否则会抛出异常。
- 若需要使用全局调度器,请传入 `OperationSystem.GLOBAL_SCHEDULER_NAME``Initialize()` 时自动创建)。
- `packageName` 为非全局调度器名称时,必须先通过 `YooAssets.CreatePackage(packageName)` 创建包裹(内部会注册对应 `OperationScheduler`),否则会抛出异常。
#### 回调监听
```csharp
/// <summary>
/// 注册任务开始回调
/// </summary>
public static void RegisterStartCallback(Action<string, AsyncOperationBase> callback);
OperationSystem **当前未提供**全局任务开始/结束回调的注册接口。
/// <summary>
/// 注册任务结束回调
/// </summary>
public static void RegisterFinishCallback(Action<string, AsyncOperationBase> callback);
```
---
### GameAsyncOperation游戏层基类
继承 `AsyncOperationBase`,为业务层提供更友好的 API。
如需监听任务结束(推荐),请直接订阅具体任务的 `Completed` 事件:
```csharp
public abstract class GameAsyncOperation : AsyncOperationBase
var operation = package.LoadAssetAsync<GameObject>(location);
operation.Completed += op =>
{
/// <summary>
/// 异步操作开始
/// </summary>
protected abstract void OnStart();
/// <summary>
/// 异步操作更新
/// </summary>
protected abstract void OnUpdate();
/// <summary>
/// 异步操作终止
/// </summary>
protected abstract void OnAbort();
/// <summary>
/// 异步等待完成(可选重写)
/// </summary>
protected virtual void OnWaitForAsyncComplete();
/// <summary>
/// 异步操作系统是否繁忙
/// </summary>
protected bool IsBusy();
/// <summary>
/// 终止异步操作
/// </summary>
protected void Abort();
}
// TODO : 根据 op.Status 判断成功/失败
};
```
---
@@ -339,15 +325,37 @@ void LoadAssetSync()
操作按 `Priority` 属性降序排列,优先级高的操作先执行。
#### 操作优先级
```csharp
var operation = package.LoadAssetAsync<GameObject>(location);
operation.Priority = 100; // 设置高优先级
```
#### 包裹优先级
通过 `ResourcePackage.PackagePriority` 可以设置包裹的调度器优先级,值越大越优先更新。
```csharp
// 创建包裹时指定优先级
var package = YooAssets.CreatePackage("MyPackage", 100);
// 运行时动态调整优先级
package.PackagePriority = 200;
// 获取当前优先级
uint priority = package.PackagePriority;
```
**使用场景:**
- 多包裹场景下,可根据游戏状态动态调整包裹优先级
- 例如:进入战斗时提高战斗资源包的优先级,退出战斗时恢复默认优先级
**排序规则:**
- 新操作添加时检查是否需要排序
- 仅当存在非零优先级时触发排序
- 使用 `List.Sort()` 进行原地排序
- 新操作添加时:若新增队列存在非零优先级,则触发排序
- 运行中修改 `Priority`:调度器会在每帧 `Update()` 的排序阶段检测 `IsDirty` 并触发重排;若在某个操作的 `InternalUpdate()` 内修改(本帧排序已完成),则新的优先级会延后一帧生效(可能与预期不符)
- 若期望本帧生效:请尽量在任务入队前或本帧调度器 `Update()` 开始前设置 `Priority`,避免在 `InternalUpdate()` 内临时调整
- 排序使用 `List.Sort()` 进行原地排序;频繁修改优先级会带来额外排序开销,建议按需使用
### 时间切片
@@ -358,6 +366,10 @@ operation.Priority = 100; // 设置高优先级
OperationSystem.MaxTimeSlice = 8;
```
**操作侧协作建议:**
- 在操作的 `InternalUpdate()` 中使用 `IsBusy``AsyncOperationBase` 的内部属性)进行“自愿让出”,将重任务拆分到多帧执行。
- 在同步等待(`WaitForAsyncComplete()`)阶段,`IsBusy` 会强制返回 `false`,以保证同步等待推进;此时需要自行评估卡顿风险。
**执行流程:**
```
@@ -420,24 +432,29 @@ OperationSystem.MaxTimeSlice = 8;
### 调试信息结构
```csharp
[Serializable]
internal struct DebugOperationInfo
{
public string OperationName; // 操作类型名
public string OperationDesc; // 操作描述
public string OperationName; // 任务名称
public string OperationDesc; // 任务说明
public uint Priority; // 优先级
public float Progress; // 进度
public string BeginTime; // 开始时间
public long ProcessTime; // 处理耗时(毫秒)
public string Status; // 状态
public List<DebugOperationInfo> Childs; // 子操作
public float Progress; // 任务进度
public string BeginTime; // 任务开始时间
public long ProcessTime; // 处理耗时(单位:毫秒)
public string Status; // 任务状态
public List<DebugOperationInfo> Childs; // 子任务列表注意JsonUtility 序列化深度限制)
}
```
> 说明:该结构体真实定义位于 `Runtime/DiagnosticSystem/DebugOperationInfo.cs`,这里仅展示关键字段以便理解。
### 获取调试信息
```csharp
// 获取指定包裹的所有操作信息
var infos = OperationSystem.GetDebugOperationInfos("DefaultPackage");
// 获取指定包裹的所有操作信息(内部调试接口)
// packageName 不允许为空;全局调度器请使用 OperationSystem.GLOBAL_SCHEDULER_NAME
// 非全局包裹需先 YooAssets.CreatePackage(packageName)
var infos = OperationSystem.GetDebugOperationInfos(OperationSystem.GLOBAL_SCHEDULER_NAME);
foreach (var info in infos)
{
@@ -457,105 +474,6 @@ Debug.Log($"处理耗时: {operation.ProcessTime}ms");
---
## 使用示例
### 自定义异步操作
```csharp
public class MyCustomOperation : GameAsyncOperation
{
private int _step = 0;
protected override void OnStart()
{
// 初始化操作
_step = 0;
}
protected override void OnUpdate()
{
// 检查系统是否繁忙(时间切片)
if (IsBusy())
return;
// 执行步骤
switch (_step)
{
case 0:
// 第一步
Progress = 0.3f;
_step = 1;
break;
case 1:
// 第二步
Progress = 0.6f;
_step = 2;
break;
case 2:
// 完成
Status = EOperationStatus.Succeed;
break;
}
}
protected override void OnAbort()
{
// 清理资源
}
}
```
### 启动自定义操作
```csharp
var operation = new MyCustomOperation();
OperationSystem.StartOperation("DefaultPackage", operation);
// 使用回调
operation.Completed += (op) =>
{
if (op.Status == EOperationStatus.Succeed)
Debug.Log("操作成功");
};
// 或使用 await
await operation.Task;
```
### 带子任务的操作
```csharp
public class ParentOperation : GameAsyncOperation
{
private ChildOperation _child;
protected override void OnStart()
{
_child = new ChildOperation();
AddChildOperation(_child); // 添加子任务
OperationSystem.StartOperation(PackageName, _child);
}
protected override void OnUpdate()
{
if (_child.IsDone)
{
if (_child.Status == EOperationStatus.Succeed)
Status = EOperationStatus.Succeed;
else
Status = EOperationStatus.Failed;
}
}
protected override void OnAbort()
{
// 子任务会自动中止
}
}
```
---
## 设计模式
### 模板方法模式
@@ -589,7 +507,7 @@ AsyncOperationBase
### 组合模式
通过 `Childs` 列表支持父子操作关系:
通过内部子任务列表支持父子操作关系:
```
ParentOperation
@@ -608,10 +526,6 @@ IEnumerator + IComparable<AsyncOperationBase>
AsyncOperationBase (抽象基类)
├── GameAsyncOperation (游戏层基类)
│ │
│ └── [业务层自定义操作]
└── [YooAsset 内部操作]
@@ -632,4 +546,4 @@ IEnumerator + IComparable<AsyncOperationBase>
4. **子任务中止**:父操作中止时会自动中止所有子操作
5. **回调异常**`Completed` 回调中的异常会被捕获并记录,不会中断系统
6. **编辑器重置**:编辑器中使用 `RuntimeInitializeOnLoadMethod` 自动重置状态
7. **循环保护**`WaitForAsyncComplete()` 有 1000 帧上限,防止无限循环
7. **循环保护**`InternalWaitForAsyncComplete()` 中建议使用 `RunBatchExecution()`(默认 1000 次)限制单次推进次数,避免陷入无限循环或长时间占用主线程

View File

@@ -62,6 +62,9 @@ namespace YooAsset
return;
}
if (IsWaitForAsyncComplete)
_handle.WaitForAsyncComplete();
if (_handle.IsDone == false)
return;
@@ -138,18 +141,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
// 等待句柄完成
if (_handle != null)
_handle.WaitForAsyncComplete();
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
internal override string InternalGetDesc()
{

View File

@@ -127,14 +127,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
internal override string InternalGetDesc()
{

View File

@@ -53,14 +53,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
internal override string InternalGetDesc()
{

View File

@@ -183,14 +183,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
internal override string InternalGetDesc()
{

View File

@@ -22,5 +22,10 @@ namespace YooAsset
/// 原生文件
/// </summary>
RawBundle = 3,
/// <summary>
/// 团结资源包
/// </summary>
InstantBundle = 4,
}
}

View File

@@ -3,13 +3,115 @@ using System.Collections.Generic;
namespace YooAsset
{
#region
/// <summary>
/// 下载器结束
/// </summary>
public struct DownloaderFinishData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 是否成功
/// </summary>
public bool Succeed;
}
/// <summary>
/// 下载器相关的更新数据
/// </summary>
public struct DownloadUpdateData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载进度 (0-1f)
/// </summary>
public float Progress;
/// <summary>
/// 下载文件总数
/// </summary>
public int TotalDownloadCount;
/// <summary>
/// 当前完成的下载文件数量
/// </summary>
public int CurrentDownloadCount;
/// <summary>
/// 下载数据总大小(单位:字节)
/// </summary>
public long TotalDownloadBytes;
/// <summary>
/// 当前完成的下载数据大小(单位:字节)
/// </summary>
public long CurrentDownloadBytes;
}
/// <summary>
/// 下载器相关的错误数据
/// </summary>
public struct DownloadErrorData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载失败的文件名称
/// </summary>
public string FileName;
/// <summary>
/// 错误信息
/// </summary>
public string ErrorInfo;
}
/// <summary>
/// 下载器相关的文件数据
/// </summary>
public struct DownloadFileData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 资源包名称
/// </summary>
public string BundleName;
/// <summary>
/// 文件名称
/// </summary>
public string FileName;
/// <summary>
/// 文件大小
/// </summary>
public long FileSize;
}
#endregion
public abstract class DownloaderOperation : AsyncOperationBase
{
private enum ESteps
{
None,
Check,
Loading,
Downloading,
Finish,
Done,
}
@@ -127,15 +229,37 @@ namespace YooAsset
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "Download list is null.";
Error = "Download bundle list is null.";
if (DownloadFinishCallback != null)
{
var data = new DownloaderFinishData();
data.PackageName = _packageName;
data.Succeed = false;
DownloadFinishCallback.Invoke(data);
}
}
else if (_bundleInfoList.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
Progress = 1f;
if (DownloadFinishCallback != null)
{
var data = new DownloaderFinishData();
data.PackageName = _packageName;
data.Succeed = true;
DownloadFinishCallback.Invoke(data);
}
}
else
{
_steps = ESteps.Loading;
_steps = ESteps.Downloading;
}
}
if (_steps == ESteps.Loading)
if (_steps == ESteps.Downloading)
{
// 检测下载器结果
_removeList.Clear();
@@ -172,7 +296,10 @@ namespace YooAsset
{
_lastDownloadBytes = downloadBytes;
_lastDownloadCount = _cachedDownloadCount;
Progress = (float)_lastDownloadBytes / TotalDownloadBytes;
if (TotalDownloadBytes == 0)
Progress = (float)_lastDownloadCount / TotalDownloadCount;
else
Progress = (float)_lastDownloadBytes / TotalDownloadBytes;
if (DownloadUpdateCallback != null)
{
@@ -209,54 +336,60 @@ namespace YooAsset
{
var data = new DownloadFileData();
data.PackageName = _packageName;
data.FileName = bundleInfo.Bundle.BundleName;
data.BundleName = bundleInfo.Bundle.BundleName;
data.FileName = bundleInfo.Bundle.FileName;
data.FileSize = bundleInfo.Bundle.FileSize;
DownloadFileBeginCallback.Invoke(data);
}
}
}
// 下载结
// 下载结
if (_downloaders.Count == 0)
{
if (_failedList.Count > 0)
_steps = ESteps.Finish;
}
}
if (_steps == ESteps.Finish)
{
if (_failedList.Count > 0)
{
var failedDownloader = _failedList[0];
string bundleName = failedDownloader.Bundle.BundleName;
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to download file : {bundleName}";
if (DownloadErrorCallback != null)
{
var failedDownloader = _failedList[0];
string bundleName = failedDownloader.Bundle.BundleName;
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to download file : {bundleName}";
if (DownloadErrorCallback != null)
{
var data = new DownloadErrorData();
data.PackageName = _packageName;
data.FileName = bundleName;
data.ErrorInfo = failedDownloader.Error;
DownloadErrorCallback.Invoke(data);
}
if (DownloadFinishCallback != null)
{
var data = new DownloaderFinishData();
data.PackageName = _packageName;
data.Succeed = false;
DownloadFinishCallback.Invoke(data);
}
var data = new DownloadErrorData();
data.PackageName = _packageName;
data.FileName = bundleName;
data.ErrorInfo = failedDownloader.Error;
DownloadErrorCallback.Invoke(data);
}
else
{
// 结算成功
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
if (DownloadFinishCallback != null)
{
var data = new DownloaderFinishData();
data.PackageName = _packageName;
data.Succeed = true;
DownloadFinishCallback.Invoke(data);
}
if (DownloadFinishCallback != null)
{
var data = new DownloaderFinishData();
data.PackageName = _packageName;
data.Succeed = false;
DownloadFinishCallback.Invoke(data);
}
}
else
{
// 结算成功
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
if (DownloadFinishCallback != null)
{
var data = new DownloaderFinishData();
data.PackageName = _packageName;
data.Succeed = true;
DownloadFinishCallback.Invoke(data);
}
}
}

View File

@@ -91,7 +91,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The manifest file version are not compatible : {fileVersion} != {ManifestDefine.FileVersion}";
Error = $"The manifest version is lower than the minimum compatible version : {fileVer} < {ver2025_8_28}";
return;
}
@@ -158,7 +158,7 @@ namespace YooAsset
FillAssetCollection(Manifest, packageAsset, replaceAssetPath);
_packageAssetCount--;
Progress = 1f - _packageAssetCount / _progressTotalValue;
Progress = 1f - (_packageAssetCount / (float)_progressTotalValue);
if (IsBusy)
break;
}
@@ -192,7 +192,7 @@ namespace YooAsset
FillBundleCollection(Manifest, packageBundle);
_packageBundleCount--;
Progress = 1f - _packageBundleCount / _progressTotalValue;
Progress = 1f - (_packageBundleCount / (float)_progressTotalValue);
if (IsBusy)
break;
}
@@ -220,14 +220,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
private void CreateAssetCollection(PackageManifest manifest, int assetCount)

View File

@@ -298,7 +298,8 @@ namespace YooAsset
return new List<BundleInfo>();
// 获取资源对象的资源包和所有依赖资源包
List<PackageBundle> checkList = new List<PackageBundle>();
HashSet<string> checkSet = new HashSet<string>();
List<PackageBundle> checkList = new List<PackageBundle>(assetInfos.Length);
foreach (var assetInfo in assetInfos)
{
if (assetInfo.IsInvalid)
@@ -309,15 +310,21 @@ namespace YooAsset
// 注意:如果清单里未找到资源包会抛出异常!
PackageBundle mainBundle = manifest.GetMainPackageBundle(assetInfo.Asset);
if (checkList.Contains(mainBundle) == false)
if (checkSet.Contains(mainBundle.BundleGUID) == false)
{
checkSet.Add(mainBundle.BundleGUID);
checkList.Add(mainBundle);
}
// 注意:如果清单里未找到资源包会抛出异常!
List<PackageBundle> mainDependBundles = manifest.GetAssetAllDependencies(assetInfo.Asset);
foreach (var dependBundle in mainDependBundles)
{
if (checkList.Contains(dependBundle) == false)
if (checkSet.Contains(dependBundle.BundleGUID) == false)
{
checkSet.Add(dependBundle.BundleGUID);
checkList.Add(dependBundle);
}
}
// 下载主资源包内所有资源对象依赖的资源包
@@ -326,14 +333,20 @@ namespace YooAsset
foreach (var otherMainAsset in mainBundle.IncludeMainAssets)
{
var otherMainBundle = manifest.GetMainPackageBundle(otherMainAsset.BundleID);
if (checkList.Contains(otherMainBundle) == false)
if (checkSet.Contains(otherMainBundle.BundleGUID) == false)
{
checkSet.Add(otherMainBundle.BundleGUID);
checkList.Add(otherMainBundle);
}
List<PackageBundle> otherDependBundles = manifest.GetAssetAllDependencies(otherMainAsset);
foreach (var dependBundle in otherDependBundles)
{
if (checkList.Contains(dependBundle) == false)
if (checkSet.Contains(dependBundle.BundleGUID) == false)
{
checkSet.Add(dependBundle.BundleGUID);
checkList.Add(dependBundle);
}
}
}
}

View File

@@ -372,6 +372,8 @@ public class PackageBundle
清单序列化工具类。
> 注意:`DeserializeFromJson` 仅用于编辑器/调试用途,不保证运行时可用;运行时请优先使用二进制清单 `DeserializeFromBinary`。
```csharp
// 验证清单数据完整性
static bool VerifyManifestData(byte[] fileData, string hashValue);
@@ -381,6 +383,7 @@ static string SerializeToJson(PackageManifest manifest);
static byte[] SerializeToBinary(PackageManifest manifest);
// 反序列化
// 注意JSON 反序列化仅用于编辑器/调试用途,运行时请优先使用二进制清单。
static PackageManifest DeserializeFromJson(string jsonContent);
static PackageManifest DeserializeFromBinary(byte[] binaryData);

View File

@@ -44,6 +44,15 @@ namespace YooAsset
}
}
/// <summary>
/// 包裹优先级(值越大越优先更新)
/// </summary>
public uint PackagePriority
{
get { return OperationSystem.GetSchedulerPriority(PackageName); }
set { OperationSystem.SetSchedulerPriority(PackageName, value); }
}
internal ResourcePackage(string packageName)
{
@@ -213,7 +222,7 @@ namespace YooAsset
options.ReleaseAllHandles = true;
options.LockLoadOperation = true;
var operation = new DestroyOperation(this, options);
OperationSystem.StartOperation(null, operation);
OperationSystem.StartOperation(OperationSystem.GLOBAL_SCHEDULER_NAME, operation);
return operation;
}
@@ -1154,7 +1163,6 @@ namespace YooAsset
#endregion
#region
[Conditional("DEBUG")]
private void DebugCheckInitialize(bool checkActiveManifest = true)
{
if (_initializeStatus == EOperationStatus.None)

View File

@@ -5,6 +5,27 @@ using System.Text;
namespace YooAsset
{
/// <summary>
/// 时间工具类
/// </summary>
internal static class TimeUtility
{
/// <summary>
/// The real time in seconds since the game started
/// </summary>
public static double RealtimeSinceStartup
{
get
{
#if UNITY_2020_3_OR_NEWER
return UnityEngine.Time.realtimeSinceStartupAsDouble;
#else
return UnityEngine.Time.realtimeSinceStartup;
#endif
}
}
}
/// <summary>
/// 路径工具类
/// </summary>

View File

@@ -59,6 +59,7 @@ namespace YooAsset
_driver.AddComponent<RemoteDebuggerInRuntime>();
#endif
// 初始化异步操作系统
OperationSystem.Initialize();
}
}
@@ -75,8 +76,8 @@ namespace YooAsset
if (_driver != null)
GameObject.Destroy(_driver);
// 终止并清空所有包裹的异步操作
ClearAllPackageOperation();
// 销毁异步操作系统
OperationSystem.DestroyAll();
// 卸载所有AssetBundle
AssetBundle.UnloadAllAssetBundles(true);
@@ -97,18 +98,6 @@ namespace YooAsset
}
}
/// <summary>
/// 终止并清空所有包裹的异步操作
/// </summary>
internal static void ClearAllPackageOperation()
{
foreach (var package in _packages)
{
OperationSystem.ClearPackageOperation(package.PackageName);
}
OperationSystem.DestroyAll();
}
/// <summary>
/// 创建资源包裹
/// </summary>
@@ -123,7 +112,7 @@ namespace YooAsset
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <param name="packagePriority">包裹优先级(值越大越优先更新)</param>
public static ResourcePackage CreatePackage(string packageName, int packagePriority)
public static ResourcePackage CreatePackage(string packageName, uint packagePriority)
{
CheckException(packageName);
if (ContainsPackage(packageName))
@@ -218,17 +207,6 @@ namespace YooAsset
return package != null;
}
/// <summary>
/// 开启一个异步操作
/// </summary>
/// <param name="operation">异步操作对象</param>
public static void StartOperation(GameAsyncOperation operation)
{
// 注意:游戏业务逻辑的包裹填写为空
OperationSystem.StartOperation(string.Empty, operation);
}
private static ResourcePackage GetPackageInternal(string packageName)
{
foreach (var package in _packages)
@@ -270,11 +248,6 @@ namespace YooAsset
/// </summary>
public static void SetOperationSystemMaxTimeSlice(long milliseconds)
{
if (milliseconds < 10)
{
milliseconds = 10;
YooLogger.Warning($"MaxTimeSlice minimum value is 10 milliseconds.");
}
OperationSystem.MaxTimeSlice = milliseconds;
}
#endregion

View File

@@ -25,7 +25,7 @@ namespace YooAsset
void OnApplicationQuit()
{
// 说明在编辑器下确保播放被停止时IO类操作被终止。
YooAssets.ClearAllPackageOperation();
YooAssets.Destroy();
}
#endif

View File

@@ -7,7 +7,7 @@ using YooAsset;
/// <summary>
/// 拷贝内置清单文件到沙盒目录
/// </summary>
public class CopyBuildinManifestOperation : GameAsyncOperation
public class CopyBuildinManifestOperation : AsyncOperationBase
{
private enum ESteps
{
@@ -32,11 +32,11 @@ public class CopyBuildinManifestOperation : GameAsyncOperation
_packageVersion = packageVersion;
_backend = new UnityWebRequestBackend();
}
protected override void OnStart()
internal override void InternalStart()
{
_steps = ESteps.CheckHashFile;
}
protected override void OnUpdate()
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -121,9 +121,6 @@ public class CopyBuildinManifestOperation : GameAsyncOperation
}
}
}
protected override void OnAbort()
{
}
private string GetBuildinYooRoot()
{

View File

@@ -7,7 +7,7 @@ using YooAsset;
/// <summary>
/// 获取包体里的内置资源清单版本
/// </summary>
public class GetBuildinPackageVersionOperation : GameAsyncOperation
public class GetBuildinPackageVersionOperation : AsyncOperationBase
{
private enum ESteps
{
@@ -31,11 +31,11 @@ public class GetBuildinPackageVersionOperation : GameAsyncOperation
_packageName = packageName;
_backend = new UnityWebRequestBackend();
}
protected override void OnStart()
internal override void InternalStart()
{
_steps = ESteps.GetPackageVersion;
}
protected override void OnUpdate()
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -68,9 +68,6 @@ public class GetBuildinPackageVersionOperation : GameAsyncOperation
}
}
}
protected override void OnAbort()
{
}
private string GetBuildinYooRoot()
{

View File

@@ -7,7 +7,7 @@ using YooAsset;
/// <summary>
/// 获取沙盒目录里缓存文件大小
/// </summary>
public class GetCacheBundleSizeOperation : GameAsyncOperation
public class GetCacheBundleSizeOperation : AsyncOperationBase
{
private enum ESteps
{
@@ -29,11 +29,11 @@ public class GetCacheBundleSizeOperation : GameAsyncOperation
{
_packageName = packageName;
}
protected override void OnStart()
internal override void InternalStart()
{
_steps = ESteps.GetCacheFiles;
}
protected override void OnUpdate()
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -57,9 +57,6 @@ public class GetCacheBundleSizeOperation : GameAsyncOperation
Status = EOperationStatus.Succeed;
}
}
protected override void OnAbort()
{
}
private string GetCacheDirectoryRoot()
{

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using UnityEngine;
using YooAsset;
public class LoadAssetsByTagOperation<TObject> : GameAsyncOperation where TObject : UnityEngine.Object
public class LoadAssetsByTagOperation<TObject> : AsyncOperationBase where TObject : UnityEngine.Object
{
private enum ESteps
{
@@ -28,11 +28,11 @@ public class LoadAssetsByTagOperation<TObject> : GameAsyncOperation where TObjec
{
_tag = tag;
}
protected override void OnStart()
internal override void InternalStart()
{
_steps = ESteps.LoadAssets;
}
protected override void OnUpdate()
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -53,7 +53,7 @@ public class LoadAssetsByTagOperation<TObject> : GameAsyncOperation where TObjec
{
int index = 0;
foreach (var handle in _handles)
{
{
if (handle.IsDone == false)
{
Progress = (float)index / _handles.Count;
@@ -77,7 +77,7 @@ public class LoadAssetsByTagOperation<TObject> : GameAsyncOperation where TObjec
string error = $"资源类型转换失败:{handle.AssetObject.name}";
Debug.LogError($"{error}");
AssetObjects.Clear();
SetFinish(false, error);
SetFailed(error);
return;
}
}
@@ -85,21 +85,23 @@ public class LoadAssetsByTagOperation<TObject> : GameAsyncOperation where TObjec
{
Debug.LogError($"{handle.LastError}");
AssetObjects.Clear();
SetFinish(false, handle.LastError);
SetFailed(handle.LastError);
return;
}
}
SetFinish(true);
SetSucceed();
}
}
protected override void OnAbort()
private void SetSucceed()
{
Status = EOperationStatus.Succeed;
_steps = ESteps.Done;
}
private void SetFinish(bool succeed, string error = "")
private void SetFailed(string error)
{
Error = error;
Status = succeed ? EOperationStatus.Succeed : EOperationStatus.Failed;
Status = EOperationStatus.Failed;
_steps = ESteps.Done;
}

View File

@@ -9,12 +9,12 @@ public static class YooAssetsExtension
public static LoadGameObjectOperation LoadGameObjectAsync(this ResourcePackage resourcePackage, string location, Vector3 position, Quaternion rotation, Transform parent, bool destroyGoOnRelease = false)
{
var operation = new LoadGameObjectOperation(location, position, rotation, parent, destroyGoOnRelease);
YooAssets.StartOperation(operation);
OperationSystem.StartOperation(OperationSystem.GLOBAL_SCHEDULER_NAME, operation);
return operation;
}
}
public class LoadGameObjectOperation : GameAsyncOperation
public class LoadGameObjectOperation : AsyncOperationBase
{
private enum ESteps
{
@@ -36,7 +36,7 @@ public class LoadGameObjectOperation : GameAsyncOperation
/// </summary>
public GameObject Go { private set; get; }
public LoadGameObjectOperation(string location, Vector3 position, Quaternion rotation, Transform parent, bool destroyGoOnRelease = false)
{
_location = location;
@@ -45,11 +45,11 @@ public class LoadGameObjectOperation : GameAsyncOperation
_parent = parent;
_destroyGoOnRelease = destroyGoOnRelease;
}
protected override void OnStart()
internal override void InternalStart()
{
_steps = ESteps.LoadAsset;
}
protected override void OnUpdate()
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -79,9 +79,6 @@ public class LoadGameObjectOperation : GameAsyncOperation
}
}
}
protected override void OnAbort()
{
}
/// <summary>
/// 释放资源句柄

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using YooAsset;
public class OperationHelper
{
/// <summary>
/// 开始一个业务实现的自定义异步任务
/// </summary>
public static void StartOperation(AsyncOperationBase operation)
{
OperationSystem.StartOperation(OperationSystem.GLOBAL_SCHEDULER_NAME, operation);
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ff8a96dd005f55346986f8a98aff8c99
guid: fd52bc7cb896369498d42a12081816ee
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -6,8 +6,6 @@ public static class OperationMonitor
{
public static void RegisterOperationCallback()
{
OperationSystem.RegisterStartCallback(OperationStartCallback);
OperationSystem.RegisterFinishCallback(OperationFinishCallback);
}
private static void OperationStartCallback(string packageName, AsyncOperationBase operation)

View File

@@ -19,7 +19,7 @@ public class Boot : MonoBehaviour
Application.runInBackground = true;
DontDestroyOnLoad(this.gameObject);
}
IEnumerator Start()
void Start()
{
// 游戏管理器
GameManager.Instance.Behaviour = this;
@@ -34,16 +34,12 @@ public class Boot : MonoBehaviour
var go = Resources.Load<GameObject>("PatchWindow");
GameObject.Instantiate(go);
// 开始补丁更新流程
var operation = new PatchOperation("DefaultPackage", PlayMode);
YooAssets.StartOperation(operation);
yield return operation;
// 设置默认的资源包
var gamePackage = YooAssets.GetPackage("DefaultPackage");
YooAssets.SetDefaultPackage(gamePackage);
// 切换到主页面场景
SceneEventDefine.ChangeToHomeScene.SendEventMessage();
// 补丁更新流程
PatchManager.Create("DefaultPackage", PlayMode);
PatchManager.Start();
}
private void Update()
{
PatchManager.Update();
}
}

View File

@@ -2,19 +2,23 @@
using System.Collections.Generic;
using UnityEngine;
using UniFramework.Machine;
using YooAsset;
internal class FsmStartGame : IStateNode
{
private PatchOperation _owner;
void IStateNode.OnCreate(StateMachine machine)
{
_owner = machine.Owner as PatchOperation;
}
void IStateNode.OnEnter()
{
PatchEventDefine.PatchStepsChange.SendEventMessage("开始游戏!");
_owner.SetFinish();
// 设置默认的资源包
var gamePackage = YooAssets.GetPackage("DefaultPackage");
YooAssets.SetDefaultPackage(gamePackage);
// 切换到主页面场景
SceneEventDefine.ChangeToHomeScene.SendEventMessage();
}
void IStateNode.OnUpdate()
{

View File

@@ -3,24 +3,13 @@ using UniFramework.Machine;
using UniFramework.Event;
using YooAsset;
public class PatchOperation : GameAsyncOperation
public static class PatchManager
{
private enum ESteps
private static readonly EventGroup _eventGroup = new EventGroup();
private static StateMachine _machine;
public static void Create(string packageName, EPlayMode playMode)
{
None,
Update,
Done,
}
private readonly EventGroup _eventGroup = new EventGroup();
private readonly StateMachine _machine;
private readonly string _packageName;
private ESteps _steps = ESteps.None;
public PatchOperation(string packageName, EPlayMode playMode)
{
_packageName = packageName;
// 注册监听事件
_eventGroup.AddListener<UserEventDefine.UserTryInitialize>(OnHandleEventMessage);
_eventGroup.AddListener<UserEventDefine.UserBeginDownloadWebFiles>(OnHandleEventMessage);
@@ -29,7 +18,7 @@ public class PatchOperation : GameAsyncOperation
_eventGroup.AddListener<UserEventDefine.UserTryDownloadWebFiles>(OnHandleEventMessage);
// 创建状态机
_machine = new StateMachine(this);
_machine = new StateMachine(null);
_machine.AddNode<FsmInitializePackage>();
_machine.AddNode<FsmRequestPackageVersion>();
_machine.AddNode<FsmUpdatePackageManifest>();
@@ -42,37 +31,19 @@ public class PatchOperation : GameAsyncOperation
_machine.SetBlackboardValue("PackageName", packageName);
_machine.SetBlackboardValue("PlayMode", playMode);
}
protected override void OnStart()
public static void Start()
{
_steps = ESteps.Update;
_machine.Run<FsmInitializePackage>();
}
protected override void OnUpdate()
public static void Update()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Update)
{
_machine.Update();
}
}
protected override void OnAbort()
{
}
public void SetFinish()
{
_steps = ESteps.Done;
_eventGroup.RemoveAllListener();
Status = EOperationStatus.Succeed;
Debug.Log($"Package {_packageName} patch done !");
_machine.Update();
}
/// <summary>
/// 接收事件
/// </summary>
private void OnHandleEventMessage(IEventMessage message)
private static void OnHandleEventMessage(IEventMessage message)
{
if (message is UserEventDefine.UserTryInitialize)
{