Sign in to follow this  
AverageJoeSSU

Lua/C++ Event System, Event Data [Solved]

Recommended Posts

I Implemented the event system as shown in lua programming gems. its fairly straightforward. Objects subscribe to events, objects can fire events using the eventmanager instance, and objects can implement listener functions to those events. One of the neccesary reqs(which works in lua already) is that you can include extra args in your fireevent. For example, EventManager:FireEvent("DamageTaken", Damage) the eventmanager uses the "..." to pass extra args around from a lua produced event... So now for my problem. I need to be able to pass event data the same way in C++ produced event. I was thinking about making a generic event type that stores anything needed by an event, but I would like it to be efficient. Any ideas? EDIT: Here is how the fire event function in C++ is called.
void FireEvent( lua_State* L, std::string eventName )
	{
		//Call the lua FireEvent on this event manager object.
		lua_pushlightuserdata( L, this );			//STACK:	em
		lua_gettable( L, LUA_REGISTRYINDEX );		//STACK:	self
		lua_pushstring( L, "FireEvent" );			//STACK:	"FireEvent", self
		lua_gettable( L, -2 );						//STACK:	FireFunc, self
		lua_pushvalue( L, -2 );						//STACK:	self, FireFunc, self
		lua_pushstring( L, eventName.c_str() );		//STACK:	self, eventName, FireFunc, self
		lua_pcall( L, 2, 0, 0 );					//STACK:	self
	}




[Edited by - AverageJoeSSU on April 8, 2010 3:25:15 PM]

Share this post


Link to post
Share on other sites
I'm not sure if I follow exactly what the question is. Are you wanting to pass C++ arguments to your C++ function that will be passed to your Lua callback? Or are you wanting to convert Lua arguments to a C++ structure? Or am I way off here?

Share this post


Link to post
Share on other sites
haha sorry it is a little confusing...
you basically got it.

the event manager is a lua class (i have my own class impl) that can fire events and take subscribers.

when i want to fire an event in lua i use this

EventManager:FireEvent("DamageTaken", Damage)

and an object that wants to 'handle' this event simple needs to do this.

T = {}

function T:DamageTaken(Damage)
Damage.defender.life -= Damage.attacker.damage
if Damage.defender.life < 0 then
Damage.defender.die()
end
end

the event manager deals with this by using the ... operator to pass extra args, and it is not required for a handler to take the args (trait of lua).

this is not doable in C++ however, and i would like it to follow a similar structure. (the function i provided is how the fireevent function is called from C++)

Share this post


Link to post
Share on other sites
If you want to call a Lua function from C and pass in a variable number of arguments (which is supported natively in C as well usign vargs) you can use the example found in the Programming for Lua book, on how they implement their generic Lua dispatch function.

http://www.lua.org/pil/25.3.html

basically it allows you to call a Lua function with a variable number of arguments so you can do something like this on the C side,

double damage = 100;

call_va ("FireEvent","sd","DamageTaken",damage);

Where call_va is the dispatch function, and "FireEvent" is a globally accessible function in Lua, "sd" is the signature of the variable data used to correctly parse it and dispatch it into Lua, the remaining arguments are the variable parameters passed onto Lua (Though i suspect the first "DamageTaken" is a required argument for ur system).

I use the code (with some modifications) and it works well enough, makes calling Lua functions a snap from C.

There is also code to extract multi-return values from Lua and pass that back to the calling C function as well.

Good Luck!

-ddn

Share this post


Link to post
Share on other sites
Since we're using C++, I prefer to use something that is type safe and exploits compile time polymorphism.

If your compiler supports variadic templates this is quite simple. Even without, you can still emulate the behavior.


//No arg
void FireEvent(lua_State* const L, const std::string& event)
{
//same as you have
}

//One arg
template<typename T1>
void FireEvent(lua_State* const L, const std::string& event, T1 a1)
{
//same as you have, but before the call push the arg and increment the number of args
...
PushArg(a1);
lua_pcall(L, 3, 0, 0);
}

//Two args
template<typename T1, typename T2>
void FireEvent(lua_State* const L, const std::string& event, T1 a1, T2 a2)
{
//and so on
...
PushArg(a1);
PushArg(a2);
lua_pcall(L, 4, 0, 0);
}




And so on until you reach the max number of arguments you wish to support (I chose 20).

The PushArg function is an overloaded free function for each type you can push onto Lua.

For example:

void PushArg(lua_State* const L, double arg)
{
lua_pushnumber(L, arg);
}
void PushArg(lua_State* const L, const std::string& arg)
{
lua_pushstring(L, arg.c_str());
}

struct LuaNil{};

void PushArg(lua_State* const L, const LuaNil&)
{
lua_pushnil(L);
}




This has the advantage that you can define complex types to be passed through fire event if you define the appropriate PushArg function. Similar to operator<< overloading and streams.

Now you can fire an event by doing:

FireEvent("DamageTaken", damage, LuaNil(), "Fire");




[Edited by - m_switch on March 19, 2010 11:48:51 PM]

Share this post


Link to post
Share on other sites
AverageJoeSSU you have said you do not want to use a Language binding but surely you could adapt one to suit your needs? Luna only takes it so far and then you start implementing your own binding which is fine if you want to spend time doing so.

I do not normally do this but just for an example I offer the following.


#include "oolua.h"

class Entity
{
public:
Entity(int life,int damage)
:m_life(life),m_damage(damage){}
int life()const{return m_life;}
void decrease_life(int damage){m_life -= damage;}
int damage()const{return m_damage;}
private:
int m_life;
int m_damage;
};

OOLUA_CLASS_NO_BASES(Entity)
OOLUA_TYPEDEFS No_public_constructors OOLUA_END_TYPES
OOLUA_MEM_FUNC_0_CONST(int ,life)
OOLUA_MEM_FUNC_1(void ,decrease_life,int)
OOLUA_MEM_FUNC_0_CONST(int, damage)
OOLUA_CLASS_END

EXPORT_OOLUA_FUNCTIONS_1_NON_CONST(Entity,decrease_life)
EXPORT_OOLUA_FUNCTIONS_2_CONST(Entity,life,damage)

int main()
{
OOLUA::Script s;
s.register_class<Entity>();
Entity entity(100,0);
Entity attacker(100,10);
s.run_chunk("damage = function(entity,attacker) "
"entity:decrease_life(attacker:damage()) "
"end");
s.call("damage",&entity,&attacker);
}


Share this post


Link to post
Share on other sites
Hmm... i see your point. The discussion of using bindings certainly applies here, but would quickly fill the thread.

The reason why I use only luna is the fact that i have tried to just bind my Engine's API, and not any rogue objects that get exposed through something other than the interface.

You do bring up an interesting reason though why this may not work in practice; in that things like event data would be better suited to be bound directly to the lua environment from C++.

These events are performance critical though which worries me. I am going to be passing up collision reports and input from c++ (as i see it now, and prolly a lot more stuff).

If i were to bind event data from C++, it would be in table form so i could treat it on the lua level like i showed above.

What do you guys think? thanks for the code demo btw, i enjoy these lua discussions.

Share this post


Link to post
Share on other sites
It really depends, under the hood most (if not all) binding libraries are doing something similar to what you're doing. Storing pointers in the registry and looking them up as needed. Like anything generalized there's a good possibility that it will be slower than a specific implementation. However, on the flip-side the binding library writers have probably spent more time profiling, optimizing and most importantly debugging.

If you're unsure, the best bet is to probably implement it the easiest way and then profile it when/if performance becomes a concern.

Share this post


Link to post
Share on other sites
Hey Guys,


Rather than make an annoyingly long post here about how I implemented these ideas and how they are working like a charm in my engine, i thought i would just make a blog post about it.

It really is a nice/important/crucial enhancement.

Thanks for the help.

link

[Edited by - AverageJoeSSU on April 8, 2010 3:24:30 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this