Win32 API 기초 : PeekMessage (2)
GetMessage 함수는 메시지가 있을 때만 반환하는 함수입니다.
즉 메시지가 없으면, 메시지가 생길 때까지 계속 대기하게 되는 거죠.
일반적인 메시지라면 true를 반환하여 메시지를 처리하고
msg.message가 WM_QUIT이라면 false를 반환하여 프로그램을 종료하죠.
이러한 특징 때문에 메시지가 없어도 자동으로
어떤 동작이 일어나게 하는데 제약이 생깁니다.
예를 들어 게임에서 플레이어의 입력이 없어도
몬스터가 움직이게 하고자 하려면 강제로 메시지를 발생시켜
몬스터의 위치를 바꾸고 갱신시켜야 하는 거죠.
이러한 문제를 해결하는 방법으로는
PeekMessage 함수를 사용하는 겁니다.
PeekMessage 함수는 메시지가 있으면 true를
메시지가 없으면 false를 반환합니다.
50번째 줄에서 볼 수 있듯이
GetMessage 함수를 PeekMessage 함수로 대체하였습니다.
4번째 인자까지는 GetMessage 함수와 같지만
마지막 인자로 PM_REMOVE를 주었죠.
이는 메시지를 확인한 PeekMessage 함수가 확인한
메시지를 큐에서 제거할지 말지에 대한 옵션으로
PM_REMOVE로 하면 메시지를 확인 후 큐에서 제거합니다.
PM_REMOVE로 하여 메시지 큐에
메시지가 계속 쌓이는 것을 방지해 줍니다.
근데 PeekMessage 함수를 while 문의
조건으로 하면 문제가 생길 겁니다.
프로그램을 켜고 아무런 동작을 하지 않으면
PeekMessage 함수가 false를 반환할 것이고
그러면 while 문 안의 코드가 실행되지 않아
바로 프로그램이 종료되겠죠?
그래서 아래 코드처럼 수정을 해야 합니다.
while 문은 항상 반복하도록 하고 PeekMessage 함수가 true를
반환하면, 즉 메시지가 큐에 존재하면 54번째 줄에 의해 msg의 멤버 message가
WM_QUIT과 같은지 체크합니다. 만약 같다면 프로그램을 종료해야 하므로
break 문을 통해 while 문을 빠져나갑니다.
그게 아니라면 57번째 줄에 의해
PeekMessage 함수를 통해 읽은 메시지에 대한
처리를 진행하는 거죠.
63번째 줄에 있는 else 문이 바로
메시지 큐에 아무런 메시지가 없을 때, 즉 사용자가 아무런 동작을
하지 않았을 때입니다. else 문 안에 NPC나 몬스터가
움직이는 등, 사용자가 아무런 메시지를 주지 않았을 때에도
발생해야 하는 동작에 대한 코드를 작성하면 됩니다.
메시지가 처리되는 시간을 파악해 봅시다.
main 함수에서 while 문을 수정했습니다.
48번째 줄에 있는 dwPrevCount는
while 문이 실행되기 직전까지 흐른 시간을 저장하는 변수입니다.
49번째 줄에 있는 dwAccCount는
메시지의 처리가 시작된 시점부터 처리가 완료된 시점까지
흐른 시간을 저장할 변수입니다.
먼저 큐에 메시지가 존재한다면 54번째 줄에 있는
if 문 안의 코드가 실행될 겁니다.
56번째 줄에 있는 iTime에
메시지 처리가 시작된 시점의 시간을 저장하죠.
그리고 67번째 줄에 있는 iAdd에
메시지 처리가 완료된 시점의 시간에서
메시지 처리가 시작된 시점의 시간을 뺍니다.
그럼 메시지 처리가 시작되고 완료되기까지
사이의 시간을 알 수 있겠죠?
예를 들어 프로그램이 시작된 지 2초가 흘러
메시지 처리가 시작되었습니다. 그러면 iTime에는
2초가 저장될 겁니다. (정확히는 2초가 흐른 시점의 어떤 값)
그리고 1초 뒤에 메시지 처리가 완료되었습니다.
그러면 2초가 흐른 시점에서 시작하여 1초 동안 처리를 하였으니
iAdd에는 3초가 저장되겠죠. 처리가 완료된 시점인 3초에서
메시지 처리가 시작된 시점인 2초를 빼면
메시지 처리 시작부터 완료까지 걸린 시간인 1초가 나오죠.
68번째 줄에서 iAdd와 기존 dwAccCount값을 더하여
dwAccCount에 대입합니다. 이 둘을 더하는 이유는
메시지가 여러 개일 수도 있기 때문이죠. 첫 번째 메시지 처리까지
걸린 시간에서 다음 메시지 처리까지 걸린 시간을 더하고,
또 다음 메시지 처리까지 걸린 시간을 더하는 등
확인된 모든 메시지가 처리될 때까지 걸린 총 시간을 얻어야 하므로
계속 더해 주는 겁니다.
이제 모든 메시지가 처리되고 더 이상 메시지가 없다면
70번째 줄에 있는 else 문에 들어갈 겁니다.
72번째 줄에 있는 dwCurCount에
아무런 동작이 없는 현재 시점을 저장합니다.
74번째 줄에 있는 if 문의 조건을 보시면
현재 시점에서 while 문이 시작되기 직전 시점을 뺀 값이
1000(1초)을 초과했을 때입니다. 즉 while 문이 시작된 이후
1초가 지났을 때입니다. 모든 메시지가 처리된 시간이 저장된
dwAccCount를 초로 표현하기 위해 1000으로 나누고 f에 저장합니다.
그리고 wchar_t형 szBuff를 선언하고
값을 와이드 문자열로 바꿔주는 swprintf_s 함수를 통해
실수형 f의 값을 문자열로 바꾸어 szBuff에 대입합니다.
이후 SetWindowText 함수를 통해
윈도우 제목 표시줄에 szBuff를 출력합니다.
윈도우 제목 표시줄에
메시지 처리까지 걸린 시간이 출력될 것입니다.
82번째 줄에서 while 문이 시작되기 직전의
시점이 저장된 dwPrevCount에 현재 시점을 저장합니다.
현재 시점을 while 문이 다시 반복될 때에는
이전 시점으로 봐야 하니까요.
83번째 줄에서 dwAccCount의 값을
0으로 초기화해 줍니다. 다음에 처리할 메시지가
얼마나 걸릴지 정확히 체크하려면
이전 메시지가 처리된 시간을 지우기 위해 초기화를 해 줘야겠죠?
프로그램을 실행하여 마우스를 움직여 보면
마우스 움직임 메시지 처리까지 걸린 시간이
윈도우 제목 표시줄에 출력됩니다.
마우스를 많이 움직이면
더 높은 값이 출력됩니다. 더 많은 움직임을
처리해야 하므로 시간이 더 걸리기 때문이죠.
하지만 아무리 마우스를 움직여 봐도
1초를 넘어가지 않습니다.
생각해 보면 메시지 기반 방식은 메시지 처리 시간은
1초 미만인데 실행되는 동안 남은 모든 시간은
메시지 받을 준비만 하고 있다는 것이죠.
게임을 만드는 데 효율적이지 않기 때문에
메시지 기반 방식은 최대한 사용하지 않을 겁니다.
물론 윈도우에서 들어오는 기본 메시지는 계속 발생합니다.
그러나 WndProc 함수를 통해 특별히 명시하지 않는 한,
DefWindowProc(기본 메시지 처리기)를 실행하게 되며
어떤 특별한 동작이 발생하지 않죠.
만약 메시지 기반 방식을 사용하지 않고
키 입력을 받고자 한다면 비동기 처리로 진행하면 됩니다.
메시지 기반 방식의 경우 윈도우에 포커싱되어 있지 않으면
마우스 클릭을 해도 아예 큐가 들어오지 않아 아무런 동작도 하지 않습니다.
포커싱되어 있는 윈도우의 메시지 큐에 메시지를 넣어주기 때문이죠.
그러나 비동기 함수를 사용하면 마우스 클릭을 했을 때
윈도우가 포커싱되어 있지 않아도 클릭이 발생했는지 파악할 수 있습니다.
대신 메시지 기반 방식은 포커싱되어 있지 않으면 알아서
메시지 처리 자체를 하지 않기 때문에 예외 처리를 따로 해 줄 필요가
없었지만, 비동기 방식으로 진행하면 윈도우가 포커싱되어 있지 않아도
어떤 동작이 윈도우에 영향을 줄 수가 있습니다.
그림판을 예로 들어 봅시다.
그림판 윈도우를 내린 상태에서는 아무리 마우스를 클릭하고
드래그하여도 그림이 그려지지 않습니다. 메시지 기반 방식처럼 말이죠.
그러나 그림판을 비동기 방식으로 만든다면
그림판 윈도우를 내린 상태에서 아무 곳이나 클릭하고
드래그를 하여도 그림판에 그림이 그려질 수도 있다는 것이죠.
따라서 비동기 방식으로 프로그램을 작성한다고 하면
윈도우가 포커싱되어 있지 않은 상태에서 특정 키가 입력되었을 때,
동작하지 않도록 하는 등 반드시 예외 처리도 해 줘야 합니다.
강의 출처 : https://www.youtube.com/watch?v=z4qpyF_UfYE