协程和异步操作
1.协程和异步都能在ILRuntime中使用
2.编译器会自动生成继承系统接口的匿名类造成跨域继承
3.使用跨域继承适配器可以完成异步操作的适配
4.避免在异步操作中使用foreach
主工程:
CoroutineAdapter.cs如下:
using UnityEngine;
using System.Collections.Generic;
using ILRuntime.Other;
using System;
using System.Collections;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.CLR.Method;
public class CoroutineAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get
{
return null;
}
}
public override Type[] BaseCLRTypes
{
get
{
//跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,对于coroutine来说是IEnumerator<object>,IEnumerator和IDisposable,
//ILRuntime虽然支持,但是一定要小心这种用法,使用不当很容易造成不可预期的问题
//日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类
return new Type[] { typeof(IEnumerator<object>), typeof(IEnumerator), typeof(IDisposable) };
}
}
public override Type AdaptorType
{
get
{
return typeof(Adaptor);
}
}
public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
return new Adaptor(appdomain, instance);
}
//Coroutine生成的类实现了IEnumerator<System.Object>, IEnumerator, IDisposable,所以都要实现,这个可以通过reflector之类的IL反编译软件得知
internal class Adaptor : IEnumerator<System.Object>, IEnumerator, IDisposable, CrossBindingAdaptorType
{
ILTypeInstance instance;
ILRuntime.Runtime.Enviorment.AppDomain appdomain;
public Adaptor()
{
}
public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}
public ILTypeInstance ILInstance { get { return instance; } }
IMethod mCurrentMethod;
bool mCurrentMethodGot;
public object Current
{
get
{
if (!mCurrentMethodGot)
{
mCurrentMethod = instance.Type.GetMethod("get_Current", 0);
if (mCurrentMethod == null)
{
//这里写System.Collections.IEnumerator.get_Current而不是直接get_Current是因为coroutine生成的类是显式实现这个接口的,通过Reflector等反编译软件可得知
//为了兼容其他只实现了单一Current属性的,所以上面先直接取了get_Current
mCurrentMethod = instance.Type.GetMethod("System.Collections.IEnumerator.get_Current", 0);
}
mCurrentMethodGot = true;
}
if (mCurrentMethod != null)
{
var res = appdomain.Invoke(mCurrentMethod, instance, null);
return res;
}
else
{
return null;
}
}
}
IMethod mDisposeMethod;
bool mDisposeMethodGot;
public void Dispose()
{
if (!mDisposeMethodGot)
{
mDisposeMethod = instance.Type.GetMethod("Dispose", 0);
if (mDisposeMethod == null)
{
mDisposeMethod = instance.Type.GetMethod("System.IDisposable.Dispose", 0);
}
mDisposeMethodGot = true;
}
if (mDisposeMethod != null)
{
appdomain.Invoke(mDisposeMethod, instance, null);
}
}
IMethod mMoveNextMethod;
bool mMoveNextMethodGot;
public bool MoveNext()
{
if (!mMoveNextMethodGot)
{
mMoveNextMethod = instance.Type.GetMethod("MoveNext", 0);
mMoveNextMethodGot = true;
}
if (mMoveNextMethod != null)
{
return (bool)appdomain.Invoke(mMoveNextMethod, instance, null);
}
else
{
return false;
}
}
IMethod mResetMethod;
bool mResetMethodGot;
public void Reset()
{
if (!mResetMethodGot)
{
mResetMethod = instance.Type.GetMethod("Reset", 0);
mResetMethodGot = true;
}
if (mResetMethod != null)
{
appdomain.Invoke(mResetMethod, instance, null);
}
}
public override string ToString()
{
IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
m = instance.Type.GetVirtualMethod(m);
if (m == null || m is ILMethod)
{
return instance.ToString();
}
else
return instance.Type.FullName;
}
}
}
CoroutineDemo.cs如下:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using ILRuntime.CLR.TypeSystem;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.Utils;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;
using ILRuntime.Runtime.Enviorment;
//下面这行为了取消使用WWW的警告,Unity2018以后推荐使用UnityWebRequest,处于兼容性考虑Demo依然使用WWW
#pragma warning disable CS0618
public class CoroutineDemo : MonoBehaviour
{
static CoroutineDemo instance;
public static CoroutineDemo Instance
{
get { return instance; }
}
//AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个
//大家在正式项目中请全局只创建一个AppDomain
AppDomain appdomain;
System.IO.MemoryStream fs;
System.IO.MemoryStream p;
void Start()
{
instance = this;
StartCoroutine(LoadHotFixAssembly());
}
IEnumerator LoadHotFixAssembly()
{
//首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒
appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
//正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,
//正式发布的时候需要大家自行从其他地方读取dll
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
#if UNITY_ANDROID
WWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
while (!www.isDone)
yield return null;
if (!string.IsNullOrEmpty(www.error))
UnityEngine.Debug.LogError(www.error);
byte[] dll = www.bytes;
www.Dispose();
//PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROID
www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
while (!www.isDone)
yield return null;
if (!string.IsNullOrEmpty(www.error))
UnityEngine.Debug.LogError(www.error);
byte[] pdb = www.bytes;
fs = new MemoryStream(dll);
p = new MemoryStream(pdb);
try
{
appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
}
catch
{
Debug.LogError("加载热更DLL失败,请确保已经通过VS打开Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL");
}
InitializeILRuntime();
OnHotFixLoaded();
}
void InitializeILRuntime()
{
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
//由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
//这里做一些ILRuntime的注册
//使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator<object>,IDisposable接口的类,因为这是跨域继承,所以需要写CrossBindAdapter(详细请看04_Inheritance教程),Demo已经直接写好,直接注册即可
appdomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
appdomain.DebugService.StartDebugService(56000);
}
unsafe void OnHotFixLoaded()
{
appdomain.Invoke("HotFix_Project.TestCoroutine", "RunTest", null, null);
}
public void DoCoroutine(IEnumerator coroutine)
{
StartCoroutine(coroutine);
}
private void OnDestroy()
{
if (fs != null)
fs.Close();
if (p != null)
p.Close();
fs = null;
p = null;
}
}
热更工程:
using System;
using System.Collections.Generic;
using UnityEngine;
namespace HotFix_Project
{
public class TestCoroutine
{
public static void RunTest()
{
CoroutineDemo.Instance.DoCoroutine(Coroutine());
}
static System.Collections.IEnumerator Coroutine()
{
Debug.Log("开始协程,t=" + Time.time);
yield return new WaitForSeconds(3);
Debug.Log("等待了3秒,t=" + Time.time);
}
}
}