NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/HotUpdate/GameLauncher.cs

483 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using HybridCLR;
using Sirenix.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
public class GameLauncher : MonoBehaviour
{
private class MethodExecutionInfo
{
public MethodInfo method;
public int sequnence;
public MethodExecutionInfo(MethodInfo method, int sequnence)
{
this.method = method;
this.sequnence = sequnence;
}
}
[Serializable]
private class DownloadContent
{
public List<string> catalogs = new();
}
const string START_SCENE_NAME = "Assets/Scenes/StartScene.unity";
const string META_DATA_DLL_PATH = "Assets/HotUpdateDlls/MetaDataDll/";
const string HOT_UPDATE_DLL_PATH = "Assets/HotUpdateDlls/HotUpdateDll/";
const string PHXH_DLL_NAME = "Phxh.dll";
const string FRAMEWORK_DLL_NAME = "FrameWork.dll";
const string GAMEPLAY_DLL_NAME = "GamePlay.dll";
//记录在playerPres里的需要下载的catalogs的ID
const string DOWNLOAD_CATALOGS_ID = "DownloadCatalogs";
//最大能容忍的下载卡住时长,一旦超过则重新开始下载
// private const float DOWNLOAD_STUCK_TIME_MAX = 10;
private Coroutine _launchCoroutine;
private byte[] _dllBytes;
private Dictionary<string, Assembly> _allHotUpdateAssemblies = new();
//version update
private AsyncOperationHandle _downloadOP;
private UI_VersionUpdate _versionUpdateUI;
private Type _versionUpdaterType;
// private float _downloadProgress;
// private float _downloadStuckTime;
private List<object> _KeysNeedToDownload = new();
//此对象里保存了需要下载的catalog每次获取新的catalog会将此对象保存到手机上如果在下载的过程中关闭了游戏下次打开还能拿到catalog继续下载
private DownloadContent _downloadContent = new();
private bool HasContentToDownload => _downloadContent != null && _downloadContent.catalogs != null &&
_downloadContent.catalogs.Count > 0;
private readonly List<string> _phxhDependencyDlls = new List<string>()
{
};
private readonly List<string> _frameWorkDependencyDlls = new List<string>()
{
//不要随意改顺序!
};
private readonly List<string> _gamePlayDependencyDlls = new List<string>()
{
//不要随意改顺序!
"BehaviorDesigner.Runtime.dll",
//"BestHTTP.dll",
"DOTweenCSharp.dll",
};
//所有使用了RuntimeInitializeOnLoadMethod attribute的程序集
private readonly List<string> _hasRuntimeInitializeOnLoadMethodAssemblies = new List<string>()
{
"BehaviorDesigner.Runtime",
//"BestHTTP",
"GamePlay",
};
public bool enableHotUpdate = true;
private void Start()
{
_launchCoroutine = StartCoroutine(Launch());
}
private void OnDestroy()
{
StopCoroutine(_launchCoroutine);
_launchCoroutine = null;
}
// private void _SafeProtect()
// {
// var typeList = new List<Type>();
// typeList.Add(typeof(BestHTTP.HTTPRequest));
// typeList.Add(typeof(BestHTTP.HTTPResponse));
// for (int i = 0; i < typeList.Count; i++)
// {
// var type = typeList[i];
// DebugUtil.LogError("type = {0}", type);
// }
// }
private IEnumerator Launch()
{
// _SafeProtect();
#if UNITY_EDITOR
enableHotUpdate = false;
#endif
Debug.Log($"Launch Game! enableHotUpdate:{enableHotUpdate}");
yield return VersionCheck();
if (HasContentToDownload)
yield return VersionUpdate();
if (enableHotUpdate)
yield return LoadAssemblies();
yield return EnterGame();
}
private IEnumerator VersionCheck()
{
//todo 如果包体版本不一致则提示用户去app store重新下载
var checkUpdateOP = Addressables.CheckForCatalogUpdates(false);
yield return checkUpdateOP;
if (checkUpdateOP.Status == AsyncOperationStatus.Succeeded)
{
_downloadContent.catalogs = checkUpdateOP.Result;
if (HasContentToDownload)
{
//如果服务器上的catalog和本地不一致则覆盖上一次需要下载的被内容
var jsonStr = JsonUtility.ToJson(_downloadContent);
PlayerPrefs.SetString(DOWNLOAD_CATALOGS_ID, jsonStr);
PlayerPrefs.Save();
}
else
{
if (PlayerPrefs.HasKey(DOWNLOAD_CATALOGS_ID))
{
var jsonStr = PlayerPrefs.GetString(DOWNLOAD_CATALOGS_ID);
JsonUtility.FromJsonOverwrite(jsonStr, _downloadContent);
}
}
if (HasContentToDownload)
{
var updateCatalogOP = Addressables.UpdateCatalogs(_downloadContent.catalogs, false);
yield return updateCatalogOP;
if (updateCatalogOP.Status == AsyncOperationStatus.Succeeded)
{
_KeysNeedToDownload.Clear();
foreach (var resourceLocator in updateCatalogOP.Result)
{
_KeysNeedToDownload.AddRange(resourceLocator.Keys);
}
}
else
{
Debug.LogError($"Update catalog failed!exception:{updateCatalogOP.OperationException.Message}");
}
Addressables.Release(updateCatalogOP);
}
}
else
{
Debug.LogError($"CheckUpdate failed!exception:{checkUpdateOP.OperationException.Message}");
}
Addressables.Release(checkUpdateOP);
yield return ReloadAddressableCatalog();
Debug.Log($"版本检查结束,是否有需要下载的内容:{HasContentToDownload}");
}
private IEnumerator VersionUpdate()
{
yield return OpenVersionUpdateUI();
yield return Download();
Debug.Log($"版本更新结束!");
}
private IEnumerator LoadAssemblies()
{
yield return LoadPhxhDependencyAssemblies();
yield return LoadPhxhAssembies();
yield return LoadFrameworkDependencyAssemblies();
yield return LoadFrameWorkAssemblies();
yield return LoadMetadataForAOTAssemblies();
yield return LoadGamePlayDependencyAssemblies();
yield return LoadGamePlayAssemblies();
yield return ReloadAddressableCatalog();
ExecuteRuntimeInitializeOnLoadMethodAttribute();
Debug.Log("加载程序集结束!");
yield return null;
}
//补充元数据
private IEnumerator LoadMetadataForAOTAssemblies()
{
var aotAssemblies = AOTGenericReferences.PatchedAOTAssemblyList;
if (aotAssemblies == null)
{
Debug.LogError($"LoadMetadataForAOTAssemblies failed, PatchedAOTAssemblyList is null!");
yield break;
}
foreach (var aotDllName in aotAssemblies)
{
var path = $"{META_DATA_DLL_PATH}{aotDllName}.bytes";
yield return ReadDllBytes(path);
if (_dllBytes != null)
{
var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(_dllBytes, HomologousImageMode.SuperSet);
Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. ret:{err}");
}
}
Debug.Log("补充元数据结束!");
}
private IEnumerator LoadPhxhDependencyAssemblies()
{
foreach (var dllName in _phxhDependencyDlls)
{
yield return LoadSingleHotUpdateAssembly(dllName);
}
Debug.Log("加载Phxh依赖程序集结束!");
}
private IEnumerator LoadPhxhAssembies()
{
yield return LoadSingleHotUpdateAssembly(PHXH_DLL_NAME);
Debug.Log("加载Phxh程序集结束!");
}
//加载FrameWork依赖的第三方热更序集
private IEnumerator LoadFrameworkDependencyAssemblies()
{
foreach (var dllName in _frameWorkDependencyDlls)
{
yield return LoadSingleHotUpdateAssembly(dllName);
}
Debug.Log("加载Framework依赖程序集结束!");
}
//加载Framework程序集
private IEnumerator LoadFrameWorkAssemblies()
{
yield return LoadSingleHotUpdateAssembly(FRAMEWORK_DLL_NAME);
Debug.Log("加载Framework程序集结束!");
}
//通过加载场景打开版本更新界面
private IEnumerator OpenVersionUpdateUI()
{
_versionUpdateUI = FindObjectOfType<UI_VersionUpdate>(true);
if (_versionUpdateUI == null)
{
Debug.LogError("cant find UI_VersionUpdate");
}
_versionUpdateUI.gameObject.SetActive(true);
return null;
}
//下载资源
private IEnumerator Download()
{
var downloadSizeOp = Addressables.GetDownloadSizeAsync((IEnumerable)_KeysNeedToDownload);
yield return downloadSizeOp;
Debug.Log($"download size:{downloadSizeOp.Result / (1024f * 1024f)}MB");
if (downloadSizeOp.Result > 0)
{
//_downloadStuckTime = 0;
Addressables.Release(downloadSizeOp);
// //下载经常在100%的时候卡住参考https://forum.unity.com/threads/addressables-1-14-2-downloaddependenciesasync-does-not-complete-randomly.966671/
// //倒数第二个回答
// var asyncLoadResources = Addressables.LoadResourceLocationsAsync((IEnumerable)_KeysNeedToDownload,
// Addressables.MergeMode.Union, null);
// IList<IResourceLocation> dependencyLoadList = new List<IResourceLocation>();
// yield return asyncLoadResources;
// foreach (var item in asyncLoadResources.Result)
// {
// dependencyLoadList.Add(item);
// }
//
// Addressables.Release(asyncLoadResources);
//
// _downloadOP =
// Addressables.DownloadDependenciesAsync(dependencyLoadList);
_downloadOP =
Addressables.DownloadDependenciesAsync((IEnumerable)_KeysNeedToDownload, Addressables.MergeMode.Union, false);
_versionUpdateUI.SetParams(_downloadOP);
// while (!_downloadOP.IsDone)
// {
// RefreshDownLoadStatus();
// if (!CheckIfNeedReDownload())
// yield return 0;
// else
// {
// //下载会经常卡在99%,此时还剩下很少的资源要下载,可以直接放弃下载,进入游戏
// //经过尝试上述方法行不通_downloadOp不能被终止如果直接release的话会导致后续逻辑报错
// Debug.LogError($"下载卡住超过时间上限,放弃下载!");
// yield return _downloadOP;
// }
// }
yield return _downloadOP;
if (_downloadOP.Status == AsyncOperationStatus.Succeeded)
Debug.Log($"下载完毕!");
else
Debug.LogError($"Download Update Content Failed! exception:{_downloadOP.OperationException.Message} \r\n {_downloadOP.OperationException.StackTrace}");
Addressables.Release(_downloadOP);
}
//清除需要下载的内容
Debug.Log($"delete key:{DOWNLOAD_CATALOGS_ID}");
PlayerPrefs.DeleteKey(DOWNLOAD_CATALOGS_ID);
}
// private void RefreshDownLoadStatus()
// {
// var currDownloadProgress = _downloadOP.GetDownloadStatus().Percent;
// if (Math.Abs(currDownloadProgress - _downloadProgress) < float.Epsilon)
// _downloadStuckTime += Time.deltaTime;
// else
// _downloadStuckTime = 0;
// _downloadProgress = currDownloadProgress;
// //Debug.Log($"下载进度:{_downloadProgress} 下载卡顿时间:{_downloadStuckTime}");
// }
//
// private bool CheckIfNeedReDownload() => _downloadStuckTime >= DOWNLOAD_STUCK_TIME_MAX;
//加载GamePlay依赖的第三方程序集
private IEnumerator LoadGamePlayDependencyAssemblies()
{
foreach (var dllName in _gamePlayDependencyDlls)
{
yield return LoadSingleHotUpdateAssembly(dllName);
}
Debug.Log("加载GamePlay依赖程序集结束!");
}
//加载GamePlay程序集
private IEnumerator LoadGamePlayAssemblies()
{
yield return LoadSingleHotUpdateAssembly(GAMEPLAY_DLL_NAME);
Debug.Log("加载GamePlay程序集结束!");
}
/// <summary>
/// Addressable初始化时热更新代码所对应的ScriptableObject的类型会被识别为System.Object需要在热更新dll加载完后重新加载一下Addressable的Catalog
/// https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors
/// </summary>
/// <returns></returns>
private IEnumerator ReloadAddressableCatalog()
{
var op = Addressables.LoadContentCatalogAsync($"{Addressables.RuntimePath}/catalog.json");
yield return op;
if (op.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"load content catalog failed, exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}");
}
}
/// <summary>
/// 反射执行被RuntimeInitializeOnLoadMethod attribute标注的函数HybirdCLR不支持该attribute
/// </summary>
private void ExecuteRuntimeInitializeOnLoadMethodAttribute()
{
var runtimeInitializedAttribute = typeof(RuntimeInitializeOnLoadMethodAttribute);
List<MethodExecutionInfo> runtimeMethods = new();
foreach (var assemblyName in _hasRuntimeInitializeOnLoadMethodAssemblies)
{
var assembly = GetAssembly(assemblyName);
if (assembly == null)
{
Debug.LogError($"找不到使用过RuntimeInitializeOnLoadMethod的assembly,name:{assemblyName}");
continue;
}
foreach (var type in assembly.GetTypes())
{
type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).ForEach(method =>
{
if (!method.IsStatic)
return;
var attri = method.GetCustomAttribute(runtimeInitializedAttribute);
if (attri == null)
return;
var sequence = (int)(attri as RuntimeInitializeOnLoadMethodAttribute).loadType;
var methodInfo = new MethodExecutionInfo(method, sequence);
runtimeMethods.Add(methodInfo);
});
}
}
runtimeMethods.Sort((a, b) => b.sequnence.CompareTo(a.sequnence));
foreach (var methodInfo in runtimeMethods)
{
Debug.Log($"call method methodName:{methodInfo.method.Name} sequnence:{methodInfo.sequnence}");
methodInfo.method.Invoke(null, null);
}
Debug.Log("调用RuntimeInitializeOnLoadMethod结束!");
}
private IEnumerator ReadDllBytes(string path)
{
var op = Addressables.LoadAssetAsync<TextAsset>(path);
yield return op;
if (op.Status == AsyncOperationStatus.Succeeded)
{
_dllBytes = op.Result.bytes;
}
else
{
Debug.LogError(
$"cant load dll,exception:{op.OperationException.Message}\r\n{op.OperationException.StackTrace}");
_dllBytes = null;
}
Addressables.Release(op);
}
private IEnumerator LoadSingleHotUpdateAssembly(string dllName)
{
var path = $"{HOT_UPDATE_DLL_PATH}{dllName}.bytes";
yield return ReadDllBytes(path);
if (_dllBytes != null)
{
var assembly = Assembly.Load(_dllBytes);
_allHotUpdateAssemblies.Add(assembly.FullName, assembly);
Debug.Log($"Load Assembly success,assembly Name:{assembly.FullName}");
// foreach (var type in GetAssembly(dllName).GetTypes())
// {
// Debug.Log($"type:{type} in assembly:{GAMEPLAY_DLL_NAME}");
// }
}
yield return null;
}
private IEnumerator EnterGame()
{
var op = Addressables.LoadSceneAsync(START_SCENE_NAME, LoadSceneMode.Single);
yield return op;
if (op.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"load scene failed,exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}");
yield break;
}
}
private Assembly GetAssembly(string assemblyName)
{
assemblyName = assemblyName.Replace(".dll", "");
IEnumerable<Assembly> allAssemblies =
enableHotUpdate ? _allHotUpdateAssemblies.Values : AppDomain.CurrentDomain.GetAssemblies();
return allAssemblies.First(assembly => assembly.FullName.Contains(assemblyName));
}
}