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

429 lines
16 KiB
C#
Raw Normal View History

2023-10-19 15:00:19 +08:00
using DG.Tweening.Plugins.Core.PathCore;
using Gameplay.SubSystems;
using HybridCLR;
using Sirenix.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.AddressableAssets.ResourceProviders;
using UnityEngine.Networking;
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 VERSION_UPDATE_SCENE = "Assets/Scenes/VersionUpdate.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";
const string VERSION_UPDATEER_NAME = "VersionUpdater";
const string FRAMEWORK_ASSEMBLY_NAME = "FrameWork";
const string SET_VERSION_UPDATE_UI_PARAM_METHOD_NAME = "SetVersionUpdateUIParam";
const string AOT_GENERIC_REFERENCES_TYPE = "AOTGenericReferences";
const string PATCHED_AOT_ASSEMBLY_LIST = "PatchedAOTAssemblyList";
private bool _hasNewVersion;
private Coroutine _launchCoroutine;
private byte[] _dllBytes;
private Dictionary<string, Assembly> _allHotUpdateAssemblies = new();
//version update
private AsyncOperationHandle _downloadOP;
private AsyncOperationHandle<SceneInstance> _versionUpdateSceneHandle;
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",
"AillieoUtils.UnityDynamicScrollView.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 IEnumerator Launch()
{
#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()
{
if (enableHotUpdate)
{
yield return LoadBasement();
}
yield return OpenVersionUpdateUI();
yield return Download();
Debug.Log($"版本更新结束!");
}
private IEnumerator LoadAssemblies()
{
if (!_hasNewVersion)
{
yield return LoadBasement();
}
yield return LoadGamePlayDependencyAssemblies();
yield return LoadGamePlayAssemblies();
yield return ReloadAddressableCatalog();
ExecuteRuntimeInitializeOnLoadMethodAttribute();
Debug.Log("加载程序集结束!");
yield return null;
}
//补充元数据
private IEnumerator LoadMetadataForAOTAssemblies()
{
var frameworkAssembly = GetAssembly(FRAMEWORK_ASSEMBLY_NAME);
var aotGenericRefType = frameworkAssembly.GetType(AOT_GENERIC_REFERENCES_TYPE);
if (aotGenericRefType == null)
{
Debug.LogError($"LoadMetadataForAOTAssemblies failed, can't find type:{AOT_GENERIC_REFERENCES_TYPE}");
yield break;
}
var listFieldInfo = aotGenericRefType.GetField(PATCHED_AOT_ASSEMBLY_LIST, BindingFlags.Public | BindingFlags.Static);
if (listFieldInfo == null)
{
Debug.LogError($"LoadMetadataForAOTAssemblies failed, can't find field:{PATCHED_AOT_ASSEMBLY_LIST}");
yield break;
}
var list = listFieldInfo.GetValue(null);
if (list == null)
{
Debug.LogError($"LoadMetadataForAOTAssemblies failed, can't find list object");
yield break;
}
List<string> aotAssemblies = list as List<string>;
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 LoadBasement()
{
yield return LoadPhxhDependencyAssemblies();
yield return LoadPhxhAssembies();
yield return LoadFrameworkDependencyAssemblies();
yield return LoadFrameWorkAssemblies();
yield return LoadMetadataForAOTAssemblies();
}
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()
{
_versionUpdateSceneHandle = Addressables.LoadSceneAsync(VERSION_UPDATE_SCENE, LoadSceneMode.Additive);
yield return _versionUpdateSceneHandle;
if (_versionUpdateSceneHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"load scene failed,sceneName:{VERSION_UPDATE_SCENE} " +
$"exception:{_versionUpdateSceneHandle.OperationException.Message} \r\n {_versionUpdateSceneHandle.OperationException.StackTrace}");
}
}
private IEnumerator CloseVersionUpdateUI()
{
yield return Addressables.UnloadSceneAsync(_versionUpdateSceneHandle);
}
//下载资源
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);
var frameWorkAssembly = GetAssembly(FRAMEWORK_ASSEMBLY_NAME);
_versionUpdaterType = frameWorkAssembly.GetType(VERSION_UPDATEER_NAME);
if (_versionUpdaterType != null)
{
var setUIParamMethod = _versionUpdaterType.GetMethod(SET_VERSION_UPDATE_UI_PARAM_METHOD_NAME, BindingFlags.Static | BindingFlags.NonPublic);
if (setUIParamMethod != null)
{
setUIParamMethod.Invoke(null, new[] { (object)_downloadOP });
}
else
{
Debug.LogError($"cant find method,type name:{_versionUpdaterType.Name} method name :{SET_VERSION_UPDATE_UI_PARAM_METHOD_NAME}");
}
}
else
{
Debug.LogError($"cant find type:{VERSION_UPDATEER_NAME} assembly name:{frameWorkAssembly.FullName}");
}
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));
}
}