Add hot-update boot pipeline, UI bundles/atlas assets, and design docs.
Ignore AIClient/Library and AIClient/HybridCLRData permanently; include HybridCLR/YooAsset project setup and UI tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
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",
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user