XLua基础
前言XLua也使用了一段时间了,一直没探索过其道理,比如为什么可以在Lua中直接使用 CS.UnityEngine.XXX 就访谒到对应的类,又或者c#中的对象是怎么在lua中保留使用的,等等一系列问题.比来花了概略三周时间阅读了XLua部门源码,实在是程度有限,理解中不免会有一些错误,还请大佬斧正。
对于学习做一个记录,也但愿能对正在阅读代码的人起到一点辅佐。本系列文章概略会分成两部门。第一部门会介绍XLua的基本使用方式,第二部门会贴出相关源码介绍此中的实现道理。
正文
作为系列文章第一篇,主要介绍XLua的使用方式。
首先贴出XLua下载地址,下载完之后直接导入Unity即可使用。第二个链接是官方附带的教程,看完可以快速上手
Lua文件加载
[*]直接执行字符串,但这种方式官方不太建议。
private LuaEnv luaenv = new LuaEnv();
luaenv.DoString(”print('hello world')”) // hello world
[*]用Lua自带的require函数即可,至于require的实现道理具体不表,百度一下有很多介绍
DoString(”require 'LuaTestMain'”)
--LuaTestMain.lua
require ”xxxx”
require ”aaaa”
require ”bbbb”
function Main()
end
Main()官方建议的方式是在C#中调用一个Lua的总开关,然后再在这个Lua的总开关中去加载其他的Lua文件,如上代码
[*]自定义Loader加载文件
private LuaEnv luaenv = new LuaEnv();
luaenv.AddLoader(MyLoader);
//这里的MyLoader中的逻辑可以自定义
private byte[] MyLoader(ref string filePath)
{
string adsPath = string.Format(”{0}/LuaScript/{1}.lua”, Application.dataPath, filePath);
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(adsPath));
}通过调用LuaEnv中的AddLoader注册回调,这样每当lua脚本中调用require函数时,城市将参数传回给回调。如果这个回调返回null,则证明loader找不到相应的lua脚本,否则返回lua脚本的内容。
有了这种加载方式,则便利措置热更新下来的lua文件,或者是加密的文件,在回调中进行相应的操作即可。至于LuaEnv中是怎么实现的,具体可以参考LuaEnv.cs和StaticLuaCallbacks.cs中的相关内容,这里不再展开,第二部门介绍道理的时候再进行分析.
C#访谒Lua
[*]通过LuaEnv.Global访谒全局变量
private LuaEnv luaenv = new LuaEnv();
luaenv.Global.Get<int>(”a”)
luaenv.Global.Get<string>(”b”)
luaenv.Global.Get<bool>(”c”)
[*]访谒全局table
[*]table映射成c#中的class或struct
在c#中定义出与Lua中table不异的数据布局类,然后使用LuaEnv.Global.Get<CsCallTable>访谒就可以了
//c#
public class CsCallTable
{
public string strA;
public int intB;
public CsCallBoolTable tableC;
}
public class CsCallBoolTable
{
public bool boolD;
}
private LuaEnv luaenv = new LuaEnv();
env.DoString(”require 'LuaTestMain'”);
var luaTb = env.Global.Get<CsCallTable>(”CsCallTable”);
Debug.Log(”strA == ” + luaTb.strA);
Debug.Log(”intB == ” + luaTb.intB);
Debug.Log(”intC == ” + luaTb.tableC == null ? ”boolTable is nill” : luaTb.tableC.boolD.ToString());
//lua
CsCallTable =
{
strA = ”aaaa”,
intB = 10,
tableC =
{
boolD = true
}
}
//注意上述的变量名c#和lua必然要统一,否则会报错找不到引用 2. table映射成c#中的interface
在c#中定义出与Lua中Table不异格式的interface即可,使用方式和上述基本一致.
public interface CSCallLua
{
string strA { get; set; }
int intB { get; set; }
int add(int a, int b);
void print();
void DebugLog();
}3. table映射成c#中的Dictionary<>或者List<>
如果感觉定义class或者interface太过于繁琐,也可以通过Dictionary或List的形式去映射Lua中的table,具体的实现可以参考官方例子CSCallLua.cs,里面有详细的例子说明
4. table映射成LuaTable
还有一种方式就是把Lua中table映射成c#中的LuaTable,这样的好处就是不需要生成代码,但是问题也很明显,缺乏类型查抄,性能低,具体可以参考CSCallLua.cs例子,LuaTable的实现道理也可以参考LuaTable.cs代码。
[*]访谒全局函数
[*]映射到delegate
这种是建议的方式,性能好很多,而且类型安全。错误谬误是要生成代码。对于function的每个参数就声明一个输入类型的参数。如果function有多个返回值,从左往右映射到c#的输出参数,输出参数包罗返回值,out参数,ref参数。参数和返回值撑持所有类型,包罗out,ref修饰的都可以撑持,设置可以撑持返回另一个delegate.
delegate的使用就更简单了,直接像个函数那样用就可以了。
//注意要加上这个标签,会按照这个标签去生成代码
public delegate int FDelegate(int a, int b, ref int outInt, out CsCallTable tb, out string str);
public class CsCallTable
{
public string strA;
public int intB;
public CsCallBoolTable tableC;
}
var luaFunc = env.Global.Get<FDelegate>(”CSCallLuaFunc”);
CsCallTable tb;
string str;
int outInt = 10;
var result = luaFunc(100, 50, ref outInt, out tb, out str);
Debug.Log(string.Format(”lua Func Result = {0}, out table = {1},out string str = {2}”, result, tb, str));
Debug.Log(string.Format(”tb.strA = {0}, tb.intB = {1}”, tb.strA, tb.intB));
Debug.Log(string.Format(”ref int count = {0}”, outInt));
//lua
CsCallTable =
{
strA = ”aaaa”,
intB = 10,
}
CSCallLuaFunc = function (a, b, ref)
ref = a + b
return a + b, ref, CsCallTable, ”aaaaaaaaaa”
end
2. 映射到LuaFunction
这种方式的优错误谬误刚好和第一种相反。 使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。因为Call函数的参数和方式的返回值都是object[],必然涉及到装箱拆箱,所以性能不如第一种方式
LuaFunction luaFunc = luaenv.Global.Get<LuaFunction>(”CSCallLuaFunc”);
var result = luaFunc.Call(10, ”bbbbbb”);
for(int i = 0; i < result.Lenght; i++) Debug.Log(string.format(”result == {0}”, result));
//lua
CSCallLuaFunc = function (a, b)
return a, b, 100000, ”aaaaaaaaaa”
end
关于C#访谒Lua的一些建议,也是官方文档上面的建议
[*]C#访谒Lua的全局table或function代价斗劲大,建议在初始化的时候把table或function映射到c#中缓存起来,后续直接使用,不要频繁的通过c#访谒造成性能消耗
[*]如果lua侧的实现的部门都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的处所。
Lua访谒c#对象
//官方建议把需要经常访谒的对象提前定义出来,减少编码和提升性能
local GameObject = CS.UnityEngine.GameObject
//创建对象
local newGameObj = CS.UnityEngine.GameObject() //对应c#中的 var newGameObject = new UnityEngine.GameObject()
local newGameObj = CS.UnityEngine.GameObject(”objName”) //在lua中还能撑持重载
//访谒静态属性
local deltaTime = CS.UnityEngine.Time.deltaTime
CS.UnityEngine.Time.timeScale = 0.5
//访谒静态方式
CS.UnityEngine.GameObject.Find('helloworld')
//访谒c#成员属性
newGameObject.name = ”xxxxx”
//访谒c#成员方式,注意此处要使用':'而不是'.'这是lua的语法糖
newGameObject:SetActive(true)
//撑持重载
newGameObject:GetComponent(typeof(CS.UnityEngine.RectTransform))
newGameObject:GegtComponent(”RectTransform”)
//访谒可变参数方式
//例如GameObject的构造函数
public GameObject(string name, parmas Type[] components)
//lua中如下访谒
local RectTransform = CS.UnityEngine.RectTransform
local Button = CS.UnityEngine.UI.Button
local Image = CS.UnityEngine.Image
local go = GameObject(”test”, typeof(RectTransform), typeof(Button), typeof(Image))
//访谒枚举
//c#中定义枚举
public enum LuaEnum
{
enum1 = 1,
enum2 = 2,
enum3 = 3,
enum4 = 4,
}
//lua中如下访谒
local enum = CS.LuaEnum.enum1
//访谒event/delegate
//c#中某个类定义event
public static event Action luaEvent;
//lua中访谒
xxxx:luaEvent('+', luaEventCallback)
xxxx:luaEvent('-', luaEventCallback)
function luaEventCallback() end以上就是一些基本的使用方式,值得一提的是,在lua中访谒c#的重载方式远远没有c#自身访谒重载方式丰硕,毕竟lua不是强类型语言,c#中的int,double,float都对应lua中的number.
64为整型撑持
lua5.1时代是不撑持64位整型(long,ulong),而lua5.3撑持了64位整型,xlua做了64位撑持的扩展库,把long,ulong都映射成了userData,而且撑持在lua中进行计算和斗劲。
C#复杂类型和table的自动转换
对于有无参构造函数的c#复杂类型(class,struct),在lua中可以直接使用一个table来代替,table中的字段能和c#类型中的public对应既可,撑持函数调用,属性访谒等等
//c#中定义
public Class Person
{
public struct Info
{
public int age;
public string name;
}
public Info personInfo;
public void PrintPersonName()
{
if(personInfo.Equals(default(Info)) == false)
return;
Debug.Log(personInfo.name);
}
}
//lua中访谒
local person = CS.Person()
person.personInfo = {age = 20, name = ”xxxx”}
person:PrintPersonName()
获取类型
如果想在lua中获取c#对象的类型,可以使用typeof(xxxxx),实现道理放不才一篇介绍此处只需要知道通过这种方式就可以拿到对应的c#类型
typeof(CS.UnityEngine.GameObject)
XLua的配置
[*]打标签
public classA {}如果想在Lua中访谒c#的类,需要在c#的类加上attribute,也就是通过白名单的形式生成中间代码,然后就可以在Lua中访谒到了.以上这种方式使用很便利,但是在il2cpp下会增加不少代码量,所以官方不建议使用。
[*]静态列表
定义一个静态列表,把需要在Lua中访谒的c#类全部插手到静态列表中
public static List<Type> LuaCallCSharp = new List<Type>(){
typeof(UnityEngine.GameObject),
typeof(UnityEngine.RectTransform),
typeof(UnityEngine.Vector3),
....
}
[*]动态列表
public static IEnumerable<Type> HotfixInject
{
get
{
return (from type in Assembly.Load(”Assembly-CSharp”).GetTypes()
where type.Namespace == null || !type.Namespace.SrartsWith(”XLua”)
select type).ToList();
}
}
[*]几种常用的标签
XLua.LuaCallCSharp
一个C#类型加了这个配置,xLua会生成这个类型的适配代码(包罗构造该类型实例,访谒其成员属性、方式,静态属性、方式),否则将会测验考试用性能较低的反射方式来访谒。反射除了性能不好之外,还会可能在il2cpp环境下因为代码裁剪而导致无法访谒,无法访谒可以通过下面的标签来解决。
XLua.ReflectionUse
一个C#类型类型加了这个配置,xLua会生成link.xml阻止il2cpp的代码剪裁。建议所有要在Lua访谒的类型,要么加LuaCallCSharp,要么加上ReflectionUse,这才能够保证在各平台都能正常运行。
XLua.CSharpCallLua
如果但愿把一个lua函数适配到一个C# delegate(一类是C#侧各种回调:UI事件,delegate参数,比如List<T>:ForEach;此外一类场景是通过LuaTable的Get函数指明一个lua函数绑定到一个delegate)。或者把一个lua table适配到一个C# interface,该delegate或者interface需要加上该配置。
XLua.GCOptimize
简单来说就是如果一个c#纯值类型加上了这个标签,xLua就会生成优化代码,该值类型在c#和Lua中传递的时候就不会有gc alloc发生,可以参考官方的05_NoGc例子
XLua.BlackList
如果不要生成一个类型的一些成员的适配代码,可以通过这个配置来实现。
由于考虑到有可能需要把重载函数的此中一个重载列入黑名单,配置方式斗劲复杂,类型是List<List<string>>,对于每个成员,在第一层List有一个条目,第二层List是个string的列表,第一个string是类型的全路径名,第二个string是成员名,如果成员是一个方式,还需要从第三个string开始,把其参数的类型全路径全列出来。
例如下面是对GameObject的一个属性以及FileInfo的一个方式列入黑名单:
public static List<List<string>> BlackList = new List<List<string>>(){
new List<string>(){”UnityEngine.GameObject”, ”networkView”},
new List<string>(){”System.IO.FileInfo”, ”GetAccessControl”, ”System.Security.AccessControl.AccessControlSections”},
};
更多细节可以访谒官方github
页:
[1]