diff --git a/Assets/YooAsset/Runtime/OperationSystem/AsyncOperationBase.cs b/Assets/YooAsset/Runtime/OperationSystem/AsyncOperationBase.cs index 9111262e..74746461 100644 --- a/Assets/YooAsset/Runtime/OperationSystem/AsyncOperationBase.cs +++ b/Assets/YooAsset/Runtime/OperationSystem/AsyncOperationBase.cs @@ -9,7 +9,6 @@ namespace YooAsset public abstract class AsyncOperationBase : IEnumerator, IComparable { private Action _callback; - private string _packageName = null; private int _whileFrame = 1000; /// @@ -47,17 +46,6 @@ namespace YooAsset /// public float Progress { get; protected set; } - /// - /// 所属包裹名称 - /// - public string PackageName - { - get - { - return _packageName; - } - } - /// /// 是否已经完成 /// @@ -76,10 +64,24 @@ namespace YooAsset { add { + if (value == null) + return; + if (IsDone) - value.Invoke(this); + { + try + { + value.Invoke(this); + } + catch (Exception ex) + { + YooLogger.Error($"Exception in completion callback: {ex}"); + } + } else + { _callback += value; + } } remove { @@ -118,14 +120,6 @@ namespace YooAsset return string.Empty; } - /// - /// 设置包裹名称 - /// - internal void SetPackageName(string packageName) - { - _packageName = packageName; - } - /// /// 添加子任务 /// @@ -194,8 +188,6 @@ namespace YooAsset if (IsDone && IsFinish == false) { IsFinish = true; - - // 进度百分百完成 Progress = 1f; // 结束记录 @@ -229,10 +221,28 @@ namespace YooAsset if (IsDone == false) { + InternalAbort(); Status = EOperationStatus.Failed; Error = "user abort"; YooLogger.Warning($"Async operaiton {this.GetType().Name} has been abort !"); - InternalAbort(); + } + } + + /// + /// 结束异步任务 + /// + internal void FinishOperation() + { + if (IsFinish == false) + { + IsFinish = true; + Progress = 1f; + + // 结束记录 + DebugEndRecording(); + + if (_taskCompletionSource != null) + _taskCompletionSource.TrySetResult(null); } } @@ -360,4 +370,4 @@ namespace YooAsset private TaskCompletionSource _taskCompletionSource; #endregion } -} \ No newline at end of file +} diff --git a/Assets/YooAsset/Runtime/OperationSystem/OperationScheduler.cs b/Assets/YooAsset/Runtime/OperationSystem/OperationScheduler.cs new file mode 100644 index 00000000..3eabac6f --- /dev/null +++ b/Assets/YooAsset/Runtime/OperationSystem/OperationScheduler.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; + +namespace YooAsset +{ + /// + /// 异步操作调度器 + /// + internal class OperationScheduler : IComparable + { + private readonly List _operations = new List(100); + private readonly List _newList = new List(100); + + /// + /// 所属包裹名称 + /// + public string PackageName { private set; get; } + + /// + /// 调度器优先级(值越大越优先) + /// + public int Priority { private set; get; } + + /// + /// 创建顺序(用于同优先级稳定排序) + /// + public int CreateIndex { private set; get; } + + + public OperationScheduler(string packageName, int priority, int createIndex) + { + PackageName = packageName; + Priority = priority; + CreateIndex = createIndex; + } + + /// + /// 开始处理异步操作 + /// + public void StartOperation(AsyncOperationBase operation) + { + _newList.Add(operation); + operation.StartOperation(); + + // 通知开始回调 + OperationSystem.InvokeStartCallback(PackageName, operation); + } + + /// + /// 更新调度器 + /// + public void Update() + { + // 移除已经完成的异步操作 + for (int i = _operations.Count - 1; i >= 0; i--) + { + var operation = _operations[i]; + 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(); + } + + // 更新进行中的异步操作 + for (int i = 0; i < _operations.Count; i++) + { + // 检查全局时间切片预算 + if (OperationSystem.IsBusy) + break; + + var operation = _operations[i]; + if (operation.IsFinish) + continue; + + operation.UpdateOperation(); + } + } + + /// + /// 清空并中止所有任务 + /// + public void ClearAll() + { + // 终止临时队列里的任务 + foreach (var operation in _newList) + { + operation.AbortOperation(); + } + _newList.Clear(); + + // 终止正在进行的任务 + foreach (var operation in _operations) + { + operation.AbortOperation(); + } + _operations.Clear(); + } + + /// + /// 获取调试信息 + /// + public List GetDebugOperationInfos() + { + int totalCount = _operations.Count + _newList.Count; + List result = new List(totalCount); + + // 包含正在执行的任务 + foreach (var operation in _operations) + { + var operationInfo = OperationSystem.GetDebugOperationInfo(operation); + result.Add(operationInfo); + } + + // 包含待处理的新任务 + foreach (var operation in _newList) + { + var operationInfo = OperationSystem.GetDebugOperationInfo(operation); + result.Add(operationInfo); + } + + return result; + } + + #region 排序接口实现 + public int CompareTo(OperationScheduler other) + { + // 优先级高的排前面 + int result = other.Priority.CompareTo(this.Priority); + if (result == 0) + { + // 优先级相同,按创建顺序 + result = this.CreateIndex.CompareTo(other.CreateIndex); + } + return result; + } + #endregion + } +} diff --git a/Assets/YooAsset/Runtime/OperationSystem/OperationScheduler.cs.meta b/Assets/YooAsset/Runtime/OperationSystem/OperationScheduler.cs.meta new file mode 100644 index 00000000..fa2797d8 --- /dev/null +++ b/Assets/YooAsset/Runtime/OperationSystem/OperationScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 919380cb845bb8146a03ed154644b89f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/OperationSystem/OperationSystem.cs b/Assets/YooAsset/Runtime/OperationSystem/OperationSystem.cs index e232ea7a..50cb53e6 100644 --- a/Assets/YooAsset/Runtime/OperationSystem/OperationSystem.cs +++ b/Assets/YooAsset/Runtime/OperationSystem/OperationSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -15,8 +15,14 @@ namespace YooAsset } #endif - private static readonly List _operations = new List(1000); - private static readonly List _newList = new List(1000); + // 全局调度器名称 + public const string GLOBAL_SCHEDULER_NAME = ""; + + private static readonly Dictionary _schedulerDic = new Dictionary(100); + private static readonly List _schedulerList = new List(100); + private static bool _schedulerListDirty = false; + private static int _createIndex = 0; + private static Action _startCallback = null; private static Action _finishCallback = null; @@ -25,12 +31,12 @@ namespace YooAsset private static long _frameTime; /// - /// 异步操作的最小时间片段 + /// 异步操作系统的每帧最大执行预算(毫秒) /// public static long MaxTimeSlice { set; get; } = long.MaxValue; /// - /// 处理器是否繁忙 + /// 异步操作系统是否繁忙 /// public static bool IsBusy { @@ -39,7 +45,10 @@ namespace YooAsset if (_watch == null) return false; - // NOTE : 单次调用开销约1微秒 + if (MaxTimeSlice == long.MaxValue) + return false; + + // 注意 : 单次调用开销约1微秒 return _watch.ElapsedMilliseconds - _frameTime >= MaxTimeSlice; } } @@ -51,6 +60,9 @@ namespace YooAsset public static void Initialize() { _watch = Stopwatch.StartNew(); + + // 创建全局调度器 + CreatePackageScheduler(GLOBAL_SCHEDULER_NAME, 0); } /// @@ -58,54 +70,23 @@ namespace YooAsset /// public static void Update() { - // 移除已经完成的异步操作 - // 注意:移除上一帧完成的异步操作,方便调试器接收到完整的信息! - for (int i = _operations.Count - 1; i >= 0; i--) + // 重新排序调度器 + if (_schedulerListDirty) { - var operation = _operations[i]; - if (operation.IsFinish) - { - _operations.RemoveAt(i); - - if (_finishCallback != null) - _finishCallback.Invoke(operation.PackageName, operation); - } + _schedulerListDirty = false; + _schedulerList.Sort(); } - // 添加新增的异步操作 - 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 checkBusy = MaxTimeSlice < long.MaxValue; + // 更新帧时间 _frameTime = _watch.ElapsedMilliseconds; - for (int i = 0; i < _operations.Count; i++) + + // 更新调度器 + for (int i = 0; i < _schedulerList.Count; i++) { - if (checkBusy && IsBusy) + if (IsBusy) break; - var operation = _operations[i]; - if (operation.IsFinish) - continue; - - operation.UpdateOperation(); + _schedulerList[i].Update(); } } @@ -114,8 +95,16 @@ namespace YooAsset /// public static void DestroyAll() { - _operations.Clear(); - _newList.Clear(); + // 清空所有调度器 + foreach (var scheduler in _schedulerList) + { + scheduler.ClearAll(); + } + _schedulerDic.Clear(); + _schedulerList.Clear(); + _schedulerListDirty = false; + _createIndex = 0; + _startCallback = null; _finishCallback = null; _watch = null; @@ -123,28 +112,48 @@ namespace YooAsset MaxTimeSlice = long.MaxValue; } + /// + /// 创建包裹调度器 + /// + internal static void CreatePackageScheduler(string packageName, int priority) + { + if (_schedulerDic.ContainsKey(packageName)) + { + throw new YooInternalException($"Package scheduler already exists: {packageName}"); + } + + var scheduler = new OperationScheduler(packageName, priority, _createIndex++); + _schedulerDic.Add(packageName, scheduler); + _schedulerList.Add(scheduler); + _schedulerListDirty = true; + } + + /// + /// 销毁包裹调度器 + /// + internal static void DestroyPackageScheduler(string packageName) + { + // 不允许销毁默认调度器 + if (packageName == GLOBAL_SCHEDULER_NAME) + { + throw new YooInternalException("Cannot destroy the global package scheduler!"); + } + + if (_schedulerDic.TryGetValue(packageName, out var scheduler)) + { + scheduler.ClearAll(); + _schedulerDic.Remove(packageName); + _schedulerList.Remove(scheduler); + } + } + /// /// 销毁包裹的所有任务 /// public static void ClearPackageOperation(string packageName) { - // 终止临时队列里的任务 - foreach (var operation in _newList) - { - if (operation.PackageName == packageName) - { - operation.AbortOperation(); - } - } - - // 终止正在进行的任务 - foreach (var operation in _operations) - { - if (operation.PackageName == packageName) - { - operation.AbortOperation(); - } - } + var scheduler = GetScheduler(packageName); + scheduler.ClearAll(); } /// @@ -152,12 +161,8 @@ namespace YooAsset /// public static void StartOperation(string packageName, AsyncOperationBase operation) { - _newList.Add(operation); - operation.SetPackageName(packageName); - operation.StartOperation(); - - if (_startCallback != null) - _startCallback.Invoke(packageName, operation); + var scheduler = GetScheduler(packageName); + scheduler.StartOperation(operation); } /// @@ -176,20 +181,55 @@ namespace YooAsset _finishCallback = callback; } + /// + /// 触发任务开始回调 + /// + internal static void InvokeStartCallback(string packageName, AsyncOperationBase operation) + { + _startCallback?.Invoke(packageName, operation); + } + + /// + /// 触发任务完成回调 + /// + internal static void InvokeFinishCallback(string packageName, AsyncOperationBase operation) + { + _finishCallback?.Invoke(packageName, operation); + } + + /// + /// 获取调度器(严格模式) + /// + 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!"); + } + #region 调试信息 internal static List GetDebugOperationInfos(string packageName) { - List result = new List(_operations.Count); - foreach (var operation in _operations) + // 空包名路由到默认调度器 + if (string.IsNullOrEmpty(packageName)) + packageName = GLOBAL_SCHEDULER_NAME; + + if (_schedulerDic.TryGetValue(packageName, out var scheduler)) { - if (operation.PackageName == packageName) - { - var operationInfo = GetDebugOperationInfo(operation); - result.Add(operationInfo); - } + return scheduler.GetDebugOperationInfos(); } - return result; + + return new List(); } + internal static DebugOperationInfo GetDebugOperationInfo(AsyncOperationBase operation) { var operationInfo = new DebugOperationInfo(); @@ -210,4 +250,4 @@ namespace YooAsset } #endregion } -} \ No newline at end of file +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operation/DownloaderOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operation/DownloaderOperation.cs index b87bcd67..de063aad 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operation/DownloaderOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operation/DownloaderOperation.cs @@ -108,9 +108,6 @@ namespace YooAsset _downloadingMaxNumber = UnityEngine.Mathf.Clamp(downloadingMaxNumber, 1, MAX_LOADER_COUNT); ; _failedTryAgain = failedTryAgain; - // 设置包裹名称 (fix #210) - SetPackageName(packageName); - // 统计下载信息 CalculatDownloaderInfo(); } diff --git a/Assets/YooAsset/Runtime/YooAssets.cs b/Assets/YooAsset/Runtime/YooAssets.cs index 490bb574..635b1822 100644 --- a/Assets/YooAsset/Runtime/YooAssets.cs +++ b/Assets/YooAsset/Runtime/YooAssets.cs @@ -114,6 +114,16 @@ namespace YooAsset /// /// 包裹名称 public static ResourcePackage CreatePackage(string packageName) + { + return CreatePackage(packageName, 0); + } + + /// + /// 创建资源包裹 + /// + /// 包裹名称 + /// 包裹优先级(值越大越优先更新) + public static ResourcePackage CreatePackage(string packageName, int packagePriority) { CheckException(packageName); if (ContainsPackage(packageName)) @@ -122,6 +132,10 @@ namespace YooAsset YooLogger.Log($"Create resource package : {packageName}"); ResourcePackage package = new ResourcePackage(packageName); _packages.Add(package); + + // 注册包裹调度器 + OperationSystem.CreatePackageScheduler(packageName, packagePriority); + return package; } @@ -185,6 +199,10 @@ namespace YooAsset } YooLogger.Log($"Remove resource package : {packageName}"); + + // 先销毁调度器,再移除包裹 + OperationSystem.DestroyPackageScheduler(packageName); + _packages.Remove(package); return true; }