Event Handling: Difference between revisions

From dreamcast.wiki
Jump to navigation Jump to search
(Created page with "Events allow us to send automated messages from one part of our program to other parts. With a simple program like we have now, that’s not so important, but in more complex programs this becomes very important. We’ll create an event handler for Gamepad Input in this section, which will let us start writing code to control our program while it’s running. We need a central object for parts of our program to talk with. Using global variables is a bad idea on the Sega...")
 
No edit summary
Line 4: Line 4:


Create a second blank text file and name it gamestate.h. This is our Header file for the gamestate, we can include it in other files which will make those files able to use it’s functions and interact with the object its class defines. Let’s create our class definition right now in gamestate.h:
Create a second blank text file and name it gamestate.h. This is our Header file for the gamestate, we can include it in other files which will make those files able to use it’s functions and interact with the object its class defines. Let’s create our class definition right now in gamestate.h:
#ifndef GAMESTATE_H
 
#define GAMESTATE_H
#ifndef GAMESTATE_H
#define GAMESTATE_H
//Game State Class stuff
//Game State Class stuff
#endif // GAMESTATE_H
#endif // GAMESTATE_H


Begin with this ifdef. This is a header guard, it will ensure that we don’t redefine our class definition multiple times. When we include our header in other source files, the #include directive essentially copies and pastes the file into our source. Without these guards, our class definition would be copied multiple times. These guards look for a lock symbol, called GAMESTATE_H. If it doesn’t exist, as it would when this code is encountered for the first time, it’ll run the rest of the header. It begins by immediately defining GAMESTATE_H. This way, the next time this code is run, the preprocessor will skip copying it, preventing our redefinition error. You can do the same thing with #pragma once in c++ files, but this is the old school way that also works in C.
Begin with this ifdef. This is a header guard, it will ensure that we don’t redefine our class definition multiple times. When we include our header in other source files, the #include directive essentially copies and pastes the file into our source. Without these guards, our class definition would be copied multiple times. These guards look for a lock symbol, called GAMESTATE_H. If it doesn’t exist, as it would when this code is encountered for the first time, it’ll run the rest of the header. It begins by immediately defining GAMESTATE_H. This way, the next time this code is run, the preprocessor will skip copying it, preventing our redefinition error. You can do the same thing with #pragma once in c++ files, but this is the old school way that also works in C.
We need some header files in our gamestate.h so we can use certain calls in our class:
We need some header files in our gamestate.h so we can use certain calls in our class:
#include "stdint.h"
#include "stdint.h"
#include <kos.h>                /* KalistiOS Dreamcast SDK */
#include <kos.h>                /* KalistiOS Dreamcast SDK */


We need to access KOS, along with standard integer types. Now we can begin to define our class:


We need to access KOS, along with standard integer types. Now we can begin to define our class:
/* This structure holds all the data about our global game state
/* This structure holds all the data about our global game state
  * we create this object once, and other objects check and interact
  * we create this object once, and other objects check and interact
  * with it. This lets them communicate with each other. */
  * with it. This lets them communicate with each other. */
class GameState
class GameState
{
{
public:
public:
     GameState();
     GameState();
uint32_t Done;          /* Is the game finished running? */
uint32_t Done;          /* Is the game finished running? */
};
};


Basic stuff, we have a private variable called Done, which represents the state of our program running. It is proper form to make the variable private, and use functions to access it, but for something this simple, it’s just as valid to keep the variable public.
Basic stuff, we have a private variable called Done, which represents the state of our program running. It is proper form to make the variable private, and use functions to access it, but for something this simple, it’s just as valid to keep the variable public.


We need to define our constructor in gamestate.cpp:
We need to define our constructor in gamestate.cpp:
GameState::GameState()
GameState::GameState()
{
{
     Done = 0;
     Done = 0;
}
}


This function is run the first time our object is created. It will automatically set Done to 0, so our program  begins by running. We will create a GameState Object in main and begin using it to control our program loop. In Main.cpp, add our gamestate.h to our includes:
This function is run the first time our object is created. It will automatically set Done to 0, so our program  begins by running. We will create a GameState Object in main and begin using it to control our program loop. In Main.cpp, add our gamestate.h to our includes:
/* Classes */
/* Classes */
#include "gamestate.h"
#include "gamestate.h"


Now, in our Main function, create the GameState Object and make Done the condition in our While...Loop.
Now, in our Main function, create the GameState Object and make Done the condition in our While...Loop.
Line 48: Line 49:


If we want to end our program, all we have to do is change Game.Done to 1 and our loop will stop. We can pass our GameState object by reference using pointers to other classes to keep up with this “global” variable, without the penalty of defining it in global space!  
If we want to end our program, all we have to do is change Game.Done to 1 and our loop will stop. We can pass our GameState object by reference using pointers to other classes to keep up with this “global” variable, without the penalty of defining it in global space!  
Now that we have a GameState to keep track of information, let’s build an Event Handler Class. Make a new text file called MapleEvents.cpp, and MapleEvents.h. Be sure to add MapleEvents.o to $(OBJS), and also add MapleEvents.h to our header section of Main.cpp.
Begin in MapleEvents.h:
#ifndef MapleEvents_H
#define MapleEvents_H
#include <arch/gdb.h>          /* gdb debugger */
#include <kos.h>                /* KalistiOS Dreamcast SDK */
#include <kos/dbglog.h>        /* debug log */
#include <arch/timer.h>        /* used for timing functions related to framerate */
#include <arch/irq.h>          /* Interrupt request functions */
#include <png/png.h>            /* Library to load png files */
#include <stdio.h>              /* Standard Input-Output */
#include <stdint.h>            /* Standard Integer types (uint_t, etc) */
/* Classes */
#include "gamestate.h"


#endif // MapleEvents_H
Now that we have a GameState to keep track of information, let’s build an Event Handler Class. Make a new text file called MapleEvents.cpp, and MapleEvents.h. Be sure to add MapleEvents.o to $(OBJS), and also add MapleEvents.h to our header section of Main.cpp. Begin in MapleEvents.h:
 
#ifndef MapleEvents_H
#define MapleEvents_H
#include <arch/gdb.h>          /* gdb debugger */
#include <kos.h>                /* KalistiOS Dreamcast SDK */
#include <kos/dbglog.h>        /* debug log */
#include <arch/timer.h>        /* used for timing functions related to framerate */
#include <arch/irq.h>          /* Interrupt request functions */
#include <png/png.h>            /* Library to load png files */
#include <stdio.h>              /* Standard Input-Output */
#include <stdint.h>            /* Standard Integer types (uint_t, etc) */
/* Classes */
#include "gamestate.h"
 
#endif // MapleEvents_H


Here we use Include Guards, and include the normal variety of KOS and standard headers. We also include our GameState class, so we can use and manipulate it if we want. Let’s create our MapleEvents class:
Here we use Include Guards, and include the normal variety of KOS and standard headers. We also include our GameState class, so we can use and manipulate it if we want. Let’s create our MapleEvents class:
class MapleEvents
class MapleEvents
{
{
public:
public:
     MapleEvents();   
     MapleEvents();   
     void PollEvent(GameState* GState);
     void PollEvent(GameState* GState);
};
};


Aside from our constructor, we also have a function called PollEvent, and it takes a pointer of GameState type. This means we can pass our GameState to it, and it can manipulate it. Define the functions in MapleEvents.cpp:
Aside from our constructor, we also have a function called PollEvent, and it takes a pointer of GameState type. This means we can pass our GameState to it, and it can manipulate it. Define the functions in MapleEvents.cpp:
#include "MapleEvents.h"
#include "MapleEvents.h"
MapleEvents::MapleEvents()
MapleEvents::MapleEvents()
{
{
}
}


Create a dummy constructor. Now let’s start building our PollEvent function, this is the function which will talk to MAPLE on the dreamcast and get a controller state back:
Create a dummy constructor. Now let’s start building our PollEvent function, this is the function which will talk to MAPLE on the dreamcast and get a controller state back:
void MapleEvents::PollEvent(GameState* GState)
void MapleEvents::PollEvent(GameState* GState)
{
{
     MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
     MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
      
      
     MAPLE_FOREACH_END()
     MAPLE_FOREACH_END()
}
}


Our function includes a pair of preprocessor macros that KOS provides, called MAPLE_FOREACH_BEGIN and MAPLE_FOREACH_END. These iterate through all the controllers in MAPLE and returns their state in an object. This object is of cont_state_t type. The MAPLE_FOREACH_BEGIN macro takes paramters. The first is the Function code, which tells MAPLE what to operate on. MAPLE_FUNC_CONTROLLER has us working on Dreamcast controllers. Other options available are:
Our function includes a pair of preprocessor macros that KOS provides, called MAPLE_FOREACH_BEGIN and MAPLE_FOREACH_END. These iterate through all the controllers in MAPLE and returns their state in an object. This object is of cont_state_t type. The MAPLE_FOREACH_BEGIN macro takes paramters. The first is the Function code, which tells MAPLE what to operate on. MAPLE_FUNC_CONTROLLER has us working on Dreamcast controllers. Other options available are:
MAPLE_FUNC_KEYBOARD: Poll the Dreamcast Keyboard
 
MAPLE_FUNC_LIGHTGUN: Poll a lightgun controllers
MAPLE_FUNC_KEYBOARD: Poll the Dreamcast Keyboard
MAPLE_FUNC_MOUSE: Poll a Dreamcast Mouse
MAPLE_FUNC_LIGHTGUN: Poll a lightgun controllers
MAPLE_FUNC_MEMCARD: Interact with the Dreamcast VMU
MAPLE_FUNC_MOUSE: Poll a Dreamcast Mouse
MAPLE_FUNC_PURUPURU: Interact with the Dreamcast Jump Pack
MAPLE_FUNC_MEMCARD: Interact with the Dreamcast VMU
MAPLE_FUNC_LCD: Interact with the VMU screen on the Dreamcast Controller
MAPLE_FUNC_PURUPURU: Interact with the Dreamcast Jump Pack
Our MAPLE_FOREACH_BEGIN macro creates a variable cont_state_t st which holds the polled controller info. This info comes in the form of a uint32_t number, which is interpreted as a bitfield. A 0 indicates a button is not being pressed, and a 1 indicates a button is currently being pressed. These are raw states, in that moment. This information isn’t too useful. If we see a 1 for a button, does that mean the button was just pressed? Is it being held down? If we see a 0, does that mean it was just released? We need more information, which means we need the last state to compare to. Our GameState object is perfect for holding this information in. In our GameState class, add a variable to hold our Previous Controller state:
MAPLE_FUNC_LCD: Interact with the VMU screen on the Dreamcast Controller
 
Our MAPLE_FOREACH_BEGIN macro creates a variable cont_state_t st which holds the polled controller info. This info comes in the form of a uint32_t number, which is interpreted as a bitfield. A 0 indicates a button is not being pressed, and a 1 indicates a button is currently being pressed. These are raw states, in that moment. This information isn’t too useful. If we see a 1 for a button, does that mean the button was just pressed? Is it being held down? If we see a 0, does that mean it was just released? We need more information, which means we need the last state to compare to. Our GameState object is perfect for holding this information in. In our GameState class, add a  
variable to hold our Previous Controller state:
     cont_state_t PrevControllerState;
     cont_state_t PrevControllerState;


Now, when we poll our controller, we can compare the results to the last frame. This means every loop, we need to update the PrevControllerState with that frame’s information. Let’s do this in our PollEvent function. For readability reasons, let’s cache our Buttons into variables:
Now, when we poll our controller, we can compare the results to the last frame. This means every loop, we need to update the PrevControllerState with that frame’s information. Let’s do this in our PollEvent function. For readability reasons, let’s cache our Buttons into variables:
void MapleEvents::PollEvent(GameState* GState)
 
{
void MapleEvents::PollEvent(GameState* GState)
{
     MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
     MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
     uint32_t PrevButtons = GState->PrevControllerState.buttons;
     uint32_t PrevButtons = GState->PrevControllerState.buttons;
Line 106: Line 112:
     GState->PrevControllerState.buttons = CurrButtons;
     GState->PrevControllerState.buttons = CurrButtons;
     MAPLE_FOREACH_END()
     MAPLE_FOREACH_END()
}
}


We get our PrevControllerState buttons into PrevButtons, and we get st->buttons (the current polled controller state) into CurrButtons. At the end of the function, just before MAPLE_FOREACH_END(), we set our PrevControllerState.buttons variable in our GameState, which is located by the GState pointer.
We get our PrevControllerState buttons into PrevButtons, and we get st->buttons (the current polled controller state) into CurrButtons. At the end of the function, just before MAPLE_FOREACH_END(), we set our PrevControllerState.buttons variable in our GameState, which is located by the GState pointer.


We can now compare button states with two samples. This lets us tell if we have just pressed, just released, or are holding down a button. All of these actions are Events. We need to create an enum type that can keep track of these actions. When we want to raise an event, we will push these enum actions onto a vector to store them. Let’s begin by defining our action events. We will do so in a new text file called EventEnums.h. This isn’t a cpp file, so it doesn’t become compiled into a translation unit object, so no EventEnums.o in our $(OBJS). In EventEnums.h, we can start building our event enum:
We can now compare button states with two samples. This lets us tell if we have just pressed, just released, or are holding down a button. All of these actions are Events. We need to create an enum type that can keep track of these actions. When we want to raise an event, we will push these enum actions onto a vector to store them. Let’s begin by defining our action events. We will do so in a new text file called EventEnums.h. This isn’t a cpp file, so it doesn’t become compiled into a translation unit object, so no EventEnums.o in our $(OBJS). In EventEnums.h, we can start building our event enum:
#ifndef GAMECONTROLLER_H
 
#define GAMECONTROLLER_H
#ifndef GAMECONTROLLER_H
enum Event_ControllerButton {
#define GAMECONTROLLER_H
enum Event_ControllerButton {
     Up = 0,
     Up = 0,
     Down = 1,
     Down = 1,
Line 125: Line 132:
     Z = 9,
     Z = 9,
     Start = 10
     Start = 10
};
};
enum Event_ControllerState {
enum Event_ControllerState {
     None = 0,
     None = 0,
     Pressed = 1,
     Pressed = 1,
     Hold = 2,
     Hold = 2,
     Released = 3
     Released = 3
};
};
typedef struct _Event_ControllerAction
typedef struct _Event_ControllerAction
{
{
     Event_ControllerButton Button;
     Event_ControllerButton Button;
     Event_ControllerState State;
     Event_ControllerState State;
} Event_ControllerAction;
} Event_ControllerAction;
#endif // GAMECONTROLLER_H
#endif // GAMECONTROLLER_H


The idea is to have two different kinds of enums, which get paired together into a structure that represents the event. On one hand of the structure, we’ll have the Event_ControllerButton enum. It has fields for every digital button, Up,down,left,right, ABCXYZ and Start.
The idea is to have two different kinds of enums, which get paired together into a structure that represents the event. On one hand of the structure, we’ll have the Event_ControllerButton enum. It has fields for every digital button, Up,down,left,right, ABCXYZ and Start.
On the other hand of the event structure, we have the Event_ControllerState enum. This describes how the button behaved during the event: Just pressed down, being held down, or was just released.
On the other hand of the event structure, we have the Event_ControllerState enum. This describes how the button behaved during the event: Just pressed down, being held down, or was just released.
With this ControllerAction event defined, we can start using it. Add our header to MapleEvents.h:
With this ControllerAction event defined, we can start using it. Add our header to MapleEvents.h:
#include "EventEnums.h"
#include "EventEnums.h"


We need a place to hold our Events as we throw them. Our GameState can hold our events so they can be passed around various parts of our program. In our GameState.h class, add the following header:
We need a place to hold our Events as we throw them. Our GameState can hold our events so they can be passed around various parts of our program. In our GameState.h class, add the following header:
#include <vector>
#include <vector>
#include "EventEnums.h"
#include "EventEnums.h"


vectors are dynamic arrays in C++. We can create a vector container for our Event_ControllerAction in our GameState, which will let us store events as we throw them. The vector array will grow and shrink as we add or delete Events from it. Add this to the public section of our GameState class:
vectors are dynamic arrays in C++. We can create a vector container for our Event_ControllerAction in our GameState, which will let us store events as we throw them. The vector array will grow and shrink as we add or delete Events from it. Add this to the public section of our GameState class:
Line 152: Line 159:


Now, let’s code the function analyze our controller state and throw events. We’ll create a helper function called QueueEvent to accomplish this:
Now, let’s code the function analyze our controller state and throw events. We’ll create a helper function called QueueEvent to accomplish this:
void MapleEvents::QueueEvent(GameState* GState, uint32_t CONT_IN, uint32_t CurrButtons, uint32_t PrevButtons, Event_ControllerButton Button)
void MapleEvents::QueueEvent(GameState* GState, uint32_t CONT_IN, uint32_t CurrButtons, uint32_t PrevButtons, Event_ControllerButton Button)
{
{
}
}


This function takes a GameState pointer, so we can access and read and write to the ControllerEvents vector. It takes a uint32_t mask called CONT_IN. We can compare this mask to our buttons state to check individual bits. KOS provides macros for the correct bitfield locations for all the dreamcast controller buttons. This function also takes the current button state in CurrButtons, and the previous button state in PrevButtons. Finally, we set a Event_ControllerButton variable, this is what we will set in our Event structure if we send an event.
This function takes a GameState pointer, so we can access and read and write to the ControllerEvents vector. It takes a uint32_t mask called CONT_IN. We can compare this mask to our buttons state to check individual bits. KOS provides macros for the correct bitfield locations for all the dreamcast controller buttons. This function also takes the current button state in CurrButtons, and the previous button state in PrevButtons. Finally, we set a Event_ControllerButton variable, this is what we will set in our Event structure if we send an event.
Inside our function, we begin by creating a temporary Event_ControllerAction called T:
Inside our function, we begin by creating a temporary Event_ControllerAction called T:
Event_ControllerAction T;
Event_ControllerAction T;
     T.State = None;
     T.State = None;


We will use T as a blank Event to configure before adding it to our event vector. Next, we’ll check the status of the Current button. The button being checked is determined by CONT_IN. The state in T begins as “None,” which we use as a flag to see if no event related to the CONT_IN button needs to be thrown. Let’s first begin by assuming the button is pressed down:
We will use T as a blank Event to configure before adding it to our event vector. Next, we’ll check the status of the Current button. The button being checked is determined by CONT_IN. The state in T begins as “None,” which we use as a flag to see if no event related to the CONT_IN button needs to be thrown. Let’s first begin by assuming the button is pressed down:
/* The current button is down */
/* The current button is down */
     if((CurrButtons & CONT_IN))
     if((CurrButtons & CONT_IN))
     {
     {
Line 183: Line 190:


That covers both events that can happen when the button is currently being pressed, but an event can arise if the button is not being pressed. This would happen if the button had been pressed down the frame before. If this discrepancy occurs, then the button has just been released. We can continue with our If statement by appending else to account for the button not being pressed:
That covers both events that can happen when the button is currently being pressed, but an event can arise if the button is not being pressed. This would happen if the button had been pressed down the frame before. If this discrepancy occurs, then the button has just been released. We can continue with our If statement by appending else to account for the button not being pressed:
else if(!(CurrButtons & CONT_IN) && (PrevButtons & CONT_IN))
else if(!(CurrButtons & CONT_IN) && (PrevButtons & CONT_IN))
     {
     {
         T.Button = Button;
         T.Button = Button;
Line 196: Line 203:
This comes at the end of our function. It first checks if our T.State is anything other than “None.” It will be something else if we threw an event. If so, add it to our ControllerEvents vector in our GameState. Push_Back() is a function that adds an element to the vector array, at the tail end.
This comes at the end of our function. It first checks if our T.State is anything other than “None.” It will be something else if we threw an event. If so, add it to our ControllerEvents vector in our GameState. Push_Back() is a function that adds an element to the vector array, at the tail end.
With our QueueEvent helper function defined, we can start using it in our PollEvent function to check for individual button presses. Add the following to our function:
With our QueueEvent helper function defined, we can start using it in our PollEvent function to check for individual button presses. Add the following to our function:
void MapleEvents::PollEvent(GameState* GState)
void MapleEvents::PollEvent(GameState* GState)
{
{
     MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
     MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
     uint32_t PrevButtons = GState->PrevControllerState.buttons;
     uint32_t PrevButtons = GState->PrevControllerState.buttons;
Line 214: Line 221:
     GState->PrevControllerState.buttons = CurrButtons;
     GState->PrevControllerState.buttons = CurrButtons;
     MAPLE_FOREACH_END()
     MAPLE_FOREACH_END()
}
}


We use QueueEvent to check for every button on our Controller. It throws the appropriate events if so. If we run this PollEvent function once per frame, it means all the subsequent functions in the frame can read controller events in our GameState. When some other part of our program uses an Event, it deletes it from the Queue. We call this “consuming” the event. Various parts of our program can have MapleEventss which go through the ControllerEvent vector and check if they have matching mappings for various controller events. By consuming the events, it means the topmost running Program functions take the controller input first. This style of event handler lets events naturally flow to parts of the program which can consumer them as needed. At the very end of our frame, we need to make sure to clear the ControllerEvent vector just in case no part of our program consumed the event.
We use QueueEvent to check for every button on our Controller. It throws the appropriate events if so. If we run this PollEvent function once per frame, it means all the subsequent functions in the frame can read controller events in our GameState. When some other part of our program uses an Event, it deletes it from the Queue. We call this “consuming” the event. Various parts of our program can have MapleEventss which go through the ControllerEvent vector and check if they have matching mappings for various controller events. By consuming the events, it means the topmost running Program functions take the controller input first. This style of event handler lets events naturally flow to parts of the program which can consumer them as needed. At the very end of our frame, we need to make sure to clear the ControllerEvent vector just in case no part of our program consumed the event.


Let’s demonstrate this by adding an MapleEvents to Main.cpp. Create the following function:
Let’s demonstrate this by adding an MapleEvents to Main.cpp. Create the following function:
/* This is the function which handles input */
/* This is the function which handles input */
void HandleControllerEvents(GameState* GState)
void HandleControllerEvents(GameState* GState)
{   
{   
static uint8_t Erased = 0;
static uint8_t Erased = 0;
     for( std::vector<Event_ControllerAction>::iterator Event_It = ControllerEvents.begin();  
     for( std::vector<Event_ControllerAction>::iterator Event_It = ControllerEvents.begin();  
Line 236: Line 243:
        }     
        }     
     } /* End ControllerEvents Iterator */
     } /* End ControllerEvents Iterator */
}
}


This function takes a GameState object via pointer. We create an iterator of type vector<Event_ControllerAction> called Event_It. This sounds scary and complex, but it’s actually very simple. The type inside the <> of the Vector in the declaration tells the Iterator how to set itself up. This allows the iterator to correctly navigate the vector array. We can use this iterator like an index into the vector which moves along a path. Our Iterator begins at the beginning of the vector, then moves to the back of the vector.  
This function takes a GameState object via pointer. We create an iterator of type vector<Event_ControllerAction> called Event_It. This sounds scary and complex, but it’s actually very simple. The type inside the <> of the Vector in the declaration tells the Iterator how to set itself up. This allows the iterator to correctly navigate the vector array. We can use this iterator like an index into the vector which moves along a path. Our Iterator begins at the beginning of the vector, then moves to the back of the vector.  
Line 243: Line 250:


Now, in between the start of this iterator loop, and the iterator deletion, we can add the checks for button events. Lets make it so when the start button is released, our program quits. We can do that like so:
Now, in between the start of this iterator loop, and the iterator deletion, we can add the checks for button events. Lets make it so when the start button is released, our program quits. We can do that like so:
Event_ControllerButton ButtonInput = It->Button;
Event_ControllerButton ButtonInput = It->Button;
         Event_ControllerState StateInput = It->State;
         Event_ControllerState StateInput = It->State;
         switch(ButtonInput)
         switch(ButtonInput)
Line 268: Line 275:
Main in Main.cpp should now look like this:
Main in Main.cpp should now look like this:


int main(int argc, char **argv) {
int main(int argc, char **argv) {
     /* init systems  */
     /* init systems  */
     gdb_init();
     gdb_init();
Line 282: Line 289:
     }
     }
     return 0;
     return 0;
}
}


We have a function in our gamestate to let it handle the input events that are thrown by Maple when it calls PollControllers. We will call that function HandleEvents(). Let’s fill that out:
We have a function in our gamestate to let it handle the input events that are thrown by Maple when it calls PollControllers. We will call that function HandleEvents(). Let’s fill that out:
/* We are going to be possibly changing the vector as we iterate through it.
/* We are going to be possibly changing the vector as we iterate through it.
     * This means we will call the vector.erase() function, which automatically
     * This means we will call the vector.erase() function, which automatically
     * returns the address of the next element in the vector, so our iterator
     * returns the address of the next element in the vector, so our iterator
Line 296: Line 303:
uint32_t Erased = 0;  /* flag we use to track if we erased an element or not */
uint32_t Erased = 0;  /* flag we use to track if we erased an element or not */
       
       
/* Erasing an element advances the iterator
/* Erasing an element advances the iterator
so if we didn't erase, advance it manually */
so if we didn't erase, advance it manually */
         if(!Erased)
         if(!Erased)
Line 310: Line 317:


Next, inside our for-loop, we can create a switch statement to examine the Button input of our Event. This will let us isolate each case by button, like so:
Next, inside our for-loop, we can create a switch statement to examine the Button input of our Event. This will let us isolate each case by button, like so:
switch(Event_It->Button)
switch(Event_It->Button)
{
{
       case Start:
       case Start:
       {
       {
//Logic for Start Button in here.
//Logic for Start Button in here.
} beak;
} beak;
}
}
This first case is for how we should handle events from the start button. Inside this event case, we can create another switch statement for the StateInput from our event. The StateInput tells us what action the Button Event is throwing. We can do so like this:
This first case is for how we should handle events from the start button. Inside this event case, we can create another switch statement for the StateInput from our event. The StateInput tells us what action the Button Event is throwing. We can do so like this:
switch(Event_It->State)
switch(Event_It->State)
{
{
     case Released:
     case Released:
     {
     {
         //Released Code
         //Released Code
     } break;
     } break;
}
}


When the Start button is Released, the following action is performed. In this case, we are setting Done to 1 (true). This will cause our Loop to end in the main() function, which will progress the program to the end, where it will quit. This maps Start to ending the program. Once we perform an action, it is customary for our Event handler to consume the event from the queue. We do that by deleting the iterator. Luckily, C++ provides a way to do this. Container types that impliment iterator, like vector does, have a function that can delete an iterator instance. It returns the address of the next element, which we can use to advance our iterator, so it isn’t lost when our object is deleted. We can do that like so:
When the Start button is Released, the following action is performed. In this case, we are setting Done to 1 (true). This will cause our Loop to end in the main() function, which will progress the program to the end, where it will quit. This maps Start to ending the program. Once we perform an action, it is customary for our Event handler to consume the event from the queue. We do that by deleting the iterator. Luckily, C++ provides a way to do this. Container types that impliment iterator, like vector does, have a function that can delete an iterator instance. It returns the address of the next element, which we can use to advance our iterator, so it isn’t lost when our object is deleted. We can do that like so:
case Released:
case Released:
{
{
     Done = 1;    /* Set program to end */
     Done = 1;    /* Set program to end */
     Erased = 1;  /* Set flag so we don't advance iterator twice */
     Erased = 1;  /* Set flag so we don't advance iterator twice */
     Event_It = ControllerEvents.erase(Event_It);  /* delete element  
     Event_It = ControllerEvents.erase(Event_It);  /* delete element  
and advance iterator once */
and advance iterator once */
}
}


If we have no other controller functions we want to map, we can ignore the rest. However, it’s proper form in a switch statement to cover all possible states. We can fill out the remaining states in our switch functions gracefully. In our switch(Event_It→State) condition:
If we have no other controller functions we want to map, we can ignore the rest. However, it’s proper form in a switch statement to cover all possible states. We can fill out the remaining states in our switch functions gracefully. In our switch(Event_It→State) condition:
Line 339: Line 346:
   case Hold:
   case Hold:
   case None:
   case None:
break;
break;


and in our switch(Event_It->Button) condition:
and in our switch(Event_It->Button) condition:
Line 352: Line 359:
   case Y:
   case Y:
   case Z:
   case Z:
break;
break;


The purpose of this system isn’t obvious yet because our program is a monolith, but when we have multiple components that have their own event handlers, it’ll be useful. It lets events trickle down, so you have have context-sensitive inputs. We’ll show how this works later on. For now, at the very end of our loop, we need to empty our ControllerEvents vector, so the new frame begins fresh. We do that with our ClearEvents() function:
The purpose of this system isn’t obvious yet because our program is a monolith, but when we have multiple components that have their own event handlers, it’ll be useful. It lets events trickle down, so you have have context-sensitive inputs. We’ll show how this works later on. For now, at the very end of our loop, we need to empty our ControllerEvents vector, so the new frame begins fresh. We do that with our ClearEvents() function:
void GameState::ClearEvents()
void GameState::ClearEvents()
{
{
     ControllerEvents.erase(ControllerEvents.begin(), ControllerEvents.end());
     ControllerEvents.erase(ControllerEvents.begin(), ControllerEvents.end());
}
}


We call this function at the end of our loop in Main.cpp:
We call this function at the end of our loop in Main.cpp:


/* Game Loop */
/* Game Loop */
     while(!Game.Done)
     while(!Game.Done)
     {
     {
Line 373: Line 380:
         Game.ClearEvents();
         Game.ClearEvents();
     }
     }
With our MapleEvents written, we can now start manipulating the program to show how various parts of the PVR work. Let’s begin by exploring how translucent textures work.

Revision as of 14:29, 5 April 2025

Events allow us to send automated messages from one part of our program to other parts. With a simple program like we have now, that’s not so important, but in more complex programs this becomes very important. We’ll create an event handler for Gamepad Input in this section, which will let us start writing code to control our program while it’s running.

We need a central object for parts of our program to talk with. Using global variables is a bad idea on the Sega Dreamcast, the elf executable format has a section for global variables which makes accessing and reading them slow. Instead, we’ll create one object in our Main() function, then pass it around. This object will contain all the info we need to know about the game, we’ll call it a Game State. Let’s create our Game State class. Begin by making a new blank text file named gamestate.cpp. Be sure to add gamestate.o to $(OBJS) in our Makefile.

Create a second blank text file and name it gamestate.h. This is our Header file for the gamestate, we can include it in other files which will make those files able to use it’s functions and interact with the object its class defines. Let’s create our class definition right now in gamestate.h:

#ifndef GAMESTATE_H
#define GAMESTATE_H

//Game State Class stuff

#endif // GAMESTATE_H

Begin with this ifdef. This is a header guard, it will ensure that we don’t redefine our class definition multiple times. When we include our header in other source files, the #include directive essentially copies and pastes the file into our source. Without these guards, our class definition would be copied multiple times. These guards look for a lock symbol, called GAMESTATE_H. If it doesn’t exist, as it would when this code is encountered for the first time, it’ll run the rest of the header. It begins by immediately defining GAMESTATE_H. This way, the next time this code is run, the preprocessor will skip copying it, preventing our redefinition error. You can do the same thing with #pragma once in c++ files, but this is the old school way that also works in C. We need some header files in our gamestate.h so we can use certain calls in our class:

#include "stdint.h"
#include <kos.h>                /* KalistiOS Dreamcast SDK */

We need to access KOS, along with standard integer types. Now we can begin to define our class:

/* This structure holds all the data about our global game state
* we create this object once, and other objects check and interact
* with it. This lets them communicate with each other. */
class GameState
{
public:
   GameState();
uint32_t Done;          /* Is the game finished running? */
};

Basic stuff, we have a private variable called Done, which represents the state of our program running. It is proper form to make the variable private, and use functions to access it, but for something this simple, it’s just as valid to keep the variable public.

We need to define our constructor in gamestate.cpp:

GameState::GameState()
{
   Done = 0;
}

This function is run the first time our object is created. It will automatically set Done to 0, so our program begins by running. We will create a GameState Object in main and begin using it to control our program loop. In Main.cpp, add our gamestate.h to our includes:

/* Classes */
#include "gamestate.h"

Now, in our Main function, create the GameState Object and make Done the condition in our While...Loop.

GameState Game;

   /* keep drawing frames until start is pressed */
   while(Game.Done == 0)
   {
       DrawFrame();
    }

If we want to end our program, all we have to do is change Game.Done to 1 and our loop will stop. We can pass our GameState object by reference using pointers to other classes to keep up with this “global” variable, without the penalty of defining it in global space!

Now that we have a GameState to keep track of information, let’s build an Event Handler Class. Make a new text file called MapleEvents.cpp, and MapleEvents.h. Be sure to add MapleEvents.o to $(OBJS), and also add MapleEvents.h to our header section of Main.cpp. Begin in MapleEvents.h:

#ifndef MapleEvents_H
#define MapleEvents_H
#include <arch/gdb.h>           /* gdb debugger */
#include <kos.h>                /* KalistiOS Dreamcast SDK */
#include <kos/dbglog.h>         /* debug log */
#include <arch/timer.h>         /* used for timing functions related to framerate */
#include <arch/irq.h>           /* Interrupt request functions */
#include <png/png.h>            /* Library to load png files */
#include <stdio.h>              /* Standard Input-Output */
#include <stdint.h>             /* Standard Integer types (uint_t, etc) */
/* Classes */
#include "gamestate.h"
#endif // MapleEvents_H

Here we use Include Guards, and include the normal variety of KOS and standard headers. We also include our GameState class, so we can use and manipulate it if we want. Let’s create our MapleEvents class:

class MapleEvents
{
public:
   MapleEvents();  
   void PollEvent(GameState* GState);
};

Aside from our constructor, we also have a function called PollEvent, and it takes a pointer of GameState type. This means we can pass our GameState to it, and it can manipulate it. Define the functions in MapleEvents.cpp:

#include "MapleEvents.h"
MapleEvents::MapleEvents()
{
}

Create a dummy constructor. Now let’s start building our PollEvent function, this is the function which will talk to MAPLE on the dreamcast and get a controller state back:

void MapleEvents::PollEvent(GameState* GState)
{
   MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
   
   MAPLE_FOREACH_END()
}

Our function includes a pair of preprocessor macros that KOS provides, called MAPLE_FOREACH_BEGIN and MAPLE_FOREACH_END. These iterate through all the controllers in MAPLE and returns their state in an object. This object is of cont_state_t type. The MAPLE_FOREACH_BEGIN macro takes paramters. The first is the Function code, which tells MAPLE what to operate on. MAPLE_FUNC_CONTROLLER has us working on Dreamcast controllers. Other options available are:

MAPLE_FUNC_KEYBOARD: Poll the Dreamcast Keyboard
MAPLE_FUNC_LIGHTGUN: Poll a lightgun controllers
MAPLE_FUNC_MOUSE: Poll a Dreamcast Mouse
MAPLE_FUNC_MEMCARD: Interact with the Dreamcast VMU
MAPLE_FUNC_PURUPURU: Interact with the Dreamcast Jump Pack
MAPLE_FUNC_LCD: Interact with the VMU screen on the Dreamcast Controller

Our MAPLE_FOREACH_BEGIN macro creates a variable cont_state_t st which holds the polled controller info. This info comes in the form of a uint32_t number, which is interpreted as a bitfield. A 0 indicates a button is not being pressed, and a 1 indicates a button is currently being pressed. These are raw states, in that moment. This information isn’t too useful. If we see a 1 for a button, does that mean the button was just pressed? Is it being held down? If we see a 0, does that mean it was just released? We need more information, which means we need the last state to compare to. Our GameState object is perfect for holding this information in. In our GameState class, add a

variable to hold our Previous Controller state:
   cont_state_t PrevControllerState;

Now, when we poll our controller, we can compare the results to the last frame. This means every loop, we need to update the PrevControllerState with that frame’s information. Let’s do this in our PollEvent function. For readability reasons, let’s cache our Buttons into variables:

void MapleEvents::PollEvent(GameState* GState)
{
   MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
   uint32_t PrevButtons = GState->PrevControllerState.buttons;
   uint32_t CurrButtons = st->buttons;
   
   GState->PrevControllerState.buttons = CurrButtons;
   MAPLE_FOREACH_END()
}

We get our PrevControllerState buttons into PrevButtons, and we get st->buttons (the current polled controller state) into CurrButtons. At the end of the function, just before MAPLE_FOREACH_END(), we set our PrevControllerState.buttons variable in our GameState, which is located by the GState pointer.

We can now compare button states with two samples. This lets us tell if we have just pressed, just released, or are holding down a button. All of these actions are Events. We need to create an enum type that can keep track of these actions. When we want to raise an event, we will push these enum actions onto a vector to store them. Let’s begin by defining our action events. We will do so in a new text file called EventEnums.h. This isn’t a cpp file, so it doesn’t become compiled into a translation unit object, so no EventEnums.o in our $(OBJS). In EventEnums.h, we can start building our event enum:

#ifndef GAMECONTROLLER_H
#define GAMECONTROLLER_H
enum Event_ControllerButton {
   Up = 0,
   Down = 1,
   Left = 2,
   Right = 3,
   A = 4,
   B = 5,
   C = 6,
   X = 7,
   Y = 8,
   Z = 9,
   Start = 10
};
enum Event_ControllerState {
   None = 0,
   Pressed = 1,
   Hold = 2,
   Released = 3
};
typedef struct _Event_ControllerAction
{
   Event_ControllerButton Button;
   Event_ControllerState State;
} Event_ControllerAction;
#endif // GAMECONTROLLER_H

The idea is to have two different kinds of enums, which get paired together into a structure that represents the event. On one hand of the structure, we’ll have the Event_ControllerButton enum. It has fields for every digital button, Up,down,left,right, ABCXYZ and Start. On the other hand of the event structure, we have the Event_ControllerState enum. This describes how the button behaved during the event: Just pressed down, being held down, or was just released. With this ControllerAction event defined, we can start using it. Add our header to MapleEvents.h:

#include "EventEnums.h"

We need a place to hold our Events as we throw them. Our GameState can hold our events so they can be passed around various parts of our program. In our GameState.h class, add the following header:

#include <vector>
#include "EventEnums.h"

vectors are dynamic arrays in C++. We can create a vector container for our Event_ControllerAction in our GameState, which will let us store events as we throw them. The vector array will grow and shrink as we add or delete Events from it. Add this to the public section of our GameState class:

   std::vector<Event_ControllerAction> ControllerEvents;

Now, let’s code the function analyze our controller state and throw events. We’ll create a helper function called QueueEvent to accomplish this:

void MapleEvents::QueueEvent(GameState* GState, uint32_t CONT_IN, uint32_t CurrButtons, uint32_t PrevButtons, Event_ControllerButton Button)
{
}

This function takes a GameState pointer, so we can access and read and write to the ControllerEvents vector. It takes a uint32_t mask called CONT_IN. We can compare this mask to our buttons state to check individual bits. KOS provides macros for the correct bitfield locations for all the dreamcast controller buttons. This function also takes the current button state in CurrButtons, and the previous button state in PrevButtons. Finally, we set a Event_ControllerButton variable, this is what we will set in our Event structure if we send an event. Inside our function, we begin by creating a temporary Event_ControllerAction called T:

Event_ControllerAction T;
   T.State = None;

We will use T as a blank Event to configure before adding it to our event vector. Next, we’ll check the status of the Current button. The button being checked is determined by CONT_IN. The state in T begins as “None,” which we use as a flag to see if no event related to the CONT_IN button needs to be thrown. Let’s first begin by assuming the button is pressed down:

/* The current button is down */
   if((CurrButtons & CONT_IN))
   {
       /* Was it down a frame before? */
       if(PrevButtons & CONT_IN)
       {
           /* It was down last frame, so it's being held */
           T.Button = Button;
           T.State = Hold;
       } else
       {
           /* It wasn't down a frame before, so it's just been pressed */
           T.Button = Button;
           T.State = Pressed;
       }
   }

The logic is that if the button is pressed down, we will check what it was doing right before. If the button was being pressed down the frame before, then the button is currently being held. We set the State in T to “hold” and record Button we sent in the parameters in T.Button. Otherwise, the button was not being pressed down the frame prior, which means this button has just been pressed. We set out state in T to “Pressed” and record the Button in T as well.

That covers both events that can happen when the button is currently being pressed, but an event can arise if the button is not being pressed. This would happen if the button had been pressed down the frame before. If this discrepancy occurs, then the button has just been released. We can continue with our If statement by appending else to account for the button not being pressed:

else if(!(CurrButtons & CONT_IN) && (PrevButtons & CONT_IN))
   {
       T.Button = Button;
       T.State = Released;
   }

In this case, we set the state in T to “released” and record the button. Now that we have found an event to throw, we can add it from our vector:

   if(T.State != None)
       GState->ControllerEvents.push_back(T);

}

This comes at the end of our function. It first checks if our T.State is anything other than “None.” It will be something else if we threw an event. If so, add it to our ControllerEvents vector in our GameState. Push_Back() is a function that adds an element to the vector array, at the tail end. With our QueueEvent helper function defined, we can start using it in our PollEvent function to check for individual button presses. Add the following to our function:

void MapleEvents::PollEvent(GameState* GState)
{
   MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
   uint32_t PrevButtons = GState->PrevControllerState.buttons;
   uint32_t CurrButtons = st->buttons;
   QueueEvent(GState, CONT_START, CurrButtons, PrevButtons, Start);
   QueueEvent(GState, CONT_A, CurrButtons, PrevButtons, A);
   QueueEvent(GState, CONT_B, CurrButtons, PrevButtons, B);
   QueueEvent(GState, CONT_C, CurrButtons, PrevButtons, C);
   QueueEvent(GState, CONT_X, CurrButtons, PrevButtons, X);
   QueueEvent(GState, CONT_Y, CurrButtons, PrevButtons, Y);
   QueueEvent(GState, CONT_Z, CurrButtons, PrevButtons, Z);
   QueueEvent(GState, CONT_DPAD_UP, CurrButtons, PrevButtons, Up);
   QueueEvent(GState, CONT_DPAD_DOWN, CurrButtons, PrevButtons, Up);
   QueueEvent(GState, CONT_DPAD_LEFT, CurrButtons, PrevButtons, Up);
   QueueEvent(GState, CONT_DPAD_RIGHT, CurrButtons, PrevButtons, Up);
   GState->PrevControllerState.buttons = CurrButtons;
   MAPLE_FOREACH_END()
}

We use QueueEvent to check for every button on our Controller. It throws the appropriate events if so. If we run this PollEvent function once per frame, it means all the subsequent functions in the frame can read controller events in our GameState. When some other part of our program uses an Event, it deletes it from the Queue. We call this “consuming” the event. Various parts of our program can have MapleEventss which go through the ControllerEvent vector and check if they have matching mappings for various controller events. By consuming the events, it means the topmost running Program functions take the controller input first. This style of event handler lets events naturally flow to parts of the program which can consumer them as needed. At the very end of our frame, we need to make sure to clear the ControllerEvent vector just in case no part of our program consumed the event.

Let’s demonstrate this by adding an MapleEvents to Main.cpp. Create the following function:

/* This is the function which handles input */
void HandleControllerEvents(GameState* GState)
{   

static uint8_t Erased = 0;

   	for( std::vector<Event_ControllerAction>::iterator Event_It = ControllerEvents.begin(); 

Event_It != ControllerEvents.end(); /* not advancing iterator in for-loop */)

   	{

uint32_t Erased = 0; /* flag we use to track if we erased an element or not */

/*Erasing an element advances the iterator so if we didn't erase, advance it manually */

       	if(!Erased)

{

            		++Event_It;

}

    	} /* End ControllerEvents Iterator */
}

This function takes a GameState object via pointer. We create an iterator of type vector<Event_ControllerAction> called Event_It. This sounds scary and complex, but it’s actually very simple. The type inside the <> of the Vector in the declaration tells the Iterator how to set itself up. This allows the iterator to correctly navigate the vector array. We can use this iterator like an index into the vector which moves along a path. Our Iterator begins at the beginning of the vector, then moves to the back of the vector.

Inside this loop as the iterator moves, we use the erase() command to delete the event from the vector. We need to call erase in a way that advances the iterator correctly, or else we’ll delete the element the iterator is pointing to and it’ll get lost. We use a flag called Erased to manage this. When an element is erased, this flag is set to 1. If we get to the end of the loop and we haven’t erased an element (and thus advanced the iterator), then we’ll manually advance it. This keeps things in a way so that the next loop will point to the correct spot.

Now, in between the start of this iterator loop, and the iterator deletion, we can add the checks for button events. Lets make it so when the start button is released, our program quits. We can do that like so:

Event_ControllerButton ButtonInput = It->Button;
       Event_ControllerState StateInput = It->State;
       switch(ButtonInput)
       {
           case Start:
           {
               switch(StateInput)
               {
                   break;
                   case Released:
                   {
                       GState->Done = 1;
                   }
                   break;
               }
           }
           break;
       }; /* End Switch(Button) */

We begin by using some convenience objects for readability. ButtonInput is the Button the event is for. StateInput is the condition of the button event. We begin with a switch statement fort he ButtonInput. This lets us use cases to select by button. Inside the Case for the start button, we can use a switch statement for StateInput to check for the button condition. Under case Released: we set Gstate->Done = 1. This will cause the program to quit when this loop is finished. We need to include a break; command to get out of our switch statements.

We can continue adding cases for other buttons with their own cases for their conditions to further map events to this part of our program. By mapping Case Start: case Released, this particular MapleEvents consumes the Start button if pressed. Now when we press start while our program is running, it’ll properly quit and reboot our dreamcast to dc-load-ip.

Main in Main.cpp should now look like this:

int main(int argc, char **argv) {
   /* init systems  */
   gdb_init();
   pvr_init_defaults();
   GameState Game;
   MapleEvents Maple;
   /* Game Loop */
   while(!Game.Done)
   {    
       Maple.PollControllers(&Game);
       Game.HandleEvents();
       Render();
   }
   return 0;
}

We have a function in our gamestate to let it handle the input events that are thrown by Maple when it calls PollControllers. We will call that function HandleEvents(). Let’s fill that out:

/* We are going to be possibly changing the vector as we iterate through it.
    * This means we will call the vector.erase() function, which automatically
    * returns the address of the next element in the vector, so our iterator
    * is not lost. This creates a conflict if we advance the iterator in the
    * for-loop declaration, so to solve this, we must manually advance the iterator
    * in the loop itself, and only advance it if we haven't erased an element. */
   
   for( std::vector<Event_ControllerAction>::iterator Event_It = ControllerEvents.begin(); Event_It != ControllerEvents.end(); /* not advancing iterator in for-loop */)
   {

uint32_t Erased = 0; /* flag we use to track if we erased an element or not */

/* 	Erasing an element advances the iterator

so if we didn't erase, advance it manually */

       if(!Erased)
       {
            ++Event_It;
       }

}

We state by creating an iterator called Event_It. This will walk through our ControllerEvents vector. After creating the iterator, we create a for loop that walks through the vector. It is possible we will be changing this vector as we iterate through it, and this causes a problem. If we advance the iterator through the for-loop declaration, then when we remove an element using vector.erase(), we’ll accidentally advance the iterator twice. This is because vector.erase() automatically advances the iterator when called. What we need to do is track if we call Vector.erase() during an iteration of a loop, and if we did not, then we’ll manually advance the iterator.

To do that, we created a flag called Erased, and set it to 0 (false). At the bottom of our for-loop iteration, we set a condition: If Erased has been set to false (not 1), then advance the iterator with Event_It++. This accounts for if we remove an element while iterating.

Next, inside our for-loop, we can create a switch statement to examine the Button input of our Event. This will let us isolate each case by button, like so:

switch(Event_It->Button)
{
     case Start:
     {

//Logic for Start Button in here. } beak;

}

This first case is for how we should handle events from the start button. Inside this event case, we can create another switch statement for the StateInput from our event. The StateInput tells us what action the Button Event is throwing. We can do so like this:

switch(Event_It->State)
{
    case Released:
    {
        //Released Code
    } break;
}

When the Start button is Released, the following action is performed. In this case, we are setting Done to 1 (true). This will cause our Loop to end in the main() function, which will progress the program to the end, where it will quit. This maps Start to ending the program. Once we perform an action, it is customary for our Event handler to consume the event from the queue. We do that by deleting the iterator. Luckily, C++ provides a way to do this. Container types that impliment iterator, like vector does, have a function that can delete an iterator instance. It returns the address of the next element, which we can use to advance our iterator, so it isn’t lost when our object is deleted. We can do that like so:

case Released:
{
   Done = 1;     /* Set program to end */
   Erased = 1;   /* Set flag so we don't advance iterator twice */
   Event_It = ControllerEvents.erase(Event_It);   /* delete element 

and advance iterator once */

}

If we have no other controller functions we want to map, we can ignore the rest. However, it’s proper form in a switch statement to cover all possible states. We can fill out the remaining states in our switch functions gracefully. In our switch(Event_It→State) condition:

  case Pressed:
  case Hold:
  case None:
break;

and in our switch(Event_It->Button) condition:

  case Up:
  case Down:
  case Left:
  case Right:
  case A:
  case B:
  case C:
  case X:
  case Y:
  case Z:
break;

The purpose of this system isn’t obvious yet because our program is a monolith, but when we have multiple components that have their own event handlers, it’ll be useful. It lets events trickle down, so you have have context-sensitive inputs. We’ll show how this works later on. For now, at the very end of our loop, we need to empty our ControllerEvents vector, so the new frame begins fresh. We do that with our ClearEvents() function:

void GameState::ClearEvents()
{
   ControllerEvents.erase(ControllerEvents.begin(), ControllerEvents.end());
}

We call this function at the end of our loop in Main.cpp:

/* Game Loop */
   while(!Game.Done)
   {
       /* Input */
       Maple.PollControllers(&Game);
       Game.HandleControllerEvents();
       /* Rendering */
       Game.Render();
       /* Clean up */
       Game.ClearEvents();
   }