410 lines
14 KiB
C#
410 lines
14 KiB
C#
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";
|
||
const string FRAMEWORK_ASSEMBLY_NAME = "FrameWork";
|
||
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 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 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()
|
||
{
|
||
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 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 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));
|
||
}
|
||
} |