Event Handling via Scripts

This week work went into adding support for receiving events and passing them down to scripts. The idea is to allow scripts to provide some logic that reacts to user actions.

Vortex has never had an event system before. When the time to design one came, we determined that we wanted a flexible and extensible system that allowed modeling different kinds of events generated on different platforms.

In order to make it extensible it was decided to go with an object-oriented design. A base Event class would provide the general interface to all events and specialized sub-classes could model specific events happening on the hardware, such as a keypress, a mouse move, or a finger swipe.

namespace vtx
{
	class Event
	{
	public:
		virtual ~Event() { }
		virtual EventType type() const = 0;
	};

	class KeyboardEvent : public Event
	{
	public:
		KeyboardEvent( short key );
		short key() const;
	protected:
		short _key;
	};

	class KeyPressedEvent : public KeyboardEvent
	{
	public:
		KeyPressedEvent( short key );
		virtual EventType type() const override;
	};

// ...etc...
}

With this simple interface in place, we can have platform-specific code wrap events in instances of these classes and pass them to the engine manager (a front controller). The engine manager takes care of moving data around so that, eventually, events make it to Lua scripts.

Handling Events from Lua

In order to receive events, scripts must add a handler function to the vtx.callbacks.on_event list. If you remember from this previous post on scripting, we were already using the vtx.callbacks namespace as a means for scripts to register a function to be called every frame. This mechanism extends on the idea.

When an event is passed to the engine, functions registered to the on_event list are called. The functions are called with a single argument: a table containing the event properties (such as which key was pressed for a keydown event).

The following example shows this interaction:

function on_event( evt )
    print( "Received event: type: " .. evt.type .. " key: " .. evt.key )

    if evt.type == 0 then
        x,y,z,w = box_xform:get_position()

        if evt.key == vtx.Event.KEY_RIGHT then
            x = x + 0.1
        elseif evt.key == vtx.Event.KEY_LEFT then
            x = x - 0.1
        elseif evt.key == vtx.Event.KEY_UP then
            y = y + 0.1
        elseif evt.key == vtx.Event.KEY_DOWN then
            y = y - 0.1
        end
        box_xform:set_position(x,y,z,w)
    end
end

-- Somewhere:
table.insert( vtx.callbacks.on_event, on_event )

The above example could be used to move an entity in the scene in response to keyboard events. Of course, in practice keyboard event handling should be dependent on time elapsed, and we probably want to handle key autorepeat too, but for this example, it clearly shows how event data can be extracted from the parameter supplied to the callback.

Events are Transient Objects

Events are handled a bit differently from everything else in the engine so far.

Unlike other engine objects, we do not expect scripts (or other subsystems) to hold on to the engine object representing the instant event. The event should be handled when it occurs. By realizing this, we notice that there is no need to keep a native pointer to the event in the Lua table.

Instead, we can simplify by creating a table and copying the important information directly into it. This way we also avoid roundtripping into the engine to fetch all event properties.

Internally, at the engine level, the event object is transient. It lives in the stack and is destroyed automatically after it’s been dispatched to all its handlers.

Handling Events in the Editor

It is important to note that when running in Editor, scripts must define a main camera in order for events to be delivered to the scripts.

This might sound counterintuitive, but it was a deliberate decision to preserve the fly-over cam controls in Editor if a script-driven camera was never defined. This is important for authoring a playground where we don’t want a script to take over the camera.

Conclusion

In the search of a perfect solution, I had been putting off adding event handling for a while. In the end, the solution devised solves our current needs in a simple and (hopefully) elegant fashion.

Now that we have a camera control API and event handling from scripts, we are in a good position for Vortex playgrounds to take over control and start exposing more elaborate functionality.

I’m excited for what’s to come. Stay tuned for more!