Commits

spencercw committed e178c37

Add reliability and usability improvements to the networking logic.

  • Participants
  • Parent commits 1c6b368

Comments (0)

Files changed (8)

File gb_emulator/include/gb_emulator/gb.h

 	void saveRam() const;
 
 private:
-	// Possible syncronisation clock sources
-	enum ClockSource
-	{
-		// The syncronisation clock is derived from the clock provided by the sound sub-system
-		CLOCK_NORMAL,
-		// The syncronisation clock provided by a remote server. This is used when an active serial
-		// comms connection is established to keep the two emulators in sync.
-		CLOCK_REMOTE
-	};
-
 	// I/O service used by various sub-systems
 	boost::asio::io_service ioService_;
 
 	// generated at exactly the same rate at which it is consumed.
 	unsigned frames_;
 
-	// The source of the synchronisation clock. This is used to keep the emulator running at the
-	// correct speed.
-	ClockSource clockSource_;
+	// Flag indicating whether we are in synchronisation with the remote host. This is used to make
+	// sure any messages are delivered to the game in time to ensure no data is lost
+	bool inSync_;
 
 	boost::scoped_ptr<GbCpu> cpu_;
 	boost::scoped_ptr<GbSound> sound_;

File gb_emulator/include/gb_emulator/gb_sound_wasapi.h

 	// Saves the sample rate that the renderer is using
 	void saveSampleRate(unsigned sampleRate);
 
+	// Generates the given number of samples on-demand to avoid underflowing the audio buffer
+	void extrapolate(unsigned samples);
+
 	// Disabled operations
 	GbSoundWasapi(const GbSoundWasapi &);
 	GbSoundWasapi & operator=(const GbSoundWasapi &);

File gb_emulator/include/gb_emulator/gb_sound_wasapi_renderer.h

 	 * \param sampleRateCallback The callback function to be called whenever the rendering sample
 	 * rate changes. This will be called during initialisation as well as any other time there is a
 	 * stream switch.
+	 * \param extrapolateCallback The callback function to be called whenever the renderer would
+	 * like the sound source to extrapolate the audio, instead of underflowing and skipping.
 	 */
 	void initialise(ATL::CComPtr<IMMDevice> device,
-		boost::function<void (unsigned)> sampleRateCallback);
+		boost::function<void (unsigned sampleRate)> sampleRateCallback,
+		boost::function<void (unsigned frames)> extrapolateCallback);
 
 	//! Checks for and handles any events then returns.
 	void pollEvents();
 
 	// The current render format type
 	boost::shared_ptr<WAVEFORMATEX> mixFormat_;
-	boost::function<void (unsigned)> sampleRateCallback_;
+	boost::function<void (unsigned sampleRate)> sampleRateCallback_;
+	boost::function<void (unsigned frames)> extrapolateCallback_;
 	RenderFormat renderFormat_;
 	uint32_t samplesPerPeriod_;
 	uint32_t bufferSize_;

File gb_emulator/src/gb.cpp

 #endif
 
 #include <boost/algorithm/string/predicate.hpp>
+#include <boost/bind.hpp>
 #include <boost/filesystem/fstream.hpp>
 #include <boost/filesystem/operations.hpp>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 
 Gb::Gb(const GbConfig &config):
 frames_(0),
-clockSource_(CLOCK_NORMAL),
+inSync_(true),
 serialCycles_(numeric_limits<int>::max()),
 timers_(*this),
 debugger_(*this, ioService_)
 			// Wait for the frames counter to be incremented. Depending on the clock source this
 			// happens in either the sound system or as a packet received from the remote server.
 			InterlockedDecrement(&frames_);
-			while (!InterlockedCompareExchange(&frames_, 0, 0))
+			while (!inSync_ || !InterlockedCompareExchange(&frames_, 0, 0))
 			{
 				sound_->pollEvents();
 				input_->poll();
 
 void Gb::soundTimerCallback()
 {
-	if (clockSource_ == CLOCK_NORMAL)
-	{
-		InterlockedIncrement(&frames_);
-	}
+	InterlockedIncrement(&frames_);
 }

File gb_emulator/src/gb_comms_serial.cpp

 #include <iostream>
 #include <limits>
 
+#include <boost/bind.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 
 #include <gb_net/tcp_client.h>
 
 void GbCommsSerial::synchronise()
 {
-	if (connected_ && server_)
+	if (connected_)
 	{
-		net_->postMessage(SYNC_ID, basic_string<uint8_t>());
+		gb_.inSync_ = false;
+		if (server_)
+		{
+			net_->postMessage(SYNC_ID, basic_string<uint8_t>());
+		}
 	}
 }
 
 {
 	clog << pt::microsec_clock::local_time() << ": Connection established to " << endpoint << "\n";
 	connected_ = true;
-
-	if (!server_)
-	{
-		// Switch to the remote clock if we are a client
-		gb_.clockSource_ = Gb::CLOCK_REMOTE;
-	}
 }
 
 void GbCommsSerial::onMessageReceived(uint16_t identifier, const basic_string<uint8_t> &message)
 
 	case SYNC_ID:
 		// Synchronisation pulse; emulate a frame
-		++gb_.frames_;
+		gb_.inSync_ = true;
+		if (!server_)
+		{
+			net_->postMessage(SYNC_ID, basic_string<uint8_t>());
+		}
 		break;
 
 	default:

File gb_emulator/src/gb_sound_wasapi.cpp

 #include <stdexcept>
 #include <string>
 
+#include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
 
 #include <atlbase.h>
 		GbSoundWasapiRenderer *tmp = new GbSoundWasapiRenderer();
 		renderer_.Attach(tmp);
 	}
-	renderer_->initialise(device, boost::bind(&GbSoundWasapi::saveSampleRate, this, _1));
+	renderer_->initialise(device, boost::bind(&GbSoundWasapi::saveSampleRate, this, _1),
+		boost::bind(&GbSoundWasapi::extrapolate, this, _1));
 }
 
 double GbSoundWasapi::poll()
 	renderer_->pollEvents();
 }
 
+void GbSoundWasapi::extrapolate(unsigned samples)
+{
+	// Generate the requested number of samples
+	for (unsigned i = 0; i != samples; ++i)
+	{
+		double left, right;
+		generateSample(left, right);
+		renderer_->pushSample(left, right);
+	}
+}
+
 void GbSoundWasapi::setTimerCallback(double frequency, boost::function<void ()> callback)
 {
 	renderer_->setTimerCallback(frequency, callback);

File gb_emulator/src/gb_sound_wasapi_renderer.cpp

 }
 
 void GbSoundWasapiRenderer::initialise(CComPtr<IMMDevice> device,
-	boost::function<void (unsigned)> sampleRateCallback)
+	boost::function<void (unsigned sampleRate)> sampleRateCallback,
+	boost::function<void (unsigned frames)> extrapolateCallback)
 {
 	device_ = device;
 	sampleRateCallback_ = sampleRateCallback;
+	extrapolateCallback_ = extrapolateCallback;
 
 	// Create the audio events
 	audioSamplesReadyEvent_.reset(CreateEvent(NULL, FALSE, FALSE, NULL), CloseHandle);
 		}
 	}
 
+	// Determine how much of the buffer we can fill with the data available
+	size_t size;
+	switch (renderFormat_)
+	{
+	case RENDER_FLOAT:
+		size = soundBufFloat_.size();
+		break;
+
+	case RENDER_INT16:
+		size = soundBufInt16_.size();
+		break;
+
+	default:
+		assert(!"unknown render format");
+		return;
+	}
+
+	assert(!(size % mixFormat_->nChannels));
+	uint32_t frames = static_cast<uint32_t>(size / mixFormat_->nChannels);
+	if (frames < framesAvailable)
+	{
+		// There is more space in the buffer than we currently have data for. It is preferable to
+		// extrapolate the audio here, rather than underflowing and creating a jarring stuttering
+		// sound
+		extrapolateCallback_(framesAvailable - frames);
+	}
+
 	// If we have one period's worth of data copy it into the buffer, otherwise skip this pass
-	size_t size;
 	const uint8_t *range1, *range2;
 	size_t range1Size, range2Size;
 
 	}
 
 	assert(!(size % mixFormat_->nChannels));
-	uint32_t frames = static_cast<uint32_t>(size / mixFormat_->nChannels);
+	frames = static_cast<uint32_t>(size / mixFormat_->nChannels);
 	if (!frames)
 	{
 		return;

File gb_emulator/src/gb_video_d3d11.cpp

 
 	// Switch the buffers. Only vsync if we only have one frame to render; this stops missed vsyncs
 	// from screwing up the emulator timing.
-	UINT vsync = gb_.clockSource_ == Gb::CLOCK_NORMAL &&
-		InterlockedCompareExchange(&gb_.frames_, 1, 1) == 1;
+	UINT vsync = InterlockedCompareExchange(&gb_.frames_, 1, 1) == 1;
 	hr = swapChain_->Present(vsync, 0);
 	if (FAILED(hr))
 	{