using System;
using System.Collections;
using AOT.Boot;
using AOT.UI;
using UnityEngine;
using YooAsset;
namespace AOT.Patch
{
///
/// 启动补丁:version 门控 → YooAsset 初始化/更新 → 回调。
///
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 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",
cdnRoot = _settings.DefaultCdnRoot,
packages = new[] { _settings.PackageName },
};
_view?.SetStatus($"版本回退本地 ({versionError})");
}
else
{
_view?.SetStatus($"远程版本 {version.resVersion}");
}
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,
CdnRoot = cdnRoot,
PackageName = _settings.PackageName,
WsUrlTemplate = _settings.WsUrlTemplate,
};
#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?.SetStatus("请求资源版本…");
_view?.SetProgress(0.35f);
var versionOp = package.RequestPackageVersionAsync();
yield return versionOp;
if (versionOp.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"请求 PackageVersion 失败: {versionOp.Error}");
yield break;
}
_view?.SetStatus($"更新清单 {versionOp.PackageVersion}");
_view?.SetProgress(0.5f);
var manifestOp = package.LoadPackageManifestAsync(new LoadPackageManifestOptions(versionOp.PackageVersion, 60));
yield return manifestOp;
if (manifestOp.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"更新清单失败: {manifestOp.Error}");
yield break;
}
_view?.SetStatus("下载资源…");
var downloader = package.CreateResourceDownloader(new ResourceDownloaderOptions(10, 3));
if (downloader.TotalDownloadCount > 0)
{
downloader.DownloadProgressChanged += info =>
{
_view?.SetProgress(0.5f + info.Progress * 0.4f);
_view?.SetStatus($"下载 {info.CurrentDownloadCount}/{info.TotalDownloadCount}");
};
downloader.StartDownload();
yield return downloader;
if (downloader.Status != EOperationStatus.Succeeded)
{
onDone?.Invoke(null, $"下载失败: {downloader.Error}");
yield break;
}
}
_view?.SetProgress(0.95f);
_view?.SetStatus("资源就绪");
onDone?.Invoke(ctx, null);
}
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(_settings.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;
}
}
}