Files
Client/Library/dxx8/samples/Multimedia/DirectPlay/DataRelay/datarelay.cpp
LGram16 e067522598 Initial commit: ROW Client source code
Game client codebase including:
- CharacterActionControl: Character and creature management
- GlobalScript: Network, items, skills, quests, utilities
- RYLClient: Main client application with GUI and event handlers
- Engine: 3D rendering engine (RYLGL)
- MemoryManager: Custom memory allocation
- Library: Third-party dependencies (DirectX, boost, etc.)
- Tools: Development utilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 16:24:34 +09:00

1690 lines
64 KiB
C++

//----------------------------------------------------------------------------
// File: DataRelay.cpp
//
// Desc: The main game file for the DataRelay sample. It connects
// players together with two dialog boxes to prompt users on the
// connection settings to join or create a session. After the user
// connects to a sesssion, the sample displays a multiplayer stage.
//
// This sample uses DirectPlay to process high volumes of incoming
// data on a seperate worker while avoiding copying the incoming packets
// by using the ReturnBuffer() call. It also shows some basics of
// thread synconization between the worker thread and the DirectPlay
// message handler thread pool.
//
// Copyright (c) 1999-2001 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#include <windows.h>
#include <basetsd.h>
#include <process.h>
#include <mmsystem.h>
#include <dxerr8.h>
#include <dplay8.h>
#include <dplobby8.h>
#include <stdio.h>
#include <assert.h>
#include "NetConnect.h"
#include "DXUtil.h"
#include "resource.h"
//-----------------------------------------------------------------------------
// Player context locking defines
//-----------------------------------------------------------------------------
CRITICAL_SECTION g_csPlayerContext;
#define PLAYER_LOCK() EnterCriticalSection( &g_csPlayerContext );
#define PLAYER_ADDREF( pPlayerInfo ) if( pPlayerInfo ) pPlayerInfo->lRefCount++;
#define PLAYER_RELEASE( pPlayerInfo ) if( pPlayerInfo ) { pPlayerInfo->lRefCount--; if( pPlayerInfo->lRefCount <= 0 ) SAFE_DELETE( pPlayerInfo ); } pPlayerInfo = NULL;
#define PLAYER_UNLOCK() LeaveCriticalSection( &g_csPlayerContext );
//-----------------------------------------------------------------------------
// Defines, and constants
//-----------------------------------------------------------------------------
#define DPLAY_SAMPLE_KEY TEXT("Software\\Microsoft\\DirectX DirectPlay Samples")
#define MAX_PLAYER_NAME 14
#define TIMERID_NETWORK 0
#define TIMERID_STATS 1
#define WM_APP_UPDATE_TARGETS (WM_APP + 0)
#define WM_APP_APPEND_TEXT (WM_APP + 1)
// This GUID allows DirectPlay to find other instances of the same game on
// the network. So it must be unique for every game, and the same for
// every instance of that game. // {BA214178-AAE6-4ea6-84E0-65CE36F84479}
GUID g_guidApp = { 0xba214178, 0xaae6, 0x4ea6, { 0x84, 0xe0, 0x65, 0xce, 0x36, 0xf8, 0x44, 0x79 } };
struct APP_PLAYER_INFO
{
LONG lRefCount; // Ref count so we can cleanup when all threads
// are done w/ this object
DPNID dpnidPlayer; // DPNID of player
TCHAR strPlayerName[MAX_PLAYER_NAME]; // Player name
DWORD dwFlags; // Player flags
APP_PLAYER_INFO* pNext;
APP_PLAYER_INFO* pPrev;
};
//-----------------------------------------------------------------------------
// App specific DirectPlay messages and structures
//-----------------------------------------------------------------------------
#define GAME_MSGID_GAMEPACKET 1
// Change compiler pack alignment to be BYTE aligned, and pop the current value
#pragma pack( push, 1 )
struct GAMEMSG_GENERIC
{
DWORD dwType;
DWORD dwPacketId;
};
#define GAMEMSG_GENERIC_SIZE 8
struct GAMEMSG_DATA_512 : GAMEMSG_GENERIC
{
BYTE pBuffer[512-GAMEMSG_GENERIC_SIZE];
};
struct GAMEMSG_DATA_256 : GAMEMSG_GENERIC
{
BYTE pBuffer[256-GAMEMSG_GENERIC_SIZE];
};
struct GAMEMSG_DATA_128 : GAMEMSG_GENERIC
{
BYTE pBuffer[128-GAMEMSG_GENERIC_SIZE];
};
struct GAMEMSG_DATA_64 : GAMEMSG_GENERIC
{
BYTE pBuffer[64-GAMEMSG_GENERIC_SIZE];
};
struct GAMEMSG_DATA_32 : GAMEMSG_GENERIC
{
BYTE pBuffer[32-GAMEMSG_GENERIC_SIZE];
};
struct GAMEMSG_DATA_16 : GAMEMSG_GENERIC
{
BYTE pBuffer[16-GAMEMSG_GENERIC_SIZE];
};
#define DATA_TYPE_NETPACKET_RECIEVE 1
#define DATA_TYPE_NETPACKET_SENT 2
#define DATA_TYPE_NETPACKET_TIMEOUT 3
struct GAMEMSG_DATA_NODE
{
GAMEMSG_GENERIC* pDataMsg;
DWORD dwPacketId;
APP_PLAYER_INFO* pPlayerFrom;
DWORD dwReceiveDataSize;
DPNHANDLE hBufferHandle;
DWORD dwType;
GAMEMSG_DATA_NODE* pNext;
GAMEMSG_DATA_NODE* pPrev;
};
// Pop the old pack alignment
#pragma pack( pop )
//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------
TCHAR g_strAppName[256] = TEXT("DataRelay");
IDirectPlay8Peer* g_pDP = NULL; // DirectPlay peer object
CNetConnectWizard* g_pNetConnectWizard = NULL; // Connection wizard
IDirectPlay8LobbiedApplication* g_pLobbiedApp = NULL; // DirectPlay lobbied app
BOOL g_bWasLobbyLaunched = FALSE; // TRUE if lobby launched
HINSTANCE g_hInst = NULL; // HINST of app
HWND g_hDlg = NULL; // HWND of main dialog
HRESULT g_hrDialog; // Exit code for app
TCHAR g_strLocalPlayerName[MAX_PATH]; // Local player name
TCHAR g_strSessionName[MAX_PATH]; // Session name
TCHAR g_strPreferredProvider[MAX_PATH]; // Provider string
HANDLE g_hDPDataAvailEvent = NULL; // Signaled if there is data to process
HANDLE g_hShutdownEvent = NULL; // Signaled if shutting down
DWORD g_dwPacketId = 0; // Packet id counter
APP_PLAYER_INFO* g_pTargetPlayer = NULL; // The current target player
BOOL g_bSendingData = FALSE; // TRUE if sending data
DPNID g_dpnidLocalPlayer = 0; // DPNID of local player
DWORD g_dwDataRecieved = 0; // Amount of data recieved
DWORD g_dwDataSent = 0; // Amount of data sent
DWORD g_dwSendTimeout = 0; // Length of send timeout
DWORD g_dwTimeBetweenSends = 0; // Time between packet sends
DWORD g_dwSendSize = 0; // Size in bytes of packet
UINT g_dwProcessNetDataThreadID = 0; // Worker thread ID
HANDLE g_hProcessNetDataThread = NULL; // Worker thread handle
APP_PLAYER_INFO g_PlayerHead; // Linked list of players connected
GAMEMSG_DATA_NODE g_DataHead; // Linked list of data to process
CRITICAL_SECTION g_csPlayerList; // CS for g_PlayerHead
CRITICAL_SECTION g_csDataList; // CS for g_DataHead
APP_PLAYER_INFO* g_pConnInfoTargetPlayer = NULL; // The current target for Connection Info
//-----------------------------------------------------------------------------
// Function-prototypes
//-----------------------------------------------------------------------------
HRESULT WINAPI DirectPlayMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer );
HRESULT WINAPI DirectPlayLobbyMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer );
INT_PTR CALLBACK SampleDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
UINT WINAPI ProcessNetDataProc( LPVOID lpParameter );
HRESULT InitDirectPlay();
HRESULT OnInitDialog( HWND hDlg );
VOID FillTargetCombo( HWND hDlg );
VOID FillOneTimeCombos( HWND hDlg );
HRESULT LinkPlayer( DPNID dpnid, APP_PLAYER_INFO** ppPlayerInfo );
HRESULT SendNetworkData();
HRESULT ProcessData();
VOID AppendTextToEditControl( HWND hDlg, TCHAR* strNewLogLine );
VOID ReadCombos( HWND hDlg );
VOID UpdateSendQueueInfo( HWND hDlg );
VOID UpdateStats( HWND hDlg );
//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Entry point for the application. Since we use a simple dialog for
// user interaction we don't need to pump messages.
//-----------------------------------------------------------------------------
INT APIENTRY WinMain( HINSTANCE hInst, HINSTANCE hPrevInst,
LPSTR pCmdLine, INT nCmdShow )
{
HRESULT hr;
BOOL bConnectSuccess = FALSE;
HKEY hDPlaySampleRegKey;
// Create events and critical sections
g_hDPDataAvailEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
g_hShutdownEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
InitializeCriticalSection( &g_csPlayerList );
InitializeCriticalSection( &g_csDataList );
InitializeCriticalSection( &g_csPlayerContext );
g_hInst = hInst;
// Init circular linked list
ZeroMemory( &g_PlayerHead, sizeof(APP_PLAYER_INFO) );
g_PlayerHead.pNext = &g_PlayerHead;
g_PlayerHead.pPrev = &g_PlayerHead;
g_PlayerHead.dpnidPlayer = DPNID_ALL_PLAYERS_GROUP;
_tcscpy( g_PlayerHead.strPlayerName, TEXT("Everyone") );
// Init circular linked list
ZeroMemory( &g_DataHead, sizeof(GAMEMSG_DATA_NODE) );
g_DataHead.pNext = &g_DataHead;
g_DataHead.pPrev = &g_DataHead;
// Read persistent state information from registry
RegCreateKeyEx( HKEY_CURRENT_USER, DPLAY_SAMPLE_KEY, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
&hDPlaySampleRegKey, NULL );
DXUtil_ReadStringRegKey( hDPlaySampleRegKey, TEXT("Player Name"),
g_strLocalPlayerName, MAX_PATH, TEXT("TestPlayer") );
DXUtil_ReadStringRegKey( hDPlaySampleRegKey, TEXT("Session Name"),
g_strSessionName, MAX_PATH, TEXT("TestGame") );
DXUtil_ReadStringRegKey( hDPlaySampleRegKey, TEXT("Preferred Provider"),
g_strPreferredProvider, MAX_PATH, TEXT("DirectPlay8 TCP/IP Service Provider") );
// Init COM so we can use CoCreateInstance
CoInitializeEx( NULL, COINIT_MULTITHREADED );
// Create helper class
g_pNetConnectWizard = new CNetConnectWizard( hInst, NULL, g_strAppName, &g_guidApp );
if( FAILED( hr = InitDirectPlay() ) )
{
DXTRACE_ERR( TEXT("InitDirectPlay"), hr );
MessageBox( NULL, TEXT("Failed initializing IDirectPlay8Peer. ")
TEXT("The sample will now quit."),
TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
return FALSE;
}
// Check if we were launched from a lobby client
if( g_bWasLobbyLaunched && g_pNetConnectWizard->HaveConnectionSettingsFromLobby() )
{
// If were lobby launched then DPL_MSGID_CONNECT has already been
// handled, so we can just tell the wizard to connect to the lobby
// that has sent us a DPL_MSGID_CONNECT msg.
if( FAILED( hr = g_pNetConnectWizard->ConnectUsingLobbySettings() ) )
{
DXTRACE_ERR( TEXT("ConnectUsingLobbySettings"), hr );
MessageBox( NULL, TEXT("Failed to connect using lobby settings. ")
TEXT("The sample will now quit."),
TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
bConnectSuccess = FALSE;
}
else
{
// Read information from g_pNetConnectWizard
_tcsncpy( g_strLocalPlayerName, g_pNetConnectWizard->GetPlayerName(), MAX_PLAYER_NAME );
g_strLocalPlayerName[MAX_PLAYER_NAME-1]=0;
bConnectSuccess = TRUE;
}
}
else
{
// If not lobby launched, prompt the user about the network
// connection and which session they would like to join or
// if they want to create a new one.
// Setup connection wizard
g_pNetConnectWizard->SetPlayerName( g_strLocalPlayerName );
g_pNetConnectWizard->SetSessionName( g_strSessionName );
g_pNetConnectWizard->SetPreferredProvider( g_strPreferredProvider );
// Do the connection wizard
hr = g_pNetConnectWizard->DoConnectWizard( FALSE );
if( FAILED( hr ) )
{
DXTRACE_ERR( TEXT("DoConnectWizard"), hr );
MessageBox( NULL, TEXT("Multiplayer connect failed. ")
TEXT("The sample will now quit."),
TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
bConnectSuccess = FALSE;
}
else if( hr == NCW_S_QUIT )
{
// The user canceled the Multiplayer connect, so quit
bConnectSuccess = FALSE;
}
else
{
bConnectSuccess = TRUE;
// Read information from g_pNetConnectWizard
_tcsncpy( g_strLocalPlayerName, g_pNetConnectWizard->GetPlayerName(), MAX_PLAYER_NAME );
g_strLocalPlayerName[MAX_PLAYER_NAME-1]=0;
_tcscpy( g_strSessionName, g_pNetConnectWizard->GetSessionName() );
_tcscpy( g_strPreferredProvider, g_pNetConnectWizard->GetPreferredProvider() );
// Write information to the registry
DXUtil_WriteStringRegKey( hDPlaySampleRegKey, TEXT("Player Name"), g_strLocalPlayerName );
DXUtil_WriteStringRegKey( hDPlaySampleRegKey, TEXT("Session Name"), g_strSessionName );
DXUtil_WriteStringRegKey( hDPlaySampleRegKey, TEXT("Preferred Provider"), g_strPreferredProvider );
}
}
if( bConnectSuccess )
{
// For this sample, we just start a simple dialog box game.
g_hrDialog = S_OK;
DialogBox( hInst, MAKEINTRESOURCE(IDD_MAIN_GAME), NULL,
(DLGPROC) SampleDlgProc );
}
// Close down process data thread
SetEvent( g_hShutdownEvent );
WaitForSingleObject( g_hProcessNetDataThread, INFINITE );
CloseHandle( g_hProcessNetDataThread );
if( FAILED( g_hrDialog ) )
{
if( g_hrDialog == DPNERR_CONNECTIONLOST )
{
MessageBox( NULL, TEXT("The DirectPlay session was lost. ")
TEXT("The sample will now quit."),
TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
}
else
{
DXTRACE_ERR( TEXT("DialogBox"), g_hrDialog );
MessageBox( NULL, TEXT("An error occured during the game. ")
TEXT("The sample will now quit."),
TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
}
}
// Cleanup DirectPlay and helper classes
g_pNetConnectWizard->Shutdown();
if( g_pDP )
{
g_pDP->Close(0);
SAFE_RELEASE( g_pDP );
}
if( g_pLobbiedApp )
{
g_pLobbiedApp->Close( 0 );
SAFE_RELEASE( g_pLobbiedApp );
}
// Don't delete the wizard until we know that
// DirectPlay is out of its message handlers.
// This will be true after Close() has been called.
SAFE_DELETE( g_pNetConnectWizard );
// Cleanup circular linked list, g_PlayerHead
{
EnterCriticalSection( &g_csPlayerList );
APP_PLAYER_INFO* pNode = g_PlayerHead.pNext;
APP_PLAYER_INFO* pDeleteNode;
while( pNode != &g_PlayerHead )
{
pDeleteNode = pNode;
pNode = pNode->pNext;
SAFE_DELETE( pDeleteNode );
}
LeaveCriticalSection( &g_csPlayerList );
}
// Cleanup circular linked list, g_DataHead
{
EnterCriticalSection( &g_csDataList );
GAMEMSG_DATA_NODE* pNode = g_DataHead.pNext;
GAMEMSG_DATA_NODE* pDeleteNode;
while( pNode != &g_DataHead )
{
pDeleteNode = pNode;
pNode = pNode->pNext;
SAFE_DELETE( pDeleteNode );
}
LeaveCriticalSection( &g_csDataList );
}
// Cleanup Win32 resources
RegCloseKey( hDPlaySampleRegKey );
CloseHandle( g_hShutdownEvent );
CloseHandle( g_hDPDataAvailEvent );
DeleteCriticalSection( &g_csPlayerList );
DeleteCriticalSection( &g_csDataList );
DeleteCriticalSection( &g_csPlayerContext );
CoUninitialize();
return TRUE;
}
//-----------------------------------------------------------------------------
// Name: InitDirectPlay()
// Desc:
//-----------------------------------------------------------------------------
HRESULT InitDirectPlay()
{
DPNHANDLE hLobbyLaunchedConnection = NULL;
HRESULT hr;
// Create IDirectPlay8Peer
if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Peer, NULL,
CLSCTX_INPROC_SERVER,
IID_IDirectPlay8Peer,
(LPVOID*) &g_pDP ) ) )
return DXTRACE_ERR( TEXT("CoCreateInstance"), hr );
// Create IDirectPlay8LobbiedApplication
if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8LobbiedApplication, NULL,
CLSCTX_INPROC_SERVER,
IID_IDirectPlay8LobbiedApplication,
(LPVOID*) &g_pLobbiedApp ) ) )
return DXTRACE_ERR( TEXT("CoCreateInstance"), hr );
// Init the helper class, now that g_pDP and g_pLobbiedApp are valid
g_pNetConnectWizard->Init( g_pDP, g_pLobbiedApp );
// Init IDirectPlay8Peer
if( FAILED( hr = g_pDP->Initialize( NULL, DirectPlayMessageHandler, 0 ) ) )
return DXTRACE_ERR( TEXT("Initialize"), hr );
// Init IDirectPlay8LobbiedApplication. Before this Initialize() returns
// a DPL_MSGID_CONNECT msg may come in to the DirectPlayLobbyMessageHandler
// so be prepared ahead of time.
if( FAILED( hr = g_pLobbiedApp->Initialize( NULL, DirectPlayLobbyMessageHandler,
&hLobbyLaunchedConnection, 0 ) ) )
return DXTRACE_ERR( TEXT("Initialize"), hr );
// IDirectPlay8LobbiedApplication::Initialize returns a handle to a connnection
// if we have been lobby launced. Initialize is guanteeded to return after
// the DPL_MSGID_CONNECT msg has been processed. So unless a we are expected
// multiple lobby connections, we do not need to remember the lobby connection
// handle since it will be recorded upon the DPL_MSGID_CONNECT msg.
g_bWasLobbyLaunched = ( hLobbyLaunchedConnection != NULL );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: SampleDlgProc()
// Desc: Handles dialog messages
//-----------------------------------------------------------------------------
INT_PTR CALLBACK SampleDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_INITDIALOG:
{
g_hDlg = hDlg;
if( FAILED( g_hrDialog = OnInitDialog( hDlg ) ) )
{
DXTRACE_ERR( TEXT("OnInitDialog"), g_hrDialog );
EndDialog( hDlg, 0 );
}
break;
}
case WM_APP_UPDATE_TARGETS:
{
// Display the player names in the UI
CheckDlgButton( hDlg, IDC_SEND_READY, (g_bSendingData) ? BST_CHECKED : BST_UNCHECKED );
PostMessage( hDlg, WM_COMMAND, IDC_SEND_READY, 0 );
FillTargetCombo( g_hDlg );
ReadCombos( g_hDlg );
break;
}
case WM_APP_APPEND_TEXT:
{
// Append a string to the edit control
TCHAR* strNewLogLine = (TCHAR*) wParam;
AppendTextToEditControl( g_hDlg, strNewLogLine );
SAFE_DELETE( strNewLogLine );
break;
}
case WM_COMMAND:
{
switch( LOWORD(wParam) )
{
case IDC_SEND_READY:
g_bSendingData = (IsDlgButtonChecked( hDlg, IDC_SEND_READY ) == BST_CHECKED );
if( g_bSendingData )
{
SetTimer( g_hDlg, TIMERID_NETWORK, g_dwTimeBetweenSends, NULL );
SetDlgItemText( hDlg, IDC_SEND_READY, TEXT("Sending...") );
}
else
{
KillTimer( g_hDlg, TIMERID_NETWORK );
SetDlgItemText( hDlg, IDC_SEND_READY, TEXT("Push to Send") );
}
break;
case IDCANCEL:
g_hrDialog = S_OK;
EndDialog( hDlg, 0 );
return TRUE;
case IDC_SEND_SIZE_COMBO:
case IDC_SEND_RATE_COMBO:
case IDC_SEND_TARGET_COMBO:
case IDC_CONNINFO_COMBO:
if( HIWORD(wParam) == CBN_SELENDOK )
ReadCombos( hDlg );
break;
}
break;
}
case WM_TIMER:
{
switch( wParam )
{
case TIMERID_NETWORK:
{
// Send network data
if( FAILED( g_hrDialog = SendNetworkData() ) )
{
DXTRACE_ERR( TEXT("SendNetworkData"), g_hrDialog );
EndDialog( hDlg, 0 );
}
break;
}
case TIMERID_STATS:
{
UpdateStats( hDlg );
break;
}
}
break;
}
}
return FALSE; // Didn't handle message
}
//-----------------------------------------------------------------------------
// Name: OnInitDialog()
// Desc: Inits the dialog.
//-----------------------------------------------------------------------------
HRESULT OnInitDialog( HWND hDlg )
{
// Load and set the icon
HICON hIcon = LoadIcon( g_hInst, MAKEINTRESOURCE( IDI_MAIN ) );
SendMessage( hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon ); // Set big icon
SendMessage( hDlg, WM_SETICON, ICON_SMALL, (LPARAM) hIcon ); // Set small icon
// Display local player's name
SetDlgItemText( hDlg, IDC_PLAYER_NAME, g_strLocalPlayerName );
// Start a thread to process the network data
g_hProcessNetDataThread = (HANDLE) _beginthreadex( NULL, 0, ProcessNetDataProc,
hDlg, 0, &g_dwProcessNetDataThreadID );
// Update the dialog box
SendMessage( hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
FillOneTimeCombos( hDlg );
ReadCombos( hDlg );
// Update the stats every second
SetTimer( hDlg, TIMERID_STATS, 1000, NULL );
if( g_pNetConnectWizard->IsHostPlayer() )
SetWindowText( hDlg, TEXT("DataRelay (Host)") );
else
SetWindowText( hDlg, TEXT("DataRelay") );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: FillTargetCombo()
// Desc: Fills the target combo with all of the players
//-----------------------------------------------------------------------------
VOID FillTargetCombo( HWND hDlg )
{
DWORD dwNumberOfActivePlayers = 0;
int nIndex;
int nCurSelect;
int nNewSelect = 0;
LONG_PTR lCurItemData;
HWND hTargetCombo;
HWND hInfoTargetCombo;
if( hDlg == NULL )
return;
hTargetCombo = GetDlgItem( hDlg, IDC_SEND_TARGET_COMBO );
if( hTargetCombo == NULL )
return;
nCurSelect = (int)SendMessage( hTargetCombo, CB_GETCURSEL, 0, 0 );
lCurItemData = (LONG_PTR)SendMessage( hTargetCombo, CB_GETITEMDATA, nCurSelect, 0 );
// Clear combo box
SendMessage( hTargetCombo, CB_RESETCONTENT, 0, 0 );
// Add "everyone"
nIndex = (int)SendMessage( hTargetCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("Everyone") );
SendMessage( hTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) &g_PlayerHead );
// Enter the player list CS, because we are about to read shared memory, and
// the data may be changed concurrently from the DirectPlay
// message handler threads
EnterCriticalSection( &g_csPlayerList );
// Add each player
APP_PLAYER_INFO* pNode = g_PlayerHead.pNext;
while( pNode != &g_PlayerHead )
{
if( (pNode->dwFlags & DPNPLAYER_LOCAL) == 0 )
{
nIndex = (int)SendMessage( hTargetCombo, CB_ADDSTRING, 0, (LPARAM) pNode->strPlayerName );
SendMessage( hTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) pNode );
if( lCurItemData == (LONG_PTR) pNode )
nNewSelect = nIndex;
}
dwNumberOfActivePlayers++;
pNode = pNode->pNext;
}
LeaveCriticalSection( &g_csPlayerList );
SendMessage( hTargetCombo, CB_SETCURSEL, nNewSelect, 0 );
// Update the number of players in game counter
TCHAR strNumberPlayers[32];
wsprintf( strNumberPlayers, TEXT("%d"), dwNumberOfActivePlayers );
SetDlgItemText( hDlg, IDC_NUM_PLAYERS, strNumberPlayers );
if( dwNumberOfActivePlayers > 1 )
{
EnableWindow( GetDlgItem( hDlg, IDC_SEND_READY ), TRUE );
}
else
{
EnableWindow( GetDlgItem( hDlg, IDC_SEND_READY ), FALSE );
CheckDlgButton( hDlg, IDC_SEND_READY, BST_UNCHECKED );
SetDlgItemText( hDlg, IDC_SEND_READY, TEXT("Push to Send") );
}
// Populate the connection info box
hInfoTargetCombo = GetDlgItem( hDlg, IDC_CONNINFO_COMBO );
nCurSelect = (int)SendMessage( hInfoTargetCombo, CB_GETCURSEL, 0, 0 );
lCurItemData = (LONG_PTR)SendMessage( hInfoTargetCombo, CB_GETITEMDATA, nCurSelect, 0 );
// Clear combo box
SendMessage( hInfoTargetCombo, CB_RESETCONTENT, 0, 0 );
// Add "none"
nIndex = (int)SendMessage( hInfoTargetCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("None") );
SendMessage( hInfoTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) &g_PlayerHead );
// Enter the player list CS, because we are about to read shared memory, and
// the data may be changed concurrently from the DirectPlay
// message handler threads
EnterCriticalSection( &g_csPlayerList );
// Add each player
pNode = g_PlayerHead.pNext;
nNewSelect = 0;
while( pNode != &g_PlayerHead )
{
if( (pNode->dwFlags & DPNPLAYER_LOCAL) == 0 )
{
nIndex = (int)SendMessage( hInfoTargetCombo, CB_ADDSTRING, 0, (LPARAM) pNode->strPlayerName );
SendMessage( hInfoTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) pNode );
if( lCurItemData == (LONG_PTR) pNode )
nNewSelect = nIndex;
}
dwNumberOfActivePlayers++;
pNode = pNode->pNext;
}
LeaveCriticalSection( &g_csPlayerList );
SendMessage( hInfoTargetCombo, CB_SETCURSEL, nNewSelect, 0 );
}
//-----------------------------------------------------------------------------
// Name: FillOneTimeCombos()
// Desc: Fill the unchanging UI combos box
//-----------------------------------------------------------------------------
VOID FillOneTimeCombos( HWND hDlg )
{
if( hDlg == NULL )
return;
HWND hRateCombo = GetDlgItem( hDlg, IDC_SEND_RATE_COMBO );
SendMessage( hRateCombo, WM_SETREDRAW, FALSE, 0 );
SendMessage( hRateCombo, CB_RESETCONTENT, 0, 0 );
SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("1000") );
SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("500") );
SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("250") );
SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("100") );
SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("50") );
SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("0") );
SendMessage( hRateCombo, WM_SETREDRAW, TRUE, 0 );
SendMessage( hRateCombo, CB_SETCURSEL, 0, 0 );
HWND hSizeCombo = GetDlgItem( hDlg, IDC_SEND_SIZE_COMBO );
SendMessage( hSizeCombo, WM_SETREDRAW, FALSE, 0 );
SendMessage( hSizeCombo, CB_RESETCONTENT, 0, 0 );
SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("512") );
SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("256") );
SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("128") );
SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("64") );
SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("32") );
SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("16") );
SendMessage( hSizeCombo, WM_SETREDRAW, TRUE, 0 );
SendMessage( hSizeCombo, CB_SETCURSEL, 0, 0 );
HWND hTimeoutCombo = GetDlgItem( hDlg, IDC_SEND_TIMEOUT_COMBO );
SendMessage( hTimeoutCombo, WM_SETREDRAW, FALSE, 0 );
SendMessage( hTimeoutCombo, CB_RESETCONTENT, 0, 0 );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("5") );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("10") );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("20") );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("50") );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("100") );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("250") );
SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("500") );
SendMessage( hTimeoutCombo, WM_SETREDRAW, TRUE, 0 );
SendMessage( hTimeoutCombo, CB_SETCURSEL, 2, 0 );
}
//-----------------------------------------------------------------------------
// Name: ReadCombos()
// Desc: Reads the state of the combos and updates the global vars
//-----------------------------------------------------------------------------
VOID ReadCombos( HWND hDlg )
{
TCHAR strText[128];
int nCurSelect;
// Get target player
HWND hTargetCombo = GetDlgItem( hDlg, IDC_SEND_TARGET_COMBO );
nCurSelect = (int)SendMessage( hTargetCombo, CB_GETCURSEL, 0, 0 );
if( nCurSelect != CB_ERR )
{
g_pTargetPlayer = (APP_PLAYER_INFO*) SendMessage( hTargetCombo, CB_GETITEMDATA,
nCurSelect, 0 );
}
// Get rate (in ms)
HWND hRateCombo = GetDlgItem( hDlg, IDC_SEND_RATE_COMBO );
nCurSelect = (int)SendMessage( hRateCombo, CB_GETCURSEL, 0, 0 );
if( nCurSelect != CB_ERR )
{
SendMessage( hRateCombo, CB_GETLBTEXT, nCurSelect, (LPARAM) strText );
g_dwTimeBetweenSends = _ttoi( strText );
KillTimer( g_hDlg, TIMERID_NETWORK );
SetTimer( g_hDlg, TIMERID_NETWORK, g_dwTimeBetweenSends, NULL );
}
// Get size (in bytes)
HWND hSizeCombo = GetDlgItem( hDlg, IDC_SEND_SIZE_COMBO );
nCurSelect = (int)SendMessage( hSizeCombo, CB_GETCURSEL, 0, 0 );
if( nCurSelect != CB_ERR )
{
SendMessage( hSizeCombo, CB_GETLBTEXT, nCurSelect, (LPARAM) strText );
g_dwSendSize = _ttoi( strText );
}
// Get timeout (in ms)
HWND hTimeoutCombo = GetDlgItem( hDlg, IDC_SEND_TIMEOUT_COMBO );
nCurSelect = (int)SendMessage( hTimeoutCombo, CB_GETCURSEL, 0, 0 );
if( nCurSelect != CB_ERR )
{
SendMessage( hTimeoutCombo, CB_GETLBTEXT, nCurSelect, (LPARAM) strText );
g_dwSendTimeout = _ttoi( strText );
}
// Get the ConnectionInfo Target player
HWND hConnTargetCombo = GetDlgItem( hDlg, IDC_CONNINFO_COMBO );
nCurSelect = (int)SendMessage( hConnTargetCombo, CB_GETCURSEL, 0, 0 );
if( nCurSelect != CB_ERR )
{
g_pConnInfoTargetPlayer = (APP_PLAYER_INFO*) SendMessage( hConnTargetCombo, CB_GETITEMDATA,
nCurSelect, 0 );
}
}
//-----------------------------------------------------------------------------
// Name: DirectPlayMessageHandler
// Desc: Handler for DirectPlay messages. This function is called by
// the DirectPlay message handler pool of threads, so be care of thread
// synchronization problems with shared memory
//-----------------------------------------------------------------------------
HRESULT WINAPI DirectPlayMessageHandler( PVOID pvUserContext,
DWORD dwMessageId,
PVOID pMsgBuffer )
{
// Try not to stay in this message handler for too long, otherwise
// there will be a backlog of data. The best solution is to
// queue data as it comes in, and then handle it on other threads
// as this sample shows
// This function is called by the DirectPlay message handler pool of
// threads, so be care of thread synchronization problems with shared memory
HRESULT hReturn = S_OK;
switch( dwMessageId )
{
case DPN_MSGID_CREATE_PLAYER:
{
HRESULT hr;
PDPNMSG_CREATE_PLAYER pCreatePlayerMsg;
pCreatePlayerMsg = (PDPNMSG_CREATE_PLAYER) pMsgBuffer;
APP_PLAYER_INFO* pPlayerInfo;
if( FAILED( hr = LinkPlayer( pCreatePlayerMsg->dpnidPlayer,
&pPlayerInfo ) ) )
return DXTRACE_ERR( TEXT("LinkPlayer"), hr );
// Tell DirectPlay to store this pPlayerInfo
// pointer in the pvPlayerContext.
pCreatePlayerMsg->pvPlayerContext = pPlayerInfo;
// Post a message to the dialog thread to update the
// UI. This keeps the DirectPlay message handler
// from blocking
if( g_hDlg != NULL )
PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
break;
}
case DPN_MSGID_DESTROY_PLAYER:
{
PDPNMSG_DESTROY_PLAYER pDestroyPlayerMsg;
pDestroyPlayerMsg = (PDPNMSG_DESTROY_PLAYER)pMsgBuffer;
APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pDestroyPlayerMsg->pvPlayerContext;
// Enter the player list CS, because we are about to
// modify shared memory, and the data may be changed
// concurrently from other DirectPlay message handler threads
EnterCriticalSection( &g_csPlayerList );
// Unlink pPlayerInfo from the chain, m_PlayerHead.
pPlayerInfo->pPrev->pNext = pPlayerInfo->pNext;
pPlayerInfo->pNext->pPrev = pPlayerInfo->pPrev;
LeaveCriticalSection( &g_csPlayerList );
// Stop sending if the target was destroyed
if( g_pTargetPlayer )
{
if( g_pTargetPlayer->dpnidPlayer == pPlayerInfo->dpnidPlayer )
g_bSendingData = FALSE;
}
PLAYER_LOCK(); // enter player context CS
PLAYER_RELEASE( pPlayerInfo ); // Release player and cleanup if needed
PLAYER_UNLOCK(); // leave player context CS
// Post a message to the dialog thread to update the
// UI. This keeps the DirectPlay message handler
// from blocking
if( g_hDlg != NULL )
PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
break;
}
case DPN_MSGID_TERMINATE_SESSION:
{
PDPNMSG_TERMINATE_SESSION pTerminateSessionMsg;
pTerminateSessionMsg = (PDPNMSG_TERMINATE_SESSION)pMsgBuffer;
g_hrDialog = DPNERR_CONNECTIONLOST;
EndDialog( g_hDlg, 0 );
break;
}
case DPN_MSGID_HOST_MIGRATE:
{
PDPNMSG_HOST_MIGRATE pHostMigrateMsg;
pHostMigrateMsg = (PDPNMSG_HOST_MIGRATE)pMsgBuffer;
if( pHostMigrateMsg->dpnidNewHost == g_dpnidLocalPlayer )
SetWindowText( g_hDlg, TEXT("DataRelay (Host)") );
break;
}
case DPN_MSGID_RECEIVE:
{
PDPNMSG_RECEIVE pReceiveMsg;
pReceiveMsg = (PDPNMSG_RECEIVE)pMsgBuffer;
APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pReceiveMsg->pvPlayerContext;
if( NULL == pPlayerInfo )
break;
GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*) pReceiveMsg->pReceiveData;
if( pMsg->dwType == GAME_MSGID_GAMEPACKET )
{
GAMEMSG_GENERIC* pDataMsg = (GAMEMSG_GENERIC*) pMsg;
// Make a new GAMEMSG_DATA_NODE and hand it off to the
// app worker thread. It will process the node, and
// then update the UI to show that the packet was processed
GAMEMSG_DATA_NODE* pDataMsgNode = new GAMEMSG_DATA_NODE;
ZeroMemory( pDataMsgNode, sizeof(GAMEMSG_DATA_NODE) );
pDataMsgNode->dwType = DATA_TYPE_NETPACKET_RECIEVE;
pDataMsgNode->dwPacketId = pDataMsg->dwPacketId;
pDataMsgNode->pDataMsg = pDataMsg;
pDataMsgNode->dwReceiveDataSize = pReceiveMsg->dwReceiveDataSize;
pDataMsgNode->pPlayerFrom = pPlayerInfo;
pDataMsgNode->hBufferHandle = pReceiveMsg->hBufferHandle;
// Enter the data list CS, because we are about to modify shared memory, and
// the data may be changed concurrently from other DirectPlay
// message handler threads, as well as the the app worker thread
EnterCriticalSection( &g_csDataList );
g_dwDataRecieved += pReceiveMsg->dwReceiveDataSize;
// Then add it to the circular linked list, g_DataHead
// so it can be processed by a worker thread
pDataMsgNode->pNext = &g_DataHead;
pDataMsgNode->pPrev = g_DataHead.pPrev;
g_DataHead.pPrev->pNext = pDataMsgNode;
g_DataHead.pPrev = pDataMsgNode;
LeaveCriticalSection( &g_csDataList );
// Tell the app worker thread that there
// is new network data availible
SetEvent( g_hDPDataAvailEvent );
// Tell DirectPlay to assume that ownership of the buffer
// has been transferred to the application, and so it will
// neither free nor modify it until ownership is returned
// to DirectPlay through the ReturnBuffer() call.
hReturn = DPNSUCCESS_PENDING;
}
break;
}
case DPN_MSGID_SEND_COMPLETE:
{
PDPNMSG_SEND_COMPLETE pSendCompleteMsg;
pSendCompleteMsg = (PDPNMSG_SEND_COMPLETE)pMsgBuffer;
GAMEMSG_GENERIC* pGameMsg;
pGameMsg = (GAMEMSG_GENERIC*) pSendCompleteMsg->pvUserContext;
if( pSendCompleteMsg->hResultCode == DPNERR_TIMEDOUT )
{
// Make a new GAMEMSG_DATA_NODE and hand it off to the
// app worker thread. It will process the node, and
// then update the UI to show that the packet timed out
GAMEMSG_DATA_NODE* pDataMsgNode = new GAMEMSG_DATA_NODE;
ZeroMemory( pDataMsgNode, sizeof(GAMEMSG_DATA_NODE) );
pDataMsgNode->dwType = DATA_TYPE_NETPACKET_TIMEOUT;
pDataMsgNode->dwPacketId = pGameMsg->dwPacketId;
// Enter the data list CS, because we are about to modify shared memory, and
// the data may be changed concurrently from other DirectPlay
// message handler threads, as well as the the app worker thread
EnterCriticalSection( &g_csDataList );
// Then add it to the circular linked list, g_DataHead
// so it can be processed by a worker thread
pDataMsgNode->pNext = &g_DataHead;
pDataMsgNode->pPrev = g_DataHead.pPrev;
g_DataHead.pPrev->pNext = pDataMsgNode;
g_DataHead.pPrev = pDataMsgNode;
LeaveCriticalSection( &g_csDataList );
// Tell the app worker thread that there
// is new data availible.
SetEvent( g_hDPDataAvailEvent );
}
SAFE_DELETE( pGameMsg );
break;
}
}
// Make sure the DirectPlay MessageHandler calls the CNetConnectWizard handler,
// so it can be informed of messages such as DPN_MSGID_ENUM_HOSTS_RESPONSE.
if( DPNSUCCESS_PENDING != hReturn && SUCCEEDED(hReturn) && g_pNetConnectWizard )
hReturn = g_pNetConnectWizard->MessageHandler( pvUserContext, dwMessageId, pMsgBuffer );
return hReturn;
}
//-----------------------------------------------------------------------------
// Name: LinkPlayer()
// Desc: Link a new player to the circular linked list, g_PlayerHead. This
// is called by the DirectPlay message handler threads, so we enter a
// CS to avoid thread syncoronization problems.
//-----------------------------------------------------------------------------
HRESULT LinkPlayer( DPNID dpnid, APP_PLAYER_INFO** ppPlayerInfo )
{
HRESULT hr;
// Create a new and fill in a APP_PLAYER_INFO
APP_PLAYER_INFO* pPlayerInfo = new APP_PLAYER_INFO;
pPlayerInfo->dpnidPlayer = dpnid;
pPlayerInfo->lRefCount = 1;
// Get the peer info and extract its name
DWORD dwSize = 0;
DPN_PLAYER_INFO* pdpPlayerInfo = NULL;
hr = DPNERR_CONNECTING;
// GetPeerInfo might return DPNERR_CONNECTING when connecting,
// so just keep calling it if it does
while( hr == DPNERR_CONNECTING )
hr = g_pDP->GetPeerInfo( dpnid, pdpPlayerInfo, &dwSize, 0 );
if( hr == DPNERR_BUFFERTOOSMALL )
{
pdpPlayerInfo = (DPN_PLAYER_INFO*) new BYTE[ dwSize ];
ZeroMemory( pdpPlayerInfo, dwSize );
pdpPlayerInfo->dwSize = sizeof(DPN_PLAYER_INFO);
hr = g_pDP->GetPeerInfo( dpnid, pdpPlayerInfo, &dwSize, 0 );
if( SUCCEEDED(hr) )
{
// This stores a extra TCHAR copy of the player name for
// easier access. This will be redundent copy since DPlay
// also keeps a copy of the player name in GetPeerInfo()
DXUtil_ConvertWideStringToGeneric( pPlayerInfo->strPlayerName,
pdpPlayerInfo->pwszName, MAX_PLAYER_NAME );
pPlayerInfo->dwFlags = pdpPlayerInfo->dwPlayerFlags;
if( pdpPlayerInfo->dwPlayerFlags & DPNPLAYER_LOCAL )
g_dpnidLocalPlayer = dpnid;
}
SAFE_DELETE_ARRAY( pdpPlayerInfo );
}
// Enter the player list CS, because we are about to modify shared memory, and
// the data may be changed concurrently from the DirectPlay
// message handler threads
EnterCriticalSection( &g_csPlayerList );
// Then add it to the circular linked list, g_PlayerHead,
pPlayerInfo->pNext = g_PlayerHead.pNext;
pPlayerInfo->pPrev = &g_PlayerHead;
g_PlayerHead.pNext->pPrev = pPlayerInfo;
g_PlayerHead.pNext = pPlayerInfo;
LeaveCriticalSection( &g_csPlayerList );
if( ppPlayerInfo )
*ppPlayerInfo = pPlayerInfo;
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: DirectPlayLobbyMessageHandler
// Desc: Handler for DirectPlay lobby messages. This function is called by
// the DirectPlay lobby message handler pool of threads, so be careful of
// thread synchronization problems with shared memory
//-----------------------------------------------------------------------------
HRESULT WINAPI DirectPlayLobbyMessageHandler( PVOID pvUserContext,
DWORD dwMessageId,
PVOID pMsgBuffer )
{
switch( dwMessageId )
{
case DPL_MSGID_CONNECT:
{
PDPL_MESSAGE_CONNECT pConnectMsg;
pConnectMsg = (PDPL_MESSAGE_CONNECT)pMsgBuffer;
// The CNetConnectWizard will handle this message for us,
// so there is nothing we need to do here for this simple
// sample.
break;
}
case DPL_MSGID_DISCONNECT:
{
PDPL_MESSAGE_DISCONNECT pDisconnectMsg;
pDisconnectMsg = (PDPL_MESSAGE_DISCONNECT)pMsgBuffer;
// We should free any data associated with the lobby
// client here, but there is none.
break;
}
case DPL_MSGID_RECEIVE:
{
PDPL_MESSAGE_RECEIVE pReceiveMsg;
pReceiveMsg = (PDPL_MESSAGE_RECEIVE)pMsgBuffer;
// The lobby client sent us data. This sample doesn't
// expected data from the client, but it is useful
// for more complex apps.
break;
}
case DPL_MSGID_CONNECTION_SETTINGS:
{
PDPL_MESSAGE_CONNECTION_SETTINGS pConnectionStatusMsg;
pConnectionStatusMsg = (PDPL_MESSAGE_CONNECTION_SETTINGS)pMsgBuffer;
// The lobby client has changed the connection settings.
// This simple sample doesn't handle this, but more complex apps may
// want to.
break;
}
}
// Make sure the DirectPlay MessageHandler calls the CNetConnectWizard handler,
// so the wizard can be informed of lobby messages such as DPL_MSGID_CONNECT
if( g_pNetConnectWizard )
return g_pNetConnectWizard->LobbyMessageHandler( pvUserContext, dwMessageId,
pMsgBuffer );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: SendNetworkData()
// Desc: Sends a network packet of the specified size to the specified target.
// This is called by the dialog UI thread.
//-----------------------------------------------------------------------------
HRESULT SendNetworkData()
{
HRESULT hr;
if( !g_bSendingData ||
IsDlgButtonChecked( g_hDlg, IDC_SEND_READY ) == BST_UNCHECKED )
{
KillTimer( g_hDlg, TIMERID_NETWORK );
return S_OK;
}
if( g_pTargetPlayer == NULL )
return E_FAIL;
GAMEMSG_GENERIC* pGameMsg = NULL;
DWORD dwBufferSize;
// Create a game packet based on the selected choices
switch( g_dwSendSize )
{
case 512:
{
GAMEMSG_DATA_512* pMsg512 = new GAMEMSG_DATA_512;
FillMemory( pMsg512->pBuffer, sizeof(pMsg512->pBuffer), 1 );
pGameMsg = pMsg512;
dwBufferSize = sizeof(GAMEMSG_DATA_512);
break;
}
case 256:
{
GAMEMSG_DATA_256* pMsg256 = new GAMEMSG_DATA_256;
FillMemory( pMsg256->pBuffer, sizeof(pMsg256->pBuffer), 2 );
pGameMsg = pMsg256;
dwBufferSize = sizeof(GAMEMSG_DATA_256);
break;
}
case 128:
{
GAMEMSG_DATA_128* pMsg128 = new GAMEMSG_DATA_128;
FillMemory( pMsg128->pBuffer, sizeof(pMsg128->pBuffer), 3 );
pGameMsg = pMsg128;
dwBufferSize = sizeof(GAMEMSG_DATA_128);
break;
}
case 64:
{
GAMEMSG_DATA_64* pMsg64 = new GAMEMSG_DATA_64;
FillMemory( pMsg64->pBuffer, sizeof(pMsg64->pBuffer), 4 );
pGameMsg = pMsg64;
dwBufferSize = sizeof(GAMEMSG_DATA_64);
break;
}
case 32:
{
GAMEMSG_DATA_32* pMsg32 = new GAMEMSG_DATA_32;
FillMemory( pMsg32->pBuffer, sizeof(pMsg32->pBuffer), 5 );
pGameMsg = pMsg32;
dwBufferSize = sizeof(GAMEMSG_DATA_32);
break;
}
default:
case 16:
{
GAMEMSG_DATA_16* pMsg16 = new GAMEMSG_DATA_16;
FillMemory( pMsg16->pBuffer, sizeof(pMsg16->pBuffer), 6 );
pGameMsg = pMsg16;
dwBufferSize = sizeof(GAMEMSG_DATA_16);
break;
}
}
// Update the rest of the game msg
g_dwPacketId++;
pGameMsg->dwPacketId = g_dwPacketId;
pGameMsg->dwType = GAME_MSGID_GAMEPACKET;
// Make a new GAMEMSG_DATA_NODE and hand it off to the
// app worker thread. It will process the node, and
// then update the UI to show that a packet was sent
GAMEMSG_DATA_NODE* pDataMsgNode = new GAMEMSG_DATA_NODE;
ZeroMemory( pDataMsgNode, sizeof(GAMEMSG_DATA_NODE) );
pDataMsgNode->dwType = DATA_TYPE_NETPACKET_SENT;
pDataMsgNode->dwPacketId = pGameMsg->dwPacketId;
pDataMsgNode->dwReceiveDataSize = dwBufferSize;
// Enter the data list CS, because we are about to modify shared memory, and
// the data may be changed concurrently from other DirectPlay
// message handler threads, as well as the the app worker thread
EnterCriticalSection( &g_csDataList );
// Then add it to the circular linked list, g_DataHead
// so it can be processed by a worker thread
pDataMsgNode->pNext = &g_DataHead;
pDataMsgNode->pPrev = g_DataHead.pPrev;
g_DataHead.pPrev->pNext = pDataMsgNode;
g_DataHead.pPrev = pDataMsgNode;
LeaveCriticalSection( &g_csDataList );
// This var is only accessed by the dialog
// thread, so it is safe to access it directly
g_dwDataSent += dwBufferSize;
DPN_BUFFER_DESC bufferDesc;
bufferDesc.dwBufferSize = dwBufferSize;
bufferDesc.pBufferData = (BYTE*) pGameMsg;
DPNHANDLE hAsync;
hr = g_pDP->SendTo( g_pTargetPlayer->dpnidPlayer, &bufferDesc, 1,
g_dwSendTimeout, pGameMsg, &hAsync,
DPNSEND_NOLOOPBACK | DPNSEND_NOCOPY );
// Ignore all errors except DPNERR_INVALIDPLAYER
if( hr == DPNERR_INVALIDPLAYER )
{
// Stop sending if the target left game
g_bSendingData = FALSE;
PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
return S_OK;
}
// Tell the app worker thread that there
// is new data availible.
SetEvent( g_hDPDataAvailEvent );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: ProcessNetDataProc()
// Desc: Worker thread that processes data found in g_DataHead,
// and updates the UI state based on the data
//-----------------------------------------------------------------------------
UINT WINAPI ProcessNetDataProc( LPVOID lpParameter )
{
HWND hDlg = (HWND) lpParameter;
DWORD dwResult;
BOOL bDone = FALSE;
HANDLE ahHandles[2];
ahHandles[0] = g_hDPDataAvailEvent;
ahHandles[1] = g_hShutdownEvent;
while( !bDone )
{
dwResult = WaitForMultipleObjects( 2, ahHandles, FALSE, INFINITE );
switch( dwResult )
{
case WAIT_OBJECT_0 + 0:
{
// g_hDPDataAvailEvent is signaled, so there is
// network data available to process
if( FAILED( g_hrDialog = ProcessData() ) )
{
DXTRACE_ERR( TEXT("ProcessData"), g_hrDialog );
bDone = TRUE;
}
break;
}
case WAIT_OBJECT_0 + 1:
{
// g_hShutdownEvent is signaled, so shut down
bDone = TRUE;
break;
}
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Name: ProcessData()
// Desc: This is called by the app worker thread to process the data found in
// g_DataHead. The DirectPlay message handler threads add new nodes
// upon events such as packet packet recieve/send or timeout.
//-----------------------------------------------------------------------------
HRESULT ProcessData()
{
TCHAR* strNewLogLine = NULL;
// Enter the data list CS, because we are about to read shared memory, and
// the data may be changed concurrently from the DirectPlay
// message handler threads
EnterCriticalSection( &g_csDataList );
GAMEMSG_DATA_NODE* pDeleteNode;
GAMEMSG_DATA_NODE* pNode = g_DataHead.pNext;
while( pNode != &g_DataHead )
{
// Allocate a string on the heap, and fill it
// with text about what happened.
strNewLogLine = new TCHAR[MAX_PATH];
switch( pNode->dwType )
{
case DATA_TYPE_NETPACKET_RECIEVE:
wsprintf( strNewLogLine, TEXT("Got %s's #%d (%d bytes)\r\n"),
pNode->pPlayerFrom->strPlayerName,
pNode->dwPacketId,
pNode->dwReceiveDataSize );
// The app can do more process of the network
// packet here here -- i.e. change the
// state of the game. This simple sample just
// updates the UI.
break;
case DATA_TYPE_NETPACKET_SENT:
wsprintf( strNewLogLine, TEXT("Sending #%d to %s (%d bytes)\r\n"),
pNode->dwPacketId,
g_pTargetPlayer->strPlayerName,
pNode->dwReceiveDataSize );
break;
case DATA_TYPE_NETPACKET_TIMEOUT:
wsprintf( strNewLogLine, TEXT("Packet #%d timed out\r\n"),
pNode->dwPacketId );
break;
}
// Post a message to the dialog thread, so it can
// take the string on the heap, and appead it to the log.
// If the log is appended from this thread, then if the dialog
// thread tries to enter the g_csDataList CS, then it will be
// locked until this thread release it, and the
// AppendTextToEditControl function relies on reponses from the
// dialog thread, so a race condition is possible unless
// the dialog thread itself appeads the text.
PostMessage( g_hDlg, WM_APP_APPEND_TEXT, (WPARAM) strNewLogLine, 0 );
// The dialog thread will free the string
strNewLogLine = NULL;
if( pNode->hBufferHandle != NULL )
{
// Done with the buffer, so return it DirectPlay,
// so that the memory can be reused
g_pDP->ReturnBuffer( pNode->hBufferHandle,0 );
}
// Unlink pPlayerInfo from the chain, g_DataHead.
pNode->pPrev->pNext = pNode->pNext;
pNode->pNext->pPrev = pNode->pPrev;
pDeleteNode = pNode;
pNode = pNode->pNext;
assert( pDeleteNode != &g_DataHead );
SAFE_DELETE( pDeleteNode );
}
LeaveCriticalSection( &g_csDataList );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: AppendTextToEditControl()
// Desc: Appends a string of text to the edit control
//-----------------------------------------------------------------------------
VOID AppendTextToEditControl( HWND hDlg, TCHAR* strNewLogLine )
{
static TCHAR strText[1024*10];
HWND hEdit = GetDlgItem( hDlg, IDC_LOG_EDIT );
SendMessage( hEdit, WM_SETREDRAW, FALSE, 0 );
GetWindowText( hEdit, strText, 1024*9 );
_tcscat( strText, strNewLogLine );
int nSecondLine = 0;
if( SendMessage( hEdit, EM_GETLINECOUNT, 0, 0 ) > 18 )
nSecondLine = (int)SendMessage( hEdit, EM_LINEINDEX, 1, 0 );
SetWindowText( hEdit, &strText[nSecondLine] );
SendMessage( hEdit, WM_SETREDRAW, TRUE, 0 );
InvalidateRect( hEdit, NULL, TRUE );
UpdateWindow( hEdit );
}
//-----------------------------------------------------------------------------
// Name: UpdateConnectionInfo()
// Desc: Using the info buffer passed in, display some interesting tidbits about
// the connection.
//-----------------------------------------------------------------------------
VOID UpdateConnectionInfo( HWND hDlg, PDPN_CONNECTION_INFO pConnectionInfo,
DWORD dwHighMessages, DWORD dwHighBytes,
DWORD dwNormalMessages, DWORD dwNormalBytes,
DWORD dwLowMessages, DWORD dwLowBytes )
{
TCHAR strText[1024];
int nDrops;
int nSends;
HWND hEdit = GetDlgItem( hDlg, IDC_CONNINFO_EDIT );
int nThumbIndex = GetScrollPos( hEdit, SB_VERT );
if( pConnectionInfo )
{
nDrops = pConnectionInfo->dwPacketsDropped + pConnectionInfo->dwPacketsRetried;
nDrops *= 10000;
nSends = pConnectionInfo->dwPacketsSentGuaranteed + pConnectionInfo->dwPacketsSentNonGuaranteed;
if(nSends)
nDrops /= nSends; // percent nDrops * 100
_stprintf(strText,
TEXT("Send Queue Messages High Priority=%d\r\n") \
TEXT("Send Queue Bytes High Priority=%d\r\n") \
TEXT("Send Queue Messages Normal Priority=%d\r\n") \
TEXT("Send Queue Bytes Normal Priority=%d\r\n") \
TEXT("Send Queue Messages Low Priority=%d\r\n") \
TEXT("Send Queue Bytes Low Priority=%d\r\n") \
TEXT("Round Trip Latency MS=%dms\r\n") \
TEXT("Throughput BPS=%d\r\n") \
TEXT("Peak Throughput BPS=%d\r\n") \
\
TEXT("Bytes Sent Guaranteed=%d\r\n") \
TEXT("Packets Sent Guaranteed=%d\r\n") \
TEXT("Bytes Sent Non-Guaranteed=%d\r\n") \
TEXT("Packets Sent Non-Guaranteed=%d\r\n") \
\
TEXT("Bytes Retried Guaranteed=%d\r\n") \
TEXT("Packets Retried Guaranteed=%d\r\n") \
TEXT("Bytes Dropped Non-Guaranteed=%d\r\n") \
TEXT("Packets Dropped Non-Guaranteed=%d\r\n") \
\
TEXT("Messages Transmitted High Priority=%d\r\n") \
TEXT("Messages Timed Out High Priority=%d\r\n") \
TEXT("Messages Transmitted Normal Priority=%d\r\n") \
TEXT("Messages Timed Out Normal Priority=%d\r\n") \
TEXT("Messages Transmitted Low Priority=%d\r\n") \
TEXT("Messages Timed Out Low Priority=%d\r\n") \
\
TEXT("Bytes Received Guaranteed=%d\r\n") \
TEXT("Packets Received Guaranteed=%d\r\n") \
TEXT("Bytes Received Non-Guaranteed=%d\r\n") \
TEXT("Packets Received Non-Guaranteed=%d\r\n") \
TEXT("Messages Received=%d\r\n") \
\
TEXT("Loss Rate=%d.%02d%%\r\n"),
dwHighMessages, dwHighBytes,
dwNormalMessages, dwNormalBytes,
dwLowMessages, dwLowBytes,
pConnectionInfo->dwRoundTripLatencyMS,
pConnectionInfo->dwThroughputBPS,
pConnectionInfo->dwPeakThroughputBPS,
pConnectionInfo->dwBytesSentGuaranteed,
pConnectionInfo->dwPacketsSentGuaranteed,
pConnectionInfo->dwBytesSentNonGuaranteed,
pConnectionInfo->dwPacketsSentNonGuaranteed,
pConnectionInfo->dwBytesRetried,
pConnectionInfo->dwPacketsRetried,
pConnectionInfo->dwBytesDropped,
pConnectionInfo->dwPacketsDropped,
pConnectionInfo->dwMessagesTransmittedHighPriority,
pConnectionInfo->dwMessagesTimedOutHighPriority,
pConnectionInfo->dwMessagesTransmittedNormalPriority,
pConnectionInfo->dwMessagesTimedOutNormalPriority,
pConnectionInfo->dwMessagesTransmittedLowPriority,
pConnectionInfo->dwMessagesTimedOutLowPriority,
pConnectionInfo->dwBytesReceivedGuaranteed,
pConnectionInfo->dwPacketsReceivedGuaranteed,
pConnectionInfo->dwBytesReceivedNonGuaranteed,
pConnectionInfo->dwPacketsReceivedNonGuaranteed,
pConnectionInfo->dwMessagesReceived,
(nDrops/100), (nDrops % 100) );
}
else
{
strText[0] = 0;
}
SetWindowText( hEdit, strText );
SendMessage( hEdit, EM_LINESCROLL, 0, nThumbIndex );
}
//-----------------------------------------------------------------------------
// Name: UpdateStats()
// Desc:
//-----------------------------------------------------------------------------
VOID UpdateStats( HWND hDlg )
{
HRESULT hr;
static DWORD s_dwLastTime = timeGetTime();
DWORD dwCurTime = timeGetTime();
if( (dwCurTime - s_dwLastTime) < 200 )
return;
TCHAR strDataIn[32];
TCHAR strDataOut[32];
FLOAT fSecondsPassed = (dwCurTime - s_dwLastTime) / 1000.0f;
FLOAT fDataIn = g_dwDataRecieved / fSecondsPassed;
FLOAT fDataOut = g_dwDataSent / fSecondsPassed;
s_dwLastTime = dwCurTime;
g_dwDataRecieved = 0;
g_dwDataSent = 0;
_stprintf( strDataIn, TEXT("%0.1f BPS"), fDataIn );
_stprintf( strDataOut, TEXT("%0.1f BPS"), fDataOut );
SetDlgItemText( hDlg, IDC_ACTUAL_SEND_RATE, strDataOut );
SetDlgItemText( hDlg, IDC_ACTUAL_RECIEVE_RATE, strDataIn );
// If a player has been selected for Connection Info Display we will handle it next
if( g_pConnInfoTargetPlayer->dpnidPlayer != 0 )
{
// Call GetConnectionInfo and display results
DPN_CONNECTION_INFO dpnConnectionInfo;
ZeroMemory( &dpnConnectionInfo, sizeof(DPN_CONNECTION_INFO) );
dpnConnectionInfo.dwSize = sizeof(DPN_CONNECTION_INFO);
hr = g_pDP->GetConnectionInfo( g_pConnInfoTargetPlayer->dpnidPlayer,
&dpnConnectionInfo,
0);
if( SUCCEEDED(hr) )
{
DWORD dwHighMessages, dwHighBytes;
DWORD dwNormalMessages, dwNormalBytes;
DWORD dwLowMessages, dwLowBytes;
hr = g_pDP->GetSendQueueInfo( g_pConnInfoTargetPlayer->dpnidPlayer,
&dwHighMessages, &dwHighBytes,
DPNGETSENDQUEUEINFO_PRIORITY_HIGH );
if( FAILED(hr) )
DXTRACE_ERR( TEXT("GetSendQueueInfo"), hr );
hr = g_pDP->GetSendQueueInfo( g_pConnInfoTargetPlayer->dpnidPlayer,
&dwNormalMessages, &dwNormalBytes,
DPNGETSENDQUEUEINFO_PRIORITY_NORMAL );
if( FAILED(hr) )
DXTRACE_ERR( TEXT("GetSendQueueInfo"), hr );
hr = g_pDP->GetSendQueueInfo( g_pConnInfoTargetPlayer->dpnidPlayer,
&dwLowMessages, &dwLowBytes,
DPNGETSENDQUEUEINFO_PRIORITY_LOW );
if( FAILED(hr) )
DXTRACE_ERR( TEXT("GetSendQueueInfo"), hr );
UpdateConnectionInfo( hDlg, &dpnConnectionInfo,
dwHighMessages, dwHighBytes,
dwNormalMessages, dwNormalBytes,
dwLowMessages, dwLowBytes );
}
else
{
// If the player goes away, the set the target to none
SendDlgItemMessage( hDlg, IDC_CONNINFO_COMBO, CB_SETCURSEL, 0, 0 );
ReadCombos( hDlg );
}
}
else
{
// Clear the conn info window if no connection is selected to display
UpdateConnectionInfo( hDlg, NULL, 0, 0, 0, 0, 0, 0 );
}
}