2016年3月29日火曜日

Luaのユーザーデータの利用例

C++側で定義したクラス(構造体)をLuaスクリプト側で使用する際には、専用のメモリブロックを利用する必要がある。

主に必要な手続きは以下。
  • void *lua_newuserdata (lua_State *L, size_t size)でユーザーデータ用のメモリブロックを確保
  • 確保したメモリブロック内に、対象クラスのインスタンスを配置
  • Luaスクリプト側に公開するメソッドを__indexメタメソッドへ登録

各ユーザーデータで取り違え(?)が生じないように以下を実施。

識別用のメタテーブルをC++側で設定しておき、Luaスクリプト側へ渡す。
(口述するサンプルコードではメタテーブルに紐づく名前を"person_type"としている)
Luaスクリプト側からユーザーデータを受け取った際(luaL_checkudata呼び出し時)には正しい型か否かを上記メタテーブルを介して検証する。


"Sample.lua"
local obj1 = person.new(20,"Luna")
print( obj1:Info() )
obj1:ChangeName("Luuga")
print( obj1:Info() )

local obj2 = person.new()
print( obj2:Info() )

期待結果










C++側
#include <lua.hpp>
#include <string>
#include <iostream>
#include <sstream>
#include <new> //. to use "placement new"

//. Luaスクリプト側に公開したいクラス
class Person
{
private:
    int m_age;
    std::string m_name;
public:
    explicit Person(const int age = 0, const std::string name = "Unknown")
        : m_age(age), m_name(name) {}

    void ChangeName(std::string name)
    {
        m_name = name;
    }

    std::string Info() const
    {
        std::ostringstream stream;
        stream << "Name is " << m_name << ", Age is " << std::to_string(m_age);
        return stream.str();
    }
};

auto DefinePerson(lua_State *L)
{
    static const luaL_Reg Factory[] =
    {
        {
            "new",
            [](lua_State *L) //. ラムダ式
            {
                auto p = lua_newuserdata(L, sizeof(Person));

                //. lua_newuserdataでアロケートしたメモリブロックへPersonのインスタンスを配置する(placement new)。
                //. lua_newuserdataによって最上位のスタックにユーザーデータがPushされているため、
                //. これに以外に2つのスタックが存在していれば、引数指定ありでnewをCallされたとみなす。
                if (3 == lua_gettop(L))
                {
                    new(p) Person(luaL_checkinteger(L, -3), luaL_checkstring(L, -2));
                }
                else
                {
                    new(p) Person();
                }

                //. スタックトップのオブジェクト(ユーザーデータ)のメタテーブルへ
                //. "person_type"に紐づくメタテーブルを設定。
                luaL_setmetatable(L, "person_type");

                return 1; //. 戻り値の数
            }
        },
        {
            nullptr,
            nullptr
        }
    };

    static const luaL_Reg Methods[] =
    {
        {
            "ChangeName",
            [](lua_State *L) //. ラムダ式
            {
                //. "person_type"に紐づくメタテーブルをもったユーザーデータかを検証し、Person*型へキャストして使用する。
                reinterpret_cast<person>(luaL_checkudata(L, 1, "person_type"))->ChangeName(
                    luaL_checkstring(L, -1) );
                return 0; //. 戻り値の数
            }
        },
        {
            "Info",
            [](lua_State *L) //. ラムダ式
            {
                //. "person_type"に紐づくメタテーブルをもったユーザーデータかを検証し、Person*型へキャストして使用する。
                auto info = reinterpret_cast<person>(luaL_checkudata(L, 1, "person_type"))->Info();
                lua_pushstring(L, info.c_str());
                return 1; //. 戻り値の数
            }
        },
        {
            nullptr,
            nullptr
        }
    };


    //. メタテーブルを作成し、キー名"person_type"としてレジストリに登録。
    luaL_newmetatable(L, "person_type");
    //. Methodsを割り当てたテーブルを作成。
    luaL_newlib(L, Methods);
    //. メタテーブル内の__indexメタメソッドに、上記テーブルを割り当てる。
    lua_setfield(L, -2, "__index");
    //. メタテーブルをPop。
    lua_pop(L, 1);

    //. Factoryメソッドを割り当てたテーブルを作成し、
    //. 呼び出し元へ制御を戻す。
    luaL_newlib(L, Factory);

    return 1; //. 戻り値の数
}

auto main() -> int
{ 
    const char* fileName = "C:\\Develop\\LuaScript\\Sample.lua";

    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    luaL_requiref(L, "person", DefinePerson, 1);
    lua_pop(L, 1);

    if (luaL_dofile(L, fileName))
    {
     printf("%s", lua_tostring(L, -1));
    }

    lua_close(L);
    std::cin.get();
}

0 件のコメント:

コメントを投稿