本文延续上一篇的思路,讨论如何在 cocos2dx 中让 C++ 回调 Lua 函数,从而实现事件机制。
相关阅读:cocos2dx 导出 C++ 类供 lua 使用
上一篇文章之所以绕弯处理,是因为当时没法直接调用 Lua 函数。这一次我们能直接调用 Lua 函数,实现思路就简单多了。
问题根源
问题的根本原因在于 tolua 工具自动生成的 hpp / cpp 文件中的函数签名不正确。虽然在 C++ 中把函数参数声明成了 LUA_FUNCTION,但实际上还是被当作 int 处理。
所以我们需要手动修改生成的函数,让它正确地把 Lua 端传过来的函数引用保存下来。
修改 tolua 生成的函数
把生成的对应函数体替换为下面的版本:
...
生成的对应 代码 改为以下
if (NULL == tolua_S)
return 0;
int argc = 0;
NetMgr* self = nullptr;
#if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
if (!tolua_isusertype(tolua_S, 1, "cc.Http", 0, &tolua_err)) goto tolua_lerror;
#endif
self = static_cast<NetMgr*>(tolua_tousertype(tolua_S, 1, 0));
#if COCOS2D_DEBUG >= 1
if (nullptr == self) {
tolua_error(tolua_S, "invalid 'self' in function 'tolua_cocos2d_Node_registerScriptHandler'\n", NULL);
return 0;
}
#endif
argc = lua_gettop(tolua_S) - 1;
if (argc == 1)
{
#if COCOS2D_DEBUG >= 1
if (!toluafix_isfunction(tolua_S, 2, "LUA_FUNCTION", 0, &tolua_err))
goto tolua_lerror;
#endif
LUA_FUNCTION handler = toluafix_ref_function(tolua_S, 2, 0);
self->setLuaFunc(handler);
lua_settop(tolua_S, 1);
return 0;
}
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d\n", "cc.Node:registerScriptHandler", argc, 1);
return 0;
#if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S, "#ferror in function 'tolua_cocos2d_Node_registerScriptHandler'.", &tolua_err);
return 0;
#endif
头文件
对应的头文件定义如下,其中 toLuaFunc 作为基类提供 setLuaFunc / callLuaFunc 两个接口,NetMgr 继承它并封装 HTTP 请求:
class toLuaFunc/*base class for to call lun function*/
{
public:
void setLuaFunc(LUA_FUNCTION handler);
protected:
void callLuaFunc(const char*ARG);
LUA_FUNCTION handler;
};
#define URL_BASE "http://127.0.0.1:8080/cocos/"
#define DATA_MAX_LENGTH 100
class NetMgr :public toLuaFunc
{
public:
static NetMgr*getInstance();
/**
* @brief new a HttpRequest
* @param action such as "login?name=1&&pass=1"
* @
*/
void getRequest(const char* action);
private:
NetMgr(){}
char _data[DATA_MAX_LENGTH];
};
cpp 实现
对应的 cpp 实现如下。setLuaFunc 只是把 Lua 端传进来的 handler 记下来,callLuaFunc 则通过 LuaEngine 分发一个 kCommonEvent 事件去回调 Lua:
#include "network_srv.h"
#include "CCLuaEngine.h"
#include "base/CCScriptSupport.h"
void toLuaFunc::setLuaFunc(LUA_FUNCTION handler)
{
this->handler = handler;
}
void toLuaFunc::callLuaFunc(const char*ARG)
{
CC_ASSERT(handler > 0, "call lua func 's handle must bigger than 0");
cocos2d::CommonScriptData data(handler, ARG);
cocos2d::ScriptEvent scriptEvent(cocos2d::kCommonEvent, &data);
cocos2d::LuaEngine::getInstance()->sendEvent(&scriptEvent);
}
void NetMgr::getRequest(const char* action)
{
log("c++ getRequest arg is %s",action);
string url = URL_BASE;
url += action;
HttpRequest*request = new HttpRequest;
request->setUrl(url.c_str());
request->setRequestType(HttpRequest::Type::GET);
request->setResponseCallback([=](HttpClient*client, HttpResponse *respone)
{
if (respone->getResponseCode() != 200)return;
vector<char>* buffer = respone->getResponseData();
CC_ASSERT(DATA_MAX_LENGTH > buffer->size(), " NetMgr buffer size overfloaw");
for (int i = 0; i < buffer->size(); i++)
{
_data[i] = (*buffer)[i];
}
_data[buffer->size()] = '\0';
callLuaFunc(_data);
});
HttpClient::getInstance()->setTimeoutForConnect(10);
HttpClient::getInstance()->send(request);
request->release();
}
NetMgr* NetMgr::getInstance()
{
static NetMgr* _netmgr__ = 0;
if (_netmgr__ == 0)
{
_netmgr__ = new NetMgr;
}
return _netmgr__;
}
Lua 端调用
Lua 侧的用法很直接:拿到 NetMgr 单例后,先用 setLuaFunc 注册一个 Lua 回调函数,再在需要的时候调用 getRequest 触发 HTTP 请求,请求完成后 C++ 会回调这个 Lua 函数:
local function menuCallbackClose()
http:getRequest("login?name=1&&pass=1")
return
end
label=cc.LabelTTF:create("login","",35)
label:setPosition(self.size.width/2,self.size.height/2);
rootNode:addChild(label)
local menuToolsItem = rootNode:getChildByName("Button_1")
menuToolsItem:addTouchEventListener(menuCallbackClose)
local function callback(msg)
label:setString(msg);
end
http=NetMgr:getInstance();
http:setLuaFunc(callback);
另一种 cpp 写法
其实 cpp 文件中的 callLuaFunc 还有另一种写法,直接通过 LuaStack 压栈并调用 handler,不走事件分发:
void toLuaFunc::callLuaFunc(const char*ARG)
{
CC_ASSERT(handler > 0, "call lua func 's handle must bigger than 0");
/*
cocos2d::CommonScriptData data(handler, ARG);
cocos2d::ScriptEvent scriptEvent(cocos2d::kCommonEvent, &data);
cocos2d::LuaEngine::getInstance()->sendEvent(&scriptEvent);
*/
cocos2d::LuaEngine::getInstance()->getLuaStack()->pushString(ARG);
cocos2d::LuaEngine::getInstance()->getLuaStack()->executeFunctionByHandler(handler, 1);
cocos2d::LuaEngine::getInstance()->getLuaStack()->clean();
}