4.3. Using Multithreading for MotionIf a system supports multithreading, then motion can be implemented inside a function with a name such as ThreadFunc(). A thread is handled by the operating system and this limits the control of the program that starts a thread over its execution. If a game has several "characters" we may assign a thread to each "character" and that tends to simplify the structure of the program: the life of each "character" is trully separate from the lives of the other "characters." On the other hand, programming with threads can be tricky as we shall show next. Using threads for the glass bead game is probably an overkill, but it provides a good way to compare it with motion using idle time. The code below shows the thread function for a bouncing bead (Step() is the same function as that shown in Listing 4.1.C.)
// Listing A
UINT CLiveBead::ThreadFunc(LPVOID pParam)
{
CLiveBead *bp = (CLiveBead *)pParam;
while(bp->Step(bp->m_bounds)==TRUE) ::Sleep(10);
return 0;
}
The first thing we notice is that even though ThreadFunc is a member of CLiveBead we need to pass the object as an argument. This is because the thread function must be declared static and therefore it is not associated with a particular instance of CLiveBead. The association between the two is achieved when the thread is launched, as shown in the code below.
// Listing B
void CLiveBead::Start(/* ... */)
{
/* other initialization code */
m_pThread = AfxBeginThread(&CLiveBead::ThreadFunc, this);
ASSERT(m_pThread != NULL);
}
Because the thread knows nothing about the game object that holds the bounding rectangle, we must copy that rectangle in the bead object. The main difference between the code of the thread function and the code of the idle function (end of Section 2.1) is that the idle function loops over the objects while the thread function loops over time. In both cases there is a call to Sleep() to suspend execution for some time, otherwise the motion may be too fast. The call to the method OnGameIdle() is affected by the event/message queue in the application, the call to ThreadFunc() is not. The appication glassgame.exe (explained in Section 4.2) lets you compare the execution under the two approaches. By clicking on ChangeMode in the menu bar the program switches between the two implementations of motion. The letters ID appear on the screen if idle time is used and the letters MT appear when multithreading is used. Now some bad news. When a thread terminates execution it deletes itself (by default), so there is no reason for the application to clean up. On the other hand if the application terminates the thread there is a need for clean up. If the application cleans up for all the threads then you get exceptions for the threads that have terminated on their own. If you do nothing, then you get memory leaks from the threads that were still running. The best solution is to have threads terminate themsleves by sending them a signal. One solution is to include a flag, m_running, in the objects that start the thread. It should be set to TRUE just before the thread is started (Listing B). We also modify the code of the function in the thread loop (Step() in our case, Listing 4.1.C) to include as first statement the code if(m_running == FALSE) return FALSE; In this way, once we set the value of m_running to FALSE the loop in Listing A will terminate and so will the thread. We can set this flag as shown in the Stop() function below. We added the Sleep() statement to make sure the Stop() function does not return before the thread had a chance to check the flag.
// Listing C
void CLiveBead::Stop()
{
if(m_running==FALSE) return;
m_running = FALSE;
::Sleep(100);
}
One possible source of problems is exiting the program without making sure to terminate the running threads. In the Microsoft Windows environment when you click on the Exit button the system sends a WM_CLOSE to the main (frame) window. You should override the default code with something like what is shown below.
// Listing D
void CMainFrame::OnClose()
{
CWnd *v = CWnd::GetFocus();
if(v != NULL) (((CChildView *)v)->m_Game).ExitGame();
CFrameWnd::OnClose(); // Default exit code
}
ExitGame() stops the threads and does any other clean up needed. In general, you need to tread carefully when writing programs using threads.
|