玩轉cocos2d-x lua-binding, 實現c++與lua混合編程

引言

城市精靈GO(http://csjl.teamtop3.com/)是一款基於cocos2d-x開發的LBS社交遊戲, 經過真實地圖的探索, 發現和抓捕隱匿於身邊的野生精靈, 利用遊戲中豐富的玩法提高和進化本身的精靈團隊, 一步一步成爲精靈訓練大師. html

 

 

本遊戲的開發混合使用了c++和lua編程, 既發揮了c++高性能, 跨平臺系統兼容的優點, 又享受了lua敏捷方便的開發效率. cocos2d-x提供了一套完備的lua-binding工具來幫助開發者實現c++和lua的代碼聯合, 能夠方便實現二者之間的數據通訊和代碼互調. 本文就從lua-binding入手, 深刻介紹c++和lua混合編程的相關細節, 探討其中可能存在的問題和發展. 本文原創發佈於博客園(http://www.cnblogs.com/dabaopku/p/5649294.html), 獨家受權XX轉載.c++

本遊戲開發基於cocos2d-x 3.2.0版本, 其餘版本用戶請祥閱官方文檔和源代碼.正則表達式

原理介紹

lua(https://www.lua.org/)做爲一種輕量級的腳本語言, 以其簡單的語法結構, 方便的c++集成能力, 高效的執行效率收到廣大遊戲開發者的熱愛, 也是cocos2d-x官方首次引入的腳本語言.編程

做爲一種腳本語言, lua是在一個運行時環境(State)裏執行的, 這個運行時環境保存了腳本運行所需的內存空間, 建立的全局變量, 加載的庫文件等. 在這個運行時環境裏還有一個棧空間(Stack), 其做用就是在lua和c語言進行數據傳遞和函數調用. lua原生實現了不少c api對棧空間進行操做, 讓開發者可以方便地實現lua腳本代碼與c編譯代碼的雙向通訊.json

本文的主題是lua和c++混合編程, 但背後實際上是lua和c api的互相調用, 全部c++的功能都要經過一層c函數的包裝, 這點是要牢記在心的, 這也正是lua-binding的核心.api

cocos2d-x提供的lua-bingding工具使用libclang分析c++源碼, 提取語法樹, 將c++的類成員函數封裝爲c函數, 而後根據參數類型自動調用lua c api, 實現對棧空間的操做, 將c++的數據傳遞給lua. lua腳本加載編譯好的c++庫, 就能夠自由調用c++裏面的類對象和成員函數了; c++的代碼則能夠直接使用lua c api, 執行一段lua腳本, 並經過棧空間獲取返回結果.安全

操做實戰

本節咱們來詳細介紹c++和lua混合編程的具體實現方法, 首先介紹若是利用cocos2d-x的工具自動把項目裏的c++代碼導出爲lua模塊, 而後介紹若是手動導出特殊類型的函數, 最後介紹實踐中的技巧和潛在隱患.網絡

自動生成lua模塊

cocos2d-x提供的lua-binding工具位於項目 tools/tolua 目錄下, 能夠看到裏面有 genbindings.py, **.ini, userconf.ini等文件, 這些就是自定義代碼導出策略的配置文件.異步

  • userconf.ini: 這個文件配置了系統運行的環境變量, 好比adk路徑, 使用系統默認值便可
  • genbindings.py: 這是生成lua-binding代碼的腳本, 代碼最後的cmd_args參數配置了須要導出的不一樣lua模塊, 根據須要在這裏添加條目便可. 括號裏的第一個參數表明了模塊名(下文介紹), 第二個參數表明了生成文件的名字; 若是想要自定義生成目錄, 能夠修改output_dir變量
  • **.ini: 這裏是導出配置的關鍵, 咱們以cocos2dx.ini爲例, 詳細介紹每個部分的做用
    • [cocos2d-x] 這裏對應上文介紹的模塊名, 要和第一個參數保持一致
    • prefix 給全部生成的c函數添加前綴, 防止命名重複
    • target_namespace 導出的lua模塊的命名空間, 重要
    • 接下來是一些編譯參數, 用於輔助libclang尋找頭文件, 設置宏參數, 若是發現clang出錯, 能夠考慮修改這裏的參數
    • headers 這個參數能夠設置一組頭文件, 程序根據這個頭文件及其包含的頭文件, 抽取出c++聲明的類, 做爲導出對象, 重要
    • classes 這個參數配置基於正則表達式的模式列表, 過濾上一個參數提取出的類, 獲得最終導出的類列表, 重要
    • skip 不想導出的類級函數, 用於處理一些和lua不兼容的c++參數, 好比 std::function, std::pair 等, 這些參數無法經過棧空間傳遞給lua, 所以也就沒辦法導出給lua使用, 須要排除掉. 重要
    • rename_functions, rename_classed 重命名導出的函數和類, 用處不大
    • classes_have_no_parents, base_classes_to_skip, abstract_classes 一些小功能, 用處不大

設置完這個文件, 運行 genbindings.py 文件, 就能夠看到生成的c++代碼了. 把這個代碼加入到項目中, 就能夠在lua腳本里直接使用c++的相關功能了.ide

好比, c++裏有這個一個類:

class GuideManager
{
public:
    static GuideManager *getInstance();
    
public:
    bool isAvailable(const std::string &id);
    void addGuide(const std::string &id);
    void finishGuide(const std::string &id);
    bool isGuideFinished(const std::string &id);
    void clear();
protected:
    std::map<std::string, bool> _finishedGuides;
    std::mutex _lock;
};

經過lua-binding獲得導出類, 就能夠在lua代碼裏直接使用:

local manager = pp.GuideManager:getInstance()
if manager:isAvailable("12345") then
    -- Show Guide
    manager:finishGuide("12345")
end

經過上述簡單的配置, 就能夠把項目裏上百個類, 幾千個成員函數直接導出, 提供給lua使用. 在生成c++文件的同時, 程序還會生成一系列沒有實際功能的lua文件, 每個文件對應一個導出類, 列出了這個類導出的全部函數以及參數類型, 方便開發者驗證導出的方法是否知足預期, 同時能夠交給第三方插件來輔助IDE進行代碼高亮與提示.

若是開發者更新了c++代碼, 只須要從新運行腳本, 更新導出文件便可.

以上操做就是cocos2d-x推薦給開發者使用的lua-binding方案, 能夠在官方網站和網絡上找到豐富的教程, 這裏再也不深刻展開.

手動導出lua模塊

在實際應用中, 手動導出一個功能模塊也是很重要的需求, 好比在c++裏面實現了一個網絡庫, 經過傳遞一個std::function做爲回調函數, 函數原型以下:

void get(const std::string &path, Json::Value &params, std::function<void(NetworkResponse *)> callback);

可是lua裏面的函數和c++的std::function並不兼容, 不能直接把lua的函數傳遞給c++使用, 所以lua-binding工具就不能自動生成代碼綁定了. 開發者須要手動實現參數的傳遞, 把lua函數轉換爲c++的std::function.

爲了克服這個困難, 咱們先來看一下lua-binding是怎樣自動生成代碼的:

int lua_pocketpet_PetModel_getSkillById(lua_State* tolua_S)
{
// 1
    int argc = 0;
    PocketPet::PetModel* cobj = nullptr;
    bool ok  = true;

    cobj = (PocketPet::PetModel*)tolua_tousertype(tolua_S,1,0);

// 2 argc = lua_gettop(tolua_S)-1; if (argc == 1) { std::string arg0; ok &= luaval_to_std_string(tolua_S, 2,&arg0); if(!ok) return 0;
// 3 PocketPet::SkillModel* ret = cobj->getSkillById(arg0); object_to_luaval<PocketPet::SkillModel>(tolua_S, "pp.SkillModel",(PocketPet::SkillModel*)ret); return 1; }
return 0; }

咱們能夠看出, lua調用c++代碼一共包含3步:

  1. 獲取c++對象
  2. 獲取參數, 校驗參數類型
  3. 調用成員函數

自動生成的代碼支持int, double等數值類型, 指針類型, std::string, std::map, std::vector, cocos2d::Map, cocos2d::Vector等模板類型, 超出這些範圍的, 就須要咱們本身實現了. 參考上述代碼, 咱們能夠先實現如下這個函數:

int lua_pocketpet_NetworkManager_getInLua(lua_State* tolua_S)
{
    int argc = 0;
    PocketPet::NetworkManager* cobj = nullptr;
    bool ok  = true;
    
    cobj = (PocketPet::NetworkManager*)tolua_tousertype(tolua_S,1,0);
    
    argc = lua_gettop(tolua_S)-1;
    if (argc == 3)
    {
        std::string arg0;
        std::string arg1;
        LUA_FUNCTION arg2;
        
        ok &= luaval_to_std_string(tolua_S, 2,&arg0);
        
        ok &= luaval_to_std_string(tolua_S, 3,&arg1);
        
// 1 arg2 = toluafix_ref_function(tolua_S, 4, 0); if(!ok) return 0; cobj->get(arg0, arg1, arg2); return 0; } return 0; }

在這裏, 咱們首先經過 toluafix_ref_function 得到一個LUA_FUNCTION(也就是 int)類型的lua函數指針, 將這個值做爲參數傳遞給業務函數. 在業務函數裏, 經過棧空間來回調這個函數指針, 以下所示:

void NetworkManager::get(const std::string &path, const std::string &params, int callback)
{
    auto func = [callback](NetworkResponse *response){
        auto engine = LuaManager::getInstance()->engine();
        engine->getLuaStack()->pushObject(response, "pp.NetworkResponse");
// 1 engine->getLuaStack()->executeFunctionByHandler(callback, 1); }; Json::Value json; this->get(path, json, func); }

咱們建立了一個std::function對象func做爲lua函數的封裝, 在func內部, 經過lua棧空間調用lua回調函數. 經過這兩層的封裝, 就實現了把lua的函數做爲c++的回調函數進行使用.

對於其餘的特殊類型, 也均可以用相似的手段來解決.

其餘技巧與潛在風險

經過lua-binding方案, 能夠方便的把c++開發的功能導入到lua裏面進行使用, 能夠方便團隊從c++向lua轉型, 提升產品後期快速迭代更新的速度. 雖然如今鼓勵腳本開發, 但c++的應用無可避免, 好比渠道sdk, 好比跨平臺適配, 好比帳號安全維護等等, 都仍是須要c++這把瑞士軍刀來應對一切挑戰. 在c++和lua之間, 除了經過棧空間傳遞數據, 咱們還能夠有多種機制來進行通訊, 從而克服lua-binding的侷限.

一種方案是開闢一塊專門的內存空間, 經過鍵值對存儲臨時對象, 雙方經過這塊共享空間傳遞必要信息. 這種方式能夠靈活的傳遞複雜的數據, 同時能夠應對異步調用c++的問題, 防止cocos2d-x對象由於跨幀被autorelease.

另外一種方案可使用消息分發, 經過數據對象的序列化與反序列化, 實現複雜數據的傳遞, 好比json對象, 但須要評估實現的性能損耗.

在使用lua-binding時, 還須要考慮線程執行的問題,若是涉及到多層回調以及ui刷新, 要確保內容的更新在主線程完成.

另外, 在c++中調用lua腳本會建立一個新的運行時環境, 不一樣運行時環境之間的數據是相互獨立的, 要格外留意腳本文件相關初始化工做是否正確執行.

總結

本文詳細介紹了使用cocos2d-x工具, 實現c++和lua混合編程的基本原理和實現方案, 但願對你們幫助.

相關文章
相關標籤/搜索