GLESGAE:The Event and Input Systems

From Pandora Wiki
Jump to: navigation, search

Contents

GLESGAE - Event and Input Systems

Fast Track

We're on SVN revision 3 now so, while I finish writing this up, feel free to grab and mess about:
svn co -r 3 http://svn3.xp-dev.com/svn/glesgae/trunk/ glesgae

Introduction

Dealing with Input - and by extension Events - can be a right pain in the backside.
Each platform generally comes with it's own set of Events to deal with - even though most of them are pretty common.
Therefore, each platform should generally have it's own Event System to deal with them, and a global Engine Event System that the game deals with after the platform specifics have been filtered out.

So what game-side events would we be interested in? Input, for one... our application being closed for whatever reason... and for platform specifics; Android has that Activity Lifecycle you need to be aware of and handle.
In terms of our Pandora, there aren't many platform events we really need to deal with. Our application being closed being perhaps the only immediate one, as well as the lid being shut.
We also need to deal with the touchscreen ( mouse events ) and keyboard though - and that'll come through X along with the other platform events.
Our nubs, face buttons, shoulder buttons and dpad we need to deal with manually, however.

Beware though, just handling Input as standard Events is a bit iffy.. so we'll abstract out some Input interfaces so we can perform some more sensible logic such as if(dpad->Left()) moveLeft(); rather than polling an event queue and pulling off the events manually.

The Event System

As said, our Event System will come in two parts - a generic version that a game will use, and a platform specific one that filters interesting events and such like.
We'll start with the generic one first, but we'll switch between them during the rest of the weekend.

Generic Event System

I'm going to model the event system sortof on the Android Activity life cycle - more that that's the weirdest setup you'll likely come across, and it's very painful to take something that doesn't conform in the slightest, and try get it working in this style.

So, we need to think about a generic set of Events that could happen that we'd be interested in. Android has the following in the Activity cycle:

  • onCreate
  • onStart
  • onPause
  • onResume
  • onRestart
  • onStop
  • onDestroy

For our purposes, we can simplify this to:

  • onStart
  • onPause
  • onResume
  • onDestroy

Gotcha I've missed out onCreate and gone to onStart ... onCreate is not guaranteed to be the first one run! If dalvik has decided to cache your library for whatever reason, this can be skipped. But I digress, this is Android stuff, not Pandora!

You might be sitting there thinking "why bother with the Android stuff if we're doing Pandora related things?"
Well, think of it like this.. we have a lid that we can close. We'd want to trigger an onPause style event when that happens, and an onResume event when it's lifted.
It's also handy to fire an event round during creation and destruction of our app, as we'll be taking the Observer pattern for the Event System, so being able to prod things to start up and shut themselves down is particulaly handy. And obviously, an onDestroy event can be triggered via quitting the game via closing the window, or whatever other means.

Event.h

#ifndef _EVENT_H_
#define _EVENT_H_

#include "EventTypes.h"

namespace GLESGAE
{
	class Event
	{
		public:
			Event(const EventType& eventType) : mEventType(eventType) {}
			virtual ~Event() {}
			
			/// Get the type of event this is.. useful for classes which mark themselves as EventObservers for multiple Events
			const EventType& getEventType() const { return mEventType; }
			
		private:
			EventType mEventType;
	};
}

#endif

Event is fairly straight forward. The atypical base class for polymorphic nightmares, with a bit of custom RTTI thrown in for good measure.
EventType itself is defined in EventTypes.h, which you can find below:

EventTypes.h

#ifndef _EVENT_TYPES_H_
#define _EVENT_TYPES_H_

#include <string>

namespace GLESGAE
{
	typedef std::string EventType;
	
	namespace SystemEvents
	{
		namespace App
		{
			extern EventType Started;
			extern EventType Paused;
			extern EventType Resumed;
			extern EventType Destroyed;
		}
		
		namespace Window
		{
			extern EventType Opened;
			extern EventType Resized;
			extern EventType Closed;
		}
	}
}

#endif

This is where things start to get a little strange.
We're using namespaces here to separate out the Event Types. This stops us from polluting the main namespace with global spam; and although these are still effectively globals, they're at least organised spam!
Another thing is we've typedefined EventType to a string. We've typedefined it so we can change it to something a bit less heavy to check against later, but strings made the most sense at the time - code obvious first, optimise later .. or K.I.S.S - Keep It Simple, Stupid ;)

The externs are of course, defined in the cpp file. They're just the namespace again as it's string, so I won't bother printing up the file as it's in the SVN if you want it anyway!

The Common Event System

As stated, we have a generic Event System and then a platform specific one.
Our generic one is pretty powerful in itself though.. let's have a look at the interface:

EventSystem.h

#ifndef _EVENT_SYSTEM_H_
#define _EVENT_SYSTEM_H_

#include "EventTypes.h"
// This defines general purpose Event System logic.
// Each of the platform specific headers ( included below ) extend this.

#include <map>
#include <vector>

namespace GLESGAE
{
	class Event;
	class EventObserver;
	class EventTrigger;
	class Window;
	class CommonEventSystem
	{
		public:
			CommonEventSystem()
			: mEventObservers()
			, mEventTriggers()
			{
				
			}
			
			virtual ~CommonEventSystem() {}
			
			/// Update the Event System.
			virtual void update() = 0;
			
			/// Bind to specified Window.
			virtual void bindToWindow(Window* const window) = 0;
			
			/// Register an Event Type.
			void registerEventType(const EventType& eventType);
			
			/// Register an Event Observer with Event Type.
			void registerObserver(const EventType& eventType, EventObserver* const observer);
			
			/// Deregister an Event Observer from Event Type.
			void deregisterObserver(const EventType& eventType, EventObserver* const observer);
			
			/// Register a Custom Event Trigger.
			void registerEventTrigger(const EventType& eventType, EventTrigger* const trigger);
			
			/// Deregister a Custom Event Trigger.
			void deregisterEventTrigger(const EventType& eventType, EventTrigger* const trigger);
			
			/// Send an Event to all Observers of this type.
			/// If you want to retain this event beyond it being in the receiving scope, you'll have to copy it.
			void sendEvent(const EventType& eventType, Event* event);
			
			/// Update all the Triggers to send Events if necessary.
			void updateAllTriggers();
			
		protected:
			std::map<EventType, std::vector<EventObserver*> > mEventObservers; //!< Outer = Event Type; Inner = Array of Event Observers for this Event Type
			std::map<EventType, EventTrigger*> mEventTriggers;
			
	};
}

#if defined(LINUX)
	#include "X11/X11EventSystem.h"
#endif

#endif

Oopsie In my mad dash to get this finished and put up, I've only just noticed a small little hiccup in my logic - updateAllTriggers should be protected or private as we only really want to call it from update, and not let random outside forces prod an event through anytime they feel like it!

Fairly weighty class, methinks!
The comments describe pretty much what each function does, but I'll describe a few of the more interesting ones.

Firstly, the class is CommonEventSystem rather than EventSystem.. and then does something strange by pulling in a platform specific header at the bottom. Why is this? So you can platform-agnostically pull in "EventSystem.h" and it'll grab the correct platform Event System for you, without having to deal with it on your side.
It does kindof go against the grain of Class Name = File Name... but for sanity sake in your application, it's much better, believe me!

Event Systems generally don't work unless they have a handle to something the OS has given them. This is usually the window, which is why we bind the Event System to a Window via the bindToWindow call. It's rather platform specific, so it's set up as a pure virtual to force it to be overloaded further up the chain.. this means it can be recast to a Window that's specific to that platform to get all the gooey bits out - X11Display, etc..

Now we get to the Events handling bit itself.
Generally, we'd be going through the following process:

  • registerEventType
  • registerEventTrigger
  • perhaps multiple registerObservers

So, we'd register an Event type.. this creates space in our Observers array. Optionally, we could ( and I might do this later ) extend the Trigger map to work in the same vein.
We then register an Event Trigger, and bind as many Event Observers to that type as we want.
This basically boils down to Triggers send events, Observers wait for them. Nice and simple!

When building your class you can inherit from EventTrigger and EventObserver, and over load their respective functions.
The platform specific Input Systems pull in EventObserver and registers itself as an Observer of many Events. This is very useful, but it does give you extra overhead in having to figure out what Event you've been given, and recasting it to access. This is why EventType has been typedefined so we can change it later, as parsing strings isn't cheap! It does make it pretty straight forward though as to what's going on, so for the time being, it's particularly useful. We'll need a Resource Manager or some form of CRC-like encoding class further down the line, and these will all be changed to use that.

EventTrigger.h

#ifndef _EVENT_TRIGGER_H_
#define _EVENT_TRIGGER_H_

namespace GLESGAE
{
	class Event;
	class EventTrigger
	{
		public:
			EventTrigger() {}
			virtual ~EventTrigger() {}
			
			/// Check if this trigger has an Event ready.
			/// This MUST return 0 if there is no Event, otherwise it's the event pointer.
			/// This event WILL be cleaned up by the Event System once all Observers have observed the event.
			virtual Event* hasEvent() = 0;
	};
}

#endif

Nice and simple, does what it says on the tin.
Make note of the comment though - as there's only the one function, it's checked to see if you return something.. if you do not have an Event when this function is called, you must return 0!

EventObserver.h

#ifndef _EVENT_OBSERVER_H_
#define _EVENT_OBSERVER_H_

namespace GLESGAE
{
	class Event;
	class EventObserver
	{
		public:
			EventObserver() {}
			virtual ~EventObserver() {}
			
			/// Trigger this Observer to receive an event.
			virtual void receiveEvent(Event* const event) = 0;
	};
}

#endif

Perhaps even simpler... it's up to the derived class to deal with event, checking types, etc..

Gotcha I haven't quite mentioned this yet as we haven't gone to it, but marking this here seems rather important. When receiveEvent is called from the EventSystem, it'll be from the sendEvent function which takes an unprotected pointer because it WILL delete your event after sending it to all Observers. This is important to note as when your receiveEvent gets triggered, if you want to keep that Event around for whatever reason, you will have to perform a deep copy on it - as a shallow copy will get wiped out!
Why do we do this? So you can effectively "fire and forget" your events, and have the system clean up after you.

The X11 Event System

Now onto the actual Event System we'll be using on our Pandoras.

It comes with it's own set of Events, so we'll define them as follows:

X11Events.h

#ifndef _X11_EVENT_TYPES_H_
#define _X11_EVENT_TYPES_H_

#include "../EventTypes.h"
#include "../Event.h"

namespace X11
{
	#include <X11/Xlib.h>
}

namespace GLESGAE
{
	namespace X11Events
	{
		namespace Input
		{
			namespace Mouse
			{
				extern EventType Moved;
				extern EventType ButtonDown;
				extern EventType ButtonUp;
				
				class MovedEvent : public Event
				{
					public:
						MovedEvent(int x, int y)
						: Event(Moved)
						, mX(x)
						, mY(y)
						{
							
						}
						
						/// retrieve the new X co-ord
						const int getX() const { return mX; }
						
						/// retrieve the new Y co-ord
						const int getY() const { return mY; }
						
					private:
						int mX;
						int mY;
				};
				
				class ButtonDownEvent : public Event
				{
					public:
						ButtonDownEvent(unsigned int button)
						: Event(ButtonDown)
						, mButton(button)
						{
							
						}
						
						/// retrieve the button pressed
						const unsigned int getButton() const { return mButton; }
						
					private:
						unsigned int mButton;
				};
				
				class ButtonUpEvent : public Event
				{
					public:
						ButtonUpEvent(unsigned int button)
						: Event(ButtonUp)
						, mButton(button)
						{
							
						}
						
						/// retrieve the button released
						const unsigned int getButton() const { return mButton; }
						
					private:
						unsigned int mButton;
				};
			}
			
			namespace Keyboard
			{
				extern EventType KeyDown;
				extern EventType KeyUp;
				
				class KeyDownEvent : public Event
				{
					public:
						KeyDownEvent(X11::KeySym key)
						: Event(KeyDown)
						, mKey(key)
						{
							
						}
						
						/// retrieve the key pressed
						const X11::KeySym getKey() const { return mKey; }
						
					private:
						X11::KeySym mKey;
				};
				
				class KeyUpEvent : public Event
				{
					public:
						KeyUpEvent(X11::KeySym key)
						: Event(KeyUp)
						, mKey(key)
						{
							
						}
						
						/// retrieve the key released
						const X11::KeySym getKey() const { return mKey; }
						
					private:
						X11::KeySym mKey;
				};
			}
		}
	}
}


#endif

You should be used to my mad coding style by now, and this is pretty straight forward anyway.
We define the Event Types as externs as normal ( so we only ever have one copy of them in the .cpp file and not recreating instances of it all over the place, ) and then define the Events themselves. As they're simple, and effectively need to be fast and inlined, I've defined them all in the header.
Only oddness is our X11 namespace hack makes a re-appearance, but other than that, it's fairly typical.

X11EventSystem.h

#ifndef _X11_EVENT_SYSTEM_H_
#define _X11_EVENT_SYSTEM_H_

namespace GLESGAE
{
	class Window;
	class X11Window;
	class EventSystem : public CommonEventSystem
	{
		public:
			EventSystem();
			~EventSystem();
			
			/// Update the Event System
			void update();
			
			/// Bind to the Window
			void bindToWindow(Window* const window);
			
		private:
			bool mActive;
			X11Window* mWindow;
			int mCurrentPointerX;
			int mCurrentPointerY;
	};
}

#endif

Not a great deal in here, is there?
We just overload the standard CommonEventSystem interface, and store an X11Window pointer directly rather than a generic Window pointer.

The fun stuff is in the cpp...

X11EventSystem.cpp

#if defined(LINUX)

#include <cstdio>

#include "../EventSystem.h"
#include "../EventTypes.h"
#include "../SystemEvents.h"
#include "X11Events.h"
#include "../../Graphics/Window/X11Window.h"

namespace X11 {
	#include <X11/Xlib.h>
}

using namespace GLESGAE;

EventSystem::EventSystem()
: CommonEventSystem()
, mActive(true)
, mWindow(0)
, mCurrentPointerX(0)
, mCurrentPointerY(0)
{
	// Register System Events
	registerEventType(SystemEvents::App::Started);
	registerEventType(SystemEvents::App::Paused);
	registerEventType(SystemEvents::App::Resumed);
	registerEventType(SystemEvents::App::Destroyed);

	registerEventType(SystemEvents::Window::Opened);
	registerEventType(SystemEvents::Window::Resized);
	registerEventType(SystemEvents::Window::Closed);
	
	// Register X11 Specific Events
	registerEventType(X11Events::Input::Mouse::Moved);
	registerEventType(X11Events::Input::Mouse::ButtonDown);
	registerEventType(X11Events::Input::Mouse::ButtonUp);
	
	registerEventType(X11Events::Input::Keyboard::KeyDown);
	registerEventType(X11Events::Input::Keyboard::KeyUp);
}

EventSystem::~EventSystem()
{
	
}

void EventSystem::bindToWindow(Window* const window)
{
	mWindow = reinterpret_cast<X11Window*>(window);	
}


void EventSystem::update()
{
	// Deal with the pointer first.
	X11::Window rootReturn;
	X11::Window childReturn;
	int rootXReturn;
	int rootYReturn;
	int pointerX;
	int pointerY;
	unsigned int maskReturn;
	
	if (true == X11::XQueryPointer(mWindow->getDisplay(), mWindow->getWindow()
									, &rootReturn, &childReturn
									, &rootXReturn, &rootYReturn
									, &pointerX, &pointerY, &maskReturn)) {
		if ((pointerX != mCurrentPointerX) && (pointerY != mCurrentPointerY))
			sendEvent(X11Events::Input::Mouse::Moved, new X11Events::Input::Mouse::MovedEvent(pointerX, pointerY));
		mCurrentPointerX = pointerX;
		mCurrentPointerY = pointerY;
	}
	
	// Rest of the events...
	X11::XEvent event;
	while (X11::XPending(mWindow->getDisplay())) {
		X11::XNextEvent(mWindow->getDisplay(), &event);
		
		switch (event.type) {
			case Expose:
				if (event.xexpose.count != 0)
					break;
				break;
			case ConfigureNotify:
				sendEvent(SystemEvents::Window::Resized, new SystemEvents::Window::ResizedEvent(event.xconfigure.width, event.xconfigure.height));
				break;
				
			case KeyPress:
				sendEvent(X11Events::Input::Keyboard::KeyDown, new X11Events::Input::Keyboard::KeyDownEvent(X11::XLookupKeysym(&event.xkey, 0)));
				break;
				
			case KeyRelease:
				sendEvent(X11Events::Input::Keyboard::KeyUp, new X11Events::Input::Keyboard::KeyUpEvent(X11::XLookupKeysym(&event.xkey, 0)));
				break;
				
			case ButtonRelease:
				sendEvent(X11Events::Input::Mouse::ButtonUp, new X11Events::Input::Mouse::ButtonUpEvent(event.xbutton.button));
				break;
				
			case ButtonPress:
				sendEvent(X11Events::Input::Mouse::ButtonDown, new X11Events::Input::Mouse::ButtonDownEvent(event.xbutton.button));
				break;
				
			case ClientMessage:
				if (*X11::XGetAtomName(mWindow->getDisplay(), event.xclient.message_type) == *"WM_PROTOCOLS") {
					sendEvent(SystemEvents::Window::Closed, new SystemEvents::Window::ClosedEvent);
					sendEvent(SystemEvents::App::Destroyed, new SystemEvents::App::DestroyedEvent);
				}
				else if (event.xclient.data.l[0] == mWindow->getDeleteMessage()) {
					sendEvent(SystemEvents::Window::Closed, new SystemEvents::Window::ClosedEvent);
					sendEvent(SystemEvents::App::Destroyed, new SystemEvents::App::DestroyedEvent);
				}

				break;
			default:
				break;
		}
	}
}

#endif

Now we have some actual code.
First off, we register all the System and X11 specific events we'll be sending. This allows us to create the arrays and set things up so that other systems can observe these events, and we can actually send them.

When we bind the Window, we recast it to our specific type and store it.. nothing else really needed.

Going through the update function, however... is a bit more involved.
I've split it up in handling the Pointer co-ordinates first, as they're dealt with slightly differently, and then running through the standard Event Loop to catch things we're interested in.

The pointer stuff is pretty straight forward - store the current position and if it's moved, send an Event. It's all API gubbins really, and you can read up on it if you like.
One hidden gotcha is XQueryPointer will give you the position of the mouse pointer as long as it's on the same screen as your application - therefore, you can get into a situation where you're receiving co-ordinates that are outside your window! So be careful!

The Event Queue itself is also API gubbins for the most part, and sending Events for the parts we're interested in.
Of note is the Client Message where we detect the closure of the window... these are generally handled via the WM_PROTOCOLS set, or via a special delete message atom... so we check for both, just in case.

The Input System

The general Input System is pretty basic, and just defines an interface for the platform specific variants to conform to.
Inputs generally link up with Events.. basic events being Keyboard and Pointers from the Windowing System.
Joysticks and other things can come from X11 as well.. though are not currently supported due to lack of time, motivation and testing equipment.

Controllers

We're again abusing namespaces here to separate things out. This does give us huge amounts of control over generic Controllers like Pads and Joysticks as we can define our own button sets pretty quickly - see the PandoraInputSystem for more details on the Pandora Buttons Set.

Generally, these are all pretty similar, so check the code out if you want more info, for I'll just define the PadController here...

Pad.h

#ifndef _PAD_H_
#define _PAD_H_

#include "Controller.h"
#include "ControllerTypes.h"

#include <vector>

namespace GLESGAE
{
	class InputSystem;
	namespace Controller
	{
		class PadController : public CommonController
		{
			friend class GLESGAE::InputSystem;
			public:
				PadController(const Controller::Id controllerId, const int buttons)
				: CommonController(Pad, controllerId)
				, mButtons(buttons)
				{
					
				}
				
				/// Get the value of the specified button
				const float getButton(const Button button) const
				{
					// TODO: actual error checking
					return mButtons[button];
				}
				
				/// Get amount of buttons
				const unsigned int getNumButtons() const { return mButtons.size(); }
								
			protected:
				/// Set Button data
				void setButton(const Button button, const float data)
				{
					// TODO: actual error checking
					mButtons[button] = data;
				}
				
			private:
				std::vector<float> mButtons;
		};
	}
}

#endif

Again, my lack of error checking is shocking, so I shall leave that up to you!

Literally all a Pad is, is an array of buttons. We've defined the array as a float in case we have pressure sensitive buttons, but we could get away with booleans for simple digital pads that are just on/off.

We've also protected the set functions, and made the Input System a friend so it can access them.
This is to stop accidental setting of the buttons from random systems, as that'd just confuse everything.

CommonController is just a base class which contains a Type and Id of the Controller.. Type being Pad, Keyboard, etc.. and the Id being a unique identifier for this Controller. Again, read the SVN if you want more details, as it's pretty straight forward.

Pandora Input System

The Pandora Input System is mostly the Linux Input System with the addition of using libPND to access the nubs and dpad/face buttons.
Like the Event System, the Inputs get automatically updated for you during the Input System update, so you don't need to poll manually.

We could have abused the Event System further by tagging each action as an event to watch, but that's perhaps a bit overboard.

So let's have a look at the Common Input System first.. it's pretty big but defines everything we'd ever want, really.

InputSystem.h

#ifndef _INPUT_SYSTEM_H_
#define _INPUT_SYSTEM_H_

#include "ControllerTypes.h"

namespace GLESGAE
{
	namespace Controller
	{
		class KeyboardController;
		class PointerController;
		class JoystickController;
		class PadController;
	}
	class CommonInputSystem
	{
		public:
			CommonInputSystem() {}
			virtual ~CommonInputSystem() {}
			
			/// Update the Input System
			virtual void update() = 0;
			
			/// Retreive number of Active Keyboards.
			virtual const Controller::Id getNumberOfKeyboards() const = 0;
			
			/// Retreive number of Active Joysticks.
			virtual const Controller::Id getNumberOfJoysticks() const = 0;
			
			/// Retreive number of Active Pads.
			virtual const Controller::Id getNumberOfPads() const = 0;
			
			/// Retreive number of Active Pointers.
			virtual const Controller::Id getNumberOfPointers() const = 0;
			
			/// Create new Keyboard - will return NULL if no more available.
			virtual Controller::KeyboardController* const newKeyboard() = 0;
			
			/// Create new Joystick - will return NULL if no more available.
			virtual Controller::JoystickController* const newJoystick() = 0;
			
			/// Create new Pad - will return NULL if no more available.
			virtual Controller::PadController* const newPad() = 0;
			
			/// Create new Pointer - will return NULL if no more available.
			virtual Controller::PointerController* const newPointer() = 0;
			
			/// Grab another instance of the specified Keyboard - returns NULL if not created.
			virtual Controller::KeyboardController* const getKeyboard(const Controller::Id id) = 0;
			
			/// Grab another instance of the specified Joystick - returns NULL if not created.
			virtual Controller::JoystickController* const getJoystick(const Controller::Id id) = 0;
			
			/// Grab another instance of the specified Pointer - returns NULL if not created.
			virtual Controller::PointerController* const getPointer(const Controller::Id id) = 0;
			
			/// Grab another instance of the specified Pad - returns NULL if not created.
			virtual Controller::PadController* const getPad(const Controller::Id id) = 0;
			
			/// Destroy a Keyboard.
			virtual void destroyKeyboard(Controller::KeyboardController* const keyboard) = 0;
			
			/// Destroy a Joystick.
			virtual void destroyJoystick(Controller::JoystickController* const joystick) = 0;
			
			/// Destroy a Pad.
			virtual void destroyPad(Controller::PadController* const pad) = 0;
			
			/// Destroy a Pointer.
			virtual void destroyPointer(Controller::PointerController* const pointer) = 0;
	};
}

#if defined(PANDORA)
	#include "Pandora/PandoraInputSystem.h"
#elif defined(LINUX)
	#include "Linux/LinuxInputSystem.h"
#endif

#endif

Again, we abuse the fact that we just need to pull in InputSystem.h and we get the platform specific variant automatically.

There isn't really much to the Input System.. we have functionality for creating, retrieving and deleting Pads, Pointers, Keyboards and Joysticks. We can also query how many there are connected to the system, and call the standard update function.

PandoraInputSystem.h

#ifndef _PANDORA_INPUT_SYSTEM_H_
#define _PANDORA_INPUT_SYSTEM_H_

#include <vector>

#include "../ControllerTypes.h"
#include "../../Events/EventObserver.h"

namespace X11
{
	#include <X11/Xlib.h>
}

namespace GLESGAE
{
	namespace Controller
	{
		namespace Pandora {
			extern Controller::Button Up;
			extern Controller::Button Down;
			extern Controller::Button Left;
			extern Controller::Button Right;
			extern Controller::Button Start;
			extern Controller::Button Select;
			extern Controller::Button Pandora;
			extern Controller::Button Y;
			extern Controller::Button B;
			extern Controller::Button X;
			extern Controller::Button A;
			extern Controller::Button L1;
			extern Controller::Button L2;
			extern Controller::Button R1;
			extern Controller::Button R2;
			
			extern Controller::Id LeftNub;
			extern Controller::Id RightNub;
			extern Controller::Id Buttons;
		}
	}
	
	class Event;
	class EventSystem;
	class InputSystem : public CommonInputSystem, public EventObserver
	{
		public:
			InputSystem(EventSystem* const eventSystem);
			~InputSystem();
			
			/// Update the Input System
			void update();
			
			/// Receive an Event
			void receiveEvent(Event* const event);
			
			/// Retreive number of Active Keyboards.
			const Controller::Id getNumberOfKeyboards() const;
			
 			/// Retreive number of Active Joysticks.
			const Controller::Id getNumberOfJoysticks() const;
			
			/// Retreive number of Active Pads.
			const Controller::Id getNumberOfPads() const ;
			
			/// Retreive number of Active Pointers.
			const Controller::Id getNumberOfPointers() const;
			
			/// Create new Keyboard - will return NULL if no more available.
			Controller::KeyboardController* const newKeyboard();
			
			/// Create new Joystick - will return NULL if no more available.
			Controller::JoystickController* const newJoystick();
			
			/// Create new Pad - will return NULL if no more available.
			Controller::PadController* const newPad();
			
			/// Create new Pointer - will return NULL if no more available.
			Controller::PointerController* const newPointer();
			
			/// Grab another instance of the specified Keyboard - returns NULL if not created.
			Controller::KeyboardController* const getKeyboard(const Controller::Id id);
			
			/// Grab another instance of the specified Joystick - returns NULL if not created.
			Controller::JoystickController* const getJoystick(const Controller::Id id);
			
			/// Grab another instance of the specified Pointer - returns NULL if not created.
			Controller::PointerController* const getPointer(const Controller::Id id);
			
			/// Grab another instance of the specified Pad - returns NULL if not created.
			Controller::PadController* const getPad(const Controller::Id id);
			
			/// Destroy a Keyboard.
			void destroyKeyboard(Controller::KeyboardController* const keyboard);
			
			/// Destroy a Joystick.
			void destroyJoystick(Controller::JoystickController* const joystick);
			
			/// Destroy a Pad.
			void destroyPad(Controller::PadController* const pad);
			
			/// Destroy a Pointer.
			void destroyPointer(Controller::PointerController* const pointer);
			
		protected:
			const Controller::KeyType convertKey(X11::KeySym x11Key);
			
		private:
			Controller::KeyboardController* mKeyboard;
			Controller::PointerController* mPointer;
			std::vector<Controller::JoystickController*> mJoysticks;
			std::vector<Controller::PadController*> mPads;
			
			EventSystem* mEventSystem;
	};
}

#endif

Effectively, we just define what's already in the CommonInputSystem, but we also have a bunch of custom buttons, so we define these as well.
We also define the Ids for the Left and Right Nubs, and the Game Button set.

PandoraInputSystem.cpp

I'm not going to print up all of this, as it's rather large.. if you want the full code, check the SVN.

Most of the class does exactly what you think, so we'll take a look at just a few of the functions here.

InputSystem::InputSystem(EventSystem* const eventSystem)
: CommonInputSystem()
, mKeyboard(0)
, mPointer(0)
, mJoysticks()
, mPads()
, mEventSystem(eventSystem)
{
	mJoysticks.push_back(new Controller::JoystickController(Controller::Pandora::LeftNub, 2U, 0U)); 	// ID, 2 Axes, 0 Buttons
	mJoysticks.push_back(new Controller::JoystickController(Controller::Pandora::RightNub, 2U, 0U)); 	// ID, 2 Axes, 0 Buttons
	
	mPads.push_back(new Controller::PadController(Controller::Pandora::Buttons, 15U)); // 15 Buttons - Up/Down/Left/Right - Start/Select/Pandora - Y/B/X/A - L1/R1/L2/R2 ( L2/R2 being optional, of course )
	
	pnd_evdev_open(pnd_evdev_dpads);
	pnd_evdev_open(pnd_evdev_nub1);
	pnd_evdev_open(pnd_evdev_nub2);
}

The Constructor is fairly straight-forward.. we setup how many Nubs we have ( marking them as Joystick devices of 2 axes and 0 buttons, effectively ) along with the buttons and then call the libpnd functions to open up the devices we want.

InputSystem::~InputSystem()
{
	if (0 != mKeyboard) {
		delete mKeyboard;
		mKeyboard = 0;
	}
	
	if (0 != mPointer) {
		delete mPointer;
		mPointer = 0;
	}
	
	for (std::vector<Controller::JoystickController*>::iterator itr(mJoysticks.begin()); itr != mJoysticks.end(); ++itr)
		delete (*itr);
		
	mJoysticks.clear();
	
	for (std::vector<Controller::PadController*>::iterator itr(mPads.begin()); itr != mPads.end(); ++itr)
		delete (*itr);
		
	mPads.clear();
	
	pnd_evdev_close(pnd_evdev_dpads);
	pnd_evdev_close(pnd_evdev_nub1);
	pnd_evdev_close(pnd_evdev_nub2);
}

The Destructor tidies up our mess, with some actual checking to ensure we're being sane about it.

void InputSystem::update()
{
	// Use libPND to update Nubs and Buttons... keyboard and pointer come through as events.
	pnd_evdev_catchup(0);
	
	pnd_nubstate_t nubState;
	
	// Left Nub
	if (pnd_evdev_nub_state(pnd_evdev_nub1, &nubState) > 0) {
		mJoysticks[Controller::Pandora::LeftNub]->setAxis(Controller::Axis::X, static_cast<float>(nubState.x));
		mJoysticks[Controller::Pandora::LeftNub]->setAxis(Controller::Axis::Y, static_cast<float>(nubState.y));
	}
	
	// Right Nub
	if (pnd_evdev_nub_state(pnd_evdev_nub2, &nubState) > 0) {
		mJoysticks[Controller::Pandora::RightNub]->setAxis(Controller::Axis::X, static_cast<float>(nubState.x));
		mJoysticks[Controller::Pandora::RightNub]->setAxis(Controller::Axis::Y, static_cast<float>(nubState.y));
	}
	
	// Buttons
	Controller::Button buttonState(pnd_evdev_dpad_state(pnd_evdev_dpads));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Left, (buttonState & pnd_evdev_left));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Right, (buttonState & pnd_evdev_right));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Up, (buttonState & pnd_evdev_up));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Down, (buttonState & pnd_evdev_down));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::X, (buttonState & pnd_evdev_x));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Y, (buttonState & pnd_evdev_y));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::A, (buttonState & pnd_evdev_a));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::B, (buttonState & pnd_evdev_b));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::L1, (buttonState & pnd_evdev_ltrigger));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::R1, (buttonState & pnd_evdev_rtrigger));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Start, (buttonState & pnd_evdev_start));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Select, (buttonState & pnd_evdev_select));
	mPads[Controller::Pandora::Buttons]->setButton(Controller::Pandora::Pandora, (buttonState & pnd_evdev_pandora));
}

Now then, the update function actually performs a fair bit of logic here.
The cleanliness of abusing namespaces also makes this fairly readable - see, method in my madness!

The first thing we do is effectively poll the pnd device state to get everything.
We then update the nubs one at a time.

Then we move on to the buttons themselves, which are stored in a bitmask - hence the funky use of the ampersand.
This is all hidden out the road of the user though, so all they need to call is const float value(pandoraButtons->getButton(Controller::Pandora::B)); and be done with it.

void InputSystem::receiveEvent(Event* const event)
{
	if (event->getEventType() == X11Events::Input::Keyboard::KeyDown)
		mKeyboard->setKey(convertKey(reinterpret_cast<X11Events::Input::Keyboard::KeyDownEvent*>(event)->getKey()), true);
	else if (event->getEventType() == X11Events::Input::Keyboard::KeyUp)
		mKeyboard->setKey(convertKey(reinterpret_cast<X11Events::Input::Keyboard::KeyUpEvent*>(event)->getKey()), false);
	else if (event->getEventType() == X11Events::Input::Mouse::Moved) {
		mPointer->setAxis(Controller::Axis::X, reinterpret_cast<X11Events::Input::Mouse::MovedEvent*>(event)->getX());
		mPointer->setAxis(Controller::Axis::Y, reinterpret_cast<X11Events::Input::Mouse::MovedEvent*>(event)->getY());
	}
	else if (event->getEventType() == X11Events::Input::Mouse::ButtonDown)
		mPointer->setButton(reinterpret_cast<X11Events::Input::Mouse::ButtonDownEvent*>(event)->getButton(), 1.0F);
	else if (event->getEventType() == X11Events::Input::Mouse::ButtonUp)
		mPointer->setButton(reinterpret_cast<X11Events::Input::Mouse::ButtonUpEvent*>(event)->getButton(), 0.0F);
}

Finally, we actually get to implement a receiveEvent function!

These get sent from our Event System as they're dealt with via X11.. so we sit and wait for an event, and figure out what type it is before acting on it.

As we currently have EventType set to a string, we can't do a switch statement here.. however, I'm planning on updating this to use unsigned ints instead so we can add in a switch statement instead - which is much faster than string comparing!

The rest of the class does what you'd think.. for example; create a new Keyboard interface, register ourselves as an observer of Keyboard events for it so we can pick them up, return the Keyboard interface, remove the Keyboard interface and deregister ourselves with the event system, etc..

The only fun function is converting the X11 Keyboard symcodes to our own Key codes... but that's just API gubbins.

A Simple Test

Of course, you probably want to see this in action!
It's still a bit empty and not hugely exciting I'm afraid.. but we shall start fixing that soon!
We are at least cleaning up properly now :)

#include <cstdio>
#include <cstdlib>

#include "../../Events/EventSystem.h"
#include "../../Input/InputSystem.h"

#if defined(LINUX)
	#include "../../Graphics/Window/X11Window.h"
#endif

#if defined(GLX)
	#include "../../Graphics/Context/GLXContext.h"
#elif defined(GLES1)
	#include "../../Graphics/Context/GLES1Context.h"
#elif defined(GLES2)
	#include "../../Graphics/Context/GLES2Context.h"
#endif

#include "../../Input/Keyboard.h"
#include "../../Input/Pad.h"

using namespace GLESGAE;

int main(void)
{
	EventSystem* eventSystem(new EventSystem);
	InputSystem* inputSystem(new InputSystem(eventSystem));
	
	#if defined(LINUX)
		X11Window* window(new X11Window);
	#endif
	
	#if defined(GLX)
		GLXContext* context(new GLXContext);
	#elif defined(GLES1)
		GLES1Context* context(new GLES1Context);
	#elif defined(GLES2)
		GLES2Context* context(new GLES2Context);
	#endif
	
	eventSystem->bindToWindow(window);
	context->bindToWindow(window);
	window->open(800, 480);
	context->initialise();
	window->refresh();
	
	Controller::KeyboardController* myKeyboard(inputSystem->newKeyboard());
	
	#ifdef PANDORA
		Controller::PadController* pandoraButtons(inputSystem->getPad(Controller::Pandora::Buttons));
	#endif

	while(false == myKeyboard->getKey(Controller::KEY_Q)) {
		window->refresh();
		eventSystem->update();
		inputSystem->update();
		#ifdef PANDORA
			if (pandoraButtons->getButton(Controller::Pandora::B))
				printf("B! I got a B! Is mummy proud? :D\n");
		#endif
	}
	
	delete context;
	delete window;
	delete inputSystem;
	delete eventSystem;
	
	return 0;
}


Building the Example

In the SVN there are Makefiles already setup for you.. just trigger make -f MakefileES1.pandora or whatever your chosen configuration is, and it'll happily build for you and spit out a GLESGAE.pandora binary for you to run.

Alternatively, if you use CodeLite, there's a Workspace/Project set for you preconfigured.

Gotcha This time, I had an issue with libpnd... I was a bit heavy handed this time and just did ln -s /usr/lib/libpnd.so.1 /usr/lib/libpnd.so which is naughty, as we should install the dev package of libpnd instead. This is why I sneakily included a version of one of the headers in the repository, something which I shall fix in the next commit.

Next Time

We'll be looking at the Renderers.. we'll have two of them - FixedFunctionRenderer and ShaderBasedRenderer - so depending on how it works out, it may be two articles or a really really heavy one!
Following that, we shall start setting up some engine defines, declare the data pipeline, and perhaps maybe even load up a model!

After that, some State Systems to organise the mess we're starting to develop.

Personal tools
community