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

Started by
9 comments, last by AverageJoeSSU 14 years ago
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]

------------------------------

redwoodpixel.com

Advertisement
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?
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()    endend

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++)

------------------------------

redwoodpixel.com

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
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 argvoid FireEvent(lua_State* const L, const std::string& event){    //same as you have}//One argtemplate<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 argstemplate<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]
Wow guys, perfect answers... and seeing as how the event data may even be a class representation of data, it will allow me to make my data fairly complex. i wonder if a combination of the two suggestions would be possible.

------------------------------

redwoodpixel.com

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_ENDEXPORT_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);}
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.

------------------------------

redwoodpixel.com

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.
sounds like a good idea... for data that is only going to live for one frame (events), i think a binding will be useful.

------------------------------

redwoodpixel.com

This topic is closed to new replies.

Advertisement