Day 13. Platform-Independent User Input

Day 13. Platform-Independent User Input
Video Length (including Q&A): 1h33

Welcome to “Handmade Hero Notes”, the book where we follow the footsteps of Handmade Hero in making the complete game from scratch, with no external libraries. If you'd like to follow along, preorder the game on handmadehero.org, and you will receive access to the GitHub repository, containing complete source code (tagged day-by-day) as well as a variety of other useful resources.

We're in the middle of taking our Windows code and turning it into platform-independent code with just a little Windows layer. This will set us up to success to allow to go to lots of platforms, without having to touch the code in the main game.

Our API is shaping up nicely, even if it's quite an early stage still. We're already supplying the platform layer with the final image and sound, and all the platform should do is simply display/queue it for the reproduction.

Cross-platformAPIboundaryPlatformGraphicsGameSoundInputTime

 Figure 1: State of our program. Great things begin with the humble origin!

In this chapter, we're going to provide our game with the user's input.

Day 12 Day 14

(Top)
Housekeeping
  1.1  Clean Up the API
  1.2  Introduce win32_handmade.h
Write the Usage Code First
  2.1  Visualize Input Over Time
  2.2  Define a Way of Handling Input
Implement the Input System
  3.1  Write the API
  3.2  Fix the Compiler Errors
  3.3  Update Windows Layer
    3.3.1  Gamepad Buttons
    3.3.2  Array Count
    3.3.3  Gamepad Stick
    3.3.4  Finishing Up
Recap
Exercises
  5.1  Fixing Compiler Errors - Solution
Programming Notions
  6.1  Function Overloading
  6.2  About Premature Design
  6.3  Nameless Structs
Navigation

   

Housekeeping

First things first, let's clean up a bit. You might get a bit lost in the code by now, and it's ok. Tidying the corners will allow you to separate things a bit more neatly, allow you to conceptualize the work you've already done, and prepare for the things ahead.

   

Clean Up the API

Right now, the bridge between the game and the platform, GameUpdateAndRender, is in a bit of a mess. Alongside things like (Render)Buffer and SoundBuffer, we pass things like XOffset or ToneHz.

internal void 
GameUpdateAndRender(game_offscreen_buffer* Buffer, int XOffset, int YOffset,
                    game_sound_output_buffer *SoundBuffer, int ToneHz);
[handmade.h]

This was a forced solution for us to keep the feature parity with our initial implementation. Let's pull out these values from our API and, for the time being, store them as local_persist (static) variables. We'll initialize them to whatever value they had in win32_handmade.cpp:

internal void
GameUpdateAndRender(game_offscreen_buffer* Buffer, game_sound_output_buffer* SoundBuffer)
{
local_persist int XOffset = 0; local_persist int YOffset = 0; local_persist int ToneHz = 256;
GameOutputSound(SoundBuffer, ToneHz); RenderWeirdGradient(Buffer, XOffset, YOffset); }
 Listing 1: [handmade.cpp] Storing input-related parameters locally.

In order to prevent any unwanted function overloading (more on this in subsection 6.1), we need to remember also update declaration in handmade.h:

internal void
GameUpdateAndRender(game_offscreen_buffer* Buffer, game_sound_output_buffer* SoundBuffer);
 Listing 2: [handmade.h] Updating GameUpdateAndRender declaration.

And, of course, we also want to remove these variables from win32_handmade.cpp. We will simply remove any interaction we were having with them in our platform layer, because at the end of the day we'll handle input differently in the platform-independent layer.

While we're at it, we can also remove the sound-related things that we moved to handmade.cpp last time:

struct win32_sound_output
{
    int SamplesPerSecond;
    int BytesPerSample;
    int SecondaryBufferSize;
    u32 RunningSampleIndex;
int ToneHz; int ToneVolume; int WavePeriod; f32 tSine;
int LatencySampleCount; }; // ... // WinMain // ...
// NOTE(casey): Graphics test int XOffset = 0; int YOffset = 0; // NOTE(casey): Sound test
win32_sound_output SoundOutput = {}; SoundOutput.SamplesPerSecond = 48000; SoundOutput.BytesPerSample = sizeof(s16) * 2; SoundOutput.SecondaryBufferSize = 2 * SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample; SoundOutput.RunningSampleIndex = 0;
SoundOutput.ToneHz = 256; SoundOutput.ToneVolume = 3000; SoundOutput.WavePeriod = SoundOutput.SamplesPerSecond / SoundOutput.ToneHz;
SoundOutput.LatencySampleCount = SoundOutput.SamplesPerSecond / 15; // ... // Controller iteration loop // ... s16 StickX = Pad->sThumbLX; s16 StickY = Pad->sThumbLY;
XOffset += StickX / 4096; YOffset += StickY / 4096; SoundOutput.ToneHz = 512 + (int)(256.0f * ((f32)StickY / 30000.0f)); SoundOutput.WavePeriod = SoundOutput.SamplesPerSecond / SoundOutput.ToneHz;
// ...
GameUpdateAndRender(&Buffer, &SoundBuffer);
 Listing 3: [win32_handmade.cpp] Propagating changes to the platform layer and doing some cleanup.
   

Introduce win32_handmade.h

While we're doing the chores, let's go ahead and extract our struct definitions in a separate .h file specific to our win32_ platform. We don't technically need it, but it keeps things tidy, plus you can always quickly reference the struct definitions when you need it. We will leave our typedefs where they are, for the time being. Go ahead and create a new file called win32_handmade.h.

#include <windows.h>
#include <stdio.h>
#include <xinput.h>
#include <dsound.h>
#include "win32_handmade.h"
struct win32_offscreen_buffer { BITMAPINFO Info; void *Memory; int Width; int Height; int Pitch; }; struct win32_window_dimension { int Width; int Height; }; struct win32_sound_output { int SamplesPerSecond; int BytesPerSample; int SecondaryBufferSize; u32 RunningSampleIndex; int LatencySampleCount; };
global_variable bool GlobalRunning; global_variable win32_offscreen_buffer GlobalBackbuffer; global_variable IDirectSoundBuffer *GlobalSecondaryBuffer;
 Listing 4: [win32_handmade.cpp] Extracting the struct definitions from .cpp....
#if !defined(WIN32_HANDMADE_H) struct win32_offscreen_buffer { BITMAPINFO Info; void *Memory; int Width; int Height; int Pitch; }; struct win32_window_dimension { int Width; int Height; }; struct win32_sound_output { int SamplesPerSecond; int BytesPerSample; int SecondaryBufferSize; u32 RunningSampleIndex; int LatencySampleCount; }; #define WIN32_HANDMADE_H #endif
 Listing 5: [win32_handmade.h] .... and adding them in a freshly introduced .h. Do you remember about the include guards? (#if/#define/#endif things)

Confused? That's a lot of code we refactored right there. Try to compile, let the compiler errors guide you. At the end of the day, you should still remain with the same picture and sound as before, with the sole exception of losing interactivity. Don't worry, we'll get that back soon!

   

Write the Usage Code First

It cannot be understated what we said last time.

Always Write the Usage Code First. — Casey Muratori, about API design
Even if we follow this approach, the road ahead isn't simple: since we don't really have a game yet, we don't even necessarily know how we're going to use the user's input! If the game was done, we could identify the optimal way to handle the input with ease. This is something that we will definitely have to handle at some point, but this is not that point.

Always be aware of the circumstances!

Right now, we don't try at all to write final code. You can read more about this philosophy in subsection 6.2.

At this point we simply have to remember that this implementation of the input is but the first pass, and the API we'll write today is but an attempt to write the correct interface. So let's have some fun, and imagine what would be useful for our game in terms of the input coming from the platform layer.

if (Input.IsAnalog) { // NOTE(casey): Use analog movement tuning } else { // NOTE(casey): Use digital movement tuning }
GameOutputSound(SoundBuffer, ToneHz); RenderWeirdGradient(Buffer, XOffset, YOffset);
 Listing 6: [handmade.cpp > GameUpdateAndRender]. Writing Usage Code First.

   

Visualize Input Over Time

Let's think about the input planning. Imagine a timeline showing the time (\(t\)) going forward and frames appearing on the screen.

0123...t

 Figure 2: Frame computation over time.

It's important to remember that a frame is not visible to the user while a frame is being computed. It's only displayed once all the work related to the frame is done: sound was written, image was prepared, and “frame flip” command was given. In the meantime, the user is watching and listening the previous frame output. They also react to it by providing various inputs: Right, Up, A, what have you.

0123...At

 Figure 3: User input in a frame.

Of course, the user only has but a fraction of a second to pass all this input in, but with some serious button mashing it's still possible to pass quite a bit of information. This is becoming even more significant as our framerate drops: in a 1/30th of a second you can pass twice as much input as in 1/60th, the character would move further during this time, etc.

Thing is, if the user is watching (and reacts to) frame 0, they provide some input during frame 1... but it will only get to us at the start of frame 2. This also mean that frame 1 will not incorporate any input that they've been doing thus far. We're always one frame behind, sometimes even two if you're inefficient with the input handling! This is one of the reasons why a high framerate is good.

There's another thing that might happen. Let's say the user pushed the A button, then released it, then pushed it again, all within 1/30th of a second. This might result in something similar to this:

01...AAA

 Figure 4: It's possible that a button is pressed, released, and then pressed again within a single frame.

So, why are we looking in all this? The reason we investigate this behavior is because we're trying to decide for ourselves: what do we record? Which user input do we capture, save and preserve for processing of the world update? Specifically for gamepad, this is important because the input input we get already doesn't capture all the updates. As we said on day 6, gamepad makes polling at some regular rate, and captures the state of the gamepad at that point. We don't know (and will never know) what happens to it between those updates, we can only get a discreet approximation of it.

????

 Figure 5: Stick wiggling info we receive from XInput API. We don't know what happens between the captured snapshots. Same logic can be applied to button pressing.

In any case, the objective for us should be the following: we don't want to miss any input that affects the game in an important way. We don't want to miss a button press happened in a quick succession with the others if, for example, this button is responsible for shooting. It feels awful when the game is unresponsive to your inputs!

   

Define a Way of Handling Input

Before we settle on our own way of handling inputs, let's look at what other people might do. A common way is something along the lines of the following:

for (int EventIndex = 0; EventIndex < EventCount; ++EventIndex) { switch (Event[EventIndex].Type) { case EventAButton: { // do something } break; case LeftStick: { // do something } break; } }
[Example]

Hopefully you can see what's going on here. We iterate over a specific EventCount, pull out Events from some event array, and process them based on their type.

With this method, platform layer does the minimal amount of work to package the information. It simply captures the input and passes it over to the game, as is.

This system, however, has an inherent problem: there's a limited amount of space for the events. If the user's input surpasses the EventCount, those inputs either lost or replace some “lower priority” ones (which are, in turn, lost as well).

You could of course make this storage pretty high, but then the question arises: why would we want to bother at all, if we don't need to? Do we really care about that? And for this particular game we don't know that we do.

What we really care about is if a button 1) started the frame up or down 2) ended the frame up or down 3) how many transitions (or half-transitions, down to up or up to down) were there in between.

There's of course information lost here: we don't know for how long was the button pressed if there was more than one transition. A button might have been pressed for 1/120th of a second, and that would be different from, say, 1/240th.

//////1/120s01/60s//1/240s01/60s

[[Example]. Difference between a 1/120s and 1/240s is 4 milliseconds.]

You can argue that there's no need for distinguishing in such granularity is unnecessary simply because players themselves might not be able to intentionally distinguish a difference between such a small time frames. And, if we're talking about button hold, we probably think about something happening across many frames, like a quarter of a second, or even more.

So, to conclude, in our game we might only need two parameters per button: if a button ended down (true or false) and how many “half-transitions” (Pressed->Unpressed and viceversa) were there.

Let's say we would use such input to change YOffset:

if (Input.IsAnalog)
{
    // NOTE(casey): Use analog movement tuning
}
else
{
    // NOTE(casey): Use digital movement tuning
}
// Input.DownButtonEndedDown; // Input.DownButtonHalfTransitionCount; if(Input.DownButtonEndedDown) { YOffset += 1; }
 Listing 7: [handmade.cpp > GameUpdateAndRender]. Potential usage of the buttons input.

Now, this is about binary input, like buttons. What about analog input, like the stick motion from the controller?

Stick position can change significantly during a frame. If we were to capture only one value based on, say, average position that stick had, it could misrepresent significantly user's input. For instance, if one started the frame with the stick resting at 0, then wailed it all the way to the 1 and released before the frame end, we'd get an average value fo 0.5 which is definitely something we don't want. And user doesn't want. Nobody wants half-wailing the stick.

So, in the end, we might adopt an event system for the sticks only, maybe capturing the state of the sticks each millisecond (which would result in ~16 input events per 1/16th of a second frame). We won't do this just yet. Instead, let's settle with something similar to what we did with the digital input, and have the following values:

Catch these values for both X and Y coordinates of the stick, and we have an API. Not necessarily the API, we'll have to wait and see.

Again, if we want to imagine how exactly would this code be used, we can simply take our ToneHz and multiply it by some small amount to get a change of the tone. We can imagine that we'll get a value from -1 to 1 for each of our stick axis, so the value in Hertz go from the base of 256 to anywhere from 128 to 384.

Another use case would be taking our XOffset and move up to 4 pixels at a time up or down based on the X position of the stick at the end of the frame.

if (Input.IsAnalog)
{
// Input.StartX; // Input.MinX; // Input.MaxX; // Input.EndX; // Input.StartY; // Input.MinY; // Input.MaxY; // Input.EndY; XOffset += (int)(4.0f * Input.EndX); ToneHz = 256 + (int)(128.0f * (Input.EndY));
} else { // NOTE(casey): Use digital movement tuning } if(Input.DownButtonEndedDown) { YOffset += 1; }
 Listing 8: [handmade.cpp > GameUpdateAndRender]. Potential API of a stick input.

The last question we have remaining is: what if we want to support multiple players? We could say that:

In usage, this would look something along the lines of:

game_controller_input *Input0 = &Input.Controllers[0];
if (Input0->IsAnalog)
{ // NOTE(casey): Use analog movement tuning
XOffset += (int)(4.0f * Input0->EndX); ToneHz = 256 + (int)(128.0f * (Input0->EndY));
} else { // NOTE(casey): Use digital movement tuning }
if(Input0->DownButtonEndedDown)
{ YOffset += 1; }
 Listing 9: [handmade.cpp > GameUpdateAndRender]. Accessing input of player 1.

This looks fine so far, let's implement it!

   

Implement the Input System

As the first pass, we're happy with the potential usage we make of the user's input. Let's create the actual API and adapt the platform code to use it.

   

Write the API

Inside the handmade.h file, we can define the following structures:

struct game_sound_output_buffer
{
    int SampleCount;
    int SamplesPerSecond;
    s16* Samples;
};
struct game_button_state { }; struct game_controller_input { }; struct game_input { };
internal void GameUpdateAndRender(game_offscreen_buffer* Buffer, game_sound_output_buffer *SoundBuffer);
 Listing 10: [handmade.h]. Defining input structures.

Let's see each structure in detail:

game_button_state is pretty straightforward: as we said, it includes a HalfTransitionCount and a boolean EndedDown. Similarly, game_input simply stores 4 controllers.

struct game_button_state 
{
s32 HalfTransitionCount; b32 EndedDown;
}; struct game_controller_input { }; struct game_input {
game_controller_input Controllers[4];
};
 Listing 11: [handmade.h]. Filling out game_button_state and game_input.

As for the game_controller_input, it's slightly more complex but we can drill it down quite easily.

So something like this:

struct game_button_state 
{
    s32 HalfTransitionCount;
    b32 EndedDown;
};

struct game_controller_input
{
b32 IsAnalog; f32 StartX; f32 StartY; f32 MinX; f32 MinY; f32 MaxX; f32 MaxY; f32 EndX; f32 EndY; game_button_state Up; game_button_state Down; game_button_state Left; game_button_state Right; game_button_state LeftShoulder; game_button_state RightShoulder;
}; struct game_input { game_controller_input Controllers[4]; };
 Listing 12: [handmade.h]. Filling out game_controller_input.

Mind you that Up/Down/Left/Right buttons don't represent the directions on a D-Pad, but rather the position of the “action” buttons on a generic controller. Thus, for XBox Controller, Up button would be represented by Y, while on a PlayStation controller it would be Triangle.

That should be fine for now, but let's go one step further and also allow to access the buttons their index. To do that, we can use a nameless union (that we've seen already in day 10) followed by an array of buttons and the same buttons in a struct. This will allow us to access them in both ways:

struct game_controller_input
{
    b32 IsAnalog; 

    f32 StartX;
    f32 StartY;
    
    f32 MinX;
    f32 MinY;
    
    f32 MaxX;
    f32 MaxY;
    
    f32 EndX;
    f32 EndY;
    
union { game_button_state Buttons[6]; struct {
game_button_state Up; game_button_state Down; game_button_state Left; game_button_state Right; game_button_state LeftShoulder; game_button_state RightShoulder;
}; };
};
 Listing 13: [handmade.h]. Improving game_controller_input.

It's a small change that doesn't bear any consequence on the final result, but it's a nice quality of life improvement.

If you're unfamiliar with how nameless structs and unions work, check out subsection 6.3.

   

Fix the Compiler Errors

We are now at a decent enough point to allow compiler guide us in the next step of implementation. We leave the following as an exercise for the reader:

When you finally arrive to only having one error in win32_handmade.cpp ('GameUpdateAndRender': function does not take 2 arguments), you are ready to proceed.

If you feel stuck, check out the solution in subsection 5.1

   

Update Windows Layer

Updating the Win32 platform layer, with the possible exception of the keyboard input, should be a pretty straightforward operation as well. For starters, we can define a game_input structure at the beginning of our frame and pass it to GameUpdateAndRender, thus fixing the last compiler error we had thus far. Remember to initialize it to 0 so that you don't pass garbage my mistake!

while (GlobalRunning)
{
game_input Input = {};
// ... GameUpdateAndRender(&Input, &Buffer, &SoundBuffer);
 Listing 14: [win32_handmade.cpp > WinMain]. Defining Input inside main Win32 loop.

Let's start implementing the most intuitive thing. We can adapt it if necessary.

   

Gamepad Buttons

For the gamepad, our input system is super simple at the moment: we only poll the ControllerState once per frame. For the buttons this means that we only really need to count the HalfTransitions if there was the change from the last time we ran this loop.

Right now, we read the input of each gamepad button in the following block:

bool Up            = Pad->wButtons & XINPUT_GAMEPAD_DPAD_UP;
bool Down          = Pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN;
bool Left          = Pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT;
bool Right         = Pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT;
bool Start         = Pad->wButtons & XINPUT_GAMEPAD_START;
bool Back          = Pad->wButtons & XINPUT_GAMEPAD_BACK;
bool LeftShoulder  = Pad->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER;
bool RightShoulder = Pad->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER;
bool AButton       = Pad->wButtons & XINPUT_GAMEPAD_A;
bool BButton       = Pad->wButtons & XINPUT_GAMEPAD_B;
bool XButton       = Pad->wButtons & XINPUT_GAMEPAD_X;
bool YButton       = Pad->wButtons & XINPUT_GAMEPAD_Y;
[win32_handmade.cpp]

We can convert this readout to what we now expect the controller readout to be by defining an utility function, and passing it everything that it needs. This function would do the following:

This is how it looks in code:

internal void
Win32LoadXInput()
{
    // ...
}
internal void Win32ProcessXInputDigitalButton(DWORD XInputButtonState, game_button_state *OldState, DWORD ButtonBit, game_button_state *NewState) { NewState->EndedDown = ((XInputButtonState & ButtonBit) == ButtonBit); NewState->HalfTransitionCount = (OldState->EndedDown != NewState->EndedDown) ? 1 : 0; }
 Listing 15: [win32_handmade.cpp]. Introducing Win32ProcessXInputDigitalButton.

We can now simply come in and process all these buttons. If you remember from above, we don't have any distinction between the actual brand of controller. So for Windows, A button is Down, X is Left and so on. Our code will thus be transformed in the following manner:

bool Up            = Pad->wButtons & XINPUT_GAMEPAD_DPAD_UP;
bool Down          = Pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN;
bool Left          = Pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT;
bool Right         = Pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT;

// bool Start         = Pad->wButtons & XINPUT_GAMEPAD_START;
// bool Back          = Pad->wButtons & XINPUT_GAMEPAD_BACK;
Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Down,XINPUT_GAMEPAD_A, &NewController->Down); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Right,XINPUT_GAMEPAD_B, &NewController->Right); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Left,XINPUT_GAMEPAD_X, &NewController->Left); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Up,XINPUT_GAMEPAD_Y, &NewController->Up); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->LeftShoulder,XINPUT_GAMEPAD_LEFT_SHOULDER, &NewController->LeftShoulder); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->RightShoulder,XINPUT_GAMEPAD_RIGHT_SHOULDER, &NewController->RightShoulder);
 Listing 16: [win32_handmade.cpp > WinMain]. Storing button input.

You'll notice a couple of things. First, we aren't currently processing all the buttons. This is because, for D-Pad, we'll need to find a way to use it alongside the stick. As for the Start and Back buttons, we'll need to decide how we're going to eventually use them, and whether we're going whatsoever.

Second, we're using OldController and NewController, while above we only defined a single Input system. This is another benefit of “writing usage code first”: you find exactly what you need to do next!

Speaking of, to get data for each gamepad we're currently iterating over a Windows-defined XUSER_MAX_COUNT. We don't really want that as we want to only iterate over a specific amount of controllers we'll support.

Thus we can edit our iteration loop as below. We can also specify the Old and New Controllers right away.

int MaxControllerCount = XUSER_MAX_COUNT; if(MaxControllerCount > ArrayCount(NewInput->Controllers)) { MaxControllerCount = ArrayCount(NewInput->Controllers); }
for (DWORD ControllerIndex = 0;
ControllerIndex < MaxControllerCount;
++ControllerIndex) {
game_controller_input *OldController = &OldInput->Controllers[ControllerIndex]; game_controller_input *NewController = &NewInput->Controllers[ControllerIndex];
// ... // Read the controller input // ... }
 Listing 17: [win32_handmade.cpp > WinMain]. Making sure we only iterate over a finite controller count.
   

Array Count

Quick aside on the ArrayCount function over there. By default, C programming language doesn't provide any utility to count the members of an array, and we don't want to use any utilities in the Standard Library. So we'll make our own utility macro which does just that. Let's do it somewhere in handmade.h:

#if !defined(HANDMADE_H)
#define ArrayCount(Array) (sizeof(Array) / sizeof((Array)[0]))
 Listing 18: [handmade.h]. Defining ArrayCount macro. In macros, you never have enough brackets.

What this macro does is simply taking the size of the array and dividing it by the size of its first element. That's all what we need to get the count of how many items there are in a given array.

   

Gamepad Stick

Using a gamepad, we can have both the analog (provided by the its stick) and digital (provided by the its d-pad) stick values. We'll keep the d-pad outside the today's scope and focus entirely on the stick.

If you recall, for each axis we need a Start, Min, Max and End position, ranging from -1.0 to 1.0. However, the position is stored as a 16-bit integer, so we'll need to normalize or convert one into the other.

Now, the way normalization works is that you simply need to divide the value by its maximum. However, the range for the possible stick values is different depending on the sign: it can go from −32768 to 32767. Technically you'll need to divide by different numbers depending on the sign:

f32 X; if(Pad->sThumbLX < 0) { X = (f32)Pad->sThumbLX / 32768.0f; } else { X = (f32)Pad->sThumbLX / 32767.0f; } f32 Y; if(Pad->sThumbLY < 0) { Y = (f32)Pad->sThumbLY / 32768.0f; } else { Y = (f32)Pad->sThumbLY / 32767.0f; }
s16 StickX = Pad->sThumbLX; s16 StickY = Pad->sThumbLY;
 Listing 19: [win32_handmade.cpp > WinMain]. Normalizing stick X and Y values.

A question might arise: if we need to get a negative value, why are we dividing by (a positive) 32768? Thing is, if you divide a negative number (which we verified we have) by a negative number, the sign cancels out, and the number becomes positive.

Now, let's supply the various values to the controller. The code below should be self-explanatory:

f32 X;
if(Pad->sThumbLX < 0)
{
    X = (f32)Pad->sThumbLX / 32768.0f;
}
else
{
    X = (f32)Pad->sThumbLX / 32767.0f;
}

f32 Y;
if(Pad->sThumbLY < 0)
{
    Y = (f32)Pad->sThumbLY / 32768.0f;
}
else
{
    Y = (f32)Pad->sThumbLY / 32767.0f;
}
NewController->StartX = OldController->EndX; NewController->StartY = OldController->EndY; // TODO(casey): Min/Max macros!!! NewController->MinX = NewController->MaxX = NewController->EndX = X; NewController->MinY = NewController->MaxY = NewController->EndY = Y; NewController->IsAnalog = true;
 Listing 20: [win32_handmade.cpp > WinMain]. Setting controller stick values. We don't have a Min/Max macros just yet, and we'll think about them next time.
   

Finishing Up

At this point it's obvious that we'll need two Input structures instead of one. We'll also need to keep track of the OldInput in the next frame, so let's take out our Input from the GlobalRunning loop and split it in two. At this point we could simply take a small array of two elements and swap them around at the end of the loop. This is how it looks like:

game_input Input[2] = {}; game_input* OldInput = &Input[0]; game_input* NewInput = &Input[1];
LARGE_INTEGER LastCounter; QueryPerformanceCounter(&LastCounter); u64 LastCycleCount = __rdtsc(); GlobalRunning = true; while (GlobalRunning) {
game_input Input = {};
// ...
GameUpdateAndRender(NewInput, &Buffer, &SoundBuffer);
// ...
game_input *Temp = NewInput; NewInput = OldInput; OldInput = Temp;
}
 Listing 21: [win32_handmade.cpp > WinMain]. Keeping track of two input systems.

We need a Temp place to store the input pointer because we can't directly swap old and new inputs. We might write a Swap utility function to do that one day like we did with ArrayCount, and you might as well try it yourself! Today's not the day, though since it's not as straightforward as with ArrayCount.

   

Recap

We've done a lot today, and we still haven't quite finished yet. In the future, we'll need to handle dead zone processing, properly initialize stick and d-pad values, and of course think about the keyboard.

But this should be it for now! Next time, we'll start talking about some people's greatest fear: memory management.

   

Exercises

   

Fixing Compiler Errors - Solution

The following is the solution for the exercise in subsection 3.2.

  1. Update GameUpdateAndRender function signature:

error C2065: 'Input': undeclared identifier
[Error text]
internal void
GameUpdateAndRender(game_input *Input, game_offscreen_buffer* Buffer, game_sound_output_buffer *SoundBuffer);
[handmade.h]
internal void
GameUpdateAndRender(game_input *Input, game_offscreen_buffer* Buffer, game_sound_output_buffer *SoundBuffer)
{ // ... }
[handmade.cpp]

  1. Fix the referencing error:

error C2228: left of '.Controllers' must have class/struct/union
note: type is 'game_input *'
note: did you intend to use '->' instead?
[Error text] Fix.
game_controller_input *Input0 = &Input->Controllers[0];
if (Input0->IsAnalog) { // ... } [<file>[handmade.cpp > GameUpdateAndRender]</file> Fix.] 3. Rename the `DownButtonEndedDown`: error C2039: 'DownButtonEndedDown': is not a member of 'game_controller_input' note: see declaration of 'game_controller_input'
[Error text]
if(Input0->Down.EndedDown)
{ XOffset += 1; }
[handmade.cpp > GameUpdateAndRender] Fix.

(Continue to subsection 3.3)

   

Programming Notions

   

Function Overloading

In C++, there's a feature called “Function overloading”. It allows you to specify different functions with the same name, each with its own set of parameters.

internal void 
GameUpdateAndRender(game_offscreen_buffer* Buffer, int XOffset, int YOffset,
                    game_sound_output_buffer *SoundBuffer, int ToneHz)
{
    // do something;
}

internal void 
GameUpdateAndRender(game_offscreen_buffer* Buffer)
{
    // do something;
}

internal void 
GameUpdateAndRender(game_sound_output_buffer *SoundBuffer, int ToneHz)
{
    // do something;
}

internal void 
GameUpdateAndRender(game_offscreen_buffer* Buffer, int XOffset, int YOffset)
{
    // do something;
}
[Example] This code is totally valid and will compile just fine in C++ mode.

How does the system know which function you want to call when? The compiler will look at the parameter/s that you pass to the function!

The order, type and the amount of parameters passed matter in this case. In the example above there's only one function that takes a parameter of type game_offscreen_buffer * and two ints (in this exact sequence).

This system is not perfect. While the compiler will do its best to match the things up, sometimes confusion might happen, which will be reported as an error at compile time. Also the compiler doesn't care about the name of the parameters, so the code snippet below will fail to build. In this case, you'll need to do some other change in order to differentiate between those function.

internal void 
GameUpdateAndRender(int XOffset) // see previous definition of 'GameUpdateAndRender'
{
    // do something;
}

internal void 
GameUpdateAndRender(int ToneHz) // error C2084: function 'GameUpdateAndRender (int)' already has a body
{
    // do something;
}
[Example] This code will not compile. But don't take our word for it, try it yourself!

We won't be using function overloading a whole lot. That said, it's comfortable having access to the feature. It allows having different functions with a same name, making things a bit more convenient and easier to read.

(Back to subsection 1.1)

   

About Premature Design

Whether you're writing your platform layer, some API, or some other code, it's always a good idea to keep in mind what exactly you are trying to solve. Similar problem might result in different code depending on the circumstance. This includes things like: kind of code we're writing, kind of standards we need to adhere to on this stage, the future this code is going to have, and so on.

In this case, our circumstance is that we iterate towards an API, as opposed to writing the API. This means we can give ourselves a bit more slack and even be sloppy sometimes! Don't spend too much time pondering on the optimal solution, allow it to present itself when the time is right. Don't waste your time on the code that is going to be deleted anyway!

Doing too much analysis and work on the code too early in the process is bad. There's an old adage “Premature Optimization is the Root of All Evil”, and many take that to mean that “Optimization” is “Making code fast”. While that is definitely true, optimization here can also refer to the design of the code itself.

Premature design is one of the most destructive forces in computing today, it's the thing that costs the most time and results in most problems in shipping code. Premature optimization is awful. Right now, we're at the beginning of the design phase of this API, and we need to understand that what we're doing here are experiments. They might go well, they might go terribly, we don't know that yet, and we won't know until we've set most of the things in place and are ready to go back and tighten up the screws and define the final API.

(Back to section 2)

   

Nameless Structs

We've been working with the structs long enough for you to be familiar with them. We declare some struct foo {};, fill it out with data and then use it in a variable. It's also should be familiar to you by now that you can put structs inside the structs, like in an infinite matryoshka doll.

However, this means that you also need to put its accessor. if you put foo Foo; inside a struct, you'll need to go through Foo in order to get its members.

struct button_states
{
    game_button_state Up;
    game_button_state Down;
    game_button_state Left;
    game_button_state Right;
    game_button_state LeftShoulder;
    game_button_state RightShoulder;
};

struct controller_state
{
    button_states Buttons;
    // ... 
};

// ...

controller_state State;
State.Buttons.Up.WasDown = true; // OK
State.Left.WasDown = false; // Error
[Example]

However, if you declare that struct directly inside, you can simply access its members without typing the name.

At this point, you have the option to drop all the names in the declarations altogether, and then it'll simply be a passthrough. It wouldn't really matter usually, except in cases when we use unions.

struct controller_state
{
    struct button_states
    {
        game_button_state Up;
        game_button_state Down;
        game_button_state Left;
        game_button_state Right;
        game_button_state LeftShoulder;
        game_button_state RightShoulder;
    } Buttons;

    // ... 
};

// ...

controller_state State;
State.Buttons.Up.WasDown = true; // OK
State.Left.WasDown = false;      // Also OK!
[Example]

Unions allow to put two or more things in the same “place”. if you declare a union union { int X; float Y; };, it will only take 32 bits, and you can access X or Y depending on your need. You really need to know what you're doing here, since it's effectively the same memory. It doesn't convert a float to an integer or back, but displays the same bits as if they were that type.

In our case, if you get a union with a struct and an array at the same memory, you can access both of them in a way you prefer. Furthermore, you can use the same nameless item trick on the unions as well, thus allowing you to access the values you want directly.

(Continue to subsection 3.2)

   

Navigation

Previous: Day 12. Platform-Independent Sound Output

Up Next: Day 14. Platform-Independent Game Memory

Back to Index

Glossary

References

formatted by Markdeep 1.10