小白学Lua
学习中我坚信要知其然,也要知其所以然,不能只当API的搬运工。
文章不带任何教学性,只是记录我的学习过程。
因为我是写了快3年C#工作写了1年半,学习1年半,最近工作需要开始触碰Lua,在这里是根据Git上的ToLua框架来进行学习。
直接开始,先看下下来的第一个例子:
01_HelloWorld
public class HelloWorld : MonoBehaviour
{
void Awake()
{
LuaState lua = new LuaState();
lua.Start();
string hello =
@"
print('hello tolua#')
";
lua.DoString(hello);
lua.CheckTop();
lua.Dispose();
lua = null;
}
}
很简单的几行句子,简单梳理一下整合出来:
1.new一个虚拟机
2.开启状态机
3.DoString()直接把代码扔进去就OK
4.检查一下状态机状态,暂且可以理解为我F12点进去之后看了下,应该是检查了一些东西,可能是方法之类的然后返回,如果不为0就会给一个警告
5.关闭虚拟机
6设为Null等待回收
2.ScriptsFromFile
点开后就看到代码上写好了展示require与dofile的区别
去掉一些GUI展示代码后
if (GUI.Button(new Rect(50, 50, 120, 45), "DoFile"))
{
strLog = "";
lua.DoFile("ScriptsFromFile.lua");
}
else if (GUI.Button(new Rect(50, 150, 120, 45), "Require"))
{
strLog = "";
lua.Require("ScriptsFromFile");
}
区别只是这里的Dofile后面跟上了后缀,Require后面没有。
然后我F12进去看了一下,
DoFile
dofile是直接就对文件进行了操作,跟进进去
public void DoFile(string fileName)
{
byte[] buffer = LoadFileBuffer(fileName);
fileName = LuaChunkName(fileName);
LuaLoadBuffer(buffer, fileName);
}
首先看到了Load读取,继续跟进
byte[] LoadFileBuffer(string fileName)
{
#if UNITY_EDITOR
if (!beStart)
{
throw new LuaException("you must call Start() first to initialize LuaState");
}
#endif
byte[] buffer = LuaFileUtils.Instance.ReadFile(fileName);
if (buffer == null)
{
string error = string.Format("cannot open {0}: No such file or directory", fileName);
error += LuaFileUtils.Instance.FindFileError(fileName);
throw new LuaException(error);
}
return buffer;
}
有一行ReadFile就可以看到了,OK他会读取这个文件内的东西。
然后Check检查,检查没有问题,就会执行,具体执行的内部代码就不说了,我简单看了一下,因为也不懂这些,也只能懂大概。
Require
同样跟进,
public void Require(string fileName)
{
int top = LuaGetTop();
int ret = LuaRequire(fileName);
if (ret != 0)
{
string err = LuaToString(-1);
LuaSetTop(top);
throw new LuaException(err, LuaException.GetLastError());
}
LuaSetTop(top);
}
发现他会先得到当前状态,然后找文件
public int LuaRequire(string fileName)
{
#if UNITY_EDITOR
string str = Path.GetExtension(fileName);
if (str == ".lua")
{
throw new LuaException("Require not need file extension: " + str);
}
#endif
return LuaDLL.tolua_require(L, fileName);
}
我发现这里他会先判断一下后缀是不是.lua,如果不是就会报错。
最后返回把他给设置到状态机上等待调用。
总结:Dofile与Require有个很大的区别就是Dofile不论什么文件都可以读而Require则需要你的后缀一定是.Lua不然就会报错至于中间的流程因为本人学疏才浅不得而知,希望有大佬能告诉我,最后的执行步骤都是一致的。
3.CallLuaFunction
Lua代码就不谈了很简单
private string script =
@" function luaFunc(num)
return num + 1
end
test = {}
test.luaFunc = luaFunc
";
造一个叫luaFunc的方法,
声明一个table test,
为test.luaFunc赋值为方法luaFunc。
往下看代码
LuaFunction luaFunc = null;
LuaState lua = null;
string tips = null;
除了LuaState还多了你个LuaFunction
除开GUI代码
new LuaResLoader();
lua = new LuaState();
lua.Start();
DelegateFactory.Init();
lua.DoString(script);
//Get the function object
luaFunc = lua.GetFunction("test.luaFunc");
if (luaFunc != null)
{
int num = luaFunc.Invoke<int, int>(123456);
Debugger.Log("generic call return: {0}", num);
num = CallFunc();
Debugger.Log("expansion call return: {0}", num);
Func<int, int> Func = luaFunc.ToDelegate<Func<int, int>>();
num = Func(123456);
Debugger.Log("Delegate call return: {0}", num);
num = lua.Invoke<int, int>("test.luaFunc", 123456, true);
Debugger.Log("luastate call return: {0}", num);
}
lua.CheckTop();
第一行这个代码就很让我疑惑干啥用的?F12进去之后也没看明白。那就注释掉看看吧。
也没问题?
那就不管继续看,创建虚拟机,开启,执行代码。
然后luaFunc这里等于 lua.GetFunction(我们的类名.方法名)
然后分别看
第一个 Invoke
int num = luaFunc.Invoke<int, int>(123456);
F12跟进
public R1 Invoke<T1, R1>(T1 arg1)
{
BeginPCall();
PushGeneric(arg1);
PCall();
R1 ret1 = CheckValue<R1>();
EndPCall();
return ret1;
}
因为这里代码有点复杂我也讲不出来,但自个也能理解,简单就我看来就是先压栈,然后返回一个引用出来,然后把我们这个方进去,然后调用,然后检查栈里的值,最后出栈结束调用返回一个值。
第二个 CallFunc
F12
int CallFunc()
{
luaFunc.BeginPCall();
luaFunc.Push(123456);
luaFunc.PCall();
int num = (int)luaFunc.CheckNumber();
luaFunc.EndPCall();
return num;
}
手动调用了刚刚的代码Begin,Push,Call,Check,End,返回
第三个 Func
第一眼看过去就知道了委托,同样F12
public T ToDelegate<T>() where T : class
{
return DelegateTraits<T>.Create(this) as T;
}
可以看到这里是自己造了一个委托,详细的就不跟进了。
第四个 lua.Invoke
乍一看没啥不一样的,仔细一看是从lua虚拟机调用的,不是Func,F12
public R1 Invoke<T1, R1>(string name, T1 arg1, bool beLogMiss)
{
int top = LuaDLL.lua_gettop(L);
try
{
if (BeginCall(name, top, beLogMiss))
{
PushGeneric(arg1);
Call(1, top + 1, top);
R1 ret1 = CheckValue<R1>(top + 2);
LuaDLL.lua_settop(L, top);
return ret1;
}
return default(R1);
}
catch (Exception e)
{
LuaDLL.lua_settop(L, top);
throw e;
}
}
继续跟进发现代码和Func都差不多,所以也就不详谈了。
总结:
所有的方法调用都会经历一个Begin,Push,Call,Check,End 这里我认为如果没有返回值就不会有Check,不过没测试,以后应该是有机会的。
在调用上没有太大的差异,
luaFunc.Invoke为泛型会有返回值
CallFunc是最基本的调用方法
luaFunc.ToDelegate作为泛型委托
lua.Invoke则是直接从Lua虚拟机调用而不是LuaFunc
就先到这里要吃晚饭了,晚上学习Socket编程了,明天这里再继续吧。