作者:自由的豬 來源: 類別:開發(fā)資源
日期:2008-07-19 在你的游戲中應(yīng)用Lua(1):在你的游戲代碼中運行解釋器
通常,你希望在你的游戲開始的時候讀取一些信息,以配置你的游戲,這些信息通常都是放到一個文本文件中,在你的游戲啟動的時候,你需要打開這個文件,然后解析字符串,找到所需要的信息。
是的,或許你認(rèn)為這樣就足夠了,為什么還要使用Lua呢?
應(yīng)用于“配置”這個目的,Lua提供給你更為強(qiáng)大,也更為靈活的表達(dá)方式,在上一種方式中,你無法根據(jù)某些條件來配置你的游戲,Lua提供給你靈活的表達(dá)方式,你可以類似于這樣來配置你的游戲:
if player:is_dead() then do_something() else do_else() end
更為重要的是,在你做了一些修改之后,完全不需要重新編譯你的游戲代碼。
通常,在游戲中你并不需要一個單獨的解釋器,你需要在游戲來運行解釋器,下面,讓我們來看看,如何在你的代碼中運行解釋器:
//這是lua所需的三個頭文件 //當(dāng)然,你需要鏈接到正確的lib #include "lua.h" #include "lauxlib.h" #include "lualib.h"
int main(int argc, char *argv[]) { lua_State *L = lua_open(); luaopen_base(L); luaopen_io(L);
const char *buf = "print('hello, world!')";
lua_dostring(buf);
lua_close(L);
return 0; }
程序輸出:hello, world!
有時你需要執(zhí)行一段字符串,有時你可能需要執(zhí)行一個文件,當(dāng)你需要執(zhí)行一個文件時,你可以這么做: lua_dofile(L, "test.lua");
看,非常簡單吧。
在你的游戲中應(yīng)用Lua(1):Getting Value
在上一篇文章我們能夠在我們的游戲代碼中執(zhí)行Lua解釋器,下面讓我們來看看如何從腳本中取得我們所需要的信息。
首先,讓我來簡單的解釋一下Lua解釋器的工作機(jī)制,Lua解釋器自身維護(hù)一個運行時棧,通過這個運行時棧,Lua解釋器向主機(jī)程序傳遞參數(shù),所以我們可以這樣來得到一個腳本變量的值:
lua_pushstring(L, "var"); //將變量的名字放入棧 lua_gettatbl(L, LUA_GLOBALSINDEX);變量的值現(xiàn)在棧頂
假設(shè)你在腳本中有一個變量 var = 100 你可以這樣來得到這個變量值: int var = lua_tonumber(L, -1);
怎么樣,是不是很簡單?
Lua定義了一個宏讓你簡單的取得一個變量的值: lua_getglobal(L, name)
我們可以這樣來取得一個變量的值: lua_getglobal(L, "var"); //變量的值現(xiàn)在棧頂 int var = lua_tonumber(L, -1);
完整的測試代碼如下:
#include "lua.h" #inculde "lauxlib.h" #include "lualib.h"
int main(int argc, char *argv[]) { lua_State *L = lua_open(); luaopen_base(L); luaopen_io(L);
const char *buf = "var = 100";
lua_dostring(L, buf);
lua_getglobal(L, "var"); int var = lua_tonumber(L, -1);
assert(var == 100);
lua_close(L);
return 0; }
在你的游戲中應(yīng)用Lua(1):調(diào)用函數(shù)
假設(shè)你在腳本中定義了一個函數(shù):
function main(number) number = number + 1 return number end
在你的游戲代碼中,你希望在某個時刻調(diào)用這個函數(shù)取得它的返回值。
在Lua中,函數(shù)等同于變量,所以你可以這樣來取得這個函數(shù):
lua_getglobal(L, "main");//函數(shù)現(xiàn)在棧頂
現(xiàn)在,我們可以調(diào)用這個函數(shù),并傳遞給它正確的參數(shù):
lua_pushnumber(L, 100); //將參數(shù)壓棧 lua_pcall(L, 1, 1, 0); //調(diào)用函數(shù),有一個參數(shù),一個返回值 //返回值現(xiàn)在棧頂 int result = lua_tonumber(L, -1);
result 就是函數(shù)的返回值
完整的測試代碼如下:
#include "lua.h" #include "lauxlib.h" #include "lualib.h"
int main(int argc, char *argv[]) { lua_State *L = lua_open(); luaopen_base(L);
const char *buf = "function main(number) number = number + 1 return number end";
lua_dostring(buf);
lua_getglobal(L, "main"); lua_pushnumber(L, 100); lua_pcall(L, 1, 1, 0);
int result = lua_tonumber(L, -1);
assert(result == 101);
lua_close(L);
return 0; }
在你的游戲中應(yīng)用Lua(2):擴(kuò)展Lua
Lua本身定位在一種輕量級的,靈活的,可擴(kuò)充的腳本語言,這意味著你可以自由的擴(kuò)充Lua,為你自己的游戲量身定做一個腳本語言。
你可以在主機(jī)程序中向腳本提供你自定的api,供腳本調(diào)用。
Lua定義了一種類型:lua_CFunction,這是一個函數(shù)指針,它的原型是: typedef int (*lua_CFunction) (lua_State *L);
這意味著只有這種類型的函數(shù)才能向Lua注冊。
首先,我們定義一個函數(shù)
int foo(lua_State *L) { //首先取出腳本執(zhí)行這個函數(shù)時壓入棧的參數(shù) //假設(shè)這個函數(shù)提供一個參數(shù),有兩個返回值
//get the first parameter const char *par = lua_tostring(L, -1);
printf("%s ", par);
//push the first result lua_pushnumber(L, 100);
//push the second result lua_pushnumber(L, 200);
//return 2 result return 2; }
我們可以在腳本中這樣調(diào)用這個函數(shù)
r1, r2 = foo("hello")
print(r1..r2)
完整的測試代碼如下:
#include "lua.h" #include "lauxlib.h" #include "lualib.h"
int foo(lua_State *L) { //首先取出腳本執(zhí)行這個函數(shù)時壓入棧的參數(shù) //假設(shè)這個函數(shù)提供一個參數(shù),有兩個返回值
//get the first parameter const char *par = lua_tostring(L, -1);
printf("%s ", par);
//push the first result lua_pushnumber(L, 100);
//push the second result lua_pushnumber(L, 200);
//return 2 result return 2; }
int main(int argc, char *argv[]) { lua_State *L = lua_open(); luaopen_base(L); luaopen_io(L);
const char *buf = "r1, r2 = foo("hello") print(r1..r2)";
lua_dostring(L, buf);
lua_close(L);
return 0; }
程序輸出: hello 100200
在你的游戲中應(yīng)用Lua(3):using lua in cpp
lua和主機(jī)程序交換參數(shù)是通過一個運行時棧來進(jìn)行的,運行時棧的信息放在一個lua_State的結(jié)構(gòu)中,lua提供的api都需要一個lua_State*的指針,除了一個:
lua_open();
這個函數(shù)將返回一個lua_State*型的指針,在你的游戲代碼中,你可以僅僅擁有一個這樣的指針,也可以有多個這樣的指針。
最后,你需要釋放這個指針,通過函數(shù):
lua_close(L);
注意這個事實,在你的主機(jī)程序中,open()與close()永遠(yuǎn)是成對出現(xiàn)的,在c++中,如果有一些事情是成對出現(xiàn)的,這通常意味著你需要一個構(gòu)造函數(shù)和一個析構(gòu)函數(shù),所以,我們首先對lua_State做一下封裝:
#ifndef LUA_EXTRALIBS #define LUA_EXTRALIBS /* empty */ #endif
static const luaL_reg lualibs[] = { {"base", luaopen_base}, {"table", luaopen_table}, {"io", luaopen_io}, {"string", luaopen_string}, {"math", luaopen_math}, {"debug", luaopen_debug}, {"loadlib", luaopen_loadlib}, /* add your libraries here */ LUA_EXTRALIBS {NULL, NULL} };
這是lua提供給用戶的一些輔助的lib,在使用lua_State的時候,你可以選擇打開或者關(guān)閉它。
完整的類實現(xiàn)如下:
//lua_State class state { public: state(bool bOpenStdLib = false) : err_fn(0) { L = lua_open();
assert(L);
if (bOpenStdLib) { open_stdlib(); } }
~state() { lua_setgcthreshold(L, 0); lua_close(L); }
void open_stdlib() { assert(L);
const luaL_reg *lib = lualibs; for (; lib->func; lib++) { lib->func(L); /* open library */ lua_settop(L, 0); /* discard any results */ } }
lua_State* get_handle() { return L; }
int error_fn() { return err_fn; }
private: lua_State *L;
int err_fn; };
通常我們僅僅在游戲代碼中使用一個lua_State*的指針,所以我們?yōu)樗鼘崿F(xiàn)一個單件,默認(rèn)打開所有l(wèi)ua提供的lib:
//return the global instance state* lua_state() { static state L(true);
return &L; }
在你的游戲中應(yīng)用Lua(3):using lua in cpp(封裝棧操作)
前面提到了lua與主機(jī)程序是通過一個運行時棧來交換信息的,所以我們把對棧的訪問做一下簡單的封裝。
我們利用從c++的函數(shù)重載機(jī)制對這些操作做封裝,重載提供給我們一種以統(tǒng)一的方式來處理操作的機(jī)制。
向lua傳遞信息是通過壓棧的操作來完成的,所以我們定義一些Push()函數(shù):
inline void Push(lua_State *L, int value); inline void Push(lua_State *L, bool value); ...
對應(yīng)簡單的c++內(nèi)建類型,我們實現(xiàn)出相同的Push函數(shù),至于函數(shù)內(nèi)部的實現(xiàn)是非常的簡單,只要利用lua提供的api來實現(xiàn)即可,例如:
inline void Push(lua_State *L, int value) { lua_pushnumber(L, value); }
這種方式帶來的好處是,在我們的代碼中我們可以以一種統(tǒng)一的方式來處理壓棧操作,如果有一種類型沒有定義相關(guān)的壓棧操作,將產(chǎn)生一個編譯期錯誤。
后面我會提到,如何將一個用戶自定義類型的指針傳遞到lua中,在那種情況下,我們的基本代碼無須改變,只要添加一個相應(yīng)的Push()函數(shù)即可。
記住close-open原則吧,它的意思是對修改是封閉的,對擴(kuò)充是開放的,好的類庫設(shè)計允許你擴(kuò)充它,而無須修改它的實現(xiàn),甚至無須重新編譯。
《c++泛型設(shè)計新思維》一書提到了一種技術(shù)叫type2type,它的本質(zhì)是很簡單:
template <typename T> struct type2type { typedef T U; };
正如你看到的,它并沒有任何數(shù)據(jù)成員,它的存在只是為了攜帶類型信息。
類型到類型的映射在應(yīng)用于重載函數(shù)時是非常有用的,應(yīng)用type2type,可以實現(xiàn)編譯期的分派。
下面看看我們?nèi)绾卧趶臈V腥〉胠ua信息時應(yīng)用type2type:
測試類型:由于lua的類型系統(tǒng)與c++是不相同的,所以,我們要對棧中的信息做一下類型檢測。
inline bool Match(type2type<bool>, lua_State *L, int idx) { return lua_type(L, idx) == LUA_TBOOLEAN; }
類似的,我們要為cpp的內(nèi)建類型提供相應(yīng)的Match函數(shù):
inline bool Match(type2type<int>, lua_State *L, int idx); inline bool Match(type2type<const char*>, lua_State *L, int idx);
...
可以看出,type2type的存在只是為了在調(diào)用Match時決議到正確的函數(shù)上,由于它沒有任何成員,所以不存在運行時的成本。
同樣,我們?yōu)閏pp內(nèi)建類型提供Get()函數(shù):
inline bool Get(type2type<bool>, lua_State *L, int idx) { return lua_toboolean(L, idx); }
inline int Get(type2type<int>, lua_State *L, int idx) { return static_cast<int>(lua_tonumber(L, idx)); }
...
我想你可能注意到了,在int Get(type2type<int>)中有一個轉(zhuǎn)型的動作,由于lua的類型系統(tǒng)與cpp的類型不同,所以轉(zhuǎn)型動作必須的。
除此之外,在Get重載函數(shù)(s)中還有一個小小的細(xì)節(jié),每個Get的函數(shù)的返回值是不相同的,因為重載機(jī)制是依靠參數(shù)的不同來識別的,而不是返回值。
前面說的都是一些基礎(chǔ)的封裝,下來我們將介紹如何向lua注冊一個多參數(shù)的c函數(shù)。還記得嗎?利用lua的api只能注冊int (*ua_CFunction)(lua_State *)型的c函數(shù),別忘記了,lua是用c寫的。
在你的游戲中應(yīng)用Lua(3):using lua in cpp(注冊不同類型的c函數(shù))之一
前面說到,我們可以利用lua提供的api,向腳本提供我們自己的函數(shù),在lua中,只有l(wèi)ua_CFunction類型的函數(shù)才能直接向lua注冊,lua_CFunction實際上是一個函數(shù)指針: typedef int (*lua_CFunction)(lua_State *L);
而在實際的應(yīng)用中,我們可能需要向lua注冊各種參數(shù)和返回值類型的函數(shù),例如,提供一個add腳本函數(shù),返回兩個值的和:
int add(int x, int y);
為了實現(xiàn)這個目的,首先,我們定義個lua_CFunction類型的函數(shù):
int add_proxy(lua_State *L) { //取得參數(shù) if (!Match(TypeWrapper<int>(), L, -1)) return 0; if (!Match(TypeWrapper<int>(), L, -2)) return 0;
int x = Get(TypeWrapper<int>(), L, -1); int y = Get(TypeWrapper<int>(), L, -1);
//調(diào)用真正的函數(shù) int result = add(x, y);
//返回結(jié)果 Push(result);
return 1; }
現(xiàn)在,我們可以向lua注冊這個函數(shù):
lua_pushstring(L, “add”); lua_pushcclosure(L, add_proxy, 0); lua_settable(L, LUA_GLOBALINDEX);
在腳本中可以這樣調(diào)用這個函數(shù):
print(add(100, 200))
從上面的步驟可以看出,如果需要向lua注冊一個非lua_CFunction類型的函數(shù),需要: 1. 為該函數(shù)實現(xiàn)一個封裝調(diào)用。 2. 在封裝調(diào)用函數(shù)中從lua棧中取得提供的參數(shù)。 3. 使用參數(shù)調(diào)用該函數(shù)。 4. 向lua傳遞其結(jié)果。
注意,我們目前只是針對全局c函數(shù),類的成員函數(shù)暫時不涉及,在cpp中,類的靜態(tài)成員函數(shù)與c函數(shù)類似。
假設(shè)我們有多個非lua_CFunction類型的函數(shù)向lua注冊,我們需要為每一個函數(shù)重復(fù)上面的步驟,產(chǎn)生一個封裝調(diào)用,可以看出,這些步驟大多是機(jī)械的,因此,我們需要一種方式自動的實現(xiàn)上面的步驟。
首先看步驟1,在cpp中,產(chǎn)生這樣一個封裝調(diào)用的函數(shù)的最佳的方式是使用template,我們需要提供一個lua_CFunction類型的模板函數(shù),在這個函數(shù)中調(diào)用真正的向腳本注冊的函數(shù),類似于這樣: template <typename Func> inline int register_proxy(lua_State *L)
現(xiàn)在的問題在于:我們要在這個函數(shù)中調(diào)用真正的函數(shù),那么我們必須要在這個函數(shù)中取得一個函數(shù)指針,然而,lua_CFunction類型的函數(shù)又不允許你在增加別的參數(shù)來提供這個函數(shù)指針,現(xiàn)在該怎么讓regisger_proxy函數(shù)知道我們真正要注冊的函數(shù)呢?
在oop中,似乎可以使用類來解決這個問題:
template <Func> struct register_helper { explicit register_helper(Func fn) : m_func(fn) {} int register_proxy(lua_State *L);
protected: Func m_func; };
可是不要忘記,lua_CFunction類型指向的是一個c函數(shù),而不是一個成員函數(shù),他們的調(diào)用方式是不一樣的,如果將上面的int register_proxy()設(shè)置為靜態(tài)成員函數(shù)也不行,因為我們需要訪問類的成員變量m_func;
讓我們再觀察一下lua_CFunction類型的函數(shù):
int register_proxy(lua_State *L);
我們看到,這里面有一個lua_State*型的指針,我們能不能將真正的函數(shù)指針放到這里面存儲,到真正調(diào)用的時候,再從里面取出來呢?
Lua提供了一個api可以存儲用戶數(shù)據(jù): Lua_newuserdata(L, size)
在適當(dāng)?shù)臅r刻,我們可以再取出這個數(shù)據(jù):
lua_touserdata(L, idx)
ok,現(xiàn)在傳遞函數(shù)指針的問題我們已經(jīng)解決了,后面再看第二步:取得參數(shù)。
在你的游戲中應(yīng)用Lua(3):using lua in cpp(注冊不同類型的c函數(shù))之二
在解決了傳遞函數(shù)指針的問題之后,讓我們來看看調(diào)用函數(shù)時會有一些什么樣的問題。
首先,當(dāng)我們通過函數(shù)指針調(diào)用這個函數(shù)的時候,由于我們面對的是未知類型的函數(shù),也就是說,我們并不知道參數(shù)的個數(shù),參數(shù)的類型,還有返回值的類
型,所以我們不能直接從lua棧中取得參數(shù),當(dāng)然,我們可以通過運行時測試棧中的信息來得到lua傳遞進(jìn)來的參數(shù)的個數(shù)和類型,這意味著我們在稍后通過函
數(shù)指針調(diào)用函數(shù)時也需要動態(tài)的根據(jù)參數(shù)的個數(shù)和類型來決議到正確的函數(shù),這樣,除了運行時的成本,cpp提供給我們的強(qiáng)類型檢查機(jī)制的好處也剩不了多少
了,我們需要的是一種靜態(tài)的編譯時的“多態(tài)”。
在cpp中,至少有兩種方法可以實現(xiàn)這點。最直接簡單的是使用函數(shù)重載,還有一種是利用模板特化機(jī)制。
簡單的介紹一下模板特化:
在cpp中,可以針對一個模板函數(shù)或者模板類寫出一些特化版本,編譯器在匹配模板參數(shù)時會尋找最合適的一個版本。類似于這樣:
templat <typename T> T foo() { T tmp(); return tmp; }
//提供特化版本 template <> int foo() { return 100; }
在main()函數(shù)中,我們可以顯示指定使用哪個版本的foo:
int main(int argc, char **argv) { cout << foo<int>() << endl; return 0; }
程序?qū)⑤敵?00,而不是0,以上代碼在 g++中編譯通過,由于vc6對于模板的支持不是很好,所以有一些模板的技術(shù)在vc6中可能不能編譯通過。
所以最好使用重載來解決這個問題,在封裝函數(shù)調(diào)用中,我們首先取得這個函數(shù)指針,然后,我們要提供一個Call函數(shù)來真正調(diào)用這個函數(shù),類似于這樣: //偽代碼 int Call(pfn, lua_State *L, int idx)
可是我們并不知道這個函數(shù)指針的類型,現(xiàn)在該怎么寫呢?別忘記了,我們的register_proxy()是一個模板函數(shù),它有一個參數(shù)表示了這個指針的類型:
template <typename Func> int register_proxy(lua_State *L) { //偽代碼,通過L參數(shù)取得這個指針 unsigned char *buffer = get_pointer(L);
//對這個指針做強(qiáng)制類型轉(zhuǎn)化,調(diào)用Call函數(shù) return Call(*(Func*)buffer, L, 1); }
由重載函數(shù)Call調(diào)用真正的函數(shù),這樣,我們可以使用lua api注冊相關(guān)的函數(shù),下來我們提供一個注冊的函數(shù):
template <typename Func> void lua_pushdirectclosure(Func fn, lua_State *L, int nUpvalue) { //偽代碼,向L存儲函數(shù)指針 save_pointer(L);
//向lua提供我們的register_proxy函數(shù) lua_pushcclosure(L, register_proxy<Func>, nUpvalue + 1); }
再定義相關(guān)的注冊宏: #define lua_register_directclosure(L, func) lua_pushstring(L, #func); lua_pushdirectclosure(func, L, 1); lua_settable(L, LUA_GLOBALINDEX)
現(xiàn)在,假設(shè)我們有一個int add(int x, int y)這樣的函數(shù),我們可以直接向lua注冊:
lua_register_directclosure(L, add);
看,最后使用起來很方便吧,我們再也不用手寫那么多的封裝調(diào)用的代碼啦,不過問題還沒有完,后面我們還得解決Call函數(shù)的問題。
在你的游戲中應(yīng)用Lua(3):using lua in cpp(注冊不同類型的c函數(shù))之三
下面,讓我們集中精力來解決Call重載函數(shù)的問題吧。
前面已經(jīng)說過來,Call重載函數(shù)接受一個函數(shù)指針,然后從lua棧中根據(jù)函數(shù)指針的類型,取得相關(guān)的參數(shù),并調(diào)用這個函數(shù),然后將返回值壓入lua棧,類似于這樣:
//偽代碼 int Call(pfn, lua_State *L, int idx)
現(xiàn)在的問題是pfn該如何聲明?我們知道這是一個函數(shù)指針,然而其參數(shù),以及返回值都是未知的類型,如果我們知道返回值和參數(shù)的類型,我們可以用一個typedef來聲明它:
typedef void (*pfn)();
int Call(pfn fn, lua_State *L, int idx);
我們知道的返回值以及參數(shù)的類型只是一個模板參數(shù)T,在cpp中,我們不能這樣寫:
template <typename T> typedef T (*Func) ();
一種解決辦法是使用類模板:
template <typename T> struct CallHelper { typedef T (*Func) (); };
然后在Call中引用它:
template <typename T> int Call(typename CallHelper::Func fn, lua_State *L, int idx)
注意typename關(guān)鍵字,如果沒有這個關(guān)鍵字,在g++中會產(chǎn)生一個編譯警告,它的意思是告訴編譯器,CallHelper::Func是一個類型,而不是變量。
如果我們這樣來解決,就需要在CallHelper中為每種情況大量定義各種類型的函數(shù)指針,還有一種方法,寫法比較古怪,考慮一個函數(shù)中參數(shù)的聲明:
void (int n);
首先是類型,然后是變量,而應(yīng)用于函數(shù)指針上:
typedef void (*pfn) (); void (pfn fn);
事實上,可以將typedef直接在參數(shù)表中寫出來:
void (void (*pfn)() );
這樣,我們的Call函數(shù)可以直接這樣寫:
//針對沒有參數(shù)的Call函數(shù) template <typename RT> int Call(RT (*Func) () , lua_State *L, int idx); { //調(diào)用Func RT ret = (*Func)();
//將返回值交給lua Push(L, ret);
//告訴lua有多少個返回值 return 1; }
//針對有一個參數(shù)的Call template <typename T, typename P1> int Call(RT (*Func)(), lua_State *L, int idx) { //從lua中取得參數(shù) if (!Match(TypeWrapper<P1>(), L, -1) return 0;
RT ret = (*Func) (Get(TypeWrapper<P1>(), L, -1));
Push(L, ret); return 1; }
按照上面的寫法,我們可以提供任意參數(shù)個數(shù)的Call函數(shù),現(xiàn)在回到最初的時候,我們的函數(shù)指針要通過lua_State *L來存儲,這只要利用lua提供的api就可以了,還記得我們的lua_pushdirectclosure函數(shù)嗎:
template <typename Func> void lua_pushdirectclosure(Func fn, lua_State *L, int nUpvalue) { //偽代碼,向L存儲函數(shù)指針 save_pointer(L);
//向lua提供我們的register_proxy函數(shù) lua_pushcclosure(L, register_proxy<Func>, nUpvalue + 1); }
其中,save_pointer(L)可以這樣實現(xiàn):
void save_pointer(lua_State *L) { unsigned char* buffer = (unsigned char*)lua_newuserdata(L, sizeof(func)); memcpy(buffer, &func, sizeof(func)); }
而在register_proxy函數(shù)中:
template <typename Func> int register_proxy(lua_State *L) { //偽代碼,通過L參數(shù)取得這個指針 unsigned char *buffer = get_pointer(L); //對這個指針做強(qiáng)制類型轉(zhuǎn)化,調(diào)用Call函數(shù) return Call(*(Func*)buffer, L, 1); } get_pointer函數(shù)可以這樣實現(xiàn):
unsigned char* get_pointer(lua_State *L) { return (unsigned char*) lua_touserdata(L, lua_upvalueindex(1)); }
這一點能夠有效運作主要依賴于這樣一個事實:
我們在lua棧中保存這個指針之后,在沒有對棧做任何操作的情況下,又把它從棧中取了出來,所以不會弄亂lua棧中的信息,記住,lua棧中的數(shù)據(jù)是由用戶保證來清空的。
到現(xiàn)在,我們已經(jīng)可以向lua注冊任意個參數(shù)的c函數(shù)了,只需簡單的一行代碼:
lua_register_directclosure(L, func)就可以啦。
在你的游戲中應(yīng)用Lua(3):Using Lua in cpp(基本數(shù)據(jù)類型、指針和引用)之一
Using Lua in cpp(基本數(shù)據(jù)類型、指針和引用)
前面介紹的都是針對cpp中的內(nèi)建基本數(shù)據(jù)類型,然而,即使是這樣,在面對指針和引用的時候,情況也會變得復(fù)雜起來。
使用前面我們已經(jīng)完成的宏lua_register_directclosure只能注冊by value形式的參數(shù)的函數(shù),當(dāng)參數(shù)中存在指針和引用的時候(再強(qiáng)調(diào)一次,目前只針對基本數(shù)據(jù)類型):
1、 如果是一個指針,通常實現(xiàn)函數(shù)的意圖是以這個指針傳遞出一個結(jié)果來。 2、 如果是一個引用,同上。 3、 如果是一個const指針,通常只有面對char*的時候才使用const,實現(xiàn)函數(shù)的意圖是,不會改變這個參數(shù)的內(nèi)容。其它情況一般都避免出現(xiàn)使用const指針。 4、 如果是一個const引用,對于基本數(shù)據(jù)類型來說,一般都避免出現(xiàn)這種情況。
Lua和cpp都允許函數(shù)用某種方式返回多個值,對于cpp來說,多個返回值是通過上述的第1和第2種情況返回的,對于lua來說,多個返回值可以直接返回:
--in Lua function swap(x, y) tmp = x x = y y = tmp
return x, y end
x = 100 y = 200
x, y = swap(x, y)
print(x..y)
程序輸出:200100
同樣的,在主機(jī)程序中,我們也可以向Lua返回多個值:
int swap(lua_State *L) { //取得兩個參數(shù) int x = Get(TypeWrapper<int>(), L, -1); int y = Get(TypeWrapper<int>(), L, -2);
//交換值 int tmp = x; x = y; y = tmp;
//向Lua返回值 Push(L, x); Push(L, y);
//告訴Lua我們返回了多少個值 return 2; }
現(xiàn)在我們可以在Lua中這樣調(diào)用這個函數(shù):
x = 100 y = 200
x, y = swap(x, y)
在我們的register_proxy函數(shù)中只能對基本數(shù)據(jù)類型的by value方式有效,根據(jù)我們上面的分析,如果我們能夠在編譯期知道,對于一個模板參數(shù)T: 1、 這是一個基本的數(shù)據(jù)類型,還是一個用戶自定義的數(shù)據(jù)類型? 2、 這是一個普通的指針,還是一個iterator? 3、 這是一個引用嗎? 4、 這是一個const 普通指針嗎? 5、 這是一個const 引用嗎?
如果我們能知道這些,那么,根據(jù)我們上面的分析,我們希望:(只針對基本數(shù)據(jù)類型) 1、 如果這是一個指針,我們希望把指針?biāo)傅膬?nèi)容返回給Lua。 2、 如果這是一個引用,我們希望把引用的指返回給Lua。 3、 如果這是const指針,我們希望將從Lua棧中取得的參數(shù)傳遞 給調(diào)用函數(shù)。 4、 如果這是一個const引用,我們也希望把從Lua棧中取得的參 |
|