Zainspirowany tym co poradził mi @alagner (bardzo dziękuję!) zacząłem główkować nad przeprojektowaniem GameLoop'a w prostym silniku, który piszę. Inputy z klawiatury rzeczywiście nie powinny być synchronizowane co klatkę obrazu (przez polling), tylko przetwarzane na zasadzie kolejkowanych eventów. Wyszło mi coś takiego (to póki co prototyp):
///////////////// Objaśnienia typów:
struct Input
{
unsigned char Index = 0
unsigned char State = 0;
}
InputCleanUp, InputQueue //std::queue<Input>
struct ControlState
{
bool Pressed = false;
bool Released = false;
bool Held = false;
bool DoubleClicked = false;
};
Control //ControlState[256]
//////////////// Kod właściwy:
void MainLoop()
{
while (!InputCleanUp.empty())
{
switch (InputCleanUp.front().State)
{
case 0: //Released
Control[InputCleanUp.front().Index].Released = false;
break;
case 1: //Pressed
Control[InputCleanUp.front().Index].Pressed = false;
break;
case 2: //Double Clicked
Control[InputCleanUp.front().Index].Pressed = false;
Control[InputCleanUp.front().Index].DoubleClicked = false;
break;
}
InputCleanUp.pop();
}
while (!InputQueue.empty())
{
switch (InputQueue.front().State)
{
case 0: //Released
Control[InputQueue.front().Index].Released = true;
Control[InputQueue.front().Index].Held = false;
break;
case 1: //Pressed
Control[InputQueue.front().Index].Pressed = true;
Control[InputQueue.front().Index].Held = true;
break;
case 2: //Double Clicked
Control[InputQueue.front().Index].Pressed = true;
Control[InputQueue.front().Index].DoubleClicked = true;
Control[InputQueue.front().Index].Held = true;
break;
}
InputCleanUp.push({ InputQueue.front().Index, InputQueue.front().State });
InputQueue.pop();
}
//Update Game Logic
//Render Frame
}
W WinProc
obsługuję komunikaty i przy każdym WM_KEYDOWN
, WM_KEYUP
i odpowiednich BUTTONDBLCLK
(dwuklik myszy) wrzucam do kolejki kod klawisza (z przedziału 0-255) i status (równy 0, 1 lub 2):
void Controls::AddInput(unsigned char uchIndex, unsigned char uchState)
{
InputQueue.push({ uchIndex, uchState });
}
A czemu dwie kolejki? Flagi Pressed
i Released
mogą być zapalone tylko na czas jednej klatki (działają jak jednorazowe eventy) i po jej zakończeniu muszą zostać zgaszone, czym zajmuje się właśnie druga kolejka InputCleanUp
, resetująca odpowiednie flagi.
Problem w tym, że główny MessageLoop
z WinApi (odpowiedziany za obsługę windowsowych komunikatów) i mój MainLoop
to dwa różne wątki, a powyższy prototyp nie jest thread-safe
i potrzebuję porady jak to rozwiązać. Wiem, że mógłbym walnąć jakiegoś mutexa
, ale boję się, że wtedy wątki będą się wzajemnie spowalniać (bo jeden będzie czekał na drugi) i przez to silnik będzie gubił klatki. A może zupełnie biorę się za to od złej strony i powinienem to zaprojektować inaczej?
Byłbym bardzo wdzięczny za wszelkie sugestie.
WM_KEYDOWN
czyWM_KEYUP
mogą się pojawić w każdym momencie klatki (po wciśnięciu bądź odpuszczeniu klawisza). A innej obróbki niż przestawianie flag klawisza końcowegoControl
tu nie ma. Co do lock'a to się zgadzam, dlatego stworzyłem ten wątek.