Browse Source

Use own DirectSound code in Windows/Direct3D9 mode as PortAudio has tendency for stutter.
Added a slow mode for flow control which is triggered on packet loss, and lasts for a fixed time.
Removed congestion-based flow control as unreliable.

Lasse Öörni 14 years ago
parent
commit
deb5455a0e

+ 279 - 25
Engine/Audio/Audio.cpp

@@ -33,18 +33,224 @@
 #include "Sound.h"
 #include "Sound.h"
 #include "SoundSource3D.h"
 #include "SoundSource3D.h"
 
 
+#ifdef USE_OPENGL
 #include <portaudio.h>
 #include <portaudio.h>
+#else
+#include "Thread.h"
+#include "Timer.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <dsound.h>
+#endif
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
 static const int MIN_BUFFERLENGTH = 20;
 static const int MIN_BUFFERLENGTH = 20;
 static const int MIN_MIXRATE = 11025;
 static const int MIN_MIXRATE = 11025;
 static const int MAX_MIXRATE = 48000;
 static const int MAX_MIXRATE = 48000;
+static const int AUDIO_FPS = 100;
 
 
+#ifdef USE_OPENGL
 static unsigned numInstances = 0;
 static unsigned numInstances = 0;
 
 
-static int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
-    const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData);
+static int AudioCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer,
+    const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData);
+#else
+class AudioStream : public Thread
+{
+public:
+    /// Construct
+    AudioStream(Audio* owner) :
+        owner_(owner),
+        dsObject_(0),
+        dsBuffer_(0)
+    {
+    }
+    
+    /// Destruct
+    ~AudioStream()
+    {
+        Close();
+        
+        if (dsObject_)
+        {
+            dsObject_->Release();
+            dsObject_ = 0;
+        }
+    }
+    
+    /// Create the DirectSound buffer
+    bool Open(unsigned windowHandle, int bufferLengthMSec, int mixRate, bool stereo)
+    {
+        Close();
+        
+        if (!dsObject_)
+        {
+            if (DirectSoundCreate(0, &dsObject_, 0) != DS_OK)
+                return false;
+        }
+        
+        if (dsObject_->SetCooperativeLevel((HWND)windowHandle, DSSCL_PRIORITY) != DS_OK)
+            return false;
+        
+        WAVEFORMATEX waveFormat;
+        waveFormat.wFormatTag = WAVE_FORMAT_PCM;
+        waveFormat.nSamplesPerSec = mixRate;
+        waveFormat.wBitsPerSample = 16;
+        
+        if (stereo)
+            waveFormat.nChannels = 2;
+        else
+            waveFormat.nChannels = 1;
+        
+        sampleSize_ = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
+        unsigned numSamples = (bufferLengthMSec * mixRate) / 1000;
+        
+        waveFormat.nAvgBytesPerSec = mixRate * sampleSize_;
+        waveFormat.nBlockAlign = sampleSize_;
+        waveFormat.cbSize = 0;
+        
+        DSBUFFERDESC bufferDesc;
+        memset(&bufferDesc, 0, sizeof(bufferDesc));
+        bufferDesc.dwSize = sizeof(bufferDesc);
+        bufferDesc.dwFlags = DSBCAPS_STICKYFOCUS;
+        bufferDesc.dwBufferBytes = numSamples * sampleSize_;
+        bufferDesc.lpwfxFormat = &waveFormat;
+        bufferSize_ = bufferDesc.dwBufferBytes;
+        
+        return dsObject_->CreateSoundBuffer(&bufferDesc, &dsBuffer_, 0) == DS_OK;
+    }
+    
+    /// Destroy the DirectSound buffer
+    void Close()
+    {
+        StopPlayback();
+        
+        if (dsBuffer_)
+        {
+            dsBuffer_->Release();
+            dsBuffer_ = 0;
+        }
+    }
+    
+    /// Start playback
+    bool StartPlayback()
+    {
+        if (IsStarted())
+            return true;
+        if (!dsBuffer_)
+            return false;
+        
+        // Clear the buffer before starting playback
+        DWORD bytes1, bytes2;
+        void *ptr1, *ptr2;
+        if (dsBuffer_->Lock(0, bufferSize_, &ptr1, &bytes1, &ptr2, &bytes2, 0) == DS_OK)
+        {
+            if (bytes1)
+                memset(ptr1, 0, bytes1);
+            if (bytes2)
+                memset(ptr2, 0, bytes2);
+            dsBuffer_->Unlock(ptr1, bytes1, ptr2, bytes2);
+        }
+        
+        if (Start())
+        {
+            SetPriority(THREAD_PRIORITY_ABOVE_NORMAL);
+            return true;
+        }
+        else
+            return false;
+    }
+    
+    /// Stop playback
+    void StopPlayback()
+    {
+        if (dsBuffer_ && IsStarted())
+            Stop();
+    }
+    
+    /// Mixing thread function
+    void ThreadFunction()
+    {
+        DWORD playCursor = 0;
+        DWORD writeCursor = 0;
+        
+        while (shouldRun_)
+        {
+            Timer audioUpdateTimer;
+            
+            // Restore buffer / restart playback if necessary
+            DWORD status;
+            dsBuffer_->GetStatus(&status);
+            if (status == DSBSTATUS_BUFFERLOST)
+            {
+                dsBuffer_->Restore();
+                dsBuffer_->GetStatus(&status);
+            }
+            if (!(status & DSBSTATUS_PLAYING))
+            {
+                dsBuffer_->Play(0, 0, DSBPLAY_LOOPING);
+                writeCursor = 0;
+            }
+            
+            // Get current buffer position
+            dsBuffer_->GetCurrentPosition(&playCursor, 0);
+            playCursor %= bufferSize_;
+            playCursor &= -((int)sampleSize_);
+            
+            if (playCursor != writeCursor)
+            {
+                int writeBytes = playCursor - writeCursor;
+                if (writeBytes < 0)
+                    writeBytes += bufferSize_;
+                
+                // Try to lock buffer
+                DWORD bytes1, bytes2;
+                void *ptr1, *ptr2;
+                if (dsBuffer_->Lock(writeCursor, writeBytes, &ptr1, &bytes1, &ptr2, &bytes2, 0) == DS_OK)
+                {
+                    // Mix sound to locked positions
+                    {
+                        MutexLock Lock(owner_->GetMutex());
+                        
+                        if (bytes1)
+                            owner_->MixOutput(ptr1, bytes1 / sampleSize_);
+                        if (bytes2)
+                            owner_->MixOutput(ptr2, bytes2 / sampleSize_);
+                    }
+                    
+                    // Unlock buffer and update write cursor
+                    dsBuffer_->Unlock(ptr1, bytes1, ptr2, bytes2);
+                    writeCursor += writeBytes;
+                    if (writeCursor >= bufferSize_)
+                        writeCursor -= bufferSize_;
+                }
+            }
+            
+            // Sleep the remaining time of the audio update period
+            int audioSleepTime = Max(1000 / AUDIO_FPS - (int)audioUpdateTimer.GetMSec(false), 0);
+            Sleep(audioSleepTime);
+        }
+        
+        dsBuffer_->Stop();
+    }
+    
+private:
+    /// Audio subsystem
+    Audio* owner_;
+    /// DirectSound interface
+    IDirectSound* dsObject_;
+    /// DirectSound buffer
+    IDirectSoundBuffer* dsBuffer_;
+    /// Sound buffer size in bytes
+    unsigned bufferSize_;
+    /// Sound buffer sample size
+    unsigned sampleSize_;
+    /// Playing flag
+    bool playing_;
+};
+#endif
 
 
 OBJECTTYPESTATIC(Audio);
 OBJECTTYPESTATIC(Audio);
 
 
@@ -62,6 +268,7 @@ Audio::Audio(Context* context) :
         masterGain_[i] = 1.0f;
         masterGain_[i] = 1.0f;
     
     
     // Initialize PortAudio under static mutex in case this is the first instance
     // Initialize PortAudio under static mutex in case this is the first instance
+    #ifdef USE_OPENGL
     {
     {
         MutexLock lock(GetStaticMutex());
         MutexLock lock(GetStaticMutex());
         if (!numInstances)
         if (!numInstances)
@@ -71,6 +278,7 @@ Audio::Audio(Context* context) :
         }
         }
         ++numInstances;
         ++numInstances;
     }
     }
+    #endif
 }
 }
 
 
 Audio::~Audio()
 Audio::~Audio()
@@ -78,6 +286,7 @@ Audio::~Audio()
     Release();
     Release();
     
     
     // Uninitialize PortAudio under static mutex in case this is the last instance
     // Uninitialize PortAudio under static mutex in case this is the last instance
+    #ifdef USE_OPENGL
     {
     {
         MutexLock lock(GetStaticMutex());
         MutexLock lock(GetStaticMutex());
         
         
@@ -85,6 +294,10 @@ Audio::~Audio()
         if (!numInstances)
         if (!numInstances)
             Pa_Terminate();
             Pa_Terminate();
     }
     }
+    #else
+    delete (AudioStream*)stream_;
+    stream_ = 0;
+    #endif
 }
 }
 
 
 bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpolate)
 bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpolate)
@@ -95,8 +308,9 @@ bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpo
     mixRate = Clamp(mixRate, MIN_MIXRATE, MAX_MIXRATE);
     mixRate = Clamp(mixRate, MIN_MIXRATE, MAX_MIXRATE);
     
     
     // Guarantee a fragment size that is low enough so that Vorbis decoding buffers do not wrap
     // Guarantee a fragment size that is low enough so that Vorbis decoding buffers do not wrap
-    unsigned fragmentSize = NextPowerOfTwo(mixRate >> 6);
+    fragmentSize_ = NextPowerOfTwo(mixRate >> 6);
     
     
+    #ifdef USE_OPENGL
     PaStreamParameters outputParams;
     PaStreamParameters outputParams;
     outputParams.device = Pa_GetDefaultOutputDevice();
     outputParams.device = Pa_GetDefaultOutputDevice();
     outputParams.channelCount = stereo ? 2 : 1;
     outputParams.channelCount = stereo ? 2 : 1;
@@ -109,14 +323,27 @@ bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpo
         LOGERROR("Failed to open audio stream");
         LOGERROR("Failed to open audio stream");
         return false;
         return false;
     }
     }
+    #else
+    if (!stream_)
+        stream_ = new AudioStream(this);
+    
+    unsigned windowHandle = 0;
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (graphics)
+        windowHandle = graphics->GetWindowHandle();
+    
+    if (!((AudioStream*)stream_)->Open(windowHandle, bufferLengthMSec, mixRate, stereo))
+    {
+        LOGERROR("Failed to open audio stream");
+        return false;
+    }
+    #endif
     
     
+    clipBuffer_ = new int[stereo ? fragmentSize_ << 1 : fragmentSize_];
     sampleSize_ = sizeof(short);
     sampleSize_ = sizeof(short);
     if (stereo)
     if (stereo)
         sampleSize_ <<= 1;
         sampleSize_ <<= 1;
     
     
-    // Allocate the clipping buffer
-    clipBuffer_ = new int[stereo ? fragmentSize << 1 : fragmentSize];
-    
     mixRate_ = mixRate;
     mixRate_ = mixRate;
     stereo_ = stereo;
     stereo_ = stereo;
     interpolate_ = interpolate;
     interpolate_ = interpolate;
@@ -149,11 +376,19 @@ bool Audio::Play()
         return false;
         return false;
     }
     }
     
     
+    #ifdef USE_OPENGL
     if (Pa_StartStream(stream_) != paNoError)
     if (Pa_StartStream(stream_) != paNoError)
     {
     {
         LOGERROR("Failed to start playback");
         LOGERROR("Failed to start playback");
         return false;
         return false;
     }
     }
+    #else
+    if (!((AudioStream*)stream_)->StartPlayback())
+    {
+        LOGERROR("Failed to start playback");
+        return false;
+    }
+    #endif
     
     
     playing_ = true;
     playing_ = true;
     return true;
     return true;
@@ -164,7 +399,12 @@ void Audio::Stop()
     if (!stream_ || !playing_)
     if (!stream_ || !playing_)
         return;
         return;
     
     
+    #ifdef USE_OPENGL
     Pa_StopStream(stream_);
     Pa_StopStream(stream_);
+    else
+    ((AudioStream*)stream_)->StopPlayback();
+    #endif
+    
     playing_ = false;
     playing_ = false;
 }
 }
 
 
@@ -230,6 +470,7 @@ void Audio::RemoveSoundSource(SoundSource* channel)
     }
     }
 }
 }
 
 
+#ifdef USE_OPENGL
 int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*
 int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*
     timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
     timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
 {
 {
@@ -242,26 +483,35 @@ int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long fra
     
     
     return 0;
     return 0;
 }
 }
+#endif
 
 
-void Audio::MixOutput(void *dest, unsigned mixSamples)
+void Audio::MixOutput(void *dest, unsigned samples)
 {
 {
-    unsigned clipSamples = mixSamples;
-    if (stereo_)
-        clipSamples <<= 1;
-    
-    // Clear clip buffer
-    memset(clipBuffer_.RawPtr(), 0, clipSamples * sizeof(int));
-    int* clipPtr = clipBuffer_.RawPtr();
-    
-    // Mix samples to clip buffer
-    for (PODVector<SoundSource*>::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i)
-        (*i)->Mix(clipPtr, mixSamples, mixRate_, stereo_, interpolate_);
-    
-    // Copy output from clip buffer to destination
-    clipPtr = clipBuffer_.RawPtr();
-    short* destPtr = (short*)dest;
-    while (clipSamples--)
-        *destPtr++ = Clamp(*clipPtr++, -32768, 32767);
+    while (samples)
+    {
+        // If sample count exceeds the fragment (clip buffer) size, split the work
+        unsigned workSamples = Min((int)samples, (int)fragmentSize_);
+        unsigned clipSamples = workSamples;
+        if (stereo_)
+            clipSamples <<= 1;
+        
+        // Clear clip buffer
+        memset(clipBuffer_.RawPtr(), 0, clipSamples * sizeof(int));
+        int* clipPtr = clipBuffer_.RawPtr();
+        
+        // Mix samples to clip buffer
+        for (PODVector<SoundSource*>::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i)
+            (*i)->Mix(clipPtr, workSamples, mixRate_, stereo_, interpolate_);
+        
+        // Copy output from clip buffer to destination
+        clipPtr = clipBuffer_.RawPtr();
+        short* destPtr = (short*)dest;
+        while (clipSamples--)
+            *destPtr++ = Clamp(*clipPtr++, -32768, 32767);
+        
+        samples -= workSamples;
+        ((unsigned char*&)destPtr) += sampleSize_ * workSamples;
+    }
 }
 }
 
 
 void Audio::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
 void Audio::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
@@ -277,9 +527,13 @@ void Audio::Release()
     
     
     if (stream_)
     if (stream_)
     {
     {
+        #ifdef USE_OPENGL
         Pa_CloseStream(stream_);
         Pa_CloseStream(stream_);
-        
         stream_ = 0;
         stream_ = 0;
+        #else
+        ((AudioStream*)stream_)->Close();
+        #endif
+        
         clipBuffer_.Reset();
         clipBuffer_.Reset();
     }
     }
 }
 }

+ 4 - 2
Engine/Audio/Audio.h

@@ -95,7 +95,7 @@ public:
     float GetSoundSourceMasterGain(SoundType type) const { return masterGain_[SOUND_MASTER] * masterGain_[type]; }
     float GetSoundSourceMasterGain(SoundType type) const { return masterGain_[SOUND_MASTER] * masterGain_[type]; }
     
     
     /// Mix sound sources into the buffer
     /// Mix sound sources into the buffer
-    void MixOutput(void *dest, unsigned mixSamples);
+    void MixOutput(void *dest, unsigned samples);
     
     
 private:
 private:
     /// Handle render update event
     /// Handle render update event
@@ -103,7 +103,7 @@ private:
     /// Stop sound output and release the sound buffer
     /// Stop sound output and release the sound buffer
     void Release();
     void Release();
     
     
-    /// PortAudio stream
+    /// Sound output stream
     void* stream_;
     void* stream_;
     /// Clipping buffer for mixing
     /// Clipping buffer for mixing
     SharedArrayPtr<int> clipBuffer_;
     SharedArrayPtr<int> clipBuffer_;
@@ -111,6 +111,8 @@ private:
     Mutex audioMutex_;
     Mutex audioMutex_;
     /// Sample size
     /// Sample size
     unsigned sampleSize_;
     unsigned sampleSize_;
+    /// Clip buffer size in samples
+    unsigned fragmentSize_;
     /// Mixing rate
     /// Mixing rate
     int mixRate_;
     int mixRate_;
     /// Stereo flag
     /// Stereo flag

+ 11 - 2
Engine/Audio/CMakeLists.txt

@@ -9,12 +9,21 @@ set (SOURCE_FILES ${CPP_FILES} ${H_FILES})
 # Include directories
 # Include directories
 include_directories (
 include_directories (
     ../Container ../Core ../Graphics ../IO ../Math ../Resource ../Scene ../../ThirdParty/STB
     ../Container ../Core ../Graphics ../IO ../Math ../Resource ../Scene ../../ThirdParty/STB
-    ../../ThirdParty/PortAudio/include
 )
 )
 
 
+if (USE_OPENGL)
+    include_directories (../../ThirdParty/PortAudio/include)
+endif ()
+
 # Define target & libraries to link
 # Define target & libraries to link
 add_library (${TARGET_NAME} STATIC ${SOURCE_FILES})
 add_library (${TARGET_NAME} STATIC ${SOURCE_FILES})
-target_link_libraries (${TARGET_NAME} Container Core Graphics IO Math PortAudio Resource Scene STB)
+target_link_libraries (${TARGET_NAME} Container Core Graphics IO Math Resource Scene STB)
+
+if (USE_OPENGL)
+    target_link_libraries (${TARGET_NAME} PortAudio)
+else ()
+    target_link_libraries (${TARGET_NAME} dsound.lib)
+endif ()
 
 
 enable_pch ()
 enable_pch ()
 finalize_lib ()
 finalize_lib ()

+ 1 - 1
Engine/Core/Thread.cpp

@@ -64,7 +64,7 @@ bool Thread::Start()
 {
 {
     // Check if already running
     // Check if already running
     if (handle_)
     if (handle_)
-        return false;
+        return true;
     
     
     shouldRun_ = true;
     shouldRun_ = true;
     #ifdef WIN32
     #ifdef WIN32

+ 2 - 2
Engine/Network/Connection.cpp

@@ -43,7 +43,7 @@
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
-static const int STATS_INTERVAL_MSEC = 2000;
+static const int STATS_INTERVAL_MSEC = 1000;
 static const String noName;
 static const String noName;
 
 
 PackageDownload::PackageDownload() :
 PackageDownload::PackageDownload() :
@@ -278,7 +278,7 @@ void Connection::SendRemoteEvents()
     {
     {
         statsTimer_.Reset();
         statsTimer_.Reset();
         char statsBuffer[256];
         char statsBuffer[256];
-        sprintf(statsBuffer, "Packets in %d Packets out %d Data in %.3f KB/s Data out %.3f KB/s", (int)connection_->PacketsInPerSec(),
+        sprintf(statsBuffer, "RTT %.3f ms Pkt in %d Pkt out %d Data in %.3f KB/s Data out %.3f KB/s", connection_->RoundTripTime(), (int)connection_->PacketsInPerSec(),
             (int)connection_->PacketsOutPerSec(), connection_->BytesInPerSec() / 1000.0f, connection_->BytesOutPerSec() / 1000.0f);
             (int)connection_->PacketsOutPerSec(), connection_->BytesInPerSec() / 1000.0f, connection_->BytesOutPerSec() / 1000.0f);
         LOGINFO(statsBuffer);
         LOGINFO(statsBuffer);
     }
     }

+ 1 - 1
ThirdParty/kNet/CMakeLists.txt

@@ -57,7 +57,7 @@ AddCompilationDefine(KNET_THREAD_CHECKING_ENABLED)
 #AddCompilationDefine(KNET_LOGGING_SUPPORT_ENABLED)
 #AddCompilationDefine(KNET_LOGGING_SUPPORT_ENABLED)
 
 
 # Enable storing profiling data from different network level events.
 # Enable storing profiling data from different network level events.
-AddCompilationDefine(KNET_NETWORK_PROFILING)
+#AddCompilationDefine(KNET_NETWORK_PROFILING)
 
 
 if (USE_BOOST)
 if (USE_BOOST)
    AddCompilationDefine(KNET_USE_BOOST)
    AddCompilationDefine(KNET_USE_BOOST)

+ 1 - 0
ThirdParty/kNet/include/kNet/UDPMessageConnection.h

@@ -149,6 +149,7 @@ private:
 
 
 	/// The flow control algorithm:
 	/// The flow control algorithm:
 	float datagramSendRate; ///< The number of datagrams/second to send.
 	float datagramSendRate; ///< The number of datagrams/second to send.
+	int slowModeDelay; ///< Go into slow increase mode for some time on receiving loss
 
 
 	// These variables correspond to RFC2988, http://tools.ietf.org/html/rfc2988 , section 2.
 	// These variables correspond to RFC2988, http://tools.ietf.org/html/rfc2988 , section 2.
 	bool rttCleared; ///< If true, smoothedRTT and rttVariation do not contain meaningful values, but "are clear".
 	bool rttCleared; ///< If true, smoothedRTT and rttVariation do not contain meaningful values, but "are clear".

+ 38 - 34
ThirdParty/kNet/src/UDPMessageConnection.cpp

@@ -54,15 +54,14 @@ static const int cMaxDatagramsToReadInOneFrame = 2048;
 /// Minimum retransmission timeout value (milliseconds)
 /// Minimum retransmission timeout value (milliseconds)
 static const float minRTOTimeoutValue = 500.f;
 static const float minRTOTimeoutValue = 500.f;
 /// Maximum retransmission timeout value (milliseconds)
 /// Maximum retransmission timeout value (milliseconds)
-static const float maxRTOTimeoutValue = 4000.f;
+static const float maxRTOTimeoutValue = 5000.f;
 
 
 UDPMessageConnection::UDPMessageConnection(Network *owner, NetworkServer *ownerServer, Socket *socket, ConnectionState startingState)
 UDPMessageConnection::UDPMessageConnection(Network *owner, NetworkServer *ownerServer, Socket *socket, ConnectionState startingState)
 :MessageConnection(owner, ownerServer, socket, startingState),
 :MessageConnection(owner, ownerServer, socket, startingState),
-retransmissionTimeout(3.f), numAcksLastFrame(0), numLossesLastFrame(0), smoothedRTT(3.f), rttVariation(0.f), rttCleared(true), // Set RTT initial values as per RFC 2988.
+numAcksLastFrame(0), numLossesLastFrame(0), rttVariation(0.f), rttCleared(true),
 lastReceivedInOrderPacketID(0), 
 lastReceivedInOrderPacketID(0), 
 lastSentInOrderPacketID(0), datagramPacketIDCounter(1),
 lastSentInOrderPacketID(0), datagramPacketIDCounter(1),
-packetLossRate(0.f), packetLossCount(0.f),
-datagramSendRate(50.f),
+packetLossRate(0.f), packetLossCount(0.f), slowModeDelay(0),
 receivedPacketIDs(64 * 1024), outboundPacketAckTrack(1024),
 receivedPacketIDs(64 * 1024), outboundPacketAckTrack(1024),
 previousReceivedPacketID(0), queuedInboundDatagrams(128)
 previousReceivedPacketID(0), queuedInboundDatagrams(128)
 {
 {
@@ -151,11 +150,11 @@ UDPMessageConnection::SocketReadResult UDPMessageConnection::ReadSocket(size_t &
 
 
 void UDPMessageConnection::Initialize()
 void UDPMessageConnection::Initialize()
 {
 {
-	// Set RTT initial values as per RFC 2988.
 	rttCleared = true;
 	rttCleared = true;
-	retransmissionTimeout = 3000.f;
-	smoothedRTT = 3000.f;
+	retransmissionTimeout = 1000.f;
+	smoothedRTT = 1000.f;
 	rttVariation = 0.f;
 	rttVariation = 0.f;
+	datagramSendRate = 50.f;
 
 
 	lastFrameTime = Clock::Tick();
 	lastFrameTime = Clock::Tick();
 	lastDatagramSendTime = Clock::Tick();
 	lastDatagramSendTime = Clock::Tick();
@@ -267,48 +266,53 @@ void UDPMessageConnection::HandleFlowControl()
 	const float minBandwidth = 50.f;
 	const float minBandwidth = 50.f;
 	const float maxBandwidth = 10000.f;
 	const float maxBandwidth = 10000.f;
 	const int framesPerSec = 30;
 	const int framesPerSec = 30;
+	const int maxSlowModeDelay = 10 * framesPerSec;
 
 
 	const tick_t frameLength = Clock::TicksPerSec() / framesPerSec; // in ticks
 	const tick_t frameLength = Clock::TicksPerSec() / framesPerSec; // in ticks
-	
-	float actualFrameRate = (float)Clock::TicksPerSec() / (float)Clock::TicksInBetween(Clock::Tick(), lastFrameTime);
-	
-	unsigned long numFrames = (unsigned long)(Clock::TicksInBetween(Clock::Tick(), lastFrameTime) / frameLength);
+	const tick_t now = Clock::Tick();
+
+	unsigned long numFrames = (unsigned long)(Clock::TicksInBetween(now, lastFrameTime) / frameLength);
 	if (numFrames > 0)
 	if (numFrames > 0)
 	{
 	{
 		if (numFrames >= framesPerSec)
 		if (numFrames >= framesPerSec)
 			numFrames = framesPerSec;
 			numFrames = framesPerSec;
 			
 			
 		int numUnacked = NumOutboundUnackedDatagrams();
 		int numUnacked = NumOutboundUnackedDatagrams();
-		float congestion = numUnacked / (max(rtt, 100.f) * 0.001f) / datagramSendRate;
 		
 		
-		// Check if more or less bandwidth is needed
-		///\todo Very simple logic for now
-		bool needMore = outboundQueue.Size() > 10;
-		bool needLess = outboundQueue.Size() == 0;
-		
-		// Need more: increase sendrate if no significant congestion, and not hitting maximum RTO
-		if (needMore && congestion < 1.f && retransmissionTimeout < maxRTOTimeoutValue * 0.9f)
+		// Reduce sendrate and go to slow increase mode on loss
+		if (numLossesLastFrame > 0)
 		{
 		{
-			float delta = (1.f - sqrtf(congestion)) * 10.f;
-			datagramSendRate = min(datagramSendRate + numFrames * delta, maxBandwidth);
+			float multiplier = max(1.f - numLossesLastFrame * 0.01f, 0.95f);
+			datagramSendRate = max(datagramSendRate * multiplier, minBandwidthOnLoss);
+			slowModeDelay = min(slowModeDelay + numLossesLastFrame * framesPerSec, maxSlowModeDelay);
 		}
 		}
-		// Need less: decrease sendrate if not already at minimum
-		else if (needLess && datagramSendRate > minBandwidth)
-			datagramSendRate = max(datagramSendRate * 0.975f, minBandwidth);
-		
-		// Reduce sendrate on congestion and loss
-		if (congestion > 1.f || numLossesLastFrame > 0)
+		else
 		{
 		{
-			float multiplier = max(1.f - max((congestion - 1.f) * 0.005f, numLossesLastFrame * 0.01f), 0.95f);
-			datagramSendRate = max(datagramSendRate * multiplier, minBandwidthOnLoss);
+			// Check if more or less bandwidth is needed
+			///\todo Very simple logic for now, can be improved
+			bool needMore = outboundQueue.Size() > 10;
+			bool needLess = outboundQueue.Size() == 0;
+			
+			// Need more: increase sendrate. Factor in RTT and acks
+			if (needMore)
+			{
+				float maxRTT = max(rtt, smoothedRTT);
+				float delta = (50.f + numAcksLastFrame) / maxRTT;
+				if (slowModeDelay > 0)
+					delta *= 0.2f;
+				datagramSendRate = min(datagramSendRate + delta, maxBandwidth);
+			}
+			// Need less: decrease sendrate if not already at minimum
+			else if (needLess && datagramSendRate > minBandwidth)
+				datagramSendRate = max(datagramSendRate * 0.98f, minBandwidth);
 		}
 		}
 		
 		
+		if (slowModeDelay > 0)
+			--slowModeDelay;
+		
 		numAcksLastFrame = 0;
 		numAcksLastFrame = 0;
 		numLossesLastFrame = 0;
 		numLossesLastFrame = 0;
-		if (numFrames < framesPerSec)
-			lastFrameTime += numFrames * frameLength;
-		else
-			lastFrameTime = Clock::Tick();
+		lastFrameTime = now;
 	}
 	}
 }
 }
 
 
@@ -1044,7 +1048,7 @@ void UDPMessageConnection::UpdateRTOCounterOnPacketLoss()
 
 
 	using namespace std;
 	using namespace std;
 
 
-	retransmissionTimeout = smoothedRTT = min(maxRTOTimeoutValue, max(minRTOTimeoutValue, smoothedRTT * 2.f));
+	// retransmissionTimeout = smoothedRTT = min(maxRTOTimeoutValue, max(minRTOTimeoutValue, smoothedRTT * 2.f));
 	// The variation just gives bogus values, so clear it altogether.
 	// The variation just gives bogus values, so clear it altogether.
 	rttVariation = 0.f;
 	rttVariation = 0.f;