ZK_Framework/Assets/Scripts/Mono/ResourceComponent/ResourceUpdatePackageHandle.cs

430 lines
18 KiB
C#

using System;
using System.Collections;
using System.IO;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using YooAsset;
namespace Game
{
public class ResourceUpdatePackageHandle
{
public EPlayMode playMode;
public string packageName;
public string buildPipeline;
public ResourceUpdatePackageHandle(EPlayMode playMode, string packageName, string buildPipeline)
{
this.playMode = playMode;
this.packageName = packageName;
this.buildPipeline = buildPipeline;
EventManager.Instance.Subscribe(UpdatePackageCallbackEventArgs.EventId, UpdatePackageCallbackEvent);
EventManager.Instance.Subscribe(EnterSingleModeEventArgs.EventId, EnterSingleModeEvent);
}
private void EnterSingleModeEvent(object sender, GameEventArgs e)
{
this.playMode = EPlayMode.OfflinePlayMode;
this.InitPackageAsync().Forget();
}
private void UpdatePackageCallbackEvent(object sender, GameEventArgs e)
{
var args = e as UpdatePackageCallbackEventArgs;
switch (args.callbackType)
{
case UpdatePackageCallbackType.:
this.InitPackageAsync().Forget();
break;
case UpdatePackageCallbackType.:
this.BeginDownloadAsync().Forget();
break;
case UpdatePackageCallbackType.:
this.UpdatePackageVersionAsync().Forget();
break;
case UpdatePackageCallbackType.:
this.UpdateManifestAsync().Forget();
break;
case UpdatePackageCallbackType.:
this.CreateDownloaderAsync().Forget();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public async UniTask InitPackageAsync(CancellationToken token = default)
{
// 创建资源包裹类
var package = YooAssets.TryGetPackage(packageName);
if (package == null)
package = YooAssets.CreatePackage(packageName);
else
{
YooAssets.DestroyPackage(this.packageName);
package = YooAssets.CreatePackage(packageName);
}
// 编辑器下的模拟模式
InitializationOperation initializationOperation = null;
if (playMode == EPlayMode.EditorSimulateMode)
{
var createParameters = new EditorSimulateModeParameters();
createParameters.SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(buildPipeline, packageName);
initializationOperation = package.InitializeAsync(createParameters);
}
// 单机运行模式
if (playMode == EPlayMode.OfflinePlayMode)
{
var createParameters = new OfflinePlayModeParameters();
createParameters.DecryptionServices = new FileStreamDecryption();
initializationOperation = package.InitializeAsync(createParameters);
}
// 联机运行模式
if (playMode == EPlayMode.HostPlayMode)
{
string defaultHostServer = GetHostServerURL();
string fallbackHostServer = GetHostServerURL();
var createParameters = new HostPlayModeParameters();
createParameters.DecryptionServices = new FileStreamDecryption();
createParameters.BuildinQueryServices = new GameQueryServices();
createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
initializationOperation = package.InitializeAsync(createParameters);
}
// WebGL运行模式
if (playMode == EPlayMode.WebPlayMode)
{
string defaultHostServer = GetHostServerURL();
string fallbackHostServer = GetHostServerURL();
var createParameters = new WebPlayModeParameters();
createParameters.DecryptionServices = new FileStreamDecryption();
createParameters.BuildinQueryServices = new GameQueryServices();
createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
initializationOperation = package.InitializeAsync(createParameters);
}
await initializationOperation;
// 如果初始化失败弹出提示界面
if (initializationOperation.Status != EOperationStatus.Succeed)
{
Debug.LogWarning($"{initializationOperation.Error}");
// 初始化失败
EventManager.Instance.FireNow(InitializeFailedEventArgs.EventId, new InitializeFailedEventArgs());
}
else
{
var version = initializationOperation.PackageVersion;
Debug.Log($"Init resource package version : {version}");
await this.UpdatePackageVersionAsync(token);
}
}
#region InitHelper
/// <summary>
/// 获取资源服务器地址
/// </summary>
private string GetHostServerURL()
{
//string hostServerIP = "http://10.0.2.2"; //安卓模拟器地址
#if UNITY_EDITOR
string hostServerIP = "http://127.0.0.1:8080";
#else
string hostServerIP = "http://192.168.0.124:8080";
#endif
string appVersion = "v1.0";
#if UNITY_EDITOR
if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android)
return $"{hostServerIP}/CDN/Android/{appVersion}";
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.iOS)
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL)
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
else
return $"{hostServerIP}/CDN/PC/{appVersion}";
#else
if (Application.platform == RuntimePlatform.Android)
return $"{hostServerIP}/CDN/Android/{appVersion}";
else if (Application.platform == RuntimePlatform.IPhonePlayer)
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
else if (Application.platform == RuntimePlatform.WebGLPlayer)
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
else
return $"{hostServerIP}/CDN/PC/{appVersion}";
#endif
}
/// <summary>
/// 远端资源地址查询服务类
/// </summary>
private class RemoteServices : IRemoteServices
{
private readonly string _defaultHostServer;
private readonly string _fallbackHostServer;
public RemoteServices(string defaultHostServer, string fallbackHostServer)
{
_defaultHostServer = defaultHostServer;
_fallbackHostServer = fallbackHostServer;
}
string IRemoteServices.GetRemoteMainURL(string fileName)
{
return $"{_defaultHostServer}/{fileName}";
}
string IRemoteServices.GetRemoteFallbackURL(string fileName)
{
return $"{_fallbackHostServer}/{fileName}";
}
}
/// <summary>
/// 资源文件流加载解密类
/// </summary>
private class FileStreamDecryption : IDecryptionServices
{
/// <summary>
/// 同步方式获取解密的资源包对象
/// 注意:加载流对象在资源包对象释放的时候会自动释放
/// </summary>
AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
{
BundleStream bundleStream = new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);
managedStream = bundleStream;
return AssetBundle.LoadFromStream(bundleStream, fileInfo.ConentCRC, GetManagedReadBufferSize());
}
/// <summary>
/// 异步方式获取解密的资源包对象
/// 注意:加载流对象在资源包对象释放的时候会自动释放
/// </summary>
AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
{
BundleStream bundleStream = new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);
managedStream = bundleStream;
return AssetBundle.LoadFromStreamAsync(bundleStream, fileInfo.ConentCRC, GetManagedReadBufferSize());
}
private static uint GetManagedReadBufferSize()
{
return 1024;
}
}
/// <summary>
/// 资源文件偏移加载解密类
/// </summary>
private class FileOffsetDecryption : IDecryptionServices
{
/// <summary>
/// 同步方式获取解密的资源包对象
/// 注意:加载流对象在资源包对象释放的时候会自动释放
/// </summary>
AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
{
managedStream = null;
return AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
}
/// <summary>
/// 异步方式获取解密的资源包对象
/// 注意:加载流对象在资源包对象释放的时候会自动释放
/// </summary>
AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
{
managedStream = null;
return AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
}
private static ulong GetFileOffset()
{
return 32;
}
}
#endregion
string packageVersion; // 最新资源版本
private async UniTask UpdatePackageVersionAsync(CancellationToken token = default)
{
Debug.Log("获取最新的资源版本 !");
await new WaitForSecondsRealtime(0.5f);
// var packageName = (string)_machine.GetBlackboardValue("PackageName");
var package = YooAssets.GetPackage(packageName);
var operation = package.UpdatePackageVersionAsync();
await operation;
if (operation.Status != EOperationStatus.Succeed)
{
Debug.LogWarning(operation.Error);
// PatchEventDefine.PackageVersionUpdateFailed.SendEventMessage();
EventManager.Instance.FireNow(PackageVersionUpdateFailedEventArgs.EventId, new PackageVersionUpdateFailedEventArgs());
Debug.Log("获取最新的资源版本失败!");
}
else
{
// _machine.SetBlackboardValue("PackageVersion", operation.PackageVersion);
// _machine.ChangeState<FsmUpdatePackageManifest>();
this.packageVersion = operation.PackageVersion;
Debug.Log($"最新资源版本为:{operation.PackageVersion}");
await this.UpdateManifestAsync(token);
}
}
private async UniTask UpdateManifestAsync(CancellationToken token = default)
{
Debug.Log("更新资源清单!");
await new WaitForSecondsRealtime(0.5f);
// var packageName = (string)_machine.GetBlackboardValue("PackageName");
// var packageVersion = (string)_machine.GetBlackboardValue("PackageVersion");
var package = YooAssets.GetPackage(packageName);
bool savePackageVersion = true;
var operation = package.UpdatePackageManifestAsync(packageVersion, savePackageVersion);
await operation;
if (operation.Status != EOperationStatus.Succeed)
{
Debug.LogWarning(operation.Error);
// PatchEventDefine.PatchManifestUpdateFailed.SendEventMessage();
EventManager.Instance.FireNow(PatchManifestUpdateFailedEventArgs.EventId, new PatchManifestUpdateFailedEventArgs());
return;
}
else
{
// _machine.ChangeState<FsmCreatePackageDownloader>();
Debug.Log("更新资源清单完成!");
await this.CreateDownloaderAsync(token);
}
}
ResourceDownloaderOperation downloader;
private async UniTask CreateDownloaderAsync(CancellationToken token = default)
{
Debug.Log("创建补丁下载器!");
await new WaitForSecondsRealtime(0.5f);
// var packageName = (string)_machine.GetBlackboardValue("PackageName");
var package = YooAssets.GetPackage(packageName);
int downloadingMaxNum = 10;
int failedTryAgain = 3;
downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);
// _machine.SetBlackboardValue("Downloader", downloader);
if (downloader.TotalDownloadCount == 0)
{
Debug.Log("Not found any download files !");
// _machine.ChangeState<FsmUpdaterDone>();
await this.UpdaterDoneAsync(token);
}
else
{
// 发现新更新文件后,挂起流程系统
// TODO: 注意:开发者需要在下载前检测磁盘空间不足
int totalDownloadCount = downloader.TotalDownloadCount;
long totalDownloadBytes = downloader.TotalDownloadBytes;
// PatchEventDefine.FoundUpdateFiles.SendEventMessage(totalDownloadCount, totalDownloadBytes);
Debug.Log("发现新更新文件后,挂起流程系统");
EventManager.Instance.FireNow(DownloadProgressUpdateEventArgs.EventId, new FoundUpdateFilesEventArgs(totalDownloadCount, totalDownloadBytes));
// this.BeginDownloadAsync(token).Forget();
}
}
private async UniTask BeginDownloadAsync(CancellationToken token = default)
{
Debug.Log("开始下载补丁文件!");
downloader.OnDownloadErrorCallback = WebFileDownloadFailed;
downloader.OnDownloadProgressCallback = DownloadProgressUpdate;
downloader.BeginDownload();
await downloader;
// 检测下载结果
if (downloader.Status != EOperationStatus.Succeed)
return;
// _machine.ChangeState<FsmDownloadPackageOver>();
await this.DownloadPackageOverAsync(token);
void DownloadProgressUpdate(int totaldownloadcount, int currentdownloadcount, long totaldownloadbytes, long currentdownloadbytes)
{
EventManager.Instance.FireNow(DownloadProgressUpdateEventArgs.EventId, new DownloadProgressUpdateEventArgs(
totaldownloadcount, currentdownloadcount, totaldownloadbytes, currentdownloadbytes));
}
void WebFileDownloadFailed(string filename, string error)
{
Debug.Log(this.packageName + " : " + error);
EventManager.Instance.FireNow(WebFileDownloadFailedEventArgs.EventId, new WebFileDownloadFailedEventArgs(this.packageName, error));
}
}
private async UniTask DownloadPackageOverAsync(CancellationToken token = default)
{
Debug.Log("下载完成");
await UniTask.Yield();
await this.ClearPackageCacheAsync(token);
}
private async UniTask ClearPackageCacheAsync(CancellationToken token = default)
{
Debug.Log("清理未使用的缓存文件!");
await UniTask.Yield();
var package = YooAssets.GetPackage(packageName);
var operation = package.ClearUnusedCacheFilesAsync();
operation.Completed += Operation_Completed;
void Operation_Completed(YooAsset.AsyncOperationBase obj)
{
// _machine.ChangeState<FsmUpdaterDone>();
this.UpdaterDoneAsync(token).Forget();
}
}
private async UniTask UpdaterDoneAsync(CancellationToken token = default)
{
Debug.Log("流程更新完毕!");
await UniTask.Yield();
EventManager.Instance.FireNow(UpdaterDoneEventArgs.EventId, new UpdaterDoneEventArgs());
}
}
}
/// <summary>
/// 资源文件解密流
/// </summary>
public class BundleStream : FileStream
{
public const byte KEY = 64;
public BundleStream(string path, FileMode mode, FileAccess access, FileShare share) : base(path, mode, access, share)
{
}
public BundleStream(string path, FileMode mode) : base(path, mode)
{
}
public override int Read(byte[] array, int offset, int count)
{
var index = base.Read(array, offset, count);
for (int i = 0; i < array.Length; i++)
{
array[i] ^= KEY;
}
return index;
}
}