cocos2d-x でC++ベースでComponentLuaを使ってLuaも実行できるようにする

#やりたいこと
サードパーティのライブラリを使っていろいろやろうとすると、Objective-C, Java+NDKが必要になってくるから、やっぱりC++ベースの方がフットワーク軽くいろいろできる。
けど、スクリプトも使いたい！
と思い調べていたら、ComponentLuaなるものを発見！
http://discuss.cocos2d-x.org/t/cocos2d-x-v3-9-released/24948

これよさそう。

#やってみる
### Luaベースでプロジェクトを作る
`cocos new なんたらかんたら -l lua`
(自分はCocosStudioベースのなので、そっちから作りました)

### AppDelegate.cppの初期化処理がLua向けなので、C++向けに書きなおす

```cpp:AppDelegate.cpp
bool AppDelegate::applicationDidFinishLaunching()
{
  // C++向けの初期化処理を追加
    // set default FPS
  // initialize director
  auto director = Director::getInstance();
  auto glview = director->getOpenGLView();
  if(!glview) {
    glview = GLViewImpl::createWithRect("appname", Rect(0, 0, 960, 640));
    director->setOpenGLView(glview);
  }
  
  director->getOpenGLView()->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);
  
  // turn on display FPS
  director->setDisplayStats(true);
  
  // set FPS. the default value is 1.0/60 if you don't call this
  director->setAnimationInterval(1.0 / 60);
  
  FileUtils::getInstance()->addSearchPath("res");

  // Luaエンジンの初期化は多分必要
  // register lua module
  auto engine = LuaEngine::getInstance();
  ScriptEngineManager::getInstance()->setScriptEngine(engine);
  lua_State* L = engine->getLuaStack()->getLuaState();
  lua_module_register(L);

  register_all_packages();

  LuaStack* stack = engine->getLuaStack();
  stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));

    //register custom function
    //LuaStack* stack = engine->getLuaStack();
    //register_custom_function(stack->getLuaState());

#if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)
    // NOTE:Please don't remove this call if you want to debug with Cocos Code IDE
    auto runtimeEngine = RuntimeEngine::getInstance();
    runtimeEngine->addRuntime(RuntimeLuaImpl::create(), kRuntimeEngineLua);
    runtimeEngine->start();
#else
  /* ここをコメントアウト
    if (engine->executeScriptFile("src/main.lua"))
    {
        return false;
    }
   */
#endif
  // シーンを開始
  Director::getInstance()->runWithScene(TitleScene::createScene());

    return true;
}
```

```cpp:TitleScene.cpp
#include "CCComponentLua.h"
#include "TitleScene.h"


USING_NS_CC;

Scene* TitleScene::createScene()
{
  // 'scene' is an autorelease object
  auto scene = Scene::create();

  // 'layer' is an autorelease object
  auto layer = TitleScene::create();

  // add layer as a child to scene
  scene->addChild(layer);

  // return the scene
  return scene;
}

// on "init" you need to initialize your instance
bool TitleScene::init()
{
  // 1. super init first
  if (!Layer::init()) {
    return false;
  }
  
  auto node = CSLoader::getInstance()->createNode("res/TitleScene.csb");
  
  auto luaComponent = ComponentLua::create("src/TitleScene.lua");
  node->addComponent(luaComponent);
  
  addChild(node);


  return true;
}

```

```lua:src/TitleScene.lua
local TitleScene = {}

TitleScene.onEnter = function(self)
  -- self:getOwner()でaddComponent()したNodeが取得できる
  print( self:getOwner():getName() )
end
TitleScene.onExit = function(self)
  -- print("onExit")
end
TitleScene.update = function(self)
  -- print(self)
end

-- it is needed to return player to let c++ nodes know it
return TitleScene

```

できた。

## おまけ

ComponentLua.cppを書き換えて、C++ <--> Luaでイベントメッセージのやりとりをできるようにしてみる。

```cpp:ComponentLua.cpp
/****************************************************************************
 Copyright (c) 2015 Chukong Technologies Inc.
 
 http://www.cocos2d-x.org
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

#include "CCComponentLua.h"
#include <string>
#include "base/CCScriptSupport.h"
#include "CCLuaEngine.h"
#include "LuaBasicConversions.h"

NS_CC_BEGIN

const std::string ComponentLua::ON_ENTER = "onEnter";
const std::string ComponentLua::ON_EXIT = "onExit";
const std::string ComponentLua::UPDATE = "update";
const std::string ComponentLua::ON_MESSAGE = "onMessage"; // 追加

#define KEY_COMPONENT  "component"

int ComponentLua::_index = 0;

ComponentLua* ComponentLua::create(const std::string& scriptFileName)
{
    CC_ASSERT(!scriptFileName.empty());
    
    initClass();
    
    auto componentLua = new(std::nothrow) ComponentLua(scriptFileName);
    if (componentLua)
    {
        componentLua->autorelease();
    }
    
    return componentLua;
}

ComponentLua::ComponentLua(const std::string& scriptFileName)
: _scriptFileName(scriptFileName)
, _table(nullptr)
, _strIndex("")
{
    _succeedLoadingScript = loadAndExecuteScript();
}

ComponentLua::~ComponentLua()
{
    removeLuaTable();
}

void ComponentLua::getScriptObjectInternal() const
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    lua_pushstring(l, KEY_COMPONENT);      // stack: "component"
    lua_rawget(l, LUA_REGISTRYINDEX);      // stack: LUA_REGISTRYINDEX["component"]
    lua_pushstring(l, _strIndex.c_str());  // stack: LUA_REGISTRYINDEX["component"] strIndex
    lua_rawget(l, -2);                     // stack: LUA_REGISTRYINDEX["component"]
}

void* ComponentLua::getScriptObject() const
{
    getScriptObjectInternal();
    return nullptr;
}

void ComponentLua::update(float delta)
{
    if (_succeedLoadingScript && getLuaFunction(ComponentLua::UPDATE))
    {
        getUserData();
        lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
        lua_pushnumber(l, delta);
        LuaEngine::getInstance()->getLuaStack()->executeFunction(2);
    }
}

void ComponentLua::onEnter()
{
    if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_ENTER))
    {
        getUserData();
        LuaEngine::getInstance()->getLuaStack()->executeFunction(1);
    }
}

void ComponentLua::onExit()
{
  if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_EXIT))
  {
    getUserData();
    LuaEngine::getInstance()->getLuaStack()->executeFunction(1);
  }
}

 // 追加
void ComponentLua::sendMessage(const ComponentLua::Message& message)
{
  if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_MESSAGE))
  {
    getUserData();
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    std_map_string_string_to_luaval(l, message);

    LuaEngine::getInstance()->getLuaStack()->executeFunction(2);
  }
}

bool ComponentLua::getLuaFunction(const std::string& functionName)
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    
    lua_pushstring(l, KEY_COMPONENT);                // stack: "component"
    lua_rawget(l, LUA_REGISTRYINDEX);                // stack: table_of_component
    lua_pushstring(l, _strIndex.c_str());            // stack: table_of_component strIndex
    lua_rawget(l, -2);                               // stack: table_of_component table_of_this
    lua_pushstring(l, functionName.c_str());         // stack: table_of_component table_of_this "update"
    lua_rawget(l, -2);                               // stack: table_of_component table_of_this table_of_this["update"]
    lua_remove(l, -2);                               // stack: table_of_component table_of_this["update"]
    lua_remove(l, -2);                               // stack: table_of_this["update"]
    
    int type = lua_type(l, -1);
//    if (type != LUA_TFUNCTION)
//    {
//        CCLOG("can not get %s function from %s", functionName.c_str(), _scriptFileName.c_str());
//    }
    
    return type == LUA_TFUNCTION;
}

bool ComponentLua::loadAndExecuteScript()
{
    // register native functions
    auto engine = LuaEngine::getInstance();
    lua_State *l = engine->getLuaStack()->getLuaState();
    
    // load script
    auto fileUtils = FileUtils::getInstance();
    std::string fullPathOfScript = fileUtils->fullPathForFilename(_scriptFileName);
    Data data = fileUtils->getDataFromFile(fullPathOfScript);
    int error = LUA_ERRFILE;
    if(data.getSize() > 0)
        error = engine->getLuaStack()->luaLoadBuffer(l, (const char*)data.getBytes(), (int)data.getSize(), fullPathOfScript.c_str());
    if (error)
    {
        CCLOG("ComponentLua::loadAndExecuteScript: %s", lua_tostring(l, -1));
        lua_pop(l, 1);
        return false;
    }
    
    // execute script
    error = lua_pcall(l, 0, 1, 0);
    if (error)
    {
        CCLOG("ComponentLua::loadAndExecuteScript: %s", lua_tostring(l, -1));
        lua_pop(l, 1);
        return false;
    }
    
    // check the return value from lua script is a table
    int type = lua_type(l, -1);
    if (type != LUA_TTABLE)
    {
        CCLOG("%s should return a table, or the script component can not work currectly", _scriptFileName.c_str());
        return false;
    }
    
    storeLuaTable();
    return true;
}

void ComponentLua::initClass()
{
    static bool run = true;
    if (run)
    {
        // LUA_REGISTRYINDEX["component"] = new table
        
        LuaEngine* engine = LuaEngine::getInstance();
        lua_State* l = engine->getLuaStack()->getLuaState();
        
        lua_pushstring(l, KEY_COMPONENT);              // stack: "component"
        lua_newtable(l);                               // stack: "component" table
        lua_rawset(l, LUA_REGISTRYINDEX);              // stack: -
        
        run = false;
    }
    
    
}

void ComponentLua::storeLuaTable()
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    
    _index++;
    _strIndex.append(StringUtils::toString(_index));
    
    // LUA_REGISTRYINDEX["component"][strIndex] = table return from lua
    lua_pushstring(l, KEY_COMPONENT);          // stack: table_return_from_lua "component"
    lua_rawget(l, LUA_REGISTRYINDEX);          // stack: table_return_from_lua table_of_component
    lua_pushstring(l, _strIndex.c_str());      // stack: table_return_from_lua table_of_component strIndex
    lua_pushvalue(l, -3);                      // stack: table_return_from_lua table_of_component strIndex table_return_from_lua
    lua_rawset(l, -3);                         // stack: table_return_from_lua table_of_component
    lua_pop(l, 1);                             // stack: table_return_from_lua
    
    // add table's elements to userdata's metatable
    object_to_luaval<cocos2d::ComponentLua>(l, "cc.ComponentLua", this);  // stack: table_return_from_lua userdata
    lua_getmetatable(l, -1);                   // stack: table_return_from_lua userdata mt
    lua_remove(l, -2);                         // stack: table_return_from_lua mt
    lua_pushnil(l);                            // stack: table_return_from_lua mt nil
    while (lua_next(l, -3))                    // stack: table_return_from_lua mt key value
    {
        lua_pushvalue(l, -2);                  // stack: table_return_from_lua mt key value key
        lua_insert(l, -2);                     // stack: table_return_from_lua mt key key value
        lua_rawset(l, -4);                     // stack: table_return_from_lua mt key
    }
    lua_pop(l, 2);
}

void ComponentLua::removeLuaTable()
{
    if (_succeedLoadingScript)
    {
        // LUA_REGISTRYINDEX["component"][strIndex] = nil
        
        lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
        
        lua_pushstring(l, KEY_COMPONENT);      // stack: "component"
        lua_rawget(l, LUA_REGISTRYINDEX);      // stack: LUA_REGISTRYINDEX["component"]
        lua_pushstring(l, _strIndex.c_str());  // stack: LUA_REGISTRYINDEX["component"] strIndex
        lua_pushnil(l);                        // stack: LUA_REGISTRYINDEX["component"] strIndex nil
        lua_rawset(l, -3);                     // stack: LUA_REGISTRYINDEX["component"]
    }
}

void ComponentLua::getUserData()
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    object_to_luaval<cocos2d::ComponentLua>(l, "cc.ComponentLua",this);
}

 // 追加
int ComponentLua::tolua_cocos2d_ComponentLua_onMessage(lua_State* tolua_S)
{
  if (NULL == tolua_S)
    return 0;
  
  int argc = 0;
  ComponentLua* self = nullptr;
  
#if COCOS2D_DEBUG >= 1
  tolua_Error tolua_err;
  if (!tolua_isusertype(tolua_S,1,"cc.ComponentLua",0,&tolua_err)) goto tolua_lerror;
#endif
  
  self = static_cast<cocos2d::ComponentLua*>(tolua_tousertype(tolua_S,1,0));
#if COCOS2D_DEBUG >= 1
  if (nullptr == self) {
    tolua_error(tolua_S,"invalid 'self' in function 'tolua_cocos2d_ComponentLua_onMessage'\n", NULL);
    return 0;
  }
#endif
  
  argc = lua_gettop(tolua_S) - 1;
  
  if (1 == argc)
  {
    ComponentLua::Message arg;
    luaval_to_std_map_string_string(tolua_S, 2, &arg, "tolua_cocos2d_ComponentLua_onMessage");
    if( self->_callbackMessage ){
      self->_callbackMessage(arg);
    }
    return 0;
  }
  
  luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d\n", "cc.Node:unscheduleUpdate", argc, 0);
  return 0;
  
#if COCOS2D_DEBUG >= 1
tolua_lerror:
  tolua_error(tolua_S,"#ferror in function 'tolua_cocos2d_ComponentLua_onMessage'.",&tolua_err);
  return 0;
#endif
}

NS_CC_END

 // 追加
int register_componentlua_moudle(lua_State* L)
{
  lua_getglobal(L, "_G");
  if (lua_istable(L,-1))//stack:...,_G,
  {
    lua_pushstring(L, "cc.ComponentLua");
    lua_rawget(L, LUA_REGISTRYINDEX);
    if (lua_istable(L,-1))
    {
      tolua_function(L, "sendMessage", ComponentLua::tolua_cocos2d_ComponentLua_onMessage);
    }
    lua_pop(L, 1);
  }
  lua_pop(L, 1);
  
  return 1;
}

```

```cpp:ComponentLua.h
/****************************************************************************
 Copyright (c) 2015 Chukong Technologies Inc.
 
 http://www.cocos2d-x.org
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

#pragma once

#include <string>
#include <unordered_map>
#include "2d/CCComponent.h"

extern int register_componentlua_moudle(lua_State* L);

NS_CC_BEGIN

class ComponentLua : public Component
{
public:
 　　 // 追加
　　  typedef std::map<std::string,std::string> Message;
    static ComponentLua* create(const std::string& scriptFileName);
    
    ComponentLua(const std::string& scriptFileName);
    /**
     * This function is used to be invoked from lua side to get the corresponding script object of this component.
     */
    void* getScriptObject() const;
    
    virtual void update(float dt);
    virtual void onEnter();
    virtual void onExit();
    // 追加
    void sendMessage(const Message& message);
    // 追加
    void addMessageCallback(const std::function<void(const Message&)> callback){
      _callbackMessage = callback;
    }
    static int tolua_cocos2d_ComponentLua_onMessage(lua_State* tolua_S);

private:
    ~ComponentLua();
    void getScriptObjectInternal() const;
    bool loadAndExecuteScript();
    void getUserData();
    void storeLuaTable();
    bool getLuaFunction(const std::string& functionName);
    void removeLuaTable();
    
    static void initClass();
    
private:
    // Script file path
    std::string _scriptFileName;
    bool _succeedLoadingScript;
    
    // a table returned from lua
    const void* _table;
    
    // string value of index
    std::string _strIndex;
    
    // the index used to get lua table, it is unique for every component
    static int _index;
    
    static const std::string ON_ENTER;
    static const std::string ON_EXIT;
    static const std::string UPDATE;
    static const std::string ON_MESSAGE; // 追加
  
 　　 // 追加
    std::function<void(const Message&)> _callbackMessage;
};

NS_CC_END

```

```cpp:lua_module_register.cpp
#include "lua_module_register.h"

#include "cocosdenshion/lua_cocos2dx_cocosdenshion_manual.h"
#include "network/lua_cocos2dx_network_manual.h"
#include "cocosbuilder/lua_cocos2dx_cocosbuilder_manual.h"
#include "cocostudio/lua_cocos2dx_coco_studio_manual.hpp"
#include "extension/lua_cocos2dx_extension_manual.h"
#include "ui/lua_cocos2dx_ui_manual.hpp"
#include "spine/lua_cocos2dx_spine_manual.hpp"
#include "3d/lua_cocos2dx_3d_manual.h"
#include "audioengine/lua_cocos2dx_audioengine_manual.h"
#include "physics3d/lua_cocos2dx_physics3d_manual.h"
#include "navmesh/lua_cocos2dx_navmesh_manual.h"
#include "CCComponentLua.h" // 追加


int lua_module_register(lua_State* L)
{
    //Dont' change the module register order unless you know what your are doing
    register_cocosdenshion_module(L);
    register_network_module(L);
    register_cocosbuilder_module(L);
    register_cocostudio_module(L);
    register_ui_moudle(L);
    register_extension_module(L);
    register_spine_module(L);
    register_cocos3d_module(L);
    register_audioengine_module(L);
    register_componentlua_moudle(L); // 追加
  
#if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION
    register_physics3d_module(L);
#endif
#if CC_USE_NAVMESH
    register_navmesh_module(L);
#endif
    return 1;
}

```

```cpp:TitleScene.cpp
  // Luaにメッセージを送る
  ComponentLua::Message message;
  for( int i = 0; i < 3; i++ ){
    message[std::to_string(i)] = 'a'+i;
  }
  luaComponent->sendMessage(message);


  // Luaからのメッセージを受け取る
  luaComponent->addMessageCallback([](const ComponentLua::Message& message){
    for( auto& m : message ){
      CCLOG("%s:%s", m.first.c_str(), m.second.c_str());
    }
  });
```

```lua:TitleScene.lua

local TitleScene = {}

TitleScene.onEnter = function(self)
  print( self:getOwner():getName() )
  -- Cにメッセージを送る
  local message = {}
  message["a"] = "b"
  message["c"] = "d"
  message["e"] = "f"
  self:sendMessage(message)
end

TitleScene.onExit = function(self)
  -- print("onExit")
end

TitleScene.update = function(self)
-- print(self)
end

-- Cからメッセージを受け取る
TitleScene.onMessage = function(self, message)
  for key, value in pairs(message) do
    print(key, value)
  end


end

-- it is needed to return player to let c++ nodes know it
return TitleScene

```