Day 24. Win32 Platform Layer Cleanup

Day 24. Win32 Platform Layer Cleanup
Video Length (including Q&A): 1h39

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.

Today is the cleanup day. We went in fast and furious, typing our way through the code, and we have left behind some bugs and inconsistencies. We'll dedicate the next two days revisiting and, hopefully, optimizing them out.

Day 23 Day 25

(Top)
Small Fixes
  1.1  Set Controller Type
  1.2  Set Fixed Window Output Size
  1.3  Improve build.bat
  1.4  Get rid of the Topmost Layered Window
  1.5  Get the File Edit Date Without Opening It
  1.6  Remove Stub Functions for Platform Calls
Improve Recording Code
  2.1  Remove References to MAX_PATH
  2.2  Store Executable Filename
  2.3  Build a Custom EXE Path Filename
  2.4  Reorganize win32_handmade.cpp
  2.5  Update Recording and Playback Functions
Recap
Navigation

   

Small Fixes

   

Set Controller Type

The first thing on our list is to clean up the state of the controller. Currently, the controller is set to IsAnalog only if it received input from D-Pad or the stick:

if ((NewController->StickAverageX != 0.0f) ||
    (NewController->StickAverageY != 0.0f))
{
    NewController->IsAnalog = true;
}

if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_UP)
{
    NewController->StickAverageY = 1.0f;
    NewController->IsAnalog = false;
}

if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
{
    NewController->StickAverageY = -1.0f;
    NewController->IsAnalog = false;
}

if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
{
    NewController->StickAverageX = -1.0f;
    NewController->IsAnalog = false;
}

if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
{
    NewController->StickAverageX = 1.0f;
    NewController->IsAnalog = false;
}

At least, that's the assumption we make. However, this assumption is incorrect. See, we swap controllers back and forth at each frame:

game_input *Temp = NewInput;
NewInput = OldInput;
OldInput = Temp;

So if we set IsAnalog on the previous frame and then don't get any input on the next one, this information will not be carried on.

 Figure 1: Controller Rotation.

This is not a serious issue just yet, but you never know in the future. So let's simply pass over this information from the OldController:

XINPUT_STATE ControllerState;
if (XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS)
{
    NewController->IsConnected = true;
NewController->IsAnalog = OldController->IsAnalog;
// NOTE(casey): This controller is plugged in // TODO(casey): See if ControllerState.dwPacketNumber increments too rapidly XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad; // ... }
 Listing 1: [win32_handmade.cpp > WinMain] Preserving IsAnalog state.

Compile and make sure there're no errors. You can even step through the code and verify that the IsAnalog state is preserved.

   

Set Fixed Window Output Size

The next thing we want to tackle is graphics output to the window. You might have noticed that some lines that we draw are thicker than the other:

 Figure 2: You can see from our debug lines that some are thicker.

Image stretching is a more complex topic than it might seem. Pixel blending comes into play, calculations must be done on the sub-pixel level, and so on. We aren't ready to tackle all this just yet, so let's simply make the following hack: We will render pixels exactly how they appear in our buffer, without any stretching.

In practice, this means that, instead of passing WindowWidth and WindowHeight to the StretchDIBits function, we will provide the buffer's width and height. This will result in the output not matching our window size, but that's fine for our purposes for now.

internal void
Win32DisplayBufferInWindow(...)
{
// NOTE(casey): For prototyping purposes, we're going to always blit // 1-to-1 pixels to make sure we don't introduce artifacts with // stretching while we are learning to code the renderer!
StretchDIBits(DeviceContext,
0, 0, Buffer->Width, Buffer->Height,
0, 0, Buffer->Width, Buffer->Height, Buffer->Memory, &Buffer->Info, DIB_RGB_COLORS, SRCCOPY); }
 Listing 2: [win32_handmade.cpp] Setting Fixed Output Size.

 Figure 3: If you Pause the game, you can see that all the lines are now of equal width.

   

Improve build.bat

We have one little thing to fix in build.bat. Right now, we're statically linking with the optimized version of the C Runtime Library. We're still not ready to ship our code to the final users, and we can definitely benefit from a slower, debug version of the library. This can be achieved by simply swapping the -MT flag with -MTd flag:

set compiler=%compiler%     -Oi                    &:: Use assembly intrinsics where possible
set compiler=%compiler% -MTd &:: Include CRT library in the executable (static linking of the debug version)
set compiler=%compiler% -Gm- &:: Disable minimal rebuild
 Listing 3: [build.bat] Importing debug version of the CRT library.

If you recall, static linking simply means that we copy the library inside our code (at the cost of a bigger executable). This is opposed to dynamic linking, where we try to find and link with it at runtime. Dynamic linking results in smaller executables but carries an inherent risk that the user might not have the necessary libraries installed on their machine.

If you compile and run, you'll notice that not much has changed. However, this change might be handy in the future as we dive deeper into the code.

   

Get rid of the Topmost Layered Window

Last time, we played around with the idea of having our window always on top and becoming semi-transparent when bringing it out off-focus. Unfortunately, this results in the window being continually in the way, as mouse clicks still end up inside the game.

Let's get rid of this implementation for now. We can re-enable it when we want.

HWND Window = CreateWindowEx(0, // WS_EX_TOPMOST | WS_EX_LAYERED,
WindowClass.lpszClassName, "Handmade Hero", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, Instance, 0);
 Listing 4: [win32_handmade.cpp > WinMain] Commenting out flags enabling layering.
case WM_CLOSE:
{
    GlobalRunning = false;
} break;

case WM_ACTIVATEAPP:
{
#if 0
if (WParam == TRUE) { SetLayeredWindowAttributes(Window, RGB(0, 0, 0), 255, LWA_ALPHA); } else { SetLayeredWindowAttributes(Window, RGB(0, 0, 0), 64, LWA_ALPHA); }
#endif
} break; case WM_DESTROY: { GlobalRunning = false; } break;
 Listing 5: [win32_handmade.cpp > Win32MainWindowCallback] Disabling Layering functionality.

If you compile and run now, you'll notice that the layering functionality is gone.

   

Get the File Edit Date Without Opening It

Another thing that's a concern is how we get the last write time of our handmade.dll file. This is how we implemented Win32GetLastWriteTime function:

inline FILETIME
Win32GetLastWriteTime(char *Filename)
{
    FILETIME LastWriteTime = {};
    
    WIN32_FIND_DATA FindData;
    HANDLE FindHandle = FindFirstFileA(Filename, &FindData);
    if (FindHandle != INVALID_HANDLE_VALUE)
    {
        LastWriteTime = FindData.ftLastWriteTime;
        FindClose(FindHandle);
    }
    
    return (LastWriteTime);
}

We currently have to open the file (get the FindHandle) to check its last write time. That's not the safest thing to do, as it potentially creates conflicts with whoever tries to access the file at the same time. Additionally, we must never forget to close the handle, potentially another point of failure.

The best thing to do here is to check the last write time without opening the file handle. Luckily, such a solution exists in Windows; it can be achieved by calling the GetFileAttributesEx function.

GetFileAttributesEx takes the following parameters:

In practice this results in the following rewriting of Win32GetLastWriteTime.

FILETIME LastWriteTime = {};
WIN32_FIND_DATA FindData; HANDLE FindHandle = FindFirstFileA(Filename, &FindData); if (FindHandle != INVALID_HANDLE_VALUE) { LastWriteTime = FindData.ftLastWriteTime; FindClose(FindHandle); }
WIN32_FILE_ATTRIBUTE_DATA Data; if(GetFileAttributesExA(Filename, GetFileExInfoStandard, &Data)) { LastWriteTime = Data.ftLastWriteTime; }
return (LastWriteTime);
 Listing 6: [win32_handmade.cpp > Win32GetLastWriteTime] Getting rid of unnecessary find handle.
   

Remove Stub Functions for Platform Calls

A small thing that we can also do to prevent compilation conflicts is to remove the stub functions in handmade.h. We currently have only two, so getting rid of those will be simple:

#define GAME_UPDATE_AND_RENDER(name) void name(game_memory *Memory, game_input *Input, game_offscreen_buffer* Buffer)
typedef GAME_UPDATE_AND_RENDER(game_update_and_render);
GAME_UPDATE_AND_RENDER(GameUpdateAndRenderStub) { }
// ... #define GAME_GET_SOUND_SAMPLES(name) void name(game_memory *Memory, game_sound_output_buffer *SoundBuffer) typedef GAME_GET_SOUND_SAMPLES(game_get_sound_samples);
GAME_GET_SOUND_SAMPLES(GameGetSoundSamplesStub) { }
 Listing 7: [handmade.h] Removing platform stub functions.

This, however, means that if the load file failed, we must pass 0 instead:

internal win32_game_code
Win32LoadGameCode(char *SourceDLLName, char *TempDLLName)
{
    win32_game_code Result = {};
    
    // ...       
    if (!Result.IsValid)
    {
Result.UpdateAndRender = 0; Result.GetSoundSamples = 0;
} return(Result); } internal void Win32UnloadGameCode(win32_game_code *GameCode) { if (GameCode->GameCodeDLL) { FreeLibrary(GameCode->GameCodeDLL); GameCode->GameCodeDLL = 0; } GameCode->IsValid = false;
GameCode->UpdateAndRender = 0; GameCode->GetSoundSamples = 0;
}
 Listing 8: [win32_handmade.cpp] Making sure that we don't get invalid pointers.

This, in turn, means that inside WinMain, where we call these functions, we must gate ourselves from reaching broken pointers and only call the procedures when they're not 0.

if (Game.UpdateAndRender) {
Game.UpdateAndRender(&GameMemory, NewInput, &Buffer);
}
// ...
if (Game.GetSoundSamples) {
Game.GetSoundSamples(&GameMemory, &SoundBuffer);
}
 Listing 9: [win32_handmade.cpp > WinMain] Checking if functions are valid before calling them.

Finally, we also can add a small comment inside the win32_game_code structure so that we don't forget that we need to check for this in the future.

struct win32_game_code
{
    HMODULE GameCodeDLL;
    FILETIME DLLLastWriteTime;
    
// IMPORTANT(casey): Either of the callbacks can be 0! // You must check before calling.
game_update_and_render *UpdateAndRender; game_get_sound_samples *GetSoundSamples; b32 IsValid; };
 Listing 10: [win32_handmade.h] Adding a comment for posterity.
   

Improve Recording Code

We now come to the most significant change for today. Our recording code saves its data inside the data folder, which we don't necessarily want. Ideally, we'd have the debug recordings being kept inside the build folder so that, if necessary, we can simply delete the folder and recreate it without any data loss.

Let's quickly review our code for writing dlls:

char SourceGameCodeDLLFilename[] = "handmade.dll";
char SourceGameCodeDLLFullPath[MAX_PATH];

CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, 
            sizeof(SourceGameCodeDLLFilename) - 1, SourceGameCodeDLLFilename, 
            sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath);

char TempGameCodeDLLFilename[] = "handmade_temp.dll";
char TempGameCodeDLLFullPath[MAX_PATH];

CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, 
            sizeof(TempGameCodeDLLFilename) - 1, TempGameCodeDLLFilename, 
            sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath);

We already refactored this code in the past once, by adding possibility of checking the executable path and rebuilding the dll paths accordingly.

Now, we want to pull this code out even further to have an agnostic function that we can recycle.

   

Remove References to MAX_PATH

First, we want to expand our win32_state to keep track of things like the EXE path and OnePastLastSlash pointer. This way, you don't need to pass them around alone.

struct win32_state
{
    u64 TotalSize;
    void *GameMemoryBlock;
    
    HANDLE RecordingHandle;
    int InputRecordingIndex;
    
    HANDLE PlaybackHandle;
    int InputPlayingIndex;
    
char EXEFilename[MAX_PATH]; char *OnePastLastEXEFilenameSlash;
};
 Listing 11: [win32_handmade.h] Preserving executable path.

That said, we already mentioned in the past that using MAX_PATH is a bad idea. MAX_PATH is an old convention often ignored by modern API (which allows having a much longer path). This puts us at risk where the executable would be located deep in someone's file system, disallowing us from functioning correctly.

Let's use an intermediary #define so that we can eventually revisit and replace it.

#define WIN32_STATE_FILENAME_COUNT MAX_PATH
struct win32_state { u64 TotalSize; void *GameMemoryBlock; HANDLE RecordingHandle; int InputRecordingIndex; HANDLE PlaybackHandle; int InputPlayingIndex;
char EXEFilename[WIN32_STATE_FILENAME_COUNT];
char *OnePastLastEXEFilenameSlash; };
 Listing 12: [win32_handmade.h] Abstracting out MAX_PATH.

Now we can safely remove any instance of MAX_PATH we have used in the past.

// NOTE(casey): Never use MAX_PATH in code that is user-facing, because it // can be dangerous and lead to bad results.
char EXEFilename[WIN32_STATE_FILENAME_COUNT]; DWORD SizeOfFilename = GetModuleFileNameA(0, EXEFilename, sizeof(EXEFilename)); char *OnePastLastSlash = EXEFilename; for (char *Scan = EXEFilename; *Scan; ++Scan) { if (*Scan == '\\') { OnePastLastSlash = Scan + 1; } } char SourceGameCodeDLLFilename[] = "handmade.dll";
char SourceGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT];
CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, sizeof(SourceGameCodeDLLFilename) - 1, SourceGameCodeDLLFilename, sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath); char TempGameCodeDLLFilename[] = "handmade_temp.dll";
char TempGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT];
CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, sizeof(TempGameCodeDLLFilename) - 1, TempGameCodeDLLFilename, sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath);
 Listing 13: [win32_handmade.cpp > WinMain] Removing MAX_PATH usage.
   

Store Executable Filename

Let's quickly extract the code storing the exe filename.

internal void Win32GetEXEFilename(win32_state *State) { char EXEFilename[WIN32_STATE_FILENAME_COUNT]; DWORD SizeOfFilename = GetModuleFileNameA(0, EXEFilename, sizeof(EXEFilename)); char *OnePastLastSlash = EXEFilename; for (char *Scan = EXEFilename; *Scan; ++Scan) { if (*Scan == '\\') { OnePastLastSlash = Scan + 1; } } }
int CALLBACK WinMain(...) {
Win32GetEXEFilename(Win32State);
char EXEFilename[WIN32_STATE_FILENAME_COUNT]; DWORD SizeOfFilename = GetModuleFileNameA(0, EXEFilename, sizeof(EXEFilename)); char *OnePastLastSlash = EXEFilename; for (char *Scan = EXEFilename; *Scan; ++Scan) { if (*Scan == '\\') { OnePastLastSlash = Scan + 1; } }
// ... }
 Listing 14: [win32_handmade.cpp] Extracting Code.

We'll need to access Win32State, so we'll move its initialization further up. Actually, this will be the first thing we do when we enter WinMain:

win32_state Win32State = {};
Win32GetEXEFilename(Win32State); // ... if (Window) {
win32_state Win32State = {}; Win32State.InputRecordingIndex = 0; Win32State.InputPlayingIndex = 0;
// ... }
 Listing 15: [win32_handmade.cpp > WinMain] Moving Win32State initialization further up.

Ok, back to the code that we extracted. We need to leverage the State that we're passing in to store EXEFilename and the place for others to write their filenames.

OnePastLastSlashEXEFilenamew:\handmade\build\win32_handmade.exeModulefilenamePlaceforotherstowritetheirfilenames

 Figure 4: As a reminder, this is what these values represent.

char EXEFilename[WIN32_STATE_FILENAME_COUNT];
DWORD SizeOfFilename = GetModuleFileNameA(0, State->EXEFilename, sizeof(State->EXEFilename)); State->OnePastLastEXEFilenameSlash = State->EXEFilename; for (char *Scan = State->EXEFilename;
*Scan; ++Scan) { if (*Scan == '\\') {
State->OnePastLastEXEFilenameSlash = Scan + 1;
} }
 Listing 16: [win32_handmade.cpp > Win32GetEXEFilename] Using win32_state we store the EXEFilename and OnePastLastSlash.
   

Build a Custom EXE Path Filename

Now we can extract some more code. We have the following code that can be recycled:

char SourceGameCodeDLLFilename[] = "handmade.dll";
char SourceGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT];

CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, 
            sizeof(SourceGameCodeDLLFilename) - 1, SourceGameCodeDLLFilename, 
            sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath);

char TempGameCodeDLLFilename[] = "handmade_temp.dll";
char TempGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT];

CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, 
            sizeof(TempGameCodeDLLFilename) - 1, TempGameCodeDLLFilename, 
            sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath);

In fact, if you look closely, this is the same code repeating twice! Let's extract the repeating code and replace these blocks with the calls to it:

internal void Win32BuildEXEPathFilename(win32_state *State, char *Filename, int DestCount, char *Dest) { char SourceGameCodeDLLFilename[] = "handmade.dll"; char SourceGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT]; CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, sizeof(SourceGameCodeDLLFilename) - 1, SourceGameCodeDLLFilename, sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath); }
int CALLBACK WinMain(...) { win32_state Win32State = {}; Win32GetEXEFilename(Win32State);
char SourceGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT]; Win32BuildEXEPathFilename(&Win32State, "handmade.dll", sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath); char TempGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT]; Win32BuildEXEPathFilename(&Win32State, "handmade_temp.dll", sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath);
char SourceGameCodeDLLFilename[] = "handmade.dll"; char SourceGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT]; CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, sizeof(SourceGameCodeDLLFilename) - 1, SourceGameCodeDLLFilename, sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath); char TempGameCodeDLLFilename[] = "handmade_temp.dll"; char TempGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT]; CatStrings(OnePastLastSlash - EXEFilename, EXEFilename, sizeof(TempGameCodeDLLFilename) - 1, TempGameCodeDLLFilename, sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath);
LARGE_INTEGER PerfCountFrequencyResult; // ... }
 Listing 17: [win32_handmade.cpp] Introducing Win32BuildEXEPathFilename.

Let's start cleaning up the function. First, we don't need the filename and the full path container, so we can simply get rid of these:

char SourceGameCodeDLLFilename[] = "handmade.dll"; char SourceGameCodeDLLFullPath[WIN32_STATE_FILENAME_COUNT];
 Listing 18: [win32_handmade.cpp > Win32BuildEXEPathFilename] Cleaning out things we don't need.

Our CatStrings call can cleaned up significantly:

CatStrings(State->OnePastLastEXEFilenameSlash - State->EXEFilename, State->EXEFilename, StringLength(Filename), Filename, DestCount, Dest);
 Listing 19: [win32_handmade.cpp > Win32BuildEXEPathFilename] Cleaning up CatStrings call from Win32BuildEXEPathFilename.

You'll notice that we didn't ask for the FilenameCount; however, we did pass to CatStrings a magic function that we called StringLength. As we'll see in a moment, there's nothing magic about it.

C strings are just bytes of data, with the last byte being 0. (at least for the ASCII strings, Unicode is slightly more involved, but we won't mess around with that for now). So we'll count all the bytes until we reach 0, and that will be our string length:

internal int StringLength(char *String) { int Count = 0; while (*String++) // While the character is not 0 (do not confuse with '0' the character!) { ++Count; } return (Count); }
internal void Win32BuildEXEPathFilename(...) { //... }
 Listing 20: [win32_handmade.cpp] Introducing StringLength.

Now we have a convenient function that we can use whenever we want to.

   

Reorganize win32_handmade.cpp

Before we move on, let's do some reorganization. We need to move the functions CatStrings, StringLength, Win32GetEXEFilename and Win32BuildEXEPathFilename to the top of win32_handmade.cpp, just before our utility functions:

typedef DIRECT_SOUND_CREATE(direct_sound_create);
internal void CatStrings(size_t SourceACount, char *SourceA, size_t SourceBCount, char *SourceB, size_t DestCount, char *Dest) { // + contents } internal int StringLength(char *String) { // + contents } internal void Win32GetEXEFilename(win32_state *State) { // + contents } internal void Win32BuildEXEPathFilename(win32_state *State, char *Filename, int DestCount, char *Dest) { // + contents }
DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUGPlatformFreeFileMemory) { // ... } // ...
internal void CatStrings(size_t SourceACount, char *SourceA, size_t SourceBCount, char *SourceB, size_t DestCount, char *Dest) { // ... } internal int StringLength(char *String) { // ... } internal void Win32GetEXEFilename(win32_state *State) { // ... } internal void Win32BuildEXEPathFilename(win32_state *State, char *Filename, int DestCount, char *Dest) { // ... }
int CALLBACK WinMain(...) { // ... }
 Listing 21: [win32_handmade.cpp] Remember to actually copy-paste the contents of these functions!!

This allows us to use these path-assembling functions on our Input Recording/Playback functions! We don't want them to live inside our data folder; this is the folder for good data.

   

Update Recording and Playback Functions

Finally, let's do the fix that we wanted. We want to create a new function that would provide anyone asking for a universal Input Storage name. Let's also rename the file as loop_edit.hmi instead of foo.hmi (.hmi standing for “HandMade Input”).

We'll use the new function inside both Win32BeginRecordingInput and Win32BeginInputPlayback.

Something for the future, we'll also assert that the SlotIndex is always 1.

internal void Win32GetInputFileLocation(win32_state *State, int SlotIndex, int DestCount, char *Dest) { Assert(SlotIndex == 1); Win32BuildEXEPathFilename(State, "loop_edit.hmi", DestCount, Dest); }
internal void Win32BeginRecordingInput(win32_state *State, int InputRecordingIndex) { State->InputRecordingIndex = InputRecordingIndex;
char Filename[WIN32_STATE_FILENAME_COUNT]; Win32GetInputFileLocation(State, InputRecordingIndex, WIN32_STATE_FILENAME_COUNT, Filename);
char *Filename = "foo.hmi";
State->RecordingHandle = CreateFileA(Filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); // ... } // ... internal void Win32BeginInputPlayback(win32_state *State, int InputPlayingIndex) { State->InputPlayingIndex = InputPlayingIndex;
char Filename[WIN32_STATE_FILENAME_COUNT]; Win32GetInputFileLocation(State, InputPlayingIndex, WIN32_STATE_FILENAME_COUNT, Filename);
char *Filename = "foo.hmi";
State->PlaybackHandle = CreateFileA(Filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); // ... }
 Listing 22: [win32_handmade.cpp] Storing input recording inside the build directory.
   

Recap

We've made plenty lesser edits for one day, but still not enough to call it done. Next time, we will finish our prototype platform layer for good.

   

Navigation

Previous: Day 23. Looped Live Code Editing

Up Next: Day 25. Finishing the Win32 Prototyping Layer

Back to Index

Glossary

MSDN

GetFileAttributesEx

Large-Page Support

formatted by Markdeep 1.13