using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if ENABLE_HYBRIDCLR
using HybridCLR;
#endif
using UnityEngine;
using TEngine;
using System.Reflection;
using YooAsset;
using Cysharp.Threading.Tasks;
namespace Procedure
{
///
/// 流程加载器 - 代码初始化
///
public class ProcedureLoadAssembly : ProcedureBase
{
private bool _enableAddressable = true;
public override bool UseNativeDialog => true;
private int _loadAssetCount;
private int _loadMetadataAssetCount;
private int _failureAssetCount;
private int _failureMetadataAssetCount;
private bool _loadAssemblyComplete;
private bool _loadMetadataAssemblyComplete;
private bool _loadAssemblyWait;
private bool _loadMetadataAssemblyWait;
private Assembly _mainLogicAssembly;
private List _hotfixAssemblyList;
private IFsm _procedureOwner;
private UpdateSetting _setting;
protected override void OnInit(IFsm procedureOwner)
{
base.OnInit(procedureOwner);
_setting = Settings.UpdateSetting;
}
protected override void OnEnter(IFsm procedureOwner)
{
base.OnEnter(procedureOwner);
Log.Debug("HybridCLR ProcedureLoadAssembly OnEnter");
_procedureOwner = procedureOwner;
LoadAssembly().Forget();
}
private async UniTaskVoid LoadAssembly()
{
_loadAssemblyComplete = false;
_hotfixAssemblyList = new List();
//AOT Assembly加载原始metadata
if (_setting.Enable)
{
#if !UNITY_EDITOR
_loadMetadataAssemblyComplete = false;
LoadMetadataForAOTAssembly();
#else
_loadMetadataAssemblyComplete = true;
#endif
}
else
{
_loadMetadataAssemblyComplete = true;
}
if (!_setting.Enable || _resourceModule.PlayMode == EPlayMode.EditorSimulateMode)
{
_mainLogicAssembly = GetMainLogicAssembly();
}
else
{
if (_setting.Enable)
{
foreach (string hotUpdateDllName in _setting.HotUpdateAssemblies)
{
var assetLocation = hotUpdateDllName;
if (!_enableAddressable)
{
assetLocation = Utility.Path.GetRegularPath(
Path.Combine(
"Assets",
_setting.AssemblyTextAssetPath,
$"{hotUpdateDllName}{_setting.AssemblyTextAssetExtension}"));
}
Log.Debug($"LoadAsset: [ {assetLocation} ]");
_loadAssetCount++;
var result = await _resourceModule.LoadAssetAsync(assetLocation);
LoadAssetSuccess(result);
}
_loadAssemblyWait = true;
}
else
{
_mainLogicAssembly = GetMainLogicAssembly();
}
}
if (_loadAssetCount == 0)
{
_loadAssemblyComplete = true;
}
}
protected override void OnUpdate(IFsm procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
if (!_loadAssemblyComplete)
{
return;
}
if (!_loadMetadataAssemblyComplete)
{
return;
}
AllAssemblyLoadComplete();
}
private void AllAssemblyLoadComplete()
{
ChangeState(_procedureOwner);
#if UNITY_EDITOR
_mainLogicAssembly = GetMainLogicAssembly();
#endif
if (_mainLogicAssembly == null)
{
Log.Fatal($"Main logic assembly missing. Please check \'ENABLE_HYBRIDCLR\' is defined in Player Settings And check the file of {_setting.LogicMainDllName}.bytes is exits.");
return;
}
var appType = _mainLogicAssembly.GetType("GameApp");
if (appType == null)
{
Log.Fatal($"Main logic type 'GameMain' missing.");
return;
}
var entryMethod = appType.GetMethod("Entrance");
if (entryMethod == null)
{
Log.Fatal($"Main logic entry method 'Entrance' missing.");
return;
}
object[] objects = new object[] { new object[] { _hotfixAssemblyList } };
entryMethod.Invoke(appType, objects);
}
private Assembly GetMainLogicAssembly()
{
_hotfixAssemblyList.Clear();
Assembly mainLogicAssembly = null;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (string.Compare(_setting.LogicMainDllName, $"{assembly.GetName().Name}.dll",
StringComparison.Ordinal) == 0)
{
mainLogicAssembly = assembly;
}
foreach (var hotUpdateDllName in _setting.HotUpdateAssemblies)
{
if (hotUpdateDllName == $"{assembly.GetName().Name}.dll")
{
_hotfixAssemblyList.Add(assembly);
}
}
if (mainLogicAssembly != null && _hotfixAssemblyList.Count == _setting.HotUpdateAssemblies.Count)
{
break;
}
}
return mainLogicAssembly;
}
///
/// 加载代码资源成功回调。
///
/// 代码资产。
private void LoadAssetSuccess(TextAsset textAsset)
{
_loadAssetCount--;
if (textAsset == null)
{
Log.Warning($"Load Assembly failed.");
return;
}
var assetName = textAsset.name;
Log.Debug($"LoadAssetSuccess, assetName: [ {assetName} ]");
try
{
var assembly = Assembly.Load(textAsset.bytes);
if (string.Compare(_setting.LogicMainDllName, assetName, StringComparison.Ordinal) == 0)
{
_mainLogicAssembly = assembly;
}
_hotfixAssemblyList.Add(assembly);
Log.Debug($"Assembly [ {assembly.GetName().Name} ] loaded");
}
catch (Exception e)
{
_failureAssetCount++;
Log.Fatal(e);
throw;
}
finally
{
_loadAssemblyComplete = _loadAssemblyWait && 0 == _loadAssetCount;
}
_resourceModule.UnloadAsset(textAsset);
}
///
/// 为Aot Assembly加载原始metadata, 这个代码放Aot或者热更新都行。
/// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行。
///
public void LoadMetadataForAOTAssembly()
{
// 可以加载任意aot assembly的对应的dll。但要求dll必须与unity build过程中生成的裁剪后的dll一致,而不能直接使用原始dll。
// 我们在BuildProcessor_xxx里添加了处理代码,这些裁剪后的dll在打包时自动被复制到 {项目目录}/HybridCLRData/AssembliesPostIl2CppStrip/{Target} 目录。
// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。
// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误
if (_setting.AOTMetaAssemblies.Count == 0)
{
_loadMetadataAssemblyComplete = true;
return;
}
foreach (string aotDllName in _setting.AOTMetaAssemblies)
{
var assetLocation = aotDllName;
if (!_enableAddressable)
{
assetLocation = Utility.Path.GetRegularPath(
Path.Combine(
"Assets",
_setting.AssemblyTextAssetPath,
$"{aotDllName}{_setting.AssemblyTextAssetExtension}"));
}
Log.Debug($"LoadMetadataAsset: [ {assetLocation} ]");
_loadMetadataAssetCount++;
_resourceModule.LoadAsset(assetLocation, LoadMetadataAssetSuccess);
}
_loadMetadataAssemblyWait = true;
}
///
/// 加载元数据资源成功回调。
///
/// 代码资产。
private void LoadMetadataAssetSuccess(TextAsset textAsset)
{
_loadMetadataAssetCount--;
if (null == textAsset)
{
Log.Debug($"LoadMetadataAssetSuccess:Load Metadata failed.");
return;
}
string assetName = textAsset.name;
Log.Debug($"LoadMetadataAssetSuccess, assetName: [ {assetName} ]");
try
{
byte[] dllBytes = textAsset.bytes;
#if ENABLE_HYBRIDCLR
// 加载assembly对应的dll,会自动为它hook。一旦Aot泛型函数的native函数不存在,用解释器版本代码
HomologousImageMode mode = HomologousImageMode.SuperSet;
LoadImageErrorCode err = (LoadImageErrorCode)HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(dllBytes,mode);
Log.Warning($"LoadMetadataForAOTAssembly:{assetName}. mode:{mode} ret:{err}");
#endif
}
catch (Exception e)
{
_failureMetadataAssetCount++;
Log.Fatal(e.Message);
throw;
}
finally
{
_loadMetadataAssemblyComplete = _loadMetadataAssemblyWait && 0 == _loadMetadataAssetCount;
}
_resourceModule.UnloadAsset(textAsset);
}
}
}