Microsoft DirectX 9.0 SDK Update (Summer 2003) |
Tutorial 10: DirectPlay Thread Pool
This tutorial shows how to use the IDirectPlay8ThreadPool interface and avoid multithreading by taking advantage of the IDirectPlay8ThreadPool::DoWork method.
Some Microsoft® DirectPlay® games choose to use DirectPlay's internal worker threads to send and receive messages. These games must implement synchronization mechanisms to avoid data corruption and deadlocking.
Because multithreading is a complex issue, DirectPlay has implemented the IDirectPlay8ThreadPool interface to help users managed their threads. One advantage to using the IDirectPlay8ThreadPool interface is that you can use the IDirectPlay8ThreadPool::SetThreadCount method to disable all of DirectPlay's internal threads. All you need to do is call the IDirectPlay8ThreadPool::DoWork method regularly in your game loop and DirectPlay will perform networking tasks only during the time specified and only on the calling thread.
This tutorial uses a peer-to-peer, lobbied application, so you should review the following tutorials before beginning this one.
- Tutorial 1: Creating a DirectPlay Object and Enumerating Service Providers
- Tutorial 2: Hosting a Session
- Tutorial 3: Enumerating Hosted Sessions
- Tutorial 4: Connecting to a Session
- Tutorial 5: Sending Messages to Other Peers
- Tutorial 6: Handling Host Migration
- Tutorial 7: Creating a Lobbyable Application
The complete sample code for this tutorial is included with the Microsoft DirectX® software development kit (SDK) and can be found at (SDK root)\Samples\C++\DirectPlay\Tutorials\Tut10_ThreadPool.
This article contains the following sections.
- User's Guide
- Creating a DirectPlay ThreadPool Object
- Setting the Thread Count
- Working in DoWork Mode
- Terminating the Application
User's Guide
When you run this tutorial sample, a window opens and you have the choice to either Host or Connect.
If you choose Host:
- A window will open and you should enter a session name. Select the Migrate Host box to allow host migration to take place in this session. Then click OK. Your session status will change to 'Hosting Session "YourSessionName".'
- You can now draw pictures in Shared Canvas. Select a color by clicking on one of the color boxes and then hold the mouse down while you move it inside the canvas window to draw. To disconnect, click Disconnect and the session ends.
- Click Exit to end the sample
If you choose Connect:
- The Connect to Session window will open and you should enter an Internet Protocol (IP) address and click Search. If any sessions are found at that address, they will be listed in the Detected Sessions box. Select a session and click Connect. If the address does not exist, a message box opens with an error message.
- Once connected, your session status will change to 'Connected to Session "YourSessionName". You can now draw pictures in Shared Canvas. Select a color by clicking on one of the color boxes and then hold the mouse down while you move it inside the canvas window to draw. To disconnect, click Disconnect.
- Click Exit to end the sample.
You can run this sample twiceonce to host a session and once to connect. When connecting, enter your computer's IP address. When connected, you can draw pictures on the canvas shared between the host and the client application. To test host migration, close the host application. The client application's session status will change to 'Hosting Session "YourSessionName".'.
Creating a DirectPlay ThreadPool Object
The first step in using the DirectPlay thread pool is to create and initialize an IDirectPlay8ThreadPool object. The IDirectPlay8ThreadPool object must be initialized before any other DirectPlay object or DirectPlay will create its own IDirectPlay8ThreadPool object when IDirectPlay8Peer is initialized. There can be only one IDirectPlay8ThreadPool object per process so if DirectPlay has created its own IDirectPlay8ThreadPool object then a subsequent call to IDirectPlay8ThreadPool::Initialize will return DPNERR_ALREADYINITIALIZED.
To create an IDirectPlay8ThreadPool object, call CoCreateInstance passing the class identifier (CLSID_DirectPlay8ThreadPool), the identifier of the interface (IID_IDirectPlay8ThreadPool), and the address of a pointer to an IDirectPlay8ThreadPool object. The following snippet from the tutorial sample shows how to create and initialize an IDirectPlay8ThreadPool object.
IDirectPlay8ThreadPool* g_pThreadPool; . . . // Create the IDirectPlay8ThreadPool interface hr = CoCreateInstance( CLSID_DirectPlay8ThreadPool, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8ThreadPool, (LPVOID*) &g_pThreadPool ); // Initialize ThreadPool hr = g_pThreadPool->Initialize(NULL, DirectPlayMessageHandler, 0 );
Setting the Thread Count
After initializing your DirectPlay objects, you should you call IDirectPlay8ThreadPool::SetThreadCount and set the thread count to zero. It is recommended that you do this before making any networking calls.
The following snippet from the tutorial sample shows how to set the thread count to zero.
// Turn off worker DirectPlay worker threads because you'll be using the // DoWork method to synchronously handle network messages. hr = g_pThreadPool->SetThreadCount( (DWORD) -1, 0, 0 ); // dwProcessorNum, dwNumThreads, dwFlags
The dwProcessorNum parameter is set to -1 to change the thread count for all processors.
Working in DoWork Mode
After you set the thread count to zero, you must call IDirectPlay8ThreadPool::DoWork to perform any DirectPlay tasks. When you call IDirectPlay8ThreadPool::DoWork, you specify the amount of time that the application should spend on DirectPlay tasks. DirectPlay will handle starting and closing threads to perform various tasks such as receiving messages. If DirectPlay finishes all the networking tasks before the specified time, the method will return early. If there are any tasks left when the time runs out, IDirectPlay8ThreadPool::DoWork will return DPNSUCCESS_PENDING.
The correct way to use IDirectPlay8ThreadPool::DoWork is to call it within the game loop. IDirectPlay8ThreadPool::DoWork will block the application until it returns, so you should limit the time your program spends on DirectPlay tasks.
The following snippet from the tutorial sample shows how to use IDirectPlay8ThreadPool::DoWork.
// Handle incoming network data // Here you're setting the allowed timeslice at 100 milliseconds. // The program will block while DoWork handles network communication // so you don't need to worry about thread synchronization issues as // you have on earlier tutorials. g_pThreadPool->DoWork( 100, 0 ); // dwAllowedTimeSlice, dwFlags
Terminating the Application
If an IDirectPlay8ThreadPool object was successfully initialized, you should call IDirectPlay8ThreadPool::Close before terminating the application. You should call IDirectPlay8ThreadPool::Close after terminating other DirectPlay objects. Before IDirectPlay8ThreadPool::Close returns, all existing DirectPlay threads will terminate and you will receive a DPN_MSGID_DESTROY_THREAD message for each thread.
The following snippet from the tutorial sample shows how to use IDirectPlay8ThreadPool::Close.
if( g_pThreadPool ) g_pThreadPool->Close(0);