//----------------------------------------------------------------------------
// File: dplay8client.cpp
//
// Desc: see main.cpp
//
// Copyright (c) 1999-2001 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#include <windows.h>
#include <d3dx.h>
#include <process.h>
#include <tchar.h>
#include <assert.h>
#include <dxerr9.h>
#include <stdio.h>
#include <dplay8.h>
#include <dpaddr.h>
#include "DummyConnector.h"
#include "StressMazeGUID.h"
#include "DXUtil.h"
#include "MazeApp.h"
#include "DPlay8Client.h"


#define DPMAZESERVER_PORT       2309
      



//-----------------------------------------------------------------------------
// Name: CDPlay8Client
// Desc: Constructor
//-----------------------------------------------------------------------------
CDPlay8Client::CDPlay8Client()
{
    m_dwNumSessions = 0;
    
    m_wActiveThreadCount = 0;
    m_wMaxThreadCount = 0;
    m_fAvgThreadCount = 0;
    m_fAvgThreadTime  = 0;
    m_fMaxThreadTime  = 0;

    m_pClient       = NULL;
    m_pDPlay        = NULL;
    m_dpnhEnum      = NULL;
    m_bSessionLost  = TRUE;
    m_dwSessionLostReason = DISCONNNECT_REASON_UNKNOWN;

    for( DWORD dwIndex = 0; dwIndex < MAX_SESSIONS; dwIndex++ )
    {
        m_pHostAddresses[dwIndex] = NULL;
        m_pDeviceAddresses[dwIndex] = NULL;
    }

    InitializeCriticalSection( &m_csLock );
    InitializeCriticalSection( &m_csThreadCountLock );
}

//-----------------------------------------------------------------------------
// Name: ~CDPlay8Client
// Desc: Destructor 
//-----------------------------------------------------------------------------
CDPlay8Client::~CDPlay8Client()
{
    DeleteCriticalSection( &m_csLock );
    DeleteCriticalSection( &m_csThreadCountLock );
}


//-----------------------------------------------------------------------------
// Name: Init
// Desc: Initialize member variables
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::Init(MazeConfig* pMazeConfig)
{
    HRESULT hr;
    
    
    m_MazeConfig = pMazeConfig;

    Shutdown();

    if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Client, NULL, 
                                       CLSCTX_ALL, IID_IDirectPlay8Client,
                                       (LPVOID*) &m_pDPlay ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr );

    // Turn off parameter validation in release builds
#ifdef _DEBUG
    const DWORD dwInitFlags = 0;
#else
    const DWORD dwInitFlags = DPNINITIALIZE_DISABLEPARAMVAL;
#endif // _DEBUG

    if( FAILED( hr = m_pDPlay->Initialize( this, StaticReceiveHandler, dwInitFlags ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("Initialize"), hr );

    // If between Range, attempt to set SPThreads.
    if((m_MazeConfig->dwSPThreads >= MIN_SP_THREADS) && (m_MazeConfig->dwSPThreads <= MAX_SP_THREADS))
    {
        SetNumSPThreads(m_MazeConfig->dwSPThreads);
    }

    // If less than MAX, set our SP Buffer.
    if(m_MazeConfig->dwSPBufferSize <= MAX_SP_BUFFER)
    {
        SetSPBuffer(m_MazeConfig->dwSPBufferSize);
    }
    
    m_fLastUpdateConnectInfoTime = DXUtil_Timer( TIMER_GETAPPTIME );
    m_bSessionLost  = TRUE;
    m_dwNumSessions = 0;

    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: GetNumSPThreads
// Desc: Get the current number of threads for the current service provider
//-----------------------------------------------------------------------------
DWORD CDPlay8Client::GetNumSPThreads()
{

    HRESULT             hr = DPN_OK;

    DPN_SP_CAPS         dnSPCaps;
    DWORD               dwFlags = NULL;
    DWORD               dwNumSPThreads = NULL;

    DXTRACE_MSG( TEXT("Attempting to Get SP Thread Count.") );

    dnSPCaps.dwSize = sizeof(DPN_SP_CAPS);
    
    hr = m_pDPlay->GetSPCaps(&CLSID_DP8SP_TCPIP, &dnSPCaps, dwFlags);
    if(hr != DPN_OK)
    {
        DXTRACE_ERR_MSGBOX( TEXT("GetSPCaps error"), hr );
        dwNumSPThreads = 0;
    }
    else
    {
        dwNumSPThreads = dnSPCaps.dwNumThreads;
    }

    return dwNumSPThreads;

}


//-----------------------------------------------------------------------------
// Name: SetNumSPThreads
// Desc: Set the number of threads for the current service provider
//-----------------------------------------------------------------------------
void CDPlay8Client::SetNumSPThreads(DWORD dwNumSPThreads)
{
    HRESULT             hr = DPN_OK;
    
    DPN_SP_CAPS         dnSPCaps;
    DWORD               dwFlags = NULL;

    dnSPCaps.dwSize = sizeof(DPN_SP_CAPS);
    hr = m_pDPlay->GetSPCaps(&CLSID_DP8SP_TCPIP, &dnSPCaps, dwFlags);
    if(hr == DPN_OK)
    {
        if((dwNumSPThreads >= MIN_SP_THREADS) && (dwNumSPThreads <= MAX_SP_THREADS))
        {
            dnSPCaps.dwNumThreads = dwNumSPThreads;
            
            // Attempt to set the number of SP Threads.
            hr = m_pDPlay->SetSPCaps(&CLSID_DP8SP_TCPIP, &dnSPCaps, dwFlags);
            if(hr != DPN_OK)
            {
                DXTRACE_ERR_MSGBOX( TEXT("SetSPCaps error."), hr );
            }
        }
    }
    else
    {
        DXTRACE_ERR_MSGBOX( TEXT("GetSPCaps error."), hr );
    }

}

//-----------------------------------------------------------------------------
// Name: GetSPBuffer
// Desc: Get the buffer size for the current service provider
//-----------------------------------------------------------------------------
DWORD CDPlay8Client::GetSPBuffer()
{

    HRESULT             hr = DPN_OK;

    DPN_SP_CAPS         dnSPCaps;
    DWORD               dwFlags = NULL;
    DWORD               dwSPBufferSize = NULL;

    DXTRACE_MSG( TEXT("Attempting to Get SP Buffer Size.") );

    dnSPCaps.dwSize = sizeof(DPN_SP_CAPS);
    
    hr = m_pDPlay->GetSPCaps(&CLSID_DP8SP_TCPIP, &dnSPCaps, dwFlags);
    if(hr != DPN_OK)
    {
        DXTRACE_ERR_MSGBOX( TEXT("GetSPCaps error"), hr );
        dwSPBufferSize = 0xffffffff;
    }
    else
    {
        dwSPBufferSize = dnSPCaps.dwSystemBufferSize;
    }

    return dwSPBufferSize;

}


//-----------------------------------------------------------------------------
// Name: SetSPBuffer
// Desc: Set the buffer size for the current service provider
//-----------------------------------------------------------------------------
void CDPlay8Client::SetSPBuffer(DWORD dwSPBufferSize)
{
    HRESULT             hr = DPN_OK;
    
    DPN_SP_CAPS         dnSPCaps;
    DWORD               dwFlags = NULL;

    dnSPCaps.dwSize = sizeof(DPN_SP_CAPS);
    hr = m_pDPlay->GetSPCaps(&CLSID_DP8SP_TCPIP, &dnSPCaps, dwFlags);
    if(hr == DPN_OK)
    {
        if(dwSPBufferSize <= MAX_SP_BUFFER)
            dnSPCaps.dwSystemBufferSize = dwSPBufferSize;

        // Attempt to set the number of SP Threads.
        hr = m_pDPlay->SetSPCaps(&CLSID_DP8SP_TCPIP, &dnSPCaps, dwFlags);
        if(hr != DPN_OK)
        {
            DXTRACE_ERR_MSGBOX( TEXT("SetSPCaps error."), hr );
        }
    }
    else   // !GetSPCase != DPN_OK
    {
        DXTRACE_ERR_MSGBOX( TEXT("GetSPCaps error."), hr );
    }

}


//-----------------------------------------------------------------------------
// Name: Shutdown
// Desc: Shutdown network communication and release DirectPlay objects
//-----------------------------------------------------------------------------
void CDPlay8Client::Shutdown()
{
    for( DWORD dwIndex = 0; dwIndex < MAX_SESSIONS; dwIndex++ )
    {
        SAFE_RELEASE( m_pHostAddresses[dwIndex] );
        SAFE_RELEASE( m_pDeviceAddresses[dwIndex] );
    }

    if( m_dpnhEnum != NULL )
    {
        // Cancel the enumeration if its in progress, and ignore any errors
        m_pDPlay->CancelAsyncOperation( m_dpnhEnum, 0 );
        m_dpnhEnum = NULL;
    }

    if( m_pDPlay != NULL )
        m_pDPlay->Close(0);

    SAFE_RELEASE( m_pDPlay );
    m_bSessionLost  = TRUE;
    m_dwNumSessions = 0;
}




//-----------------------------------------------------------------------------
// Name: StartSessionEnum
// Desc: Start session enumeration to locate active servers
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::StartSessionEnum( const TCHAR* ipaddress )
{
    if( NULL == m_pDPlay )
        return E_FAIL;

    DPN_APPLICATION_DESC   dpnAppDesc;
    IDirectPlay8Address*   pDP8AddressHost  = NULL;
    IDirectPlay8Address*   pDP8AddressLocal = NULL;
    WCHAR*                 wszHostName      = NULL;
    HRESULT                hr;
    DWORD                  dwPort;

    m_dwNumSessions = 0;

    if( m_dpnhEnum != NULL )
    {
        // If an enumeration is already running, cancel 
        // it and start a new one.  Ignore any errors from CancelAsyncOperation
        m_pDPlay->CancelAsyncOperation( m_dpnhEnum, 0 );
        m_dpnhEnum = NULL;
    }

    // Create the local device address object
    if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL, 
                                       CLSCTX_ALL, IID_IDirectPlay8Address,
                                       (LPVOID*) &pDP8AddressLocal ) ) )
    {
        DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr );
        goto LCleanup;
    }

    // Set IP service provider
    if( FAILED( hr = pDP8AddressLocal->SetSP( &CLSID_DP8SP_TCPIP ) ) )
    {
        DXTRACE_ERR_MSGBOX( TEXT("SetSP"), hr );
        goto LCleanup;
    }


    // Create the remote host address object
    if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL, 
                                       CLSCTX_ALL, IID_IDirectPlay8Address,
                                       (LPVOID*) &pDP8AddressHost ) ) )
    {
        DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr );
        goto LCleanup;
    }

    // Set IP service provider
    if( FAILED( hr = pDP8AddressHost->SetSP( &CLSID_DP8SP_TCPIP ) ) )
    {
        DXTRACE_ERR_MSGBOX( TEXT("SetSP"), hr );
        goto LCleanup;
    }

    // Maze uses a fixed port, so add it to the host address
    dwPort = DPMAZESERVER_PORT;
    hr = pDP8AddressHost->AddComponent( DPNA_KEY_PORT, 
                                      &dwPort, sizeof(dwPort),
                                      DPNA_DATATYPE_DWORD );
    if( FAILED(hr) )
    {
        DXTRACE_ERR_MSGBOX( TEXT("AddComponent"), hr );
        goto LCleanup;
    }

    // Set the remote host name (if provided)
    if( ipaddress != NULL && ipaddress[0] != 0 )
    {
        wszHostName = new WCHAR[_tcslen(ipaddress)+1];
        if( NULL == wszHostName )
        {
            hr = E_OUTOFMEMORY;
            DXTRACE_ERR_MSGBOX( TEXT("StartSessionEnum"), hr );
            goto LCleanup;
        }

        DXUtil_ConvertGenericStringToWideCch( wszHostName, ipaddress, _tcslen(ipaddress)+1 );

        hr = pDP8AddressHost->AddComponent( DPNA_KEY_HOSTNAME, wszHostName, 
                                            (wcslen(wszHostName)+1)*sizeof(WCHAR), 
                                            DPNA_DATATYPE_STRING );
        if( FAILED(hr) )
        {
            DXTRACE_ERR_MSGBOX( TEXT("AddComponent"), hr );
            goto LCleanup;
        }
    }

    ZeroMemory( &dpnAppDesc, sizeof( DPN_APPLICATION_DESC ) );
    dpnAppDesc.dwSize = sizeof( DPN_APPLICATION_DESC );
    dpnAppDesc.guidApplication = StressMazeAppGUID;

    // Enumerate all StressMazeApp hosts running on IP service providers
    hr = m_pDPlay->EnumHosts( &dpnAppDesc, pDP8AddressHost, 
                              pDP8AddressLocal, NULL, 
                              0, INFINITE, 0, INFINITE, NULL, 
                              &m_dpnhEnum, 0 );
    if( hr != DPNERR_PENDING && FAILED(hr) )
    {
        DXTRACE_ERR( TEXT("EnumHosts"), hr );
        goto LCleanup;
    }

LCleanup:
    SAFE_RELEASE( pDP8AddressHost);
    SAFE_RELEASE( pDP8AddressLocal );
    SAFE_DELETE_ARRAY( wszHostName );

    return hr;
}




//-----------------------------------------------------------------------------
// Name: StopSessionEnum
// Desc: Stop the active session enumeration
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::StopSessionEnum()
{
    if( NULL == m_pDPlay )
        return E_FAIL;

    // If an enumeration is already running, cancel it and ignore
    // any errors from CancelAsyncOperation
    if( m_dpnhEnum != NULL )
    {
        m_pDPlay->CancelAsyncOperation( m_dpnhEnum, 0 );
        m_dpnhEnum = NULL;
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: EnumSessionCallback
// Desc: Callback for detected session events
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::EnumSessionCallback( const DPN_APPLICATION_DESC *pdesc, 
                                         IDirectPlay8Address* pDP8AddressHost,
                                         IDirectPlay8Address* pDP8AddressDevice )
{
    HRESULT hr = DPN_OK; 

    if( NULL == m_pDPlay )
        return DPN_OK;

    EnterCriticalSection( &m_csLock );

    if( m_dwNumSessions < MAX_SESSIONS-1 )
    {
        // Search for existing record for this session, if 
        // there is one, break this loop so we just update
        // the current entry.
        for( DWORD dwIndex = 0; dwIndex < m_dwNumSessions; dwIndex++ )
        {
            if( m_Sessions[dwIndex].guidInstance == pdesc->guidInstance )
                break;
        }
        
        memcpy( &m_Sessions[dwIndex], pdesc, sizeof( DPN_APPLICATION_DESC ) );

        // Copy pDP8AddressHost to m_pHostAddresses[dwIndex]
        SAFE_RELEASE( m_pHostAddresses[dwIndex] );
        hr = pDP8AddressHost->QueryInterface( IID_IDirectPlay8Address, 
                                         (LPVOID*) &m_pHostAddresses[dwIndex] );
        if( FAILED(hr) )
        {
            DXTRACE_ERR( TEXT("QueryInterface"), hr );
            goto LCleanReturn;
        }

        // Copy pDP8AddressDevice to m_pDeviceAddresses[dwIndex]
        SAFE_RELEASE( m_pDeviceAddresses[dwIndex] );
        hr = pDP8AddressDevice->QueryInterface( IID_IDirectPlay8Address, 
                                         (LPVOID*) &m_pDeviceAddresses[dwIndex] );
        if( FAILED(hr) )
        {
            DXTRACE_ERR( TEXT("QueryInterface"), hr );
            goto LCleanReturn;
        }

        if( pdesc->pwszSessionName != NULL )
        {
            DXUtil_ConvertWideStringToGenericCch( m_szSessionNames[dwIndex], pdesc->pwszSessionName, 
            					 MAX_SESSION_NAME );
        }
        else
        {
            _tcscpy( m_szSessionNames[dwIndex], TEXT("Untitled") );
        }

        if( m_dwNumSessions == dwIndex )
            m_dwNumSessions++;
    }

LCleanReturn:
    LeaveCriticalSection( &m_csLock );

    return DPN_OK; // Continue the enumeration
}




//-----------------------------------------------------------------------------
// Name: JoinSession
// Desc: Join the session at the given index
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::JoinSession( DWORD num )
{
    HRESULT hr;
    IDirectPlay8Address* pHostAddress = NULL;
    IDirectPlay8Address* pDeviceAddress = NULL;

    if( m_pDPlay == NULL )
        return E_FAIL;

    DXTRACE( TEXT("MazeClient: Trying to connect to server\n") );

    DPN_APPLICATION_DESC dpnAppDesc;
    ZeroMemory( &dpnAppDesc, sizeof( DPN_APPLICATION_DESC ) );
    dpnAppDesc.dwSize          = sizeof( DPN_APPLICATION_DESC );
    dpnAppDesc.guidApplication = StressMazeAppGUID;
    dpnAppDesc.guidInstance    = m_Sessions[num].guidInstance;

    EnterCriticalSection( &m_csLock );
    
    // Copy the host and device address pointers, and addref them.
    // If this is not done, then there is a rare chance that 
    // EnumSessionCallback() may be called during the Connect() call 
    // and destory the address before DirectPlay gets a chance to copy them.
    pHostAddress = m_pHostAddresses[num];
    pHostAddress->AddRef();

    pDeviceAddress = m_pDeviceAddresses[num];
    pDeviceAddress->AddRef();

    LeaveCriticalSection( &m_csLock );

    // Connect to the remote host
    // The enumeration is automatically canceled after Connect is called 
    if( FAILED( hr = m_pDPlay->Connect( &dpnAppDesc,        // Application description
                                        pHostAddress,       // Session host address
                                        pDeviceAddress,     // Address of device used to connect to the host
                                        NULL, NULL,         // Security descriptions & credientials (MBZ in DPlay8)
                                        NULL, 0,            // User data & its size
                                        NULL,               // Asynchronous connection context (returned with DPNMSG_CONNECT_COMPLETE in async handshaking)
                                        NULL,               // Asynchronous connection handle (used to cancel connection process)
                                        DPNOP_SYNC ) ) )    // Connect synchronously
    {
        if( hr == DPNERR_NORESPONSE || hr == DPNERR_ABORTED )
            goto LCleanup; // These are possible if the server exits while joining 

        if( hr == DPNERR_INVALIDINSTANCE )
            goto LCleanup; // This is possible if the original server exits and another server comes online while we are connecting

        DXTRACE_ERR( TEXT("Connect"), hr );
        goto LCleanup;
    }

    m_bSessionLost = FALSE;
    
    DXTRACE( TEXT("MazeClient: Connected to server.  Enum automatically canceled\n") );

    UpdateConnectionInfo();
    m_fLastUpdateConnectInfoTime = DXUtil_Timer( TIMER_GETAPPTIME );


LCleanup:
    SAFE_RELEASE( pHostAddress );
    SAFE_RELEASE( pDeviceAddress );

    return hr;
}




//-----------------------------------------------------------------------------
// Name: SendPacket
// Desc: Send the given packet of data to the server
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::SendPacket( void* pData, DWORD dwSize, 
                                   BOOL bGuaranteed, DWORD dwTimeout )
{
    if( NULL == m_pDPlay )
        return S_OK;

    DPNHANDLE  hAsync;
    DWORD      dwFlags;
    DPNHANDLE* phAsync;

    if( bGuaranteed )
    {
        // If we are guaranteed then we must specify
        // DPNSEND_NOCOMPLETE and pass in non-null for the 
        // pvAsyncContext
        dwFlags = DPNSEND_GUARANTEED;
    }
    else
    {
        // If we aren't guaranteed then we can
        // specify DPNSEND_NOCOMPLETE.  And when 
        // DPNSEND_NOCOMPLETE is on pvAsyncContext
        // must be NULL.
        dwFlags = DPNSEND_NOCOMPLETE;
    }
    
    // Must pass in a value for the Asyn handle. Will be thrown when proc completes.
    phAsync = &hAsync;

    DPN_BUFFER_DESC dpnBufferDesc;
    dpnBufferDesc.dwBufferSize = dwSize;
    dpnBufferDesc.pBufferData = (PBYTE) pData;

    // Update the throughput counter
    m_dwThroughputBytes += dwSize;

    // DirectPlay will tell via the message handler 
    // if there are any severe errors, so ignore any errors 
    m_pDPlay->Send( &dpnBufferDesc, 1, dwTimeout, 
                    NULL, phAsync, dwFlags );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: StaticReceiveHandler
// Desc: Static helper for the receive handler
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::StaticReceiveHandler( void *pvContext, DWORD dwMessageType, 
                                             void *pvMessage )
{
    CDPlay8Client* pThisObject = (CDPlay8Client*) pvContext;

    return pThisObject->ReceiveHandler( pvContext, dwMessageType, pvMessage );
}



//-----------------------------------------------------------------------------
// Name: ReceiveHandler
// Desc: DirectPlay message handler
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::ReceiveHandler( void *pvContext, DWORD dwMessageType, 
                                       void *pvMessage )
{
     // Increment our Active Thread Counter.
    EnterCriticalSection( &m_csThreadCountLock );

    //Get the time when we entered the Message Handler
    FLOAT fStartTime = DXUtil_Timer( TIMER_GETAPPTIME );

    m_wActiveThreadCount++;

    if(m_wActiveThreadCount > m_wMaxThreadCount)
        m_wMaxThreadCount = m_wActiveThreadCount;

    
    // Calculate an average.
    FLOAT fdiff = m_wActiveThreadCount - m_fAvgThreadCount;
    m_fAvgThreadCount += fdiff/8;
    
    LeaveCriticalSection( &m_csThreadCountLock );

    
    switch( dwMessageType )
    {
        case DPN_MSGID_RECEIVE:
        {
            PDPNMSG_RECEIVE pRecvData = (PDPNMSG_RECEIVE) pvMessage;

            // Update the throughput counter
            m_dwThroughputBytes += pRecvData->dwReceiveDataSize;

            if( m_pClient != NULL )
            {
                m_pClient->OnPacket( pRecvData->dpnidSender, 
                                     pRecvData->pReceiveData, 
                                     pRecvData->dwReceiveDataSize );
            }
            break;
        }
        
        case DPN_MSGID_TERMINATE_SESSION:
        {
            m_dwSessionLostReason = DISCONNNECT_REASON_UNKNOWN;
            PDPNMSG_TERMINATE_SESSION pTermMsg = (PDPNMSG_TERMINATE_SESSION) pvMessage;

            // The MazeServer passes a DWORD in pvTerminateData if 
            // it disconnected us, otherwise it will be null.
            if( pTermMsg->pvTerminateData != NULL )
            {
                DWORD* pdw = (DWORD*) pTermMsg->pvTerminateData;
                m_dwSessionLostReason = *pdw;
            }

            if( m_pClient != NULL )
                m_pClient->OnSessionLost( m_dwSessionLostReason );

            // Now that the session is lost we need to restart DirectPlay by calling
            // Close() and Init() on m_pDPlay, however this can not be 
            // done in the DirectPlay message callback, so the main thread will
            // do this when IsSessionLost() returns TRUE
            m_bSessionLost = TRUE;
            break;
        }

        case DPN_MSGID_ENUM_HOSTS_RESPONSE:
        {
            PDPNMSG_ENUM_HOSTS_RESPONSE pEnumResponse = (PDPNMSG_ENUM_HOSTS_RESPONSE) pvMessage;

            EnumSessionCallback( pEnumResponse->pApplicationDescription, 
                                 pEnumResponse->pAddressSender,
                                 pEnumResponse->pAddressDevice );
            break;
        }
    }

 
    EnterCriticalSection( &m_csThreadCountLock );
    
    m_wActiveThreadCount-- ;
    
    // Calculate an average.

    FLOAT fDiffTime = ( DXUtil_Timer( TIMER_GETAPPTIME ) - fStartTime ) - m_fAvgThreadTime;
    m_fAvgThreadTime += fDiffTime/8;

    //Get the Max time in the thread.
    if ( fDiffTime > m_fMaxThreadTime )
    {
        m_fMaxThreadTime = fDiffTime;
    }
    
    LeaveCriticalSection( &m_csThreadCountLock );

    return S_OK;
}


//-----------------------------------------------------------------------------
// Name: UpdateConnectionInfo
// Desc: Update the current connection information
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::UpdateConnectionInfo()
{
    if( NULL == m_pDPlay )
        return E_FAIL;

    // Update the DPN_CONNECTION_INFO every 1/2 second...
    float fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );
    if( fCurTime - m_fLastUpdateConnectInfoTime > 0.5f )
    {
        // Call GetConnectionInfo to get DirectPlay stats about connection 
        ZeroMemory( &m_dpnConnectionInfo, sizeof(DPN_CONNECTION_INFO) );
        m_dpnConnectionInfo.dwSize = sizeof(DPN_CONNECTION_INFO);
        m_pDPlay->GetConnectionInfo( &m_dpnConnectionInfo, 0 );

        // Call GetSendQueueInfo to get DirectPlay stats about messages
        m_pDPlay->GetSendQueueInfo( &m_dwHighPriMessages, &m_dwHighPriBytes, 
                                    DPNGETSENDQUEUEINFO_PRIORITY_HIGH );
        m_pDPlay->GetSendQueueInfo( &m_dwNormalPriMessages, &m_dwNormalPriBytes, 
                                    DPNGETSENDQUEUEINFO_PRIORITY_NORMAL );
        m_pDPlay->GetSendQueueInfo( &m_dwLowPriMessages, &m_dwLowPriBytes, 
                                    DPNGETSENDQUEUEINFO_PRIORITY_LOW );

        m_fLastUpdateConnectInfoTime = fCurTime;
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: GetThroughputBPS
// Desc: Get the current throughput in bytes per second
//-----------------------------------------------------------------------------
DWORD CDPlay8Client::GetThroughputBPS()
{
    static float s_fLastThroughputBPSTime = DXUtil_Timer( TIMER_GETAPPTIME );
    float fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );
    if( fCurTime - s_fLastThroughputBPSTime > 1.0f )
    {
        m_fThroughputBPS         = (float) m_dwThroughputBytes / (fCurTime - s_fLastThroughputBPSTime);

        s_fLastThroughputBPSTime = fCurTime;
        m_dwThroughputBytes      = 0;
    }

    return (DWORD) m_fThroughputBPS;
}




//-----------------------------------------------------------------------------
// Name: GetRoundTripLatencyMS
// Desc: Get current round trip latency in milliseconds
//-----------------------------------------------------------------------------
DWORD CDPlay8Client::GetRoundTripLatencyMS()
{
    UpdateConnectionInfo();
    return m_dpnConnectionInfo.dwRoundTripLatencyMS;
}




//-----------------------------------------------------------------------------
// Name: GetConnectionInfo
// Desc: Get the current connection information
//-----------------------------------------------------------------------------
HRESULT CDPlay8Client::GetConnectionInfo( TCHAR* strConnectionInfo, DWORD dwBufLength )
{
    // Validate parameters
    if( strConnectionInfo == NULL || dwBufLength == 0 )
        return E_INVALIDARG;

    UpdateConnectionInfo();

    int nCharsStored = 0;
    nCharsStored = _sntprintf( strConnectionInfo, dwBufLength-1,
                        TEXT("     Thread Count: Current=%d Avg= %.2f Max=%d\n")        \
                        TEXT("     Thread Time:  Avg= %.4f Max=%.4f(s)\n")              \
                                                                                        \
                        TEXT("     Round Trip Latency MS=%dms\n")                       \
                        TEXT("     Throughput BPS: Current=%d Peak=%d\n")               \
                                                                                        \
                        TEXT("     Messages Received=%d\n")                             \
                                                                                        \
                        TEXT("     Sent: GB=%d GP=%d NGB=%d NGP=%d\n")                  \
                        TEXT("     Received: GB=%d GP=%d NGB=%d NGP=%d\n")              \
                                                                                        \
                        TEXT("     Messages Transmitted: HP=%d NP=%d LP=%d\n")          \
                        TEXT("     Messages Timed Out: HP=%d NP=%d LP=%d\n")            \
                                                                                        \
                        TEXT("     Retried: GB=%d GP=%d\n")                             \
                        TEXT("     Dropped: NGB=%d NGP=%d\n")                           \
                                                                                        \
                        TEXT("     Send Queue Messages: HP=%d NP=%d LP=%d\n")           \
                        TEXT("     Send Queue Bytes: HP=%d NP=%d LP=%d\n"),             \
                                                                                        \
                                                                                        \
                                                                                        \
                        m_wActiveThreadCount, m_fAvgThreadCount, m_wMaxThreadCount,
                        m_fAvgThreadTime, m_fMaxThreadTime,
                        m_dpnConnectionInfo.dwRoundTripLatencyMS, 
                        m_dpnConnectionInfo.dwThroughputBPS, 
                        m_dpnConnectionInfo.dwPeakThroughputBPS,
        
                        m_dpnConnectionInfo.dwMessagesReceived,
        
                        m_dpnConnectionInfo.dwBytesSentGuaranteed,
                        m_dpnConnectionInfo.dwPacketsSentGuaranteed,
                        m_dpnConnectionInfo.dwBytesSentNonGuaranteed,
                        m_dpnConnectionInfo.dwPacketsSentNonGuaranteed,
        
                        m_dpnConnectionInfo.dwBytesReceivedGuaranteed,
                        m_dpnConnectionInfo.dwPacketsReceivedGuaranteed,
                        m_dpnConnectionInfo.dwBytesReceivedNonGuaranteed,
                        m_dpnConnectionInfo.dwPacketsReceivedNonGuaranteed,
        
                        m_dpnConnectionInfo.dwMessagesTransmittedHighPriority,
                        m_dpnConnectionInfo.dwMessagesTransmittedNormalPriority,
                        m_dpnConnectionInfo.dwMessagesTransmittedLowPriority,
        
                        m_dpnConnectionInfo.dwMessagesTimedOutHighPriority,
                        m_dpnConnectionInfo.dwMessagesTimedOutNormalPriority,
                        m_dpnConnectionInfo.dwMessagesTimedOutLowPriority,
        
                        m_dpnConnectionInfo.dwBytesRetried,
                        m_dpnConnectionInfo.dwPacketsRetried,
        
                        m_dpnConnectionInfo.dwBytesDropped,
                        m_dpnConnectionInfo.dwPacketsDropped,
        
                        m_dwHighPriMessages, m_dwNormalPriMessages, m_dwLowPriMessages, 
                        m_dwHighPriBytes, m_dwNormalPriBytes, m_dwLowPriBytes
               );

    // Null terminate
    strConnectionInfo[ dwBufLength-1 ] = 0;

    // _sntprintf() returns a negative number if buffer was too small
    if( nCharsStored < 0 )
        return DPNERR_BUFFERTOOSMALL;

    return S_OK;
}
