Files
AIGame/AIClient/Assets/AOT/Scripts/Patch/PatchRunner.cs
T

309 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using AOT.Boot;
using AOT.UI;
using UnityEngine;
using YooAsset;
namespace AOT.Patch
{
/// <summary>
/// 启动补丁:version 门控 → YooAsset 初始化/更新 → 回调。
/// </summary>
public sealed class PatchRunner
{
readonly MonoBehaviour _host;
readonly BootSettings _settings;
readonly BootPatchView _view;
public PatchRunner(MonoBehaviour host, BootSettings settings, BootPatchView view)
{
_host = host;
_settings = settings;
_view = view;
}
public IEnumerator Run(Action<BootContext, string> onDone)
{
_view?.SetStatus("正在检查更新…");
_view?.SetProgress(0.05f);
VersionInfo version = null;
string versionError = null;
yield return new VersionClient(_settings.VersionUrl, _settings.AllowStreamingVersionFallback)
.Fetch((info, err) =>
{
version = info;
versionError = err;
});
if (version == null)
{
// 无 CDN 时仍允许编辑器直通;真机 Host/Web 则失败。
if (!CanContinueWithoutVersion())
{
onDone?.Invoke(null, $"获取 version.json 失败: {versionError}");
yield break;
}
version = new VersionInfo
{
appVersionMin = _settings.AppVersion,
appVersionMax = _settings.AppVersion,
resVersion = "local",
packVersion = "local",
cdnRoot = _settings.DefaultCdnRoot,
packages = new[] { _settings.PackageName },
};
Debug.LogWarning($"[Boot] Local version fallback: {versionError}");
}
else
{
Debug.Log($"[Boot] Remote art={version.resVersion} pack={version.packVersion}");
}
if (VersionInfo.CompareVersion(_settings.AppVersion, version.appVersionMin) < 0)
{
onDone?.Invoke(null, $"壳版本过低,请更新微信小游戏包(当前 {_settings.AppVersion} < 最低 {version.appVersionMin})");
yield break;
}
string cdnRoot = string.IsNullOrEmpty(version.cdnRoot) ? _settings.DefaultCdnRoot : version.cdnRoot;
var ctx = new BootContext
{
AppVersion = _settings.AppVersion,
ResVersion = version.resVersion,
PackVersion = version.packVersion,
CdnRoot = cdnRoot,
PackageName = _settings.PackageName,
HttpBase = _settings.HttpBase,
WsUrlTemplate = _settings.WsUrlTemplate,
Notice = version.notice ?? string.Empty,
UiTheme = string.IsNullOrWhiteSpace(version.uiTheme) ? _settings.UiTheme : version.uiTheme,
};
#if UNITY_EDITOR
if (_settings.EditorDirectHotUpdate)
{
_view?.SetStatus("正在准备冒险…");
_view?.SetProgress(0.9f);
onDone?.Invoke(ctx, null);
yield break;
}
#endif
_view?.SetStatus("正在准备资源…");
_view?.SetProgress(0.15f);
if (!YooAssets.IsInitialized)
YooAssets.Initialize();
if (!YooAssets.TryGetPackage(_settings.PackageName, out var package))
package = YooAssets.CreatePackage(_settings.PackageName);
ctx.Package = package;
var playMode = ResolvePlayMode();
var initOp = CreateInitializeOperation(package, playMode, cdnRoot);
if (initOp == null)
{
onDone?.Invoke(null, $"不支持的 PlayMode: {playMode}");
yield break;
}
yield return initOp;
if (initOp.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"YooAsset 初始化失败: {initOp.Error}");
yield break;
}
_view?.SetProgress(0.35f);
yield return EnsureManifest(package, version.packVersion, "热更");
if (_stepError != null)
{
onDone?.Invoke(null, _stepError);
yield break;
}
_view?.SetProgress(0.4f);
_view?.SetStatus("正在下载资源…");
var downloader = package.CreateResourceDownloader(new ResourceDownloaderOptions(10, 3));
if (downloader.TotalDownloadCount > 0)
{
downloader.DownloadProgressChanged += info =>
{
_view?.SetProgress(0.4f + info.Progress * 0.15f);
_view?.SetStatus($"正在下载资源… {info.CurrentDownloadCount}/{info.TotalDownloadCount}");
};
downloader.StartDownload();
yield return downloader;
if (downloader.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"下载失败: {downloader.Error}");
yield break;
}
}
// UI Prefabs are AssetBundles in Art; DefaultPackage is the raw hot-update DLL.
// Initialize explicitly listed companion packages before gameplay starts.
var prepared = new System.Collections.Generic.HashSet<string>(StringComparer.Ordinal) { _settings.PackageName };
foreach (var packageName in version.packages ?? Array.Empty<string>())
{
if (string.IsNullOrWhiteSpace(packageName) || !prepared.Add(packageName)) continue;
if (!YooAssets.TryGetPackage(packageName, out var companion))
companion = YooAssets.CreatePackage(packageName);
var companionRoot = cdnRoot.TrimEnd('/');
var slash = companionRoot.LastIndexOf('/');
companionRoot = companionRoot.Substring(0, slash + 1) + packageName;
_view?.SetStatus("正在准备营地…");
if (companion.InitializeStatus != EOperationStatus.Succeeded)
{
var companionInit = CreateInitializeOperation(companion, playMode, companionRoot);
if (companionInit == null)
{
onDone?.Invoke(null, $"不支持资源包 {packageName} 的 PlayMode: {playMode}");
yield break;
}
yield return companionInit;
if (companionInit.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"资源包 {packageName} 初始化失败: {companionInit.Error}");
yield break;
}
}
var pinned = packageName == "Art" ? version.resVersion : null;
yield return EnsureManifest(companion, pinned, packageName);
if (_stepError != null)
{
onDone?.Invoke(null, _stepError);
yield break;
}
var companionDownload = companion.CreateResourceDownloader(new ResourceDownloaderOptions(10, 3));
if (companionDownload.TotalDownloadCount > 0)
{
_view?.SetStatus("正在下载营地资源…");
companionDownload.DownloadProgressChanged += info =>
{
_view?.SetProgress(.55f + info.Progress * .35f);
_view?.SetStatus($"正在下载资源… {info.CurrentDownloadCount}/{info.TotalDownloadCount}");
};
companionDownload.StartDownload();
yield return companionDownload;
if (companionDownload.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"资源包 {packageName} 下载失败: {companionDownload.Error}");
yield break;
}
}
}
_view?.SetProgress(0.95f);
_view?.SetStatus("资源就绪");
onDone?.Invoke(ctx, null);
}
string _stepError;
/// <summary>
/// pinnedVersion 来自 version.json:热更用 packVersion,Art 用 resVersion。
/// 为空时再回退到包目录里的 .version 文件。
/// </summary>
IEnumerator EnsureManifest(ResourcePackage package, string pinnedVersion, string label)
{
_stepError = null;
var packageVersion = pinnedVersion;
if (string.IsNullOrEmpty(packageVersion))
{
_view?.SetStatus("正在检查资源…");
var versionOp = package.RequestPackageVersionAsync();
yield return versionOp;
if (versionOp.Status != EOperationStatus.Succeeded)
{
_stepError = $"请求{label}版本失败: {versionOp.Error}";
yield break;
}
packageVersion = versionOp.PackageVersion;
}
_view?.SetStatus("正在检查资源…");
var manifestOp = package.LoadPackageManifestAsync(new LoadPackageManifestOptions(packageVersion, 60));
yield return manifestOp;
if (manifestOp.Status != EOperationStatus.Succeeded)
_stepError = $"更新{label}清单失败: {manifestOp.Error}";
}
bool CanContinueWithoutVersion()
{
#if UNITY_EDITOR
return _settings.EditorDirectHotUpdate;
#else
return false;
#endif
}
EPlayMode ResolvePlayMode()
{
#if UNITY_EDITOR
return _settings.PlayMode;
#elif UNITY_WEBGL
return EPlayMode.WebPlayMode;
#else
return _settings.PlayMode == EPlayMode.EditorSimulateMode
? EPlayMode.HostPlayMode
: _settings.PlayMode;
#endif
}
InitializePackageOperation CreateInitializeOperation(ResourcePackage package, EPlayMode playMode, string cdnRoot)
{
if (playMode == EPlayMode.EditorSimulateMode)
{
#if UNITY_EDITOR
var buildResult = EditorSimulateBuildInvoker.Build(package.PackageName, (int)EBundleType.VirtualAssetBundle);
var options = new EditorSimulateModeOptions
{
EditorFileSystemParameters = FileSystemParameters.CreateDefaultEditorFileSystemParameters(buildResult.PackageRootDirectory)
};
return package.InitializePackageAsync(options);
#else
return null;
#endif
}
if (playMode == EPlayMode.OfflinePlayMode)
{
var options = new OfflinePlayModeOptions
{
BuiltinFileSystemParameters = FileSystemParameters.CreateDefaultBuiltinFileSystemParameters()
};
return package.InitializePackageAsync(options);
}
if (playMode == EPlayMode.HostPlayMode)
{
var remote = new CdnRemoteService(cdnRoot);
var options = new HostPlayModeOptions
{
BuiltinFileSystemParameters = FileSystemParameters.CreateDefaultBuiltinFileSystemParameters(),
CacheFileSystemParameters = FileSystemParameters.CreateDefaultSandboxFileSystemParameters(remote)
};
options.BuiltinFileSystemParameters.AddParameter(EFileSystemParameter.CopyBuiltinPackageManifest, true);
return package.InitializePackageAsync(options);
}
if (playMode == EPlayMode.WebPlayMode)
{
// 微信小游戏正式接入时替换为 WechatFileSystem;M0 先用默认 WebNetwork。
var remote = new CdnRemoteService(cdnRoot);
var options = new WebPlayModeOptions
{
WebNetworkFileSystemParameters = FileSystemParameters.CreateDefaultWebNetworkFileSystemParameters(remote, true)
};
return package.InitializePackageAsync(options);
}
return null;
}
}
}