예제 #1
0
/* Wait for the specified network event to occur, or cancellation, whichever
 * happens first. On cancellation, return -1; on error, return the error
 * code; on success, return 0. */
int NetworkStream_Win32::WaitForCompletionOrCancellation( int iEvent )
{
	while(1)
	{
		int iRet = WaitForSingleObject( m_hCompletionEvent, INFINITE );
		if( iRet != WAIT_OBJECT_0 )
			continue;

		m_Mutex.Lock();

		// This will reset the event. Do this while we hold the lock.
		WSANETWORKEVENTS events;
		WSAEnumNetworkEvents( m_Socket, m_hCompletionEvent, &events );

		// Was the event signalled due to cancellation?
		if( m_State == STATE_CANCELLED )
		{
			m_Mutex.Unlock();
			return -1;
		}

		m_Mutex.Unlock();

		// If the event didn't actually occur, keep waiting.
		if( (events.lNetworkEvents & (1<<iEvent)) )
			return events.iErrorCode[iEvent];

		/* If the socket was closed while we were waiting, stop. Note that when the
		 * connection closes immediately after sending data, we'll receive both this
		 * message and FD_READ at the same time. Only do this if the event we really
		 * want hasn't happened yet. */
		if( (events.lNetworkEvents & (1<<FD_CLOSE_BIT)) )
			return WSAECONNRESET;
	}
}
예제 #2
0
/* Set the timeout length, and reset the timer. */
void RageFileDriverTimeout::SetTimeout( float fSeconds )
{
	g_apWorkersMutex.Lock();
	for( unsigned i = 0; i < g_apWorkers.size(); ++i )
		g_apWorkers[i]->SetTimeout( fSeconds );
	g_apWorkersMutex.Unlock();
}
예제 #3
0
static void InitThreads()
{
	/* We don't have to worry about two threads calling this at once, since it's
	 * called when we create a thread. */
	static bool bInitialized = false;
	if( bInitialized )
		return;

	g_ThreadSlotsLock.Lock();

	/* Libraries might start threads on their own, which might call user callbacks,
	 * which could come back here.  Make sure we don't accidentally initialize twice. */
	if( bInitialized )
	{
		g_ThreadSlotsLock.Unlock();
		return;
	}

	bInitialized = true;

	/* Register the "unknown thread" slot. */
	int slot = FindEmptyThreadSlot();
	strcpy( g_ThreadSlots[slot].name, "Unknown thread" );
	g_ThreadSlots[slot].id = GetInvalidThreadId();
	sprintf( g_ThreadSlots[slot].ThreadFormattedOutput, "Unknown thread" );
	g_pUnknownThreadSlot = &g_ThreadSlots[slot];

	g_ThreadSlotsLock.Unlock();
}
	const PolyphaseFilter *MakePolyphaseFilter( int iUpFactor, float fCutoffFrequency )
	{
		PolyphaseFiltersLock.Lock();
		pair<int,float> params( make_pair(iUpFactor, fCutoffFrequency) );
		FilterMap::const_iterator it = g_mapPolyphaseFilters.find(params);
		if( it != g_mapPolyphaseFilters.end() )
		{
			/* We already have a filter for this upsampling factor and cutoff; use it. */
			PolyphaseFilter *pPolyphase = it->second;
			PolyphaseFiltersLock.Unlock();
			return pPolyphase;
		}
		int iWinSize = L*iUpFactor;
		float *pFIR = new float[iWinSize];
		GenerateSincLowPassFilter( pFIR, iWinSize, fCutoffFrequency );
		ApplyKaiserWindow( pFIR, iWinSize, 8 );
		NormalizeVector( pFIR, iWinSize );
		MultiplyVector( &pFIR[0], &pFIR[iWinSize], (float) iUpFactor );

		PolyphaseFilter *pPolyphase = new PolyphaseFilter( iUpFactor );
		pPolyphase->Generate( pFIR );
		delete [] pFIR;

		g_mapPolyphaseFilters[params] = pPolyphase;
		PolyphaseFiltersLock.Unlock();
		return pPolyphase;
	}
예제 #5
0
void RageSoundManager::Update(float delta)
{
	FlushPosMapQueue();

	/* Scan the owned_sounds list for sounds that are no longer playing, and delete them. */
	g_SoundManMutex.Lock(); /* lock for access to owned_sounds */
	set<RageSound *> ToDelete;
	for( set<RageSound *>::iterator it = owned_sounds.begin(); it != owned_sounds.end(); ++it )
	{
		RageSound *pSound = *it;
		if( pSound->IsPlaying() )
			continue;

		LOG->Trace("XXX: deleting '%s'", pSound->GetLoadedFilePath().c_str());

		ToDelete.insert( pSound );
	}
	
	for( set<RageSound *>::iterator it = ToDelete.begin(); it != ToDelete.end(); ++it )
		owned_sounds.erase( *it );
	g_SoundManMutex.Unlock(); /* finished with owned_sounds */

	/* Be sure to release g_SoundManMutex before deleting sounds. */
	for( set<RageSound *>::iterator it = ToDelete.begin(); it != ToDelete.end(); ++it )
		delete *it;

	if( driver != NULL )
		driver->Update(delta);
}
예제 #6
0
RageSoundManager::~RageSoundManager()
{
	g_SoundManMutex.Lock(); /* lock for access to owned_sounds */
	set<RageSound *> sounds = owned_sounds;
	g_SoundManMutex.Unlock(); /* finished with owned_sounds */

	/* Clear any sounds that we own and havn't freed yet. */
	set<RageSound *>::iterator j = sounds.begin();
	while(j != sounds.end())
		delete *(j++);

	/* Don't lock while deleting the driver (the decoder thread might deadlock). */
	delete driver;
}
	const PolyphaseFilter *FindNearestPolyphaseFilter( int iUpFactor, float fCutoffFrequency )
	{
		/* Find a cached filter with the same iUpFactor and a nearby cutoff frequency.
		 * Round the cutoff down, if possible; it's better to filter out too much than
		 * too little. */
		PolyphaseFiltersLock.Lock();
		pair<int,float> params( make_pair(iUpFactor, fCutoffFrequency + 0.0001f) );
		FilterMap::const_iterator it = g_mapPolyphaseFilters.upper_bound( params );
		if( it != g_mapPolyphaseFilters.begin() )
			--it;
		ASSERT( it->first.first == iUpFactor );
		PolyphaseFilter *pPolyphase = it->second;
		PolyphaseFiltersLock.Unlock();
		return pPolyphase;
	}
예제 #8
0
bool ThreadedMemoryCardWorker::StorageDevicesChanged( vector<UsbStorageDevice> &aOut )
{
	UsbStorageDevicesMutex.Lock();
	if( !m_bUsbStorageDevicesChanged )
	{
		UsbStorageDevicesMutex.Unlock();
		return false;
	}

	aOut = m_aUsbStorageDevices;
	m_aUsbStorageDevices.clear();
	m_bUsbStorageDevicesChanged = false;

	UsbStorageDevicesMutex.Unlock();
	return true;
}
예제 #9
0
void NetworkStream_Win32::SetError( const RString &sError )
{
	m_Mutex.Lock();
	if( m_State != STATE_CANCELLED )
	{
		m_sError = sError;
		m_State = STATE_ERROR;
	}

	if( m_Socket != INVALID_SOCKET )
	{
		closesocket( m_Socket );
		m_Socket = INVALID_SOCKET;
	}

	m_Mutex.Unlock();
}
예제 #10
0
void ThreadedMemoryCardWorker::DoHeartbeat()
{
	if( m_MountThreadState == paused )
		return;

	// If true, detect and mount. If false, only detect.
	bool bMount = (m_MountThreadState == detect_and_mount);

	vector<UsbStorageDevice> aStorageDevices;
	//LOG->Trace("update");
	if( !m_pDriver->DoOneUpdate( bMount, aStorageDevices ) )
		return;

	UsbStorageDevicesMutex.Lock();
	m_aUsbStorageDevices = aStorageDevices;
	m_bUsbStorageDevicesChanged = true;
	UsbStorageDevicesMutex.Unlock();
}
예제 #11
0
ThreadedFileWorker::~ThreadedFileWorker()
{
	StopThread();

	if( m_pChildDriver != NULL )
		FILEMAN->ReleaseFileDriver( m_pChildDriver );

	/* Unregister ourself. */
	g_apWorkersMutex.Lock();
	for( unsigned i = 0; i < g_apWorkers.size(); ++i )
	{
		if( g_apWorkers[i] == this )
		{
			g_apWorkers.erase( g_apWorkers.begin()+i );
			break;
		}
	}
	g_apWorkersMutex.Unlock();
}
예제 #12
0
void NetworkStream_Win32::Close()
{
	if( m_State == STATE_IDLE )
		return;
	
	/* If we have an active, stable connection, make sure we flush any data
	 * completely before closing. If you don't want to do this, call Cancel()
	 * first. */
	Shutdown();

	m_Mutex.Lock();
	if( m_State == STATE_CANCELLED )
		return;

	if( m_Socket != INVALID_SOCKET )
		closesocket( m_Socket );
	m_Socket = INVALID_SOCKET;
	m_State = STATE_IDLE;
	m_Mutex.Unlock();
}
예제 #13
0
ThreadedFileWorker::ThreadedFileWorker( CString sPath ):
	RageWorkerThread( sPath ),
	m_DeletedFilesLock( sPath + "DeletedFilesLock" )
{
	/* Grab a reference to the child driver.  We'll operate on it directly. */
	m_pChildDriver = FILEMAN->GetFileDriver( sPath );
	if( m_pChildDriver == NULL )
		WARN( ssprintf("ThreadedFileWorker: Mountpoint \"%s\" not found", sPath.c_str()) );

	m_pResultFile = NULL;
	m_pRequestFile = NULL;
	m_pResultBuffer = NULL;
	m_pRequestBuffer = NULL;

	g_apWorkersMutex.Lock();
	g_apWorkers.push_back( this );
	g_apWorkersMutex.Unlock();

	StartThread();
}
예제 #14
0
void RageMutex::Lock()
{
	if( m_LockedBy == (uint64_t) GetThisThreadId() )
	{
		++m_LockCnt;
		return;
	}

	if( !m_pMutex->Lock() )
	{
		const ThreadSlot *ThisSlot = GetThreadSlotFromID( GetThisThreadId() );
		const ThreadSlot *OtherSlot = GetThreadSlotFromID( m_LockedBy );

		CString ThisSlotName = "(???" ")"; // stupid trigraph warnings
		CString OtherSlotName = "(???" ")"; // stupid trigraph warnings
		if( ThisSlot )
			ThisSlotName = ssprintf( "%s (%i)", ThisSlot->GetThreadName(), (int) ThisSlot->id );
		if( OtherSlot )
			OtherSlotName = ssprintf( "%s (%i)", OtherSlot->GetThreadName(), (int) OtherSlot->id );
		const CString sReason = ssprintf( "Thread deadlock on mutex %s between %s and %s",
			GetName().c_str(), ThisSlotName.c_str(), OtherSlotName.c_str() );

#if defined(CRASH_HANDLER)
		/* Don't leave g_ThreadSlotsLock when we call ForceCrashHandlerDeadlock. */
		g_ThreadSlotsLock.Lock();
		uint64_t CrashHandle = OtherSlot? OtherSlot->id:0;
		g_ThreadSlotsLock.Unlock();

		/* Pass the crash handle of the other thread, so it can backtrace that thread. */
		ForceCrashHandlerDeadlock( sReason, CrashHandle );
#else
		FAIL_M( sReason );
#endif
	}

	m_LockedBy = GetThisThreadId();

	/* This has internal thread safety issues itself (eg. one thread may delete
	 * a mutex while another locks one); disable for now. */
//	MarkLockedMutex();
}
예제 #15
0
void NetworkStream_Win32::Cancel()
{
	m_Mutex.Lock();

	// Mark cancellation.
	m_State = STATE_CANCELLED;

	// If resolving, abort the resolve.
	if( m_hResolve != NULL )
	{
		/* When we cancel the request, no message at all will be sent to the window,
		 * so we need to do it ourself to inform it that it was cancelled. Be sure
		 * to only do this on successful cancel. */
		if( WSACancelAsyncRequest(m_hResolve) == 0 )
			PostMessage( m_hResolveHwnd, WM_USER+1, 0, 0 );
	}

	// Break out if we're waiting in WaitForCompletionOrCancellation().
	SetEvent( m_hCompletionEvent );

	m_Mutex.Unlock();
}
예제 #16
0
void ThreadedFileWorker::Close( RageFileBasic *pFile )
{
	ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */

	if( pFile == NULL )
		return;

	if( !IsTimedOut() )
	{
		/* If we're not in a timed-out state, try to wait for the deletion to complete
		 * before continuing. */
		m_pRequestFile = pFile;
		if( !DoRequest(REQ_CLOSE) )
			return;
		m_pRequestFile = NULL;
	}
	else
	{
		/* Delete the file when the timeout completes. */
		m_DeletedFilesLock.Lock();
		m_apDeletedFiles.push_back( pFile );
		m_DeletedFilesLock.Unlock();
	}
}
예제 #17
0
void CachedObjectHelpers::Lock()
{
	m_Mutex.Lock();
}
예제 #18
0
/* Register the given sound, and return a unique ID. */
void RageSoundManager::RegisterSound( RageSound *p )
{
	g_SoundManMutex.Lock(); /* lock for access to all_sounds */
	all_sounds[p->GetID()] = p;
	g_SoundManMutex.Unlock(); /* finished with all_sounds */
}
예제 #19
0
void RageSoundManager::UnregisterSound( RageSound *p )
{
	g_SoundManMutex.Lock(); /* lock for access to all_sounds */
	all_sounds.erase( p->GetID() );
	g_SoundManMutex.Unlock(); /* finished with all_sounds */
}
예제 #20
0
void ScreenUserPacks::HandleScreenMessage( const ScreenMessage SM )
{
#if 0
	/* Not compatible with FileCopy callback system; was this ever used? -- vyhd */
	if ( SM == SM_CancelTransfer )
	{
		Dialog::OK("SM_CancelTransfer");

		InterruptCopy();

		InputEventArray throwaway;
		INPUTFILTER->GetInputEvents( throwaway );

		LOG->Warn("Cancelled Transfer of user pack.");
	}
#endif
	if ( SM == SM_LinkedMenuChange )
	{
		m_pCurLOM = m_pCurLOM->SwitchToNextMenu();
		return;
	}
	if ( SM == SM_ConfirmDeleteZip )
	{
		SCREENMAN->Prompt( SM_AnswerConfirmDeleteZip, 
			"Proceed to delete pack from machine?",
			PROMPT_YES_NO, ANSWER_NO );
	}
	if ( SM == SM_AnswerConfirmDeleteZip )
	{
		if (ScreenPrompt::s_LastAnswer == ANSWER_NO)
			return;
		CString sSelection = m_AddedZips.GetCurrentSelection();
		g_CurSelection = sSelection;
		bool bSuccess = UPACKMAN->Remove( USER_PACK_SAVE_PATH + sSelection );
		if (bSuccess)
		{
			m_SoundDelete.Play();
			ReloadZips();
			m_bRestart = true;
		}
		else
		{
			SCREENMAN->SystemMessage( "Failed to delete zip file from machine. Check your file permissions." );
		}
	}
	if ( SM == SM_ConfirmAddZip )
	{
		SCREENMAN->Prompt( SM_AnswerConfirmAddZip, 
			"Proceed to add pack to machine?",
			PROMPT_YES_NO, ANSWER_NO );
	}
	if ( SM == SM_AnswerConfirmAddZip )
	{
		CString sError;

		m_bPrompt = false;
		if (ScreenPrompt::s_LastAnswer == ANSWER_NO)
			return;

		m_bStopThread = true;
		m_PlayerSongLoadThread.Wait();

		MountMutex.Lock();
#if defined(LINUX) && defined(ITG_ARCADE)
		system( "mount -o remount,rw /itgdata" );
#endif
		MEMCARDMAN->LockCards();
		MEMCARDMAN->MountCard(m_CurPlayer, 99999);
		CString sSelection = m_USBZips.GetCurrentSelection();
		{

////////////////////////
#define XFER_CLEANUP MEMCARDMAN->UnmountCard(m_CurPlayer); \
MEMCARDMAN->UnlockCards(); \
MountMutex.Unlock(); \
m_bStopThread = false; \
m_PlayerSongLoadThread.Create( InitSASSongThread, this )
////////////////////////

			g_CurXferFile = MEM_CARD_MOUNT_POINT[m_CurPlayer] + "/" + USER_PACK_TRANSFER_PATH + sSelection;
			if ( !UPACKMAN->IsPackTransferable( sSelection, g_CurXferFile, sError ) || !UPACKMAN->IsPackMountable( g_CurXferFile, sError ) )
			{
				SCREENMAN->SystemMessage( "Could not add pack to machine:\n" + sError );
				XFER_CLEANUP;
				return;
			}

			sError = ""; //  ??
			RageTimer start;
			DrawTimer.Touch();
			g_iLastCurrentBytes = 0;
			g_UpdateDuration.Touch();
			if (!UPACKMAN->TransferPack( g_CurXferFile, sSelection, UpdateXferProgress, sError ) )
			{
				SCREENMAN->SystemMessage( "Transfer error:\n" + sError );
				XFER_CLEANUP;
				SCREENMAN->HideOverlayMessage();
				SCREENMAN->ZeroNextUpdate();
				return;
			}
			LOG->Debug( "Transferred %s in %f seconds.", g_CurXferFile.c_str(), start.Ago() );
		}
#if defined(LINUX) && defined(ITG_ARCADE)
		sync();
		system( "mount -o remount,ro /itgdata" );
#endif
		SCREENMAN->HideOverlayMessage();
		SCREENMAN->ZeroNextUpdate();
		FILEMAN->FlushDirCache(USER_PACK_SAVE_PATH);

		m_bRestart = true;

		m_SoundTransferDone.Play();
		ReloadZips();

		XFER_CLEANUP;
#undef XFER_CLEANUP
	}
	switch( SM )
	{
	case SM_GoToNextScreen:
	case SM_GoToPrevScreen:
		SCREENMAN->SetNewScreen( PREV_SCREEN );
		break;
	}
}
예제 #21
0
void ThreadedFileWorker::HandleRequest( int iRequest )
{
	{
		m_DeletedFilesLock.Lock();
		vector<RageFileBasic *> apDeletedFiles = m_apDeletedFiles;
		m_apDeletedFiles.clear();
		m_DeletedFilesLock.Unlock();

		for( unsigned i = 0; i < apDeletedFiles.size(); ++i )
			delete apDeletedFiles[i];
	}

	/* We have a request. */
	switch( iRequest )
	{
	case REQ_OPEN:
		ASSERT( m_pResultFile == NULL );
		ASSERT( !m_sRequestPath.empty() );
		m_iResultRequest = 0;
		m_pResultFile = m_pChildDriver->Open( m_sRequestPath, m_iRequestMode, m_iResultRequest );
		break;

	case REQ_CLOSE:
		ASSERT( m_pRequestFile != NULL );
		delete m_pRequestFile;

		/* Clear m_pRequestFile, so RequestTimedOut doesn't double-delete. */
		m_pRequestFile = NULL;
		break;

	case REQ_GET_FILE_SIZE:
		ASSERT( m_pRequestFile != NULL );
		m_iResultRequest = m_pRequestFile->GetFileSize();
		break;

	case REQ_SEEK:
		ASSERT( m_pRequestFile != NULL );
		m_iResultRequest = m_pRequestFile->Seek( m_iRequestPos );
		m_sResultError = m_pRequestFile->GetError();
		break;

	case REQ_READ:
		ASSERT( m_pRequestFile != NULL );
		ASSERT( m_pResultBuffer != NULL );
		m_iResultRequest = m_pRequestFile->Read( m_pResultBuffer, m_iRequestSize );
		m_sResultError = m_pRequestFile->GetError();
		break;

	case REQ_WRITE:
		ASSERT( m_pRequestFile != NULL );
		ASSERT( m_pRequestBuffer != NULL );
		m_iResultRequest = m_pRequestFile->Write( m_pRequestBuffer, m_iRequestSize );
		m_sResultError = m_pRequestFile->GetError();
		break;

	case REQ_FLUSH:
		ASSERT( m_pRequestFile != NULL );
		m_iResultRequest = m_pRequestFile->Flush();
		m_sResultError = m_pRequestFile->GetError();
		break;

	case REQ_COPY:
		ASSERT( m_pRequestFile != NULL );
		m_pResultFile = m_pRequestFile->Copy();
		break;

	case REQ_POPULATE_FILE_SET:
		ASSERT( !m_sRequestPath.empty() );
		m_ResultFileSet = FileSet();
		m_pChildDriver->FDB->GetFileSetCopy( m_sRequestPath, m_ResultFileSet );
		break;

	case REQ_FLUSH_DIR_CACHE:
		m_pChildDriver->FlushDirCache( m_sRequestPath );
		break;

	case REQ_REMOVE:
		ASSERT( !m_sRequestPath.empty() );
		m_iResultRequest = m_pChildDriver->Remove( m_sRequestPath )? 0:-1;
		break;

	case REQ_MOVE:
		ASSERT( !m_sRequestPath.empty() );
		ASSERT( !m_sRequestPath2.empty() );
		m_iResultRequest = m_pChildDriver->Move( m_sRequestPath, m_sRequestPath2 ) ? 0 : -1;
		break;

	default:
		FAIL_M( ssprintf("%i", iRequest) );
	}
}
예제 #22
0
void RageSoundManager::DeleteSoundWhenFinished( RageSound *pSound )
{
	g_SoundManMutex.Lock(); /* lock for access to owned_sounds */
	owned_sounds.insert( pSound );
	g_SoundManMutex.Unlock(); /* finished with owned_sounds */
}
예제 #23
0
void RageSoundManager::SetPrefs(float MixVol)
{
	g_SoundManMutex.Lock(); /* lock for access to MixVolume */
	MixVolume = MixVol;
	g_SoundManMutex.Unlock(); /* finished with MixVolume */
}
예제 #24
0
void NetworkStream_Win32::Open( const RString &sHost, int iPort, ConnectionType ct )
{
	m_Mutex.Lock();
	if( m_State == STATE_CANCELLED )
	{
		m_Mutex.Unlock();
		return;
	}

	// Always shut down a stream completely before reusing it.
	ASSERT_M( m_State == STATE_IDLE, ssprintf("%s:%i: %i", sHost.c_str(), iPort, m_State) );

	m_sHost = sHost;
	m_iPort = iPort;

	// Look up the hostname.
	hostent *pHost = NULL;
	char pBuf[MAXGETHOSTSTRUCT];
	{
		pHost = (hostent *) pBuf;

		ResolveMessageWindow mw;
		m_hResolve = WSAAsyncGetHostByName(
			mw.GetHwnd(),
			WM_USER,
			m_sHost,
			(char *) pHost,
			MAXGETHOSTSTRUCT
		);
		m_hResolveHwnd = mw.GetHwnd();
		m_Mutex.Unlock();

		mw.Run();

		m_Mutex.Lock();
		m_hResolve = NULL;
		m_hResolveHwnd = NULL;
		if( m_State == STATE_CANCELLED )
		{
			m_Mutex.Unlock();
			return;
		}
		int iError = mw.GetResult();
		if( iError )
		{
			SetError( ssprintf("DNS error: %s", WinSockErrorToString(iError).c_str() ) );
			m_Mutex.Unlock();
			return;
		}
		m_Mutex.Unlock();
	}

	{
		sockaddr_in addr;
		addr.sin_addr.s_addr = *(DWORD *)pHost->h_addr_list[0];
		addr.sin_family = PF_INET;
		addr.sin_port = htons( (uint16_t) iPort );

		m_Mutex.Lock();
		m_Socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
		if( m_Socket == INVALID_SOCKET )
		{
			int iError = WSAGetLastError();
			SetError( ssprintf("Error creating socket: %s", WinSockErrorToString(iError).c_str() ) );
			return;
		}

		/* Set up the completion event to be signalled when these events occur.
		 * This also sets the socket to nonblocking. */
		WSAEventSelect( m_Socket, m_hCompletionEvent, FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE );

		//fcntl( m_Socket, O_NONBLOCK, 1 );

		// Start opening the connection.
		int iResult = connect(m_Socket, (SOCKADDR *) &addr, sizeof(addr));

		m_Mutex.Unlock();

		// We expect EINPROGRESS/WSAEWOULDBLOCK.
		if( iResult == SOCKET_ERROR )
		{
			// Block until the connection attempt completes.
			int iError = WSAGetLastError();
			if( iError == WSAEWOULDBLOCK )
				iError = WaitForCompletionOrCancellation( FD_CONNECT_BIT );
			if( iError )
			{
				SetError( ssprintf("Couldn't connect: %s", WinSockErrorToString(iError).c_str() ) );
				return;
			}
		}
	}

	m_State = STATE_CONNECTED;
}