internal void Win32ProcessPendingMessages(win32_state *State, game_controller_input *KeyboardController) { MSG Message; while (PeekMessage(&Message, 0, 0, 0, PM_REMOVE)) { switch (Message.message) { case WM_QUIT: { GlobalRunning = false; } break; case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { uint32 VKCode = (uint32)Message.wParam; bool32 WasDown = ((Message.lParam & (1 << 30)) != 0); bool32 IsDown = ((Message.lParam & (1 << 31)) == 0); if (WasDown != IsDown) { if (VKCode == 'W') { Win32ProcessKeyboardMessage(&KeyboardController->MoveUp, IsDown); } else if (VKCode == 'A') { Win32ProcessKeyboardMessage(&KeyboardController->MoveLeft, IsDown); } else if (VKCode == 'S') { Win32ProcessKeyboardMessage(&KeyboardController->MoveDown, IsDown); } else if (VKCode == 'D') { Win32ProcessKeyboardMessage(&KeyboardController->MoveRight, IsDown); } else if (VKCode == 'Q') { Win32ProcessKeyboardMessage(&KeyboardController->LeftShoulder, IsDown); } else if (VKCode == 'E') { Win32ProcessKeyboardMessage(&KeyboardController->RightShoulder, IsDown); } else if (VKCode == VK_UP) { Win32ProcessKeyboardMessage(&KeyboardController->ActionUp, IsDown); } else if (VKCode == VK_DOWN) { Win32ProcessKeyboardMessage(&KeyboardController->ActionDown, IsDown); } else if (VKCode == VK_LEFT) { Win32ProcessKeyboardMessage(&KeyboardController->ActionLeft, IsDown); } else if (VKCode == VK_RIGHT) { Win32ProcessKeyboardMessage(&KeyboardController->ActionRight, IsDown); } else if (VKCode == VK_ESCAPE) { Win32ProcessKeyboardMessage(&KeyboardController->Back, IsDown); } else if (VKCode == VK_SPACE) { Win32ProcessKeyboardMessage(&KeyboardController->Start, IsDown); } #if HANDMADE_INTERNAL else if (VKCode == 'P') { if (IsDown) { GlobalPause = !GlobalPause; } } else if (VKCode == 'L') { if (IsDown) { if (State->InputPlaybackIndex == 0) { if (State->InputRecordingIndex == 0) { Win32BeginRecordingInput(State, 1); } else { Win32EndRecordingInput(State); Win32BeginInputPlayback(State, 1); } } else { Win32EndInputPlayback(State); } } } #endif if (IsDown) { bool32 AltKeyWasDown = (Message.lParam & (1 << 29)); if ((VKCode == VK_F4) && AltKeyWasDown) { GlobalRunning = false; } if ((VKCode == VK_RETURN) && AltKeyWasDown) { if (Message.hwnd) { ToggleFullscreen(Message.hwnd); } } } } } break; default: { TranslateMessage(&Message); DispatchMessageA(&Message); } break; } } }
int CALLBACK WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CommandLine, int ShowCode) { win32_state Win32State = {}; LARGE_INTEGER PerfCounterFrequencyResult; QueryPerformanceFrequency(&PerfCounterFrequencyResult); GlobalPerfCounterFrequency = PerfCounterFrequencyResult.QuadPart; Win32GetEXEFileName(&Win32State); char SourceGameCodeDLLFullPath[WIN32_STATE_FILE_NAME_COUNT]; Win32BuildEXEPathFileName(&Win32State, "handmade.dll", sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath); char TempGameCodeDLLFullPath[WIN32_STATE_FILE_NAME_COUNT]; Win32BuildEXEPathFileName(&Win32State, "handmade_temp.dll", sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath); char GameCodeLockFullPath[WIN32_STATE_FILE_NAME_COUNT]; Win32BuildEXEPathFileName(&Win32State, "lock.tmp", sizeof(GameCodeLockFullPath), GameCodeLockFullPath); // NOTE: setting windows scheduler granularity for our sleep() call UINT DesiredSchedulerMS = 1; bool32 SleepIsGranular = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR); Win32LoadXInput(); Win32ResizeDIBSection(&GlobalBackbuffer, 960, 540); #if HANDMADE_INTERNAL DEBUGGlobalShowCursor = true; #endif WNDCLASSEXA WindowClass = {}; WindowClass.style = CS_VREDRAW | CS_HREDRAW; WindowClass.cbSize = sizeof(WNDCLASSEXA); WindowClass.lpfnWndProc = Win32MainWindowCallback; WindowClass.hInstance = Instance; WindowClass.hCursor = LoadCursor(0, IDC_ARROW); // WindowClass.hIcon; WindowClass.lpszClassName = "HandmadeHeroWindowClass"; if (RegisterClassExA(&WindowClass)) { // WS_EX_TOPMOST|WS_EX_LAYERED HWND Window = CreateWindowExA(0, WindowClass.lpszClassName, "Handmade Hero", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 996, 600, 0, 0, Instance, 0); if (Window) { // TODO: reliably query this from windows int MonitorRefreshHz = 60; HDC RefreshDC = GetDC(Window); int Win32RefreshRate = GetDeviceCaps(RefreshDC, VREFRESH); ReleaseDC(Window, RefreshDC); if (Win32RefreshRate > 1) { MonitorRefreshHz = Win32RefreshRate; if (MonitorRefreshHz == 59) { MonitorRefreshHz = 60; } } real32 GameUpdateHz = MonitorRefreshHz / 2.0f; real32 TargetSecondsPerFrame = 1.0f / GameUpdateHz; // NOTE: sound test win32_sound_output SoundOutput = {}; SoundOutput.SamplesPerSecond = 48000; SoundOutput.BytesPerSample = sizeof(int16) * 2; SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample; // TODO: compute this variance and see what the lowest reasonable value is SoundOutput.SafetyBytes = (int)((real32)SoundOutput.SamplesPerSecond * (real32)SoundOutput.BytesPerSample / GameUpdateHz / 3.0f); Win32InitDSound(Window, SoundOutput.SamplesPerSecond, SoundOutput.SecondaryBufferSize); Win32ClearSoundBuffer(&SoundOutput); GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); GlobalRunning = true; // TODO: pool with bitmap virtualalloc int16 *Samples = (int16 *)VirtualAlloc(0, SoundOutput.SecondaryBufferSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); #if HANDMADE_INTERNAL LPVOID BaseAddress = (LPVOID)Terabytes(2); #else LPVOID BaseAddress = 0; #endif game_memory GameMemory = {}; GameMemory.PermanentStorageSize = Megabytes(64); GameMemory.TransientStorageSize = Gigabytes(1); GameMemory.DEBUGPlatformFreeFileMemory = DEBUGPlatformFreeFileMemory; GameMemory.DEBUGPlatformReadEntireFile = DEBUGPlatformReadEntireFile; GameMemory.DEBUGPlatformWriteEntireFile = DEBUGPlatformWriteEntireFile; // TODO: handle various memory footprints using system metrics // TODO: use MEM_LARGE_PAGES Win32State.TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize; Win32State.GameMemoryBlock = VirtualAlloc(BaseAddress, (size_t)Win32State.TotalSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); GameMemory.PermanentStorage = Win32State.GameMemoryBlock; GameMemory.TransientStorage = (uint8 *)GameMemory.PermanentStorage + GameMemory.PermanentStorageSize; for (int ReplayIndex = 1; ReplayIndex < ArrayCount(Win32State.ReplayBuffers); ++ReplayIndex) { // TODO: recording system still takes too long on record start win32_replay_buffer *ReplayBuffer = &Win32State.ReplayBuffers[ReplayIndex]; Win32GetInputFileLocation(&Win32State, false, ReplayIndex, sizeof(ReplayBuffer->FileName), ReplayBuffer->FileName); ReplayBuffer->FileHandle = CreateFileA(ReplayBuffer->FileName, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); LARGE_INTEGER MaxSize; MaxSize.QuadPart = Win32State.TotalSize; ReplayBuffer->MemoryMap = CreateFileMapping(ReplayBuffer->FileHandle, 0, PAGE_READWRITE, MaxSize.HighPart, MaxSize.LowPart, 0); ReplayBuffer->MemoryBlock = MapViewOfFile(ReplayBuffer->MemoryMap, FILE_MAP_ALL_ACCESS, 0, 0, (size_t)Win32State.TotalSize); if (!ReplayBuffer->MemoryBlock) { // TODO: diagnostic } } if (Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage) { game_input Input[2] = {}; game_input *NewInput = &Input[0]; game_input *OldInput = &Input[1]; LARGE_INTEGER LastCounter = Win32GetWallClock(); LARGE_INTEGER FlipWallClock = Win32GetWallClock(); int DebugTimeMarkerIndex = 0; win32_debug_time_marker DebugTimeMarkers[30] = { 0 }; // TODO: handle startup specially bool32 SoundIsValid = false; DWORD AudioLatencyBytes = 0; real32 AudioLatencySeconds = 0; win32_game_code Game = Win32LoadGameCode(SourceGameCodeDLLFullPath, TempGameCodeDLLFullPath, GameCodeLockFullPath); uint64 LastCycleCount = __rdtsc(); while (GlobalRunning) { NewInput->FrameTimeDelta = TargetSecondsPerFrame; FILETIME NewDLLWriteTime = Win32GetLastWriteTime(SourceGameCodeDLLFullPath); if (CompareFileTime(&NewDLLWriteTime, &Game.DLLLastWriteTime) != 0) { Win32UnloadGameCode(&Game); Game = Win32LoadGameCode(SourceGameCodeDLLFullPath, TempGameCodeDLLFullPath, GameCodeLockFullPath); } // TODO: zeroing macro // TODO: we cant zero everything as the up/down state will be wrong game_controller_input *OldKeyboardController = GetController(OldInput, 0); game_controller_input *NewKeyboardController = GetController(NewInput, 0); *NewKeyboardController = {}; NewKeyboardController->IsConnected = true; for (int ButtonIndex = 0; ButtonIndex < ArrayCount(NewKeyboardController->Buttons); ++ButtonIndex) { NewKeyboardController->Buttons[ButtonIndex].EndedDown = OldKeyboardController->Buttons[ButtonIndex].EndedDown; } Win32ProcessPendingMessages(&Win32State, NewKeyboardController); if (!GlobalPause) { POINT MousePoint; GetCursorPos(&MousePoint); ScreenToClient(Window, &MousePoint); NewInput->MouseX = MousePoint.x; NewInput->MouseY = MousePoint.y; NewInput->MouseZ = 0; // TODO: support mousewheel? Win32ProcessKeyboardMessage(&NewInput->MouseButtons[0], GetKeyState(VK_LBUTTON) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[1], GetKeyState(VK_MBUTTON) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[2], GetKeyState(VK_RBUTTON) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[3], GetKeyState(VK_XBUTTON1) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[4], GetKeyState(VK_XBUTTON2) & (1 << 15)); // TODO: should we poll this more frequently // TODO: dont poll disconnected controllers to prevent xinput frame rate impact DWORD MaxControllerCount = XUSER_MAX_COUNT; if (MaxControllerCount > (ArrayCount(NewInput->Controllers) - 1)) { MaxControllerCount = (ArrayCount(NewInput->Controllers) - 1); } for (DWORD ControllerIndex = 0; ControllerIndex < MaxControllerCount; ++ControllerIndex) { DWORD OurControllerIndex = ControllerIndex + 1; game_controller_input *OldController = GetController(OldInput, OurControllerIndex); game_controller_input *NewController = GetController(NewInput, OurControllerIndex); XINPUT_STATE ControllerState; if (XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS) { NewController->IsConnected = true; NewController->IsAnalog = OldController->IsAnalog; // TODO: See if ControllerState.dwPacketNumber increments too rapidly XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad; // TODO: currently handling a square deadzone, check if it is circular NewController->StickAverageX = Win32ProcessXInputStickValue(Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); NewController->StickAverageY = Win32ProcessXInputStickValue(Pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); 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; } real32 Threshold = 0.5f; Win32ProcessXInputDigitalButton((NewController->StickAverageX < -Threshold) ? 1 : 0, &OldController->MoveLeft, 1, &NewController->MoveLeft); Win32ProcessXInputDigitalButton((NewController->StickAverageX > Threshold) ? 1 : 0, &OldController->MoveRight, 1, &NewController->MoveRight); Win32ProcessXInputDigitalButton((NewController->StickAverageY < -Threshold) ? 1 : 0, &OldController->MoveDown, 1, &NewController->MoveDown); Win32ProcessXInputDigitalButton((NewController->StickAverageY > Threshold) ? 1 : 0, &OldController->MoveUp, 1, &NewController->MoveUp); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionDown, XINPUT_GAMEPAD_A, &NewController->ActionDown); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionRight, XINPUT_GAMEPAD_B, &NewController->ActionRight); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionLeft, XINPUT_GAMEPAD_X, &NewController->ActionLeft); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionUp, XINPUT_GAMEPAD_Y, &NewController->ActionUp); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->LeftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER, &NewController->LeftShoulder); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->RightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER, &NewController->RightShoulder); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Start, XINPUT_GAMEPAD_START, &NewController->Start); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Back, XINPUT_GAMEPAD_BACK, &NewController->Back); } else { // NOTE: the controller is not available NewController->IsConnected = false; } } thread_context Thread = {}; game_offscreen_buffer Buffer = {}; Buffer.Memory = GlobalBackbuffer.Memory; Buffer.Width = GlobalBackbuffer.Width; Buffer.Height = GlobalBackbuffer.Height; Buffer.Pitch = GlobalBackbuffer.Pitch; Buffer.BytesPerPixel = GlobalBackbuffer.BytesPerPixel; if (Win32State.InputRecordingIndex) { Win32RecordInput(&Win32State, NewInput); } if (Win32State.InputPlaybackIndex) { Win32PlaybackInput(&Win32State, NewInput); } if (Game.UpdateAndRender) { Game.UpdateAndRender(&Thread, &GameMemory, NewInput, &Buffer); } LARGE_INTEGER AudioWallClock = Win32GetWallClock(); real32 FromBeginToAudioSeconds = Win32GetSecondsElapsed(FlipWallClock, AudioWallClock); DWORD PlayCursor; DWORD WriteCursor; if (GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) { /* NOTE: Sound output explanation We define a safety value that is the number of samples we think our game update loop can vary by. When we wake up to write audio we look at the play cursor position and forecast where we think it will be on the next frame boundary. If the write cursor is before that by at least our safety value, the target fill position is the frame boundary plus one frame. This gives us perfect audio sync if the audio latency is low enough. If the write cursor is after that safety value then we assume we can't sync the audio so we write one frames worth of audio plus the safety values worth of samples. */ if (!SoundIsValid) { SoundOutput.RunningSampleIndex = WriteCursor / SoundOutput.BytesPerSample; SoundIsValid = true; } DWORD ByteToLock = (SoundOutput.RunningSampleIndex * SoundOutput.BytesPerSample) % SoundOutput.SecondaryBufferSize; DWORD ExpectedSoundBytesPerFrame = (int)((real32)(SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample) / GameUpdateHz); real32 SecondsLeftUntilFlip = TargetSecondsPerFrame - FromBeginToAudioSeconds; DWORD ExpectedBytesUntilFlip = (DWORD)((SecondsLeftUntilFlip / TargetSecondsPerFrame) * (real32)ExpectedSoundBytesPerFrame); DWORD ExpectedFrameBoundaryByte = PlayCursor + ExpectedBytesUntilFlip; DWORD SafeWriteCursor = WriteCursor; if (SafeWriteCursor < PlayCursor) { SafeWriteCursor += SoundOutput.SecondaryBufferSize; } Assert(SafeWriteCursor >= PlayCursor); SafeWriteCursor += SoundOutput.SafetyBytes; bool32 AudioCardIsLowLatency = (SafeWriteCursor < ExpectedFrameBoundaryByte); DWORD TargetCursor = 0; if (AudioCardIsLowLatency) { TargetCursor = ExpectedFrameBoundaryByte + ExpectedSoundBytesPerFrame; } else { TargetCursor = WriteCursor + ExpectedSoundBytesPerFrame + SoundOutput.SafetyBytes; } TargetCursor = TargetCursor % SoundOutput.SecondaryBufferSize; DWORD BytesToWrite = 0; if (ByteToLock > TargetCursor) { BytesToWrite = SoundOutput.SecondaryBufferSize - ByteToLock; BytesToWrite += TargetCursor; } else { BytesToWrite = TargetCursor - ByteToLock; } game_sound_output_buffer SoundBuffer = {}; SoundBuffer.SamplesPerSecond = SoundOutput.SamplesPerSecond; SoundBuffer.SampleCount = BytesToWrite / SoundOutput.BytesPerSample; SoundBuffer.Samples = Samples; if (Game.GetSoundSamples) { Game.GetSoundSamples(&Thread, &GameMemory, &SoundBuffer); } #if HANDMADE_INTERNAL win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex]; Marker->OutputPlayCursor = PlayCursor; Marker->OutputWriteCursor = WriteCursor; Marker->OutputLocation = ByteToLock; Marker->OutputByteCount = BytesToWrite; Marker->ExpectedFlipPlayCursor = ExpectedFrameBoundaryByte; DWORD UnwrappedWriteCursor = WriteCursor; if (UnwrappedWriteCursor < PlayCursor) { UnwrappedWriteCursor += SoundOutput.SecondaryBufferSize; } AudioLatencyBytes = UnwrappedWriteCursor - PlayCursor; AudioLatencySeconds = ((real32)AudioLatencyBytes / (real32)SoundOutput.BytesPerSample) / (real32)SoundOutput.SamplesPerSecond; #if 0 char DebugSoundBuffer[256]; sprintf_s(DebugSoundBuffer, "BTL:%u TC:%u BTW:%u - PC:%u WC:%u DELTA:%u (%fs)\n", ByteToLock, TargetCursor, BytesToWrite, PlayCursor, WriteCursor, AudioLatencyBytes, AudioLatencySeconds); OutputDebugStringA(DebugSoundBuffer); #endif #endif Win32FillSoundBuffer(&SoundOutput, ByteToLock, BytesToWrite, &SoundBuffer); } else { SoundIsValid = false; } LARGE_INTEGER WorkCounter = Win32GetWallClock(); real32 WorkSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter); // TODO: not tested yet! real32 SecondsElapsedForFrame = WorkSecondsElapsed; if (SecondsElapsedForFrame < TargetSecondsPerFrame) { if (SleepIsGranular) { DWORD SleepMS = (DWORD)(1000.0f * (TargetSecondsPerFrame - SecondsElapsedForFrame)); if (SleepMS > 0) { Sleep(SleepMS); } } real32 TestSecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock()); if (TestSecondsElapsedForFrame < TargetSecondsPerFrame) { // TODO: log missed sleep here } while (SecondsElapsedForFrame < TargetSecondsPerFrame) { SecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock()); } } else { // TODO: missed frame rate // TODO: logging } LARGE_INTEGER EndCounter = Win32GetWallClock(); real32 MSPerFrame = 1000.0f * Win32GetSecondsElapsed(LastCounter, EndCounter); LastCounter = EndCounter; win32_window_dimension Dimension = Win32GetWindowDimension(Window); HDC BufferDC = GetDC(Window); Win32DisplayBufferInWindow(&GlobalBackbuffer, BufferDC, Dimension.Width, Dimension.Height); ReleaseDC(Window, BufferDC); FlipWallClock = Win32GetWallClock(); #if HANDMADE_INTERNAL DWORD DebugPlayCursor; DWORD DebugWriteCursor; if (GlobalSecondaryBuffer->GetCurrentPosition(&DebugPlayCursor, &DebugWriteCursor) == DS_OK) { Assert(DebugTimeMarkerIndex < ArrayCount(DebugTimeMarkers)); win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex]; Marker->FlipPlayCursor = DebugPlayCursor; Marker->FlipWriteCursor = DebugWriteCursor; } #endif game_input *Temp = NewInput; NewInput = OldInput; OldInput = Temp; // TODO: should these be cleared? #if 0 uint64 EndCycleCount = __rdtsc(); uint64 CyclesElapsed = EndCycleCount - LastCycleCount; LastCycleCount = EndCycleCount; // real32 FPS = (real32)GlobalPerfCounterFrequency / (real32)CounterElapsed; real32 FPS = 0.0f; real32 MCPF = (real32)CyclesElapsed / (1000.0f * 1000.0f); char DebugPerformanceBuffer[256]; sprintf_s(DebugPerformanceBuffer, "%0.02fmspf, %0.02ffps, %0.02fmcpf\n", MSPerFrame, FPS, MCPF); OutputDebugStringA(DebugPerformanceBuffer); #endif #if HANDMADE_INTERNAL ++DebugTimeMarkerIndex; if (DebugTimeMarkerIndex == ArrayCount(DebugTimeMarkers)) { DebugTimeMarkerIndex = 0; } #endif } } } else { // TODO: logging } } else { // TODO: logging } } else { // TODO: logging } return(0); }
internal void Win32ProcessPendingMessages(game_controller_input* KeyboardController) { MSG Message; while(PeekMessageA(&Message, 0, 0, 0, PM_REMOVE)) { switch(Message.message) { case WM_QUIT: { GlobalRunning = false; } break; case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { u32 VKCode = (u32)Message.wParam; b32 WasDown = ((Message.lParam & (1 << 30)) != 0); b32 IsDown = ((Message.lParam & (1 << 31)) == 0); if(WasDown != IsDown) { if(VKCode == 'W') { Win32ProcessKeyboardMessage(&KeyboardController->MoveForward, IsDown); } else if(VKCode == 'A') { Win32ProcessKeyboardMessage(&KeyboardController->MoveLeft, IsDown); } else if(VKCode == 'S') { Win32ProcessKeyboardMessage(&KeyboardController->MoveBackward, IsDown); } else if(VKCode == 'D') { Win32ProcessKeyboardMessage(&KeyboardController->MoveRight, IsDown); } else if(VKCode == VK_UP) { Win32ProcessKeyboardMessage(&KeyboardController->DPadUp, IsDown); } else if(VKCode == VK_LEFT) { Win32ProcessKeyboardMessage(&KeyboardController->DPadLeft, IsDown); } else if(VKCode == VK_DOWN) { Win32ProcessKeyboardMessage(&KeyboardController->DPadDown, IsDown); } else if(VKCode == VK_RIGHT) { Win32ProcessKeyboardMessage(&KeyboardController->DPadRight, IsDown); } else if(VKCode == VK_ESCAPE) { Win32ProcessKeyboardMessage(&KeyboardController->Start, IsDown); } // NOTE(zak): Bind A, B, X, Y on controller to buttons on keyboard #if LUDUS_INTERNAL else if(VKCode == 'P') { if(IsDown) { GlobalPause = !GlobalPause; } } else if(VKCode == VK_F1) { if(IsDown) { GlobalDebugMode = !GlobalDebugMode; } } #endif else if(IsDown) { b32 AltKeyWasDown = (Message.lParam & (1 << 29)); if((VKCode == VK_F4) && AltKeyWasDown) { GlobalRunning = false; } if((VKCode == VK_RETURN) && AltKeyWasDown) { Win32ToggleFullscreen(Message.hwnd); } } } } break; default: { TranslateMessage(&Message); DispatchMessageA(&Message); } break; } } }