Luabind

Get answers to all your basic programming questions. No Ogre questions, please!
Uollas
Gnoblar
Posts: 5
Joined: Fri Sep 23, 2005 11:25 am

Luabind

Post by Uollas »

Hi all!
I'm developing an FPS game and I'm using Ogre for graphic and Lua (with luabind) for scripting: I'm not very familar with Lua but it sounds very promising especially in games development (thanks for this article in the wiki)

Consider a Player class

Code: Select all

class Player
{
public:
    Player(const string &name = "") : m_name(name) { }
    virtual ~Player() { }

public:
    virtual void execute(const string &action, const string &param = "")
    {
        // ...
    }

protected:
    string m_name;
};
and its wrapper

Code: Select all

module(L)
[
    class_<Player>("Player")
        .def(constructor<const string &>())
        .def("execute", Player::execute)
]
And now the questions :)

First
I know that luabind is inspired to boost::python but I cannot find how to resolve a call to a function that has default arguments (this is the boost::python implementation)
I'd like to write a lua script like this

Code: Select all

player = Player()
player:execute('walk')
but now luabind throw an exception because it expects this code

Code: Select all

player = Player('')
player:execute('walk', '')
Second
Lua allows me to write this code

Code: Select all

player.spawn = function()
    -- code
end
and the I can call player.spawn(), why then isn't possible to write
player.execute() but I have to use player:execute() ?Maybe there is another statement to bind a method in place of ".def" to make this work...

Sorry for the english,
Thanks!
User avatar
johnhpus
Platinum Sponsor
Platinum Sponsor
Posts: 1186
Joined: Sat Apr 17, 2004 2:49 am
x 3

Post by johnhpus »

but I cannot find how to resolve a call to a function that has default arguments
Neither could I :) I've looked for that information before and again just now before posting. I didn't really spend much time trying to find out simply because default parameters are a luxury and not a strict necessity. If you find a solution to this please share.

Second
Lua allows me to write this code
Code:

player.spawn = function()
-- code
end

and the I can call player.spawn(), why then isn't possible to write
player.execute() but I have to use player:execute()
In luabind you call a class's methods using ':' instead of '.'
The reason that your example code works (I think) is that because your actually assigning a function as a value of a member variable. So even though your executing a function, your doing it through a function pointer that is a member variable and is accessed using '.'

I wrote the Wiki article and I'm no expert on Luabind. My knowledge only goes as far as what I've had to do with is so far. I've been meaning to write a second but I wasn't sure if anyone had even read the thing. Hell, I need to update the existing one first.
Uollas
Gnoblar
Posts: 5
Joined: Fri Sep 23, 2005 11:25 am

Post by Uollas »

Thx for reply, I couldn't find any solution to fix this problem too...
Btw I have another question :)
Consider this script

Code: Select all

function player:onDie
	-- stuffs
end
The player variable is a global one created and binded from C++ in this way

Code: Select all

void bind_player(lua_state *state, Player *player)
{
	luabind::get_globals(state)["player"] = player;
	player->self = luabind::get_globals(state)["player"];
}
where Player::self is a luabind::object instance
This allow me to check at runtime if an event for the player variable is declared in the script, for example

Code: Select all

void Player::hasEvent(const string &name)
{
	return self.at(name).type() == LUA_TFUNCTION;		
}
void Player::onDie()
{
	if(hasEvent("onDie"))
		luabind::call_member <void>(self, "onDie"); 		
}
In this sample the player instance is binded from the C++ side, my problem is to bind an instance of a class created from the script

C++ side

Code: Select all

class GameObject : public ScriptableObject
{
public:
	GameObject(const string &mesh) 
	{
		// ...
	}
		
	virtual void onDestroy()
	{
		if(hasEvent("onDestroy"))	// same as before
			luabind::call_member <void>(self, "onDestroy"); 		
	}
};
lua side

Code: Select all

table = GameObject('table.mesh')
function table:onDestroy()
	-- stuffs
end
How can I bind GameObject::self to the 'table' variable?
I hope this is possible because this is a very important requisite for the "script side" of my project...
Thanks again!
User avatar
johnhpus
Platinum Sponsor
Platinum Sponsor
Posts: 1186
Joined: Sat Apr 17, 2004 2:49 am
x 3

Post by johnhpus »

In this sample the player instance is binded from the C++ side, my problem is to bind an instance of a class created from the script

C++ side
Code:

class GameObject : public ScriptableObject
{
public:
GameObject(const string &mesh)
{
// ...
}

virtual void onDestroy()
{
if(hasEvent("onDestroy")) // same as before
luabind::call_member <void>(self, "onDestroy");
}
};


lua side
Code:

table = GameObject('table.mesh')
function table:onDestroy()
-- stuffs
end

How can I bind GameObject::self to the 'table' variable?
Maybe I'm just dense right now (it's almost 4:30 am here) but I just can't understand what it is your asking in this question. My best guess right now is that your asking how to create an instance of a class in script and pass ownership of that instance out to C++. If that's the case try reading this http://luabind.sourceforge.net/docs.html#policies_adopt

I'll look at your post again when I get up tomorrow to see if I can understand it any better. In the mean time if you want to try describing it another way or ask a different question feel free.
Uollas
Gnoblar
Posts: 5
Joined: Fri Sep 23, 2005 11:25 am

Post by Uollas »

Maybe I'm just dense right now (it's almost 4:30 am here) but I just can't understand what it is your asking in this question. My best guess right now is that your asking how to create an instance of a class in script and pass ownership of that instance out to C++. If that's the case try reading this http://luabind.sourceforge.net/docs.html#policies_adopt
That's not exactly the problem (the real problem is probably my english :lol:)
I'll look at your post again when I get up tomorrow to see if I can understand it any better. In the mean time if you want to try describing it another way or ask a different question feel free.
Ok I'll try again:

let's consider a Singleton class, Game for example:

Code: Select all

class Game : public Ogre::Singleton <Game>
{
public:
   Game()
   {
      // Initialize the lua context
      m_script = lua_open();
      luabind::open(m_script);		

      // Tells lua that there is a global variable called "game"
      luabind::get_globals(m_script)["game"] = this;	

      // Saves the reference to the global variable "game" so that 
      // we can refer to it from c++
      m_self = luabind::get_globals(m_script)["game"];	
   }
   void onStart()
   {
      // Calls a method of the global variable "game" called onStart in the script
      luabind::call_member<void>(m_self, "onStart"); 	
   }
   void onStop()
   {
      // Calls a method of the global variable "game" called onStop in the script
      luabind::call_member<void>(m_self, "onStop"); 	
   }

public:
   lua_State *m_script;
   
protected:
   luabind::object m_self;
};
in the lua script I can write

Code: Select all

function game:onStart()
  -- my game initialization here
end
function game:onStop()
  -- my game cleanup here
end
No problems here but the point is that the variable game is "created" from C++ because the Game instance is allocated in C++
Consider instead a GameEntity class ("allocated" only from lua):

Code: Select all

class GameEntity
{
public:
   GameEntity(string mesh)
   {
      // Takes the lua context from the game class
      lua_State *script = Game::getSingleton().m_script;

      // m_self = ?  
   }

   virtual void onEvent(string name)
   {
      // Calls a member function of this instance in the script
      // called "onEvent" passing the event name to it
      luabind::call_member<void>(m_self, "onEvent", name); 	
   }

protected:
   Ogre::Entity *m_pEntity;
   luabind::object m_self;
};
and from lua

Code: Select all

function game:onStart()
   -- creates a ninja GameEntity
   ninja = GameEntity('ninja.mesh') 

   -- creates an ogre GameEntity
   ogrehead = GameEntity('ogrehead.mesh')   
end
-- handles events for the ninja instance
function ninja:onEvent(name)
  -- stuffs for ninja 
end
-- handles events for the ogrehead instance
function ogrehead:onEvent(name)
  -- stuffs for ogrehead
end
My problem is how to manage events of instances allocated from lua
because from c++ I haven't the back reference to the luabind::object class so I can't call luabind::call_member as before for the Game class

CEGui uses an alternative approach to manage events
It creates a global function handler that takes as argument the object (or a param that allows to extract it) that receives the event:

Code: Select all

-- the CloseClicked event handler
function fwCloseClicked(eventArgs)
	local we = CEGUI.toWindowEventArgs(eventArgs)
	CEGUI.WindowManager:getSingleton():destroyWindow(we.window) -- destroy the frame window
end
Following this approach my example could be converted into this:

Code: Select all

class GameEntity
{
public:
   GameEntity(string mesh)
   {
   }

   virtual void onEvent(string name)
   {
      // Calls a global function in the script passing the instance and the function name 
      luabind::call_function<void>(Game::getSingleton().m_script, "eventsHandler", this, name); 	
   }

protected:
   Ogre::Entity *m_pEntity;
};

Code: Select all

function game:onStart()
   -- creates a ninja GameEntity
   ninja = GameEntity('ninja.mesh') 

   -- creates an ogre GameEntity
   ogrehead = GameEntity('ogrehead.mesh')   
end
-- handles events for all GameEntity instances
function eventsHandler(inst, name)
end
This works but I'd like to call a member function, not a global one...

Very thanks for the interest!
User avatar
johnhpus
Platinum Sponsor
Platinum Sponsor
Posts: 1186
Joined: Sat Apr 17, 2004 2:49 am
x 3

Post by johnhpus »

Sorry, you've gone beyond my knowledge of Luabind I think. Hope you can work something out.
User avatar
Robomaniac
Hobgoblin
Posts: 508
Joined: Tue Feb 03, 2004 6:39 am

Post by Robomaniac »

I think you're making it too complex by trying to implement oop in lua. I have tried about every solution for binding code to lua (tolua++ was the easiest imo, luabind the hardest/most effort) but I finally settled on not trying to give lua more capabilities then it actually has. From what it sounds like (forgive me if this is wrong), you're implementing some sort of event system. I recommend you forget all about individual entity objects being stored in lua. Make a entity manager class in your C++ code and then just pass a value that references that entity (in my game, I use an integer id).

I think that you should rethink your scripting process and design of your code so it fits better in the design of a scripting system. I can share more of my implementation with you, but I have to go to class.

You can get in contact with me over AIM or MSN (bottom of this post for my address) or just post here.
phear hingo

My Webpage
Uollas
Gnoblar
Posts: 5
Joined: Fri Sep 23, 2005 11:25 am

Post by Uollas »

From what it sounds like (forgive me if this is wrong), you're implementing some sort of event system.
Yes that's right :)
I think that you should rethink your scripting process and design of your code so it fits better in the design of a scripting system. I can share more of my implementation with you, but I have to go to class.
I rethinked :D, what about this implementation

C++

Code: Select all

// This is a base class for all classes in my game
class Object
{
public:
	Object() { }
	virtual ~Object() { }
};

// Here we have an event class that calls every handler when raised

class Event
{
	typedef std::list <luabind::object> Handlers;

public:
	Event(Object &owner) : m_owner(owner) { }
	virtual ~Event() { }

	Object & getOwner() const { return m_owner; }

   // This method allows to bind a lua function to this event
	void add(const luabind::object &handler)
	{
		if(handler.is_valid() && std::find(m_handlers.begin(), m_handlers.end(), handler) == m_handlers.end())
			m_handlers.push_back(handler);
	}	
   // Removes a binded handler
	void remove(const luabind::object &handler)
	{
		Handlers::iterator i = std::find(m_handlers.begin(), m_handlers.end(), handler);
		if(i != m_handlers.end())
			m_handlers.erase(i);
	}

   // Calls every handler supplying some parameters if necessary
	void operator()() 
   { 
      for(Handlers::iterator i = m_handlers.begin(); i != m_handlers.end(); ++i)
         luabind::call_function<void> (*i, this);
   }
	template <typename T>
	void operator()(const T &p) 
   { 
      for(Handlers::iterator i = m_handlers.begin(); i != m_handlers.end(); ++i)
         luabind::call_function<void> (*i, this, p);
   }

	template <typename T1, typename T2>
	void operator()(const T1 &p1, const T2 &p2) 
   { 
      for(Handlers::iterator i = m_handlers.begin(); i != m_handlers.end(); ++i)
         luabind::call_function<void> (*i, this, p1, p2);
   }

protected:
	Object &m_owner;
	Handlers m_handlers;
};

typedef std::map <string, Event *> EventsMap;  // Basic association for an event to its name

class Entity : public Object
{
public:
	Entity(const string &mesh) { // stuffs... }
	virtual ~Entity() { // clean up }

   // Return a reference to the specified method, if it doesn't exists I'll create that on the fly (note: this function must be const)
	Event & getEvent(const string &name) const
	{
		Event *event = NULL;
		EventsMap::iterator i = m_events.find(name);
		if(i == m_events.end())
		{
			event = new Event(const_cast <Entity &> (*this));
			m_events[name] = event;
		}
		else
		{
			event = i->second;
		}
		return *event;
	}

   // Some utility wrappers
	inline Event & event_collision() const { return getEvent("collided"); }	
	inline Event & event_destroy() const { return getEvent("destroyed"); }	

	void fireEvent(const string &name) { getEvent(name)(); }
	template <typename T>
	void fireEvent(const string &name, const T &p) { getEvent(name)(p); }
	template <typename T1, typename T2>
	void fireEvent(const string &name, const T1 &p1, const T2 &p2) { getEvent(name)(p1, p2); }

	virtual void onCollision() { fireEvent("collided"); }
	virtual void onDestroy() { fireEvent("destroyed"); }

protected:	
	mutable EventsMap m_events;
};
Binding

Code: Select all

void bind(lua_State *L)
{
	luabind::module(L)
   [
		luabind::class_<Object>("Object")		
   ];

	luabind::module(L)
	[
		luabind::class_<Event>("Event")		
			.property("owner", Event::getOwner, dependency(result, _1))			
			.def("add", Event::add)
			.def("remove", Event::remove)					
	];

	luabind::module(L)
	[
		luabind::class_<Entity>("Entity") 	
			.def(luabind::constructor<const string &>())				
			.def("getEvent", Entity::getEvent, dependency(result, _1))	
			.property("collided", Entity::event_collision, dependency(result, _1))	
			.property("destroyed", Entity::event_destroy, dependency(result, _1))				
	];
}
And finally in lua

Code: Select all

function engine:onStart()
	table = Entity('table.mesh')
	table.collided:add(collisions_handler)
	table.destroyed:add(destructions_handler)
end

function collisions_handler(e)
	-- local obj = e.owner
end

function destructions_handler(e)
	-- local obj = e.owner
end
It would have been very very cool a sintax like this

Code: Select all

function engine:onStart()
	table = Entity('table.mesh')
	table.collided += collisions_handler
	table.destroyed += destructions_handler
end
but luabind (lua in general) doesn't allow that as python :twisted:

So, what about?
genva
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1603
Joined: Wed Oct 20, 2004 7:54 am
Location: Beijing, China
x 1

Post by genva »

lua hasn't has operator +=, as well as -=, *=, /=. luabind can't do more things than lua can do in lua-script.
Uollas
Gnoblar
Posts: 5
Joined: Fri Sep 23, 2005 11:25 am

Post by Uollas »

Genva wrote:lua hasn't has operator +=, as well as -=, *=, /=. luabind can't do more things than lua can do in lua-script.
Yes, that's why I wrote "lua in general" :lol: