Event-Listener with Lua and C++

Started by
14 comments, last by Angelic Ice 8 years ago

Hello gamedev.net community!

I have a question about implementing a proper event listener. It shall be located in C++ but shall also get new event-listenings via Lua.

An event would be for example:

- Player walks into a set collision area.

- Player inputs a-button and is in reach of object x.

- Variable y inside the JSON-file reaches value 5.

and so on...

First of all, there are multiple level.lua-files that have a momentum where they describe those callback events.

I would like to store these event triggers in the C++-part of my engine, to avoid running Lua every iteration.

I thought about using an array of functions, storing functions that check if event conditions are met.

For example Lua calls a C++-function and passes arguments to it. The C++-function would then read out the arguements.

One arguement would determine what kind of function(-template) shall be selected and passes the other variables to it. These would be like which button to be pressed or the coordinates of that collision field.

An example on the Lua-side:

register_on_hit("name of this callback", x-coordinate, y-coordinate)

And somewhere in the map-file would be a function with the same name as the registered callback. This will be called by C++ once the function inside the function array with the id of "name of this callback" returns true.

Once all callbacks would be registered in the C++-array, the map-file closes and will be opened again when C++ realises that a function has been called.

When a new map-file will be loaded, the array will be cleaned of all registered potential events (except not map dependant events).

Is this a proper way of having an event listener? Are function arrays not recommended? Is there something I should do differently?

Advertisement

If your event functions are written in lua, you can simply pass the function itself rather than its name. That saves you finding a function by name in the scope.


local foo = function(position)
 ...
end

register_func(foo, x, y)  -- not "foo"

If you also expose the trigger reasons to lua, you could have it create c++ trigger reasons, that it passes along in a function like the above.

Eg make a lua function that returns a "new TriggerReason;" object, and have lua pass that as part of the "regster_func" call.

If I am reading this right, you want listeners in Lua. But your Event handling in C++?

Unless you don't mind undeterministic behavior, the best way I can think of doing this is by using a Message Board setup.


Basically you have C++ hold off onto writing events back to Lua until the time that it can receive them comes to an end. Once this is done, you write the events back to Lua into a double buffered global array. This will be our "message board".

Each listener will just iterate through the message board, looking for events it cares about. The listener will ignore data it doesn't care about, and act on data it does care about. This data will not be removed however, as other things may wish to act on the same message.

Only at the end of the end of the frame will the read message board be deleted, and swapped for the new one, which will in turn be processed on the update.

Note though, this is not the system you want to be using if events are to be exclusive to entities.

Example being a player fires a bullet into a monster. The bullet makes contact to the monster. At that instance, the bullet has a direct passage to reference the monster. It gives the monster a message immediately, then dies. On the monster's update the message will be processed from his personal mailbox, and exclusively to him.

Edit: If you don't want messages to be scattered around the heap, you can build a struct BLOB of with a book keepign header. Then a buffer of chars that you write data into with placement new, then cast them back. This will work when it comes to networking as well.

Alberth, how would I receive that function as a value in C++? Does it has to do with the lua_registryindex?

Tangletail, Lua shall describe what kind of triggers should be implemented and also execute once the requirements are met.

If a requirement is met shall be tested in the C++.

Does this align with what you describe?

Not quite. In general experience, it's easier to do the event firing in C++, and have it fire off events to lua without giving two craps if anyone is listening to it, then let Lua do the the rest.

For example, C++ will fire events for collisions, triggers being entered, etc. It will build a simple struct containing some basic details of the triggering instance (who triggered it, against what it triggered). Lua will handle handle testing these events, and the implementation details.

Don't worry too much about the performance of Lua. Lua is -very- fast. The problem comes when you make too many callbacks between lua and C++. That's why you send the events all at a single time.

Well, in my case the events would be fired in C++ but the spectrum of what C++ shall test would be determined by Lua.

On this way I can avoid calling Lua. Even when you say Lua is fast, is calling it every frame not rather a bad idea?

I can see that this way avoids those templateish functions inside C++. I would not have to predefine all possible variations but making a proper comprehensive struct.

It sounds more dynamic, especially when I just want to add new values to check.

According to what you described, I would fire information every frame, for example like player is x 3 y 2, player is x 4 y 2 ...

Or when the player collides with an NPC, presses a button and what not...?

Moreover when a player uses the interaction input key, the engine looks for close up objects/NPCs and would store information in a struct?

Or would I rather just tell the struct, that a button has been pressed and give all possible distances to every object? Any better way to provide these information?

After that that, it would be tested by Lua if a certain criteria is met by running through all the possible events being defined in the map-file by comparing them to the passed struct/table?

Alberth, how would I receive that function as a value in C++? Does it has to do with the lua_registryindex?
I don't know the implementation details at C level, but no doubt it would be some lua object.

In Lua, functions are first class citizens, you can make a function, and pass it around as data just like plain integers.

Edit: stackoverflow seems to know: http://stackoverflow.com/questions/2688040/how-to-callback-a-lua-function-from-a-c-function

Ah, thanks for linking me that. Already found something similar to it. Alberth, I would be curious about your opinion about having a "message board" of firing events as Tangletail described. Do you think it is a better implementation?

In either case, you need to have some starting point to arrive into Lua from C++ ("fire off events to lua" also needs something known in Lua that you can call).

From that you have choices. You can keep lists of Lua points to call in C++, or you can manage a table with such points in Lua.

The latter is arguably simpler in Lua, as Lua is a higher level language.

As for performance, the general rule in my experience is that your guess is wrong. I don't do much optimization, but if I do, I always guess where the performance problem is, and so far, it consistently turned up at different and very unexpected spots instead after profiling. Not once did I guess correctly.

As such, your worries about performance are probably wrong too, until you can proof it.

I can think of a few options to get further from here. One option is to measure and test before you decide. The biggest problem with that imho is that you don't run the real application, so any timing results are tricky to interpret.

A second option is to go with the simplest solution first, and see how it runs. If' it's ok, you're done, otherwise profile and improve. The disadvantage of that is that you might need a few improvement iterations, which mean you will throw away some code. In this case, most of that code will be lua, I think, which is not very costly to create.

Other options are probably to build a few alternatives so you can compare things. More costly in time, but it might give better results or insight.

Thanks for your comprehensive answer. I will look into both implementations and see if I can find something that would suit my needs the most. If there are more possibilities and any opinions about the mentioned ways, I would be really curious to hear about them.

This topic is closed to new replies.

Advertisement