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

402 lines
13 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.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;
}
}
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";
private bool _hasNewVersion;
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 List<object> _updateKeys = new();
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 (_hasNewVersion)
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)
{
var catalogUpdateIDs = checkUpdateOP.Result;
if (catalogUpdateIDs != null && catalogUpdateIDs.Count > 0)
{
_hasNewVersion = true;
var updateCatalogOP = Addressables.UpdateCatalogs(catalogUpdateIDs, false);
yield return updateCatalogOP;
if (updateCatalogOP.Status == AsyncOperationStatus.Succeeded)
{
foreach (var resourceLocator in updateCatalogOP.Result)
{
_updateKeys.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($"版本检查结束,是否有新版本:{_hasNewVersion}");
}
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>();
if (_versionUpdateUI == null)
{
Debug.LogError("cant find UI_VersionUpdate");
}
return null;
}
private IEnumerator CloseVersionUpdateUI()
{
return null;
}
//下载资源
private IEnumerator Download()
{
var downloadSizeOp = Addressables.GetDownloadSizeAsync((IEnumerable)_updateKeys);
yield return downloadSizeOp;
Debug.Log($"download size:{downloadSizeOp.Result / (1024f * 1024f)}MB");
if (downloadSizeOp.Result > 0)
{
_downloadOP =
Addressables.DownloadDependenciesAsync((IEnumerable)_updateKeys, Addressables.MergeMode.Union, false);
_versionUpdateUI.SetParams(_downloadOP);
yield return _downloadOP;
if (_downloadOP.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"Download Update Content Failed! exception:{_downloadOP.OperationException.Message} \r\n {_downloadOP.OperationException.StackTrace}");
}
Addressables.Release(_downloadOP);
}
Addressables.Release(downloadSizeOp);
}
//加载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;
yield break;
}
}
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}");
}
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;
}
if (_hasNewVersion)
yield return CloseVersionUpdateUI();
}
private Assembly GetAssembly(string assemblyName)
{
IEnumerable<Assembly> allAssemblies =
enableHotUpdate ? _allHotUpdateAssemblies.Values : AppDomain.CurrentDomain.GetAssemblies();
return allAssemblies.First(assembly => assembly.FullName.Contains(assemblyName));
}
}