572 lines
20 KiB
C#
572 lines
20 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
|
||
namespace AssetManagement
|
||
{
|
||
/// <summary>
|
||
/// 统一处理加载和下载两个流程的基础单位
|
||
/// 外界访问资源包单位的唯一接口
|
||
/// </summary>
|
||
public class BundleRecord
|
||
{
|
||
// 注:已知问题 fileSize 可能不会在重新初始化后改变
|
||
// 但是显然,保存AssetPathData是更糟糕的选择
|
||
// 外部处理不当,会导致更新前挂起的资源会有进度条不正确的问题,但是不造成大的影响
|
||
public readonly int fileSize;
|
||
|
||
// 基本加载流程:
|
||
// 从UpdateHelper测试是否存在Persist,如果存在,试图从Persist加载
|
||
// 否则试图从Streaming加载
|
||
// 加载失败后,试图用资源服务器下载
|
||
public readonly BundleManager manager;
|
||
|
||
public readonly string name;
|
||
|
||
public BundleRecord(AssetDependencyItem pathData, BundleManager manager)
|
||
{
|
||
name = pathData.name;
|
||
fileSize = pathData.fileSize;
|
||
this.manager = manager;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否主动加载的资源包,主动加载需要主动释放
|
||
/// </summary>
|
||
public bool active { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 被其他资源包依赖次数
|
||
/// </summary>
|
||
public int depended { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 依赖的其他资源包
|
||
/// </summary>
|
||
// 注:只有主动加载会记录依赖,依赖加载的资源不记录
|
||
public List<BundleRecord> dependencies { get; private set; }
|
||
|
||
public AssetBundle assetBundle { get; private set; }
|
||
|
||
public BundleLoader loader { get; private set; }
|
||
public BundleDownloader downloader { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 当前加载状态
|
||
/// </summary>
|
||
public BundleState state { get; private set; }
|
||
|
||
// 当前是否处于使用状态
|
||
public bool isInUse { get; private set; }
|
||
// 是否已经下载过一次
|
||
// 加载失败时,如果没有下载记录,允许强行执行一次下载流程
|
||
public bool hasDownloaded { get; private set; }
|
||
|
||
public event UnityAction<BundleRecord> onStateUpdate;
|
||
|
||
/// <summary>
|
||
/// 同步主动加载
|
||
/// </summary>
|
||
// 注:这个几把玩意现在有已知bug - 如果被依赖包出现Error状态,将不会透传到上层
|
||
public void ActiveLoadSync()
|
||
{
|
||
#if UNITY_EDITOR
|
||
BundleDebugManager.AppendBundle(name);
|
||
#endif
|
||
var temp = state;
|
||
if (state == BundleState.Loading)
|
||
{
|
||
// 现在肯定是active状态,不需要搞过多判定
|
||
active = true;
|
||
// 使用特殊方法转异步到同步
|
||
if (dependencies != null)
|
||
{
|
||
// 注:需要先刷新全部依赖树的状态,然后刷新依赖状态
|
||
// 注:这堆破烂很蛋疼,需要先更新全部,然后再发状态改变
|
||
var beforeState = dependencies.Select(a => a.state).ToArray();
|
||
foreach (var dependency in dependencies)
|
||
dependency.LoadDependencySync();
|
||
for (var i = 0; i < dependencies.Count; i++)
|
||
dependencies[i].SendStateUpdate(beforeState[i]);
|
||
// foreach (var dependency in dependencies)
|
||
// if (dependency.active)
|
||
// dependency.RefreshWaitDependencies();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 注:执行之前需要BundleManager进行全面检查
|
||
// 异步加载中的被调用情况,应该绝对过滤掉
|
||
// 这里不做过多异常处理
|
||
if (!active)
|
||
{
|
||
active = true;
|
||
if (dependencies == null)
|
||
dependencies = manager.GetDependencies(name);
|
||
// 注:需要全部依赖包加载完成,然后才能刷新本身active的依赖包
|
||
// 结构很诡异,AddDependencySync,因此做成private
|
||
// foreach (var dependency in dependencies)
|
||
// dependency.LoadSync();
|
||
var beforeState = dependencies.Select(a => a.state).ToArray();
|
||
foreach (var dependency in dependencies)
|
||
dependency.AddDependencySync();
|
||
for (var i = 0; i < dependencies.Count; i++)
|
||
dependencies[i].SendStateUpdate(beforeState[i]);
|
||
// foreach (var dependency in dependencies)
|
||
// if (dependency.active)
|
||
// dependency.RefreshWaitDependencies();
|
||
}
|
||
}
|
||
UpdateInUse();
|
||
LoadSync();
|
||
state = assetBundle ? BundleState.WaitDependency : BundleState.Error;
|
||
RefreshWaitDependencies();
|
||
SendStateUpdate(temp);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步依赖加载
|
||
/// </summary>
|
||
private void AddDependencySync()
|
||
{
|
||
depended = Mathf.Max(0, depended) + 1;
|
||
UpdateInUse();
|
||
LoadDependencySync();
|
||
}
|
||
|
||
private void LoadDependencySync()
|
||
{
|
||
// 注:执行之前需要BundleManager进行全面检查
|
||
// 异步加载中的被调用情况,应该绝对过滤掉
|
||
// 这里不做过多异常处理
|
||
LoadSync();
|
||
// 同步加载不处理active被依赖的流程 - 全部给上层处理
|
||
state = assetBundle ? BundleState.Done : BundleState.Error;
|
||
}
|
||
|
||
private void LoadSync()
|
||
{
|
||
if (!assetBundle)
|
||
{
|
||
// 编辑器额外检查下路径提前报错
|
||
// 不节约这点性能了,运行时报错看着好看点
|
||
// #if UNITY_EDITOR
|
||
var pathState = manager.assetPathHub.GetPathState(name);
|
||
switch (pathState)
|
||
{
|
||
case AssetPathState.online:
|
||
Debug.LogError(string.Format("Unable to sync load online asset {0}!", name));
|
||
break;
|
||
case AssetPathState.unmarked:
|
||
Debug.LogError(string.Format("Unable to sync load unmarked asset {0}!", name));
|
||
break;
|
||
default:
|
||
{
|
||
// #endif
|
||
if (loader != null)
|
||
{
|
||
manager.loadHub.onComplete -= OnLoadComplete;
|
||
manager.loadHub.StopUnit(loader);
|
||
}
|
||
var path = manager.assetPathHub.GetPath(name);
|
||
assetBundle = AssetBundle.LoadFromFile(path + AssetConst.bundleVariant);
|
||
// #if UNITY_EDITOR
|
||
break;
|
||
}
|
||
}
|
||
// #endif
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步依赖加载
|
||
/// </summary>
|
||
public void ActiveLoad()
|
||
{
|
||
#if UNITY_EDITOR
|
||
BundleDebugManager.AppendBundle(name);
|
||
#endif
|
||
if (!active)
|
||
{
|
||
active = true;
|
||
if (dependencies == null)
|
||
{
|
||
dependencies = manager.GetDependencies(name);
|
||
foreach (var dependency in dependencies)
|
||
dependency.onStateUpdate += OnDependencyUpdate;
|
||
}
|
||
|
||
foreach (var t in dependencies)
|
||
t.AddDependency();
|
||
// 特殊处理由依赖包升级到主动包的流程
|
||
if (state == BundleState.Done)
|
||
{
|
||
state = BundleState.WaitDependency;
|
||
RefreshWaitDependencies();
|
||
}
|
||
|
||
RefreshInUse();
|
||
}
|
||
}
|
||
|
||
public void ActiveUnload()
|
||
{
|
||
if (active)
|
||
{
|
||
active = false;
|
||
if (dependencies != null)
|
||
foreach (var t in dependencies)
|
||
t.RemoveDependency();
|
||
RefreshInUse();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步依赖加载
|
||
/// </summary>
|
||
public void AddDependency()
|
||
{
|
||
depended = Mathf.Max(0, depended) + 1;
|
||
RefreshInUse();
|
||
}
|
||
|
||
public void RemoveDependency()
|
||
{
|
||
depended = Mathf.Max(0, depended - 1);
|
||
RefreshInUse();
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
manager.downloadHub.onComplete -= OnDownloadComplete;
|
||
manager.loadHub.onComplete -= OnLoadComplete;
|
||
RemoveDependencyListen();
|
||
}
|
||
|
||
public float GetTotalFinishBytes()
|
||
{
|
||
var bytes = GetSelfFinishBytes();
|
||
if (dependencies != null)
|
||
{
|
||
foreach (var dependency in dependencies)
|
||
{
|
||
bytes += dependency.GetSelfFinishBytes();
|
||
}
|
||
}
|
||
|
||
return bytes;
|
||
}
|
||
|
||
public float GetSelfFinishBytes()
|
||
{
|
||
return GetSelfProgress() * fileSize;
|
||
}
|
||
|
||
public float GetTotalProgress()
|
||
{
|
||
var finishBytes = GetTotalFinishBytes();
|
||
float totalBytes = fileSize;
|
||
if (dependencies != null)
|
||
foreach (var dependency in dependencies)
|
||
totalBytes += dependency.fileSize;
|
||
return finishBytes / totalBytes;
|
||
}
|
||
|
||
public float GetSelfProgress()
|
||
{
|
||
float progress;
|
||
switch (state)
|
||
{
|
||
case BundleState.Done:
|
||
case BundleState.WaitDependency:
|
||
case BundleState.Error:
|
||
{
|
||
progress = 1f;
|
||
}
|
||
break;
|
||
case BundleState.WaitUnload:
|
||
case BundleState.Unload:
|
||
{
|
||
progress = 0f;
|
||
}
|
||
break;
|
||
case BundleState.Downloading:
|
||
progress = downloader != null ? downloader.GetProgress() : 0f;
|
||
break;
|
||
case BundleState.Loading:
|
||
progress = loader != null ? loader.GetProgress() : 0f;
|
||
break;
|
||
default:
|
||
progress = 0f;
|
||
throw new ArgumentOutOfRangeException();
|
||
}
|
||
return progress;
|
||
}
|
||
|
||
private void RefreshInUse()
|
||
{
|
||
if (UpdateInUse())
|
||
{
|
||
if (name == "ui/sprite/login")
|
||
Debug.LogWarning("Update In Use: " + isInUse + " Active: " + active);
|
||
if (isInUse)
|
||
TryLoadAssetBundle();
|
||
else
|
||
TryUnloadAssetBundle();
|
||
}
|
||
}
|
||
|
||
private bool UpdateInUse()
|
||
{
|
||
var willInUse = active || depended > 0;
|
||
var temp = willInUse != isInUse;
|
||
isInUse = willInUse;
|
||
return temp;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 如果有主动和被动加载引用,并且AssetBundle尚未加载,就开始加载
|
||
/// </summary>
|
||
private void TryLoadAssetBundle()
|
||
{
|
||
var temp = state;
|
||
// 自身已经加成完成的情况
|
||
if (state != BundleState.Done && state != BundleState.WaitDependency)
|
||
{
|
||
if (assetBundle == null)
|
||
{
|
||
// 从等待加载完成后卸载 复活为加载中
|
||
if (state == BundleState.WaitUnload)
|
||
state = BundleState.Loading;
|
||
else
|
||
{
|
||
var pathType = manager.assetPathHub.GetPathState(name);
|
||
switch (pathType)
|
||
{
|
||
case AssetPathState.online:
|
||
StartDownload();
|
||
break;
|
||
case AssetPathState.persist:
|
||
case AssetPathState.streaming:
|
||
{
|
||
loader = manager.loadHub.LoadUnit(name);
|
||
if (loader != null)
|
||
{
|
||
manager.loadHub.onComplete += OnLoadComplete;
|
||
state = BundleState.Loading;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("Failed to start load unit for " + name);
|
||
state = BundleState.Error;
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
{
|
||
Debug.LogError("Unhandled bundle path type " + pathType);
|
||
state = BundleState.Error;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else if (active)
|
||
{
|
||
state = BundleState.WaitDependency;
|
||
RefreshWaitDependencies();
|
||
}
|
||
else
|
||
state = BundleState.Done;
|
||
}
|
||
SendStateUpdate(temp);
|
||
}
|
||
|
||
private void StartDownload()
|
||
{
|
||
hasDownloaded = true;
|
||
// 主动下载优先度调整为 1
|
||
downloader = manager.downloadHub.LoadUnit(name, 1);
|
||
if (downloader != null)
|
||
{
|
||
manager.downloadHub.onComplete += OnDownloadComplete;
|
||
state = BundleState.Downloading;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("Failed to start download unit for " + name);
|
||
state = BundleState.Error;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 如果主动和被动加载引用都清空了,开始卸载AssetBundle
|
||
/// </summary>
|
||
private void TryUnloadAssetBundle()
|
||
{
|
||
var temp = state;
|
||
if (assetBundle != null)
|
||
try
|
||
{
|
||
assetBundle.Unload(true);
|
||
assetBundle = null;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError(e);
|
||
}
|
||
|
||
if (loader != null)
|
||
{
|
||
state = manager.loadHub.StopUnit(loader) ? BundleState.Unload : BundleState.WaitUnload;
|
||
if (state != BundleState.WaitUnload)
|
||
manager.loadHub.onComplete -= OnLoadComplete;
|
||
}
|
||
else if (downloader != null)
|
||
{
|
||
state = manager.downloadHub.StopUnit(downloader) ? BundleState.Unload : BundleState.WaitUnload;
|
||
if (state != BundleState.WaitUnload)
|
||
manager.downloadHub.onComplete -= OnDownloadComplete;
|
||
}
|
||
else
|
||
{
|
||
state = BundleState.Unload;
|
||
}
|
||
|
||
SendStateUpdate(temp);
|
||
}
|
||
|
||
private void OnDownloadComplete(BundleDownloader unit)
|
||
{
|
||
var temp = state;
|
||
var load = false;
|
||
if (unit == downloader)
|
||
{
|
||
manager.downloadHub.onComplete -= OnDownloadComplete;
|
||
if (downloader.state == AsyncLoadUnitState.Done)
|
||
{
|
||
// 如果是WaitUnload状态,直接析构加载流程
|
||
if (state == BundleState.WaitUnload)
|
||
state = BundleState.Unload;
|
||
else
|
||
load = true;
|
||
}
|
||
else
|
||
{
|
||
state = BundleState.Error;
|
||
}
|
||
|
||
downloader = null;
|
||
}
|
||
|
||
if (load)
|
||
TryLoadAssetBundle();
|
||
SendStateUpdate(temp);
|
||
}
|
||
|
||
private void OnLoadComplete(AsyncLoadUnit unit)
|
||
{
|
||
var temp = state;
|
||
var unload = false;
|
||
if (unit == loader)
|
||
{
|
||
manager.loadHub.onComplete -= OnLoadComplete;
|
||
if (loader.state == AsyncLoadUnitState.Done)
|
||
assetBundle = loader.bundle;
|
||
if (!assetBundle)
|
||
state = BundleState.Error;
|
||
else
|
||
{
|
||
// 如果是WaitUnload状态,直接析构加载的Bundle
|
||
if (state == BundleState.WaitUnload)
|
||
unload = true;
|
||
else
|
||
state = BundleState.WaitDependency;
|
||
}
|
||
|
||
loader = null;
|
||
}
|
||
|
||
if (unload)
|
||
{
|
||
TryUnloadAssetBundle();
|
||
}
|
||
else
|
||
{
|
||
if (state == BundleState.WaitDependency)
|
||
RefreshWaitDependencies();
|
||
if (state == BundleState.Done)
|
||
RemoveDependencyListen();
|
||
else if (state == BundleState.Error)
|
||
{
|
||
manager.assetPathHub.LoadError(name);
|
||
if (hasDownloaded)
|
||
RemoveDependencyListen();
|
||
else
|
||
StartDownload();
|
||
}
|
||
SendStateUpdate(temp);
|
||
}
|
||
}
|
||
|
||
private void RefreshWaitDependencies()
|
||
{
|
||
if (dependencies == null)
|
||
{
|
||
state = BundleState.Done;
|
||
}
|
||
else
|
||
{
|
||
state = BundleState.Done;
|
||
foreach (var dependency in dependencies)
|
||
if (dependency.state == BundleState.Error)
|
||
{
|
||
state = BundleState.Error;
|
||
break;
|
||
}
|
||
else if (dependency.state != BundleState.Done)
|
||
{
|
||
state = BundleState.WaitDependency;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnDependencyUpdate(BundleRecord record)
|
||
{
|
||
if (state == BundleState.WaitDependency)
|
||
{
|
||
var temp = state;
|
||
RefreshWaitDependencies();
|
||
if (state == BundleState.Done || state == BundleState.Error)
|
||
RemoveDependency();
|
||
SendStateUpdate(temp);
|
||
}
|
||
}
|
||
|
||
private void RemoveDependencyListen()
|
||
{
|
||
if (dependencies != null)
|
||
foreach (var dependency in dependencies)
|
||
dependency.onStateUpdate -= OnDependencyUpdate;
|
||
}
|
||
|
||
private void SendStateUpdate(BundleState oldState)
|
||
{
|
||
if (oldState != state && onStateUpdate != null)
|
||
onStateUpdate.Invoke(this);
|
||
}
|
||
}
|
||
|
||
public enum BundleState
|
||
{
|
||
Unload, // 已经析构完成
|
||
WaitUnload, // 试图析构,但需要等待AssetBundle完成加载以防止泄漏
|
||
Downloading, // 自身下载中
|
||
Loading, // 自身加载中
|
||
WaitDependency, // 自身加载完成,等待依赖项加载完成
|
||
Done, // 全部加载完成,可以提取资源
|
||
Error // 资源加载失败。可能由于资源不存在或者其他网络问题
|
||
}
|
||
} |