好记性不如铅笔头

C && C++, cocos2dx, 编程

cocos2dx-Lua学习笔记:CCLuaStack

最近在学习cocos2dx和Lua的交互,这里笔记下学习心得。

CONTENTS

备注:

1 作者对于Lua的使用较少,纯粹从使用出发,对它的理解较浅,可能有错误,还请路过的各位大牛多多指正。
2 本笔记代码部分参考cocos2dx2.2.3代码,代码版权归原作者所有。
3 由于作者时间,经验,能力有限,笔记可能不完整,以后随用随补充吧。

CCLuaStack代码较多,里面实现了大部分Lua与C++代码的交互细节,这里一点点的笔记吧。

lua_print:

打印栈中的所有的内容。参考网址如下:【 http://116.62.110.235/blog/lua-basic-notes-stack/ 】

namespace {
int lua_print(lua_State * luastate)
{
    int nargs = lua_gettop(luastate);

    std::string t;
    for (int i=1; i <= nargs; i++)
    {
        if (lua_istable(luastate, i))
            t += "table";
        else if (lua_isnone(luastate, i))
            t += "none";
        else if (lua_isnil(luastate, i))
            t += "nil";
        else if (lua_isboolean(luastate, i))
        {
            if (lua_toboolean(luastate, i) != 0)
                t += "true";
            else
                t += "false";
        }
        else if (lua_isfunction(luastate, i))
            t += "function";
        else if (lua_islightuserdata(luastate, i))
            t += "lightuserdata";
        else if (lua_isthread(luastate, i))
            t += "thread";
        else
        {
            const char * str = lua_tostring(luastate, i);
            if (str)
                t += lua_tostring(luastate, i);
            else
                t += lua_typename(luastate, lua_type(luastate, i));
        }
        if (i!=nargs)
            t += "\t";
    }
    CCLOG("[LUA-print] %s", t.c_str());

    return 0;
}
}  // namespace {

创建与初始化:

代码虽然很简短,但是函数调用很多。这里先笔记下最上层的函数调用,底层的函数实现分别笔记。

//创建一个新的lua运行环境
CCLuaStack *CCLuaStack::create(void)
{
    CCLuaStack *stack = new CCLuaStack();
    stack->init();
    stack->autorelease();
    return stack;
}
//将一个已有的lua运行环境附到一个新的LuaStacck上。
CCLuaStack *CCLuaStack::attach(lua_State *L)
{
    CCLuaStack *stack = new CCLuaStack();
    stack->initWithLuaState(L);
    stack->autorelease();
    return stack;
}

//初始化一个新的lua运行环境
bool CCLuaStack::init(void)
{
    m_state = lua_open();  //新建一个lua环境
    luaL_openlibs(m_state); //将lua标准库注册到lua环境中
    tolua_Cocos2d_open(m_state); //将cocos2dx的函数注册到lua环境中,后续笔记
    toluafix_open(m_state);

    // Register our version of the global "print" function
    const luaL_reg global_functions [] = {
        {"print", lua_print},
        {NULL, NULL}
    };
    luaL_register(m_state, "_G", global_functions); //将自定义的print函数注册到全局table中,后续笔记
    tolua_CocoStudio_open(m_state); //将cocosstudio的函数注册到lua环境中,后续笔记
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
    CCLuaObjcBridge::luaopen_luaoc(m_state);
#endif
    //将剩余的一些函数和变量注册到lua环境中,后续笔记
    register_all_cocos2dx_manual(m_state);
    register_all_cocos2dx_extension_manual(m_state);
    register_all_cocos2dx_studio_manual(m_state);
    // add cocos2dx loader
    addLuaLoader(cocos2dx_lua_loader);

    return true;
}

//将一个已有的lua运行环境附到本LuaStack上,直接赋值即可。
bool CCLuaStack::initWithLuaState(lua_State *L)
{
    m_state = L;
    return true;
}

修改变量package[“path”]:

将新的lua文件路径添加到Lua保存的路径中。参考网址如下:【 http://116.62.110.235/blog/lua-basic-notes-stack-variable-exchange/ 】

void CCLuaStack::addSearchPath(const char* path)
{
    lua_getglobal(m_state, "package");                                  /* L: package */
    lua_getfield(m_state, -1, "path");                /* get package.path, L: package path */
    const char* cur_path =  lua_tostring(m_state, -1);
    lua_pushfstring(m_state, "%s;%s/?.lua", cur_path, path);            /* L: package path newpath */
    lua_setfield(m_state, -3, "path");          /* package.path = newpath, L: package path */
    lua_pop(m_state, 2);                                                /* L: - */
}

addLuaLoader:

将自定义的loader方法添加到Lua环境中。

void CCLuaStack::addLuaLoader(lua_CFunction func)
{
    if (!func) return;
    
    // stack content after the invoking of the function
    // get loader table
    lua_getglobal(m_state, "package");                                  /* L: package */
    lua_getfield(m_state, -1, "loaders");                               /* L: package, loaders */
    
    // insert loader into index 2
    lua_pushcfunction(m_state, func);                                   /* L: package, loaders, func */
    for (int i = lua_objlen(m_state, -2) + 1; i > 2; --i)
    {
        lua_rawgeti(m_state, -2, i - 1);                                /* L: package, loaders, func, function */
        // we call lua_rawgeti, so the loader table now is at -3
        lua_rawseti(m_state, -3, i);                                    /* L: package, loaders, func */
    }
    lua_rawseti(m_state, -2, 2);                                        /* L: package, loaders */
    
    // set loaders into package
    lua_setfield(m_state, -2, "loaders");                               /* L: package */
    
    lua_pop(m_state, 1);
}

在Lua中删掉与某个Object有关的注册信息:

void CCLuaStack::removeScriptObjectByCCObject(CCObject* pObj)
{
    toluafix_remove_ccobject_by_refid(m_state, pObj->m_nLuaID);
}

void CCLuaStack::removeScriptHandler(int nHandler)
{
    toluafix_remove_function_by_refid(m_state, nHandler);
}

在C中调用Lua脚本:

参考网址如下:【 http://116.62.110.235/blog/lua-basic-notes-c-call-lua/ 】

//执行Lua脚本字符串
int CCLuaStack::executeString(const char *codes)
{
	//首先把字符串载入到Lua环境中
    luaL_loadstring(m_state, codes);
    return executeFunction(0);//以0个入参执行该脚本。
}

//执行Lua脚本文件
int CCLuaStack::executeScriptFile(const char* filename)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    std::string code("require \"");
    code.append(filename);
    code.append("\"");
    return executeString(code.c_str());
#else
//首先根据文件名找到文件的绝对路径
    std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(filename);
    ++m_callFromLua;
//执行该文件
    int nRet = luaL_dofile(m_state, fullPath.c_str());
    --m_callFromLua;
//通过对m_callFromLua的判断,来确认lua脚本是正常执行完毕的。
    CC_ASSERT(m_callFromLua >= 0);
    // lua_gc(m_state, LUA_GCCOLLECT, 0);
    
    if (nRet != 0)
    {//如果lua脚本文件有错误,这里把错误放到栈顶,这里打印出来。
        CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1));
        lua_pop(m_state, 1);//弹出栈顶的错误信息,保证栈恢复到以前。
        return nRet;
    }
    //执行成功,忽略掉脚本返回值,直接返回0
    return 0;
#endif
}
//执行注册在Lua环境中的函数
int CCLuaStack::executeGlobalFunction(const char* functionName)
{
	//首先在Lua环境中找到该函数,如果找到了,那么该函数会放到栈顶
    lua_getglobal(m_state, functionName);       /* query function by name, stack: function */
    if (!lua_isfunction(m_state, -1))
    {//如果栈顶不是函数,说明没有找到,直接日志,清理栈,返回。
        CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);
        lua_pop(m_state, 1);
        return 0;
    }
    //找到了,执行
    return executeFunction(0);
}

//执行函数,默认此时栈已经OK,即函数被先压栈,参数以正序压栈,
//numArgs表示入参的数量
int CCLuaStack::executeFunction(int numArgs)
{
	//根据入参的数量,获取到函数在栈中的位置
    int functionIndex = -(numArgs + 1);
    if (!lua_isfunction(m_state, functionIndex))//检查该位置是否是函数
    {
    	//不是,直接返回,并且清理栈
        CCLOG("value at stack [%d] is not function", functionIndex);
        lua_pop(m_state, numArgs + 1); // remove function and arguments
        return 0;
    }

//在Lua环境中找到函数__G__TRACKBACK__
    int traceback = 0;
    lua_getglobal(m_state, "__G__TRACKBACK__");                         /* L: ... func arg1 arg2 ... G */
    if (!lua_isfunction(m_state, -1))
    {//如果没有找到__G__TRACKBACK__,那么就把栈顶清理掉
        lua_pop(m_state, 1);                                            /* L: ... func arg1 arg2 ... */
    }
    else
    {//找到了,就把__G__TRACKBACK__向后放,放到func后面
        lua_insert(m_state, functionIndex - 1);                         /* L: ... G func arg1 arg2 ... */
        traceback = functionIndex - 1;
    }
    
    int error = 0;
    ++m_callFromLua;
    //调用lua_pcall,注意这里设置了1个返回值,而且设置了错误处理函数索引traceback
    error = lua_pcall(m_state, numArgs, 1, traceback);                  /* L: ... [G] ret */
    --m_callFromLua;
    if (error)
    {
    	//如果有错误,那么根据是否有traceback函数来清理栈
        if (traceback == 0)
        {
            CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1));        /* L: ... error */
            lua_pop(m_state, 1); // remove error message from stack
        }
        else                                                            /* L: ... G error */
        {
            lua_pop(m_state, 2); // remove __G__TRACKBACK__ and error message from stack
        }
        return 0;
    }
    //没有错误,就处理返回值
    // get return value
    int ret = 0;
    if (lua_isnumber(m_state, -1))
    {
        ret = lua_tointeger(m_state, -1);
    }
    else if (lua_isboolean(m_state, -1))
    {
        ret = lua_toboolean(m_state, -1);
    }
    // remove return value from stack
    lua_pop(m_state, 1);                                                /* L: ... [G] */
    
    //如果有错误处理函数,在弹一次栈。使栈回复原样。
    if (traceback)
    {
        lua_pop(m_state, 1); // remove __G__TRACKBACK__ from stack      /* L: ... */
    }
    //返回 函数的返回值
    return ret;
}
//根据注册函数的ID执行该函数
int CCLuaStack::executeFunctionByHandler(int nHandler, int numArgs)
{
  int ret = 0;
  //首先在Lua环境中找到这个函数
  if (pushFunctionByHandler(nHandler))                /* L: ... arg1 arg2 ... func */
  {
  	//找到之后,把该函数在栈中的位置调到入参下面,以便符合函数执行的要求
    if (numArgs > 0)
    {
      lua_insert(m_state, -(numArgs + 1));            /* L: ... func arg1 arg2 ... */
    }
    //执行函数
    ret = executeFunction(numArgs);
  }
  //清理栈,并返回结果
  lua_settop(m_state, 0);
  return ret;
}
//人工产生一个错误信息
bool CCLuaStack::handleAssert(const char *msg)
{
  if (m_callFromLua == 0) return false;
  //将错误信息放到栈顶
  lua_pushfstring(m_state, "ASSERT FAILED ON LUA EXECUTE: %s", msg ? msg : "unknown");
		//人工产生一个Lua错误。
  lua_error(m_state);
  return true;
}

int CCLuaStack::reallocateScriptHandler(int nHandler)
{
  LUA_FUNCTION  nNewHandle = -1;
  
  if (pushFunctionByHandler(nHandler))
  {
     nNewHandle = toluafix_ref_function(m_state,lua_gettop(m_state),0);
  }
/*
  toluafix_get_function_by_refid(m_state,nNewHandle);
  if (!lua_isfunction(m_state, -1))
  {
    CCLOG("Error!");
  }
  lua_settop(m_state, 0);
*/
  return nNewHandle;

}
//和executeFunction类似,不过这里的Lua函数会返回多个值。这里用pResultArray来保存返回值。
int CCLuaStack::executeFunctionReturnArray(int nHandler,int nNumArgs,int nNummResults,CCArray* pResultArray)
{
  if (NULL == pResultArray)
    return 0;
		//首先在Lua环境中找到这个函数
  if (pushFunctionByHandler(nHandler))         /* L: ... arg1 arg2 ... func */
  {
    if (nNumArgs > 0)
    {
    		//调整栈的顺序,以符合Lua的调用规则
      lua_insert(m_state, -(nNumArgs + 1));     /* L: ... func arg1 arg2 ... */
      int functionIndex = -(nNumArgs + 1);
      if (!lua_isfunction(m_state, functionIndex))
      {
        CCLOG("value at stack [%d] is not function", functionIndex);
        lua_pop(m_state, nNumArgs + 1); // remove function and arguments
        return 0;
      }
      
      //获取错误处理函数
      int traceback = 0;
      lua_getglobal(m_state, "__G__TRACKBACK__");             /* L: ... func arg1 arg2 ... G */
      if (!lua_isfunction(m_state, -1))
      {
        lua_pop(m_state, 1);                      /* L: ... func arg1 arg2 ... */
      }
      else
      {
        lua_insert(m_state, functionIndex - 1);             /* L: ... G func arg1 arg2 ... */
        traceback = functionIndex - 1;
      }
      
      int error = 0;
      ++m_callFromLua;
      //执行函数调用,这里设置了需要返回nNummResults个返回值
      error = lua_pcall(m_state, nNumArgs, nNummResults, traceback);          /* L: ... [G] ret1 ret2 ... retResults*/
      --m_callFromLua;
      if (error)
      {
      	//有错误则进行错误处理
        if (traceback == 0)
        {
          CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1));    /* L: ... error */
          lua_pop(m_state, 1); // remove error message from stack
        }
        else                              /* L: ... G error */
        {
          lua_pop(m_state, 2); // remove __G__TRACKBACK__ and error message from stack
        }
        return 0;
      }
      
      // get return value,don't pass LUA_MULTRET to numResults,
      if (nNummResults <= 0) return 0; //执行完毕,查询栈获取返回值,并吧返回值加到pResultArray中。 
            for (int i = 0 ; i < nNummResults; i++) { if (lua_type(m_state, -1) == LUA_TBOOLEAN) { bool value = lua_toboolean(m_state, -1); pResultArray->addObject(CCBool::create(value)) ;
                    
                }else if (lua_type(m_state, -1) == LUA_TNUMBER) {
                    
                    double value = lua_tonumber(m_state, -1);
                    pResultArray->addObject(CCDouble::create(value));
                    
                }else if (lua_type(m_state, -1) == LUA_TSTRING) {
                    
                    const char* value = lua_tostring(m_state, -1);
                    pResultArray->addObject(CCString::create(value));
                    
                }else{
                    
                    pResultArray->addObject(static_cast(tolua_tousertype(m_state, -1, NULL)));
                }
        // remove return value from stack
        lua_pop(m_state, 1);                        /* L: ... [G] ret1 ret2 ... ret*/
      }
      /* L: ... [G]*/
      
      if (traceback)
      {
        lua_pop(m_state, 1); // remove __G__TRACKBACK__ from stack    /* L: ... */
      }
    }
  }
  
  lua_settop(m_state, 0);
  
  return 1;
}

其中,关于【 __G__TRACKBACK__ 】,可以查看一下HelloLua工程中的hello.lua文件,有部分内容如下:

-- for CCLuaEngine traceback
function __G__TRACKBACK__(msg)
    cclog("----------------------------------------")
    cclog("LUA ERROR: " .. tostring(msg) .. "\n")
    cclog(debug.traceback())
    cclog("----------------------------------------")
end

基本类型压栈的处理:

//清空栈
void CCLuaStack::clean(void)
{
    lua_settop(m_state, 0);
}
//以下为各种压栈
void CCLuaStack::pushInt(int intValue)
{
    lua_pushinteger(m_state, intValue);
}
void CCLuaStack::pushFloat(float floatValue)
{
    lua_pushnumber(m_state, floatValue);
}
void CCLuaStack::pushBoolean(bool boolValue)
{
    lua_pushboolean(m_state, boolValue);
}
void CCLuaStack::pushString(const char* stringValue)
{
    lua_pushstring(m_state, stringValue);
}
void CCLuaStack::pushString(const char* stringValue, int length)
{
    lua_pushlstring(m_state, stringValue, length);
}
void CCLuaStack::pushNil(void)
{
    lua_pushnil(m_state);
}

复杂类型压栈的处理:

//把ccobject压入堆栈
void CCLuaStack::pushCCObject(CCObject* objectValue, const char* typeName)
{
    toluafix_pushusertype_ccobject(m_state, objectValue->m_uID, &objectValue->m_nLuaID, objectValue, typeName);
}
//吧CCLuaValue压入堆栈,这里根据value的类型进行不同的操作。
void CCLuaStack::pushCCLuaValue(const CCLuaValue& value)
{
    const CCLuaValueType type = value.getType();
    if (type == CCLuaValueTypeInt)
    {
        return pushInt(value.intValue());
    }
    else if (type == CCLuaValueTypeFloat)
    {
        return pushFloat(value.floatValue());
    }
    else if (type == CCLuaValueTypeBoolean)
    {
        return pushBoolean(value.booleanValue());
    }
    else if (type == CCLuaValueTypeString)
    {
        return pushString(value.stringValue().c_str());
    }
    else if (type == CCLuaValueTypeDict)
    {
        pushCCLuaValueDict(value.dictValue());
    }
    else if (type == CCLuaValueTypeArray)
    {
        pushCCLuaValueArray(value.arrayValue());
    }
    else if (type == CCLuaValueTypeCCObject)
    {
        pushCCObject(value.ccobjectValue(), value.getCCObjectTypename().c_str());
    }
}
//把一个字典压入堆栈。
void CCLuaStack::pushCCLuaValueDict(const CCLuaValueDict& dict)
{
    lua_newtable(m_state);                                              /* L: table */
    for (CCLuaValueDictIterator it = dict.begin(); it != dict.end(); ++it)
    {
        lua_pushstring(m_state, it->first.c_str());                     /* L: table key */
        pushCCLuaValue(it->second);                                     /* L: table key value */
        lua_rawset(m_state, -3);                     /* table.key = value, L: table */
    }
}
//把一个数组压入堆栈
void CCLuaStack::pushCCLuaValueArray(const CCLuaValueArray& array)
{
    lua_newtable(m_state);                                              /* L: table */
    int index = 1;
    for (CCLuaValueArrayIterator it = array.begin(); it != array.end(); ++it)
    {
        pushCCLuaValue(*it);                                            /* L: table value */
        lua_rawseti(m_state, -2, index);          /* table[index] = value, L: table */
        ++index;
    }
}

//把一个函数压入堆栈,这里由于函数已经注册到了Lua环境中,这里只要把它放到栈顶即可
bool CCLuaStack::pushFunctionByHandler(int nHandler)
{
    toluafix_get_function_by_refid(m_state, nHandler);                  /* L: ... func */
    if (!lua_isfunction(m_state, -1))
    {
        CCLOG("[LUA ERROR] function refid '%d' does not reference a Lua function", nHandler);
        lua_pop(m_state, 1);
        return false;
    }
    return true;
}

 

发表评论

5 + 3 =

此站点使用 Akismet 来减少垃圾评论。了解我们如何处理您的评论数据