[SRC] - Yet another Lua objects binder
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 28.12.09 15:04
Оценка: 22 (3)
Приветствую, уважаемые!

В процессе написания игрушки в полный рост встала задача биндить С++-объекты в Lua. По некоторым причинам сторонние библиотеки не подошли (то руки мои кривые, то лень, то просто другой взгляд на удобный биндинг). Собственно, предложенное ниже решение не претендует на полноту и истину в последней инстанции, однако, вполне возможно, пригодится кому-нибудь из вас.

Один из главных пойнтов написания велосипеда был в том, что были не нужны ни настраиваемые конструкторы, ни темплейтный биндинг методов напрямую — суть в том, что была уже разработана некая база классов, возможности которых вдруг захотелось поиметь из скриптования на Lua. Очевидно, что самый простой способ для этого — отдать публикацию своих методов и свойств на откуп самим классам, что и было сделано.

В приведенных исходниках закомментированные строчки показывают использование движка HGE для загрузки скриптов прямо из архива (все это легко переделать на вызов скриптов из файла), плюс добавляются парочку обработчиков функций (скажем, preload используется в проекте вместо стандартного include, чтобы грузить файлы опять-таки из архива к игре, а bitor — реализует аналог оператора | ). В общем, молчать дальше сил нет

В самооправдание — код местами не найс, в смысле — выглядит не всегда красиво.

Хидер (LuaStarter.h):

#pragma once

#include "lua.hpp"
#pragma comment(lib,"lua51.lib")

#include <assert.h>
#include <vector>
#include <map>
#include <string>
#include <algorithm>

/////////////////////////////////////////////////////////////////////////////////
// Simple starter for lua engine
/////////////////////////////////////////////////////////////////////////////////
class LuaEngine
{
lua_State* lua;

void AttachMyLoader();

public:
    void DoFile(const char* fileName);

    lua_State* Lua()
    {
        return lua;
    };

    void Close()
    {
        if(lua)
            lua_close(lua);

        lua = NULL;
    }
    LuaEngine(bool openLibs)
    {
        lua = lua_open();
        if(openLibs)
            luaL_openlibs(lua);

        AttachMyLoader();
    };
    ~LuaEngine(void)
    {
        Close();
    };

     operator lua_State* ()
    {
        return this->lua;
    };
};

//////////////////////////////////////////////////////////////////////
// LUA SETTERS
//////////////////////////////////////////////////////////////////////
template<typename T> 
void luaPushParam(lua_State* luaVM, T t);

template<> 
void luaPushParam(lua_State* luaVM, int p);

template<> 
void luaPushParam(lua_State* luaVM, char* p);

template<> 
void luaPushParam(lua_State* luaVM, char const* p);

template<> 
void luaPushParam(lua_State* luaVM, double p);

template<> 
void luaPushParam(lua_State* luaVM, bool p);

// UNTESTED PIECE OF CODE :(
template<typename T>
void luaPushParam(lua_State* L, const std::vector<T>& arg)
{
  lua_newtable(L); // [-1] - table
  const size_t size = arg.size();
  for(size_t i = 0; arg.size() > i; ++i)
  {
    lua_pushnumber(L, i + 1); // [-2] - index
    luaPushParam(L, arg[i]); // [-3] - value
    lua_settable(L, -3); // [-1] - table
  }

}

template<typename TKey, typename TValue>
void luaPushParam(lua_State* L, const std::map<TKey, TValue>& arg)
{
  lua_newtable(L); // stack: [-1] - table
  for(typename std::map<TKey, TValue>::const_iterator i = arg.begin(); arg.end() != i; ++i)
  {
    luaPushParam(L, i->first); // [-2] - table key
    luaPushParam(L, i->second); // [-3] - table key value
    lua_settable(L, -3); // [-1] - table
  }

}
//////////////////////////////////////////////////////////////////////
// LUA GETTERS
//////////////////////////////////////////////////////////////////////
template<typename T> 
void luaGetParam(lua_State* luaVM, int idx, T& param);

template<> 
void luaGetParam(lua_State* luaVM, int idx, const char*& param);

template<> 
void luaGetParam(lua_State* luaVM, int idx, int& param);

template<> 
void luaGetParam(lua_State* luaVM, int idx, DWORD& param);

template<> 
void luaGetParam(lua_State* luaVM, int idx, double& param);

template<> 
void luaGetParam(lua_State* luaVM, int idx, bool& param);

// UNTESTED PIECE OF CODE :(
template<typename TKey, typename TValue>
void luaGetParam(lua_State* L, int index, std::map<TKey, TValue>& ret);

template<typename T>
void luaGetParam(lua_State* L, int index, std::vector<T>& ret);

template<typename T>
void luaGetParam(lua_State* L, int index, const char* key, T& ret);

////////////////////////////////////////////////////////////////////////
// bind object functions helpers
////////////////////////////////////////////////////////////////////////
struct ILuaBindableEvents // events handlers for dynamically binded methods and properties
{
    // dynamic methods handler
    virtual int OnMethod(lua_State* state, int MethodID, const char* MethodName) = 0;
    // dynamic property get handler
    virtual int OnPropertyGet(lua_State* state, const char* PropertyName) = 0;
    // dynamic property set handler
    virtual int OnPropertySet(lua_State* state, const char* PropertyName) = 0;
};


// binder for dynamic objects methods
template<class T >
class LuaObjectBinder
{
private:

    T* m_BindedObj; // binded object
    lua_State* lua; // Lua VM

    typedef struct
    {
        LuaObjectBinder<T>* _this; // this pointer
        const char* MethodName; // name of binded method
        int MethodID; // binded method id

    } CThisData; // holder for dynamic methods

    typedef std::vector<CThisData> CThisVec;

    CThisVec m_Methods; // methods to bind

    // hook function for our methods
    static int MethodHandler(lua_State* state)
    {
        // get data pointer
        CThisData* data = (CThisData* ) lua_touserdata(state, lua_upvalueindex(1));
        // call OnMethod in binded object
        return data->_this->OnMethod(state,data);

    }
    static int PropertyGetHandler(lua_State* state)
    {
        // get this pointer
        LuaObjectBinder<T>* _this = (LuaObjectBinder<T>*) lua_touserdata(state, lua_upvalueindex(1));

        // check for valid property name
        if(!lua_isstring(state,-1))
            return 0;

        // get property name
        const char* propname = lua_tostring(state,-1);

        // call handler
        return _this->m_BindedObj->OnPropertyGet(state,propname);
    }

    static int PropertySetHandler(lua_State* state)
    {
        // get this pointer
        LuaObjectBinder<T>* _this = (LuaObjectBinder<T>*) lua_touserdata(state, lua_upvalueindex(1));

        // get property name index
        int top = lua_gettop(state);
        int propnameidx = -(top-1);

        // check for valid property name
        if(!lua_isstring(state,propnameidx))
            return 0;

        // get property name
        const char* propname = lua_tostring(state,propnameidx);

        // call handler
        return _this->m_BindedObj->OnPropertySet(state,propname);
    }

    int OnMethod(lua_State* state, CThisData* data)
    {
        return m_BindedObj->OnMethod(state,data->MethodID,data->MethodName);
    }

public:
    void AddMethod(const char* name, int methodId)
    {
            CThisData dt;
            dt._this = this;
            dt.MethodName = name;
            dt.MethodID = methodId;
            m_Methods.push_back(dt);
    };


    void Bind() // bind object methods and properties
    {

        // create new table for object
        lua_newtable(lua); // [-1] - table
        int methods = lua_gettop(lua); // methods in table

        // create metatable
        lua_newtable(lua);

        // create property get handler
        lua_pushstring(lua,"__index");
        lua_pushlightuserdata(lua, (void*) this); 
        lua_pushcclosure(lua, PropertyGetHandler, 1);
        lua_settable(lua, -3); // metatable.__index = PropertyGetHandler;

        // create property set handler
        lua_pushstring(lua,"__newindex");
        lua_pushlightuserdata(lua, (void*) this); 
        lua_pushcclosure(lua, PropertySetHandler, 1);
        lua_settable(lua, -3); // metatable.__newindex = PropertySetHandler;

        // set metatable for our object
        lua_setmetatable(lua,methods);


        // bind methods to the our object table
        for(CThisVec::iterator it = m_Methods.begin();it != m_Methods.end(); ++it)
        {
            lua_pushstring(lua, (*it).MethodName); // [-1] - method name, [-2] - table
            lua_pushlightuserdata(lua, (void* )&(*it)); 
            lua_pushcclosure(lua, MethodHandler, 1); // [-1] - function, [-2] - method name, [-3] - table

            // bind without metamethods called
            lua_rawset(lua, -3); // [-1] - table

        } // for

        // init object table done.
        // now object placed to the Lua stack
        // and we ready to add other params to stack.

    };

private:

    // hide copy ctor and assignment operator
    LuaObjectBinder(const LuaObjectBinder& rhs);
    LuaObjectBinder operator=(const LuaObjectBinder& rhs);

public:
    LuaObjectBinder(lua_State* l, T* bindableObj,unsigned int methodsToReserve=0)
        : lua(l)
        , m_BindedObj(bindableObj)
    {
        if(methodsToReserve)
            m_Methods.reserve(methodsToReserve);
    };
    ~LuaObjectBinder(){};

     lua_State* Lua()
    {
        return this->lua;
    };

};


class LuaFunction; // forward declaration

struct ILuaFunctionEvents // events for function (optional)
{
    virtual void BeforeCall(LuaFunction* func) = 0;
    virtual void AfterCall(LuaFunction* func) = 0;
};

class LuaFunction
{
private:
    int returns; // number of wanted returns
    int stackPtr; // stack pointer
    ILuaFunctionEvents* events; // events handler
    lua_State* lua; // lua VM
    const char* name; // function name
    int params; // params count
    const char* errMsg;
    bool hasCallErrors;

    bool HasReturns() {    return (returns > 0); }

public:

    // assign the event handler
    void SetSubscriber(ILuaFunctionEvents* ev) {events = ev;};

    // we want N return values from function
    void WantReturns(int num) {returns = num; };

    // add one or more params to the stack
    void AddParam(int p) { luaPushParam(lua,p); ++params; };
    void AddParam(char* p) { luaPushParam(lua,p); ++params; };
    void AddParam(const char* p) { luaPushParam(lua,p); ++params; };
    void AddParam(double p) { luaPushParam(lua,p); ++params; };
    void AddParam(bool p) { luaPushParam(lua,p); ++params; };

    // add dynamic object to the LUA stack
    template<class T>
    void AddParam(LuaObjectBinder<T>* binder) 
    {
        binder->Bind(); // bind dynamic object by creating object table
        ++params;
    };


    // gets return value from stack
    void ReturnValue(const char*& res) // return string from lua
    {
        res = NULL;
        if(!HasReturns())
            return;

        luaGetParam(lua,stackPtr,res);
        --stackPtr;

    }
    void ReturnValue(int& res) // return int from lua
    {
        res = -1;
        if(!HasReturns())
            return;

        luaGetParam(lua,stackPtr,res);

        --stackPtr;
    }
    void ReturnValue(double& res) // return double from lua
    {
        res = -1;
        if(!HasReturns())
            return;

        luaGetParam(lua,stackPtr,res);

        --stackPtr;
    }
    void ReturnValue(bool& res) // return bool from lua
    {
        res = false;
        if(!HasReturns())
            return;

        luaGetParam(lua,stackPtr,res);

        --stackPtr;
    }

    const char* ErrorMessage()
    {
        return errMsg;
    };

    bool CallSucceeded()
    {
        return !hasCallErrors;
    };

    // call LUA function
    bool Call()
    {
        errMsg = NULL;
        hasCallErrors = false;

        if(events)
            events->BeforeCall(this);

        // call function
        int res = lua_pcall(lua,params,returns,0);
        hasCallErrors = res != 0;

        if(!hasCallErrors) // no errors
        {
            if(HasReturns())
                stackPtr = lua_gettop(lua);

        } 
        else
        {
            luaGetParam(lua,-1,errMsg);
#ifdef _DEBUG
            ::MessageBox(GetForegroundWindow(),errMsg,NULL,MB_OK | MB_TASKMODAL);
            exit(res);
#endif
        } // else

            if(events)
                events->AfterCall(this);

        return !hasCallErrors;
    }


public:
    LuaFunction(lua_State* l,const char* n)
        : lua(l)
        , name(n)
        , params(0)
        , returns(0)
        , stackPtr(0)
        , events(NULL)

    {
        // get function pointer to the Lua stack
        lua_getfield(lua, LUA_GLOBALSINDEX, name);
    };
    ~LuaFunction() 
    {
    };

     lua_State* Lua()
    {
        return this->lua;
    };

};


Сырец (LuaStarter.cpp):

#include "StdAfx.h"
#include "LuaStarter.h"

static int BitorOp(lua_State* lua)
{
    int params = lua_gettop(lua);
    int res = 0;
    if(params > 0)
    {
        int param = 0;

        for(int i=0;i<params;++i)
        {
            luaGetParam(lua,-1,param);
            res |= param;
        } // for

    } // if(params > 0)
    luaPushParam(lua,res);
    return 1;
}

static int MyLoader(lua_State* pState)
{
    std::string module = lua_tostring(pState, 1);

    std::string fullPath = module;
    fullPath += _T(".lua");

    //CHGEStarter* engine = CHGEStarter::Instance();

    DWORD size = 0;
    const TCHAR* buf = (const TCHAR*) _T("тут код скрипта");//engine->GetHGE()->Resource_Load(fullPath.c_str(),&size);

    if(size > 0)
    {

        lua_getfield(pState, LUA_REGISTRYINDEX, _T("_LOADED"));    // push "package"

        const TCHAR* name = module.c_str();

        luaL_loadbuffer(pState, buf, size, fullPath.c_str());    
        lua_pushstring(pState, name);  // pass name as argument to module 

        lua_call(pState, 1, 1);  // run loaded module 

        if (!lua_isnil(pState, -1))  // non-nil return? 
            lua_setfield(pState, 2, name);  // _LOADED[name] = returned value 

        lua_getfield(pState, 2, name);
        lua_pushboolean(pState, 1);  // use true as result 
        lua_pushvalue(pState, -1);  // extra copy to be returned 
        lua_setfield(pState, 2, name);  // _LOADED[name] = true 

         lua_pop(pState, 1);

    }
    //engine->FreeInstance();
    return 0;
}


void LuaEngine::DoFile(const char* fileName)
{
    //CHGEStarter* engine = CHGEStarter::Instance();

    const char* fPtr = fileName;

    if(*fPtr == _T('\\') || *fPtr == _T('/')) // skip beginning slashes
        ++fPtr;

    std::string fullPath = fPtr;

    DWORD size = 0;
    const TCHAR* buf = (const TCHAR*) _T("тут код скрипта");//engine->GetHGE()->Resource_Load(fileName,&size);

    //engine->FreeInstance();

    luaL_loadbuffer(lua,buf,size,fileName);
    lua_pcall(lua,0,LUA_MULTRET,0);
    
}
void LuaEngine::AttachMyLoader()
{
    lua_register( lua, _T("preload"), MyLoader );
    lua_register(lua,_T("bitor"), BitorOp );
}

template<> 
void luaPushParam(lua_State* luaVM, char* p)
{
    // push string to the Lua stack
    lua_pushstring(luaVM, p);
}

template<> 
void luaPushParam(lua_State* luaVM, char const* p)
{
    // push string to the Lua stack
    lua_pushstring(luaVM, p);
}

template<> 
void luaPushParam(lua_State* luaVM, bool p)
{
    // push boolean to the Lua stack
    lua_pushboolean(luaVM, p);
}

template<> 
void luaPushParam(lua_State* luaVM, double p)
{
    // push double to the Lua stack
    lua_pushnumber(luaVM, p);
}

template<> 
void luaPushParam(lua_State* luaVM, int p)
{
    // push int to the Lua stack
    lua_pushnumber(luaVM,p);
}


template<> 
void luaGetParam(lua_State* luaVM, int idx, const char*& param)
{
    assert(lua_isstring(luaVM,idx));

    param = lua_tostring(luaVM,idx);
    // pop returned value from stack
    lua_pop(luaVM, 1);
}

template<> 
void luaGetParam(lua_State* luaVM, int idx, int& param)
{
    assert(lua_isnumber(luaVM,idx));
    param = static_cast<int>(lua_tonumber(luaVM,idx));
    // pop returned value from stack
    lua_pop(luaVM, 1);
}

template<> 
void luaGetParam(lua_State* luaVM, int idx, DWORD& param)
{
    assert(lua_isnumber(luaVM,idx));
    param = static_cast<DWORD>(lua_tonumber(luaVM,idx));
    // pop returned value from stack
    lua_pop(luaVM, 1);
}


template<> 
void luaGetParam(lua_State* luaVM, int idx, double& param)
{
    assert(lua_isnumber(luaVM,idx));
    param = (double) lua_tonumber(luaVM,idx);
    // pop returned value from stack
    lua_pop(luaVM, 1);
}

template<> 
void luaGetParam(lua_State* luaVM, int idx, bool& param)
{
    assert(lua_isboolean(luaVM,idx));

    param = lua_toboolean(luaVM,idx) != 0;
    // pop returned value from stack
    lua_pop(luaVM, 1);
}

template<typename TKey, typename TValue>
void luaGetParam(lua_State* L, int index, std::map<TKey, TValue>& ret)
{
  // stack:
  if(!lua_istable(L, index))
    return;

  lua_pushvalue(L, index); // stack: map
  lua_pushnil(L); // stack: map nil

  while(lua_next(L, -2)) // stack: map key value
  {
    TKey key;
    luaGetParam(L, -2, key);
    TValue value;
    luaGetParam(L, -1, value);
    ret[key] = value;
    lua_pop(L, 1); // stack: map key
  }
  // stack: map
  lua_pop(L, 1); // stack:

}

template<typename T>
void luaGetParam(lua_State* L, int index, std::vector<T>& ret)
{
  // stack:
  if(!lua_istable(L, index))
    return;

  lua_pushvalue(L, index); // stack: vector

  const int count = luaL_getn(L, -1);
  for(int i = 1; count >= i; ++i)
  {
    lua_pushnumber(L, i);
    lua_gettable(L, -2);
    T value;
    luaGetParam(L, -1, value);
    ret.push_back(value);
    lua_pop(L, 1); // stack: vector
  }
  lua_pop(L, 1); // stack:

}

template<typename T>
void luaGetParam(lua_State* L, int index, const char* key, T& ret)
{
  // stack: table
  lua_getfield(L, index? index : LUA_GLOBALSINDEX, key); // stack: table value
  luaGetParam(L, -1, ret);
  lua_pop(L, 1); // stack: table
}


Пример использования:

class CDynamic : public ILuaBindableEvents
{
private:
  LuaObjectBinder<CDynamic>* binder;
public:

 LuaObjectBinder<CDynamic>* GetBinder() {return binder; };
    // our script object handlers
  virtual int OnPropertyGet(lua_State* state, const char* PropertyName)
  {
   // тут возвращаем значение внутреннего свойства в скриптовую машину
  };
  virtual int OnPropertySet(lua_State* state, const char* PropertyName)
  {
    // тут устанавливаем внутреннее значение свойства
  };
  virtual int OnMethod(lua_State* state, int MethodID, const char* MethodName)
  {
    switch(MethodID)
    {
       case 1: // HelloWorld
       {
         luaPushParam(state,_T("Hello there!"));
         return 1;
       }
    }
  };

  void BindTo(lua_State* state)
  {
    binder = new LuaObjectBinder<CDinamic>(state,this);
    binder->AddMethod(_T("HelloWorld"),1);
  };

}

// создаем экземпляр скриптовой машины
LuaEngine engine(true);
engine.DoFile(_T("scripts/test.lua")); // парсим файл

CDynamic dyn; // наш объект с динамическими методами
dyn.BindTo(engine.Lua()); // настраиваем методы

LuaFunction func(engine.Lua(),_T("SayHello")); // экземпляр функции скрипта
func.AddParam<CDynamic>(dyn.GetBinder()); // передаем объект в скриптовую машину

func.Call(); // вызываем функцию


Где функция в скрипте выглядит примерно так:

function SayHello(obj)
 local message = obj:HelloWorld() -- теперь в message находится строка "Hello there"
end


Вот примерно в таком разрезе. Выкорчевывал из проекта, при возникновении вопросов/проблем — велкам.

З.Ы. Да, некоторый код (совсем некоторый, например, развертывание вектора в Lua) — взят из открытых источников, ссылки не помню, но если автор напомнит — с удовольствием вставлю ссылку на него следующим постом.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.