Merge remote-tracking branch 'upstream/master' into 19765

This commit is contained in:
Mohammed Nafees 2014-06-25 01:50:29 +05:30
commit d5d2107b97
28 changed files with 885 additions and 156 deletions

View file

@ -54,9 +54,6 @@
#include "AudioMixer.h"
const short JITTER_BUFFER_MSECS = 12;
const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0);
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
@ -67,6 +64,8 @@ void attachNewBufferToNode(Node *newNode) {
}
}
bool AudioMixer::_useDynamicJitterBuffers = false;
AudioMixer::AudioMixer(const QByteArray& packet) :
ThreadedAssignment(packet),
_trailingSleepRatio(1.0f),
@ -427,12 +426,46 @@ void AudioMixer::sendStatsPacket() {
} else {
statsObject["average_mixes_per_listener"] = 0.0;
}
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
_sumListeners = 0;
_sumMixes = 0;
_numStatFrames = 0;
// NOTE: These stats can be too large to fit in an MTU, so we break it up into multiple packts...
QJsonObject statsObject2;
// add stats for each listerner
bool somethingToSend = false;
int sizeOfStats = 0;
int TOO_BIG_FOR_MTU = 1200; // some extra space for JSONification
NodeList* nodeList = NodeList::getInstance();
int clientNumber = 0;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
clientNumber++;
AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
if (clientData) {
QString property = "jitterStats." + node->getUUID().toString();
QString value = clientData->getJitterBufferStats();
statsObject2[qPrintable(property)] = value;
somethingToSend = true;
sizeOfStats += property.size() + value.size();
}
// if we're too large, send the packet
if (sizeOfStats > TOO_BIG_FOR_MTU) {
nodeList->sendStatsToDomainServer(statsObject2);
sizeOfStats = 0;
statsObject2 = QJsonObject(); // clear it
somethingToSend = false;
}
}
if (somethingToSend) {
nodeList->sendStatsToDomainServer(statsObject2);
}
}
void AudioMixer::run() {
@ -471,6 +504,16 @@ void AudioMixer::run() {
<< QString("%1, %2, %3").arg(destinationCenter.x).arg(destinationCenter.y).arg(destinationCenter.z);
}
// check the payload to see if we have asked for dynamicJitterBuffer support
const QString DYNAMIC_JITTER_BUFFER_REGEX_STRING = "--dynamicJitterBuffer";
QRegExp dynamicJitterBufferMatch(DYNAMIC_JITTER_BUFFER_REGEX_STRING);
if (dynamicJitterBufferMatch.indexIn(_payload) != -1) {
qDebug() << "Enable dynamic jitter buffers.";
_useDynamicJitterBuffers = true;
} else {
qDebug() << "Dynamic jitter buffers disabled, using old behavior.";
}
int nextFrame = 0;
QElapsedTimer timer;
timer.start();
@ -487,8 +530,7 @@ void AudioMixer::run() {
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData()) {
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES,
_sourceUnattenuatedZone,
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(_sourceUnattenuatedZone,
_listenerUnattenuatedZone);
}
}

View file

@ -34,6 +34,9 @@ public slots:
void readPendingDatagrams();
void sendStatsPacket();
static bool getUseDynamicJitterBuffers() { return _useDynamicJitterBuffers; }
private:
/// adds one buffer to the mix for a listening node
void addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd,
@ -54,6 +57,7 @@ private:
int _sumMixes;
AABox* _sourceUnattenuatedZone;
AABox* _listenerUnattenuatedZone;
static bool _useDynamicJitterBuffers;
};
#endif // hifi_AudioMixer_h

View file

@ -16,6 +16,7 @@
#include "InjectedAudioRingBuffer.h"
#include "AudioMixer.h"
#include "AudioMixerClientData.h"
AudioMixerClientData::AudioMixerClientData() :
@ -65,7 +66,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
if (!avatarRingBuffer) {
// we don't have an AvatarAudioRingBuffer yet, so add it
avatarRingBuffer = new AvatarAudioRingBuffer(isStereo);
avatarRingBuffer = new AvatarAudioRingBuffer(isStereo, AudioMixer::getUseDynamicJitterBuffers());
_ringBuffers.push_back(avatarRingBuffer);
}
@ -88,7 +89,8 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
if (!matchingInjectedRingBuffer) {
// we don't have a matching injected audio ring buffer, so add it
matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier);
matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier,
AudioMixer::getUseDynamicJitterBuffers());
_ringBuffers.push_back(matchingInjectedRingBuffer);
}
@ -98,10 +100,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
return 0;
}
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
AABox* checkSourceZone, AABox* listenerZone) {
void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, AABox* listenerZone) {
for (int i = 0; i < _ringBuffers.size(); i++) {
if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) {
if (_ringBuffers[i]->shouldBeAddedToMix()) {
// this is a ring buffer that is ready to go
// set its flag so we know to push its buffer when all is said and done
_ringBuffers[i]->setWillBeAddedToMix(true);
@ -120,20 +121,62 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSam
}
void AudioMixerClientData::pushBuffersAfterFrameSend() {
for (int i = 0; i < _ringBuffers.size(); i++) {
QList<PositionalAudioRingBuffer*>::iterator i = _ringBuffers.begin();
while (i != _ringBuffers.end()) {
// this was a used buffer, push the output pointer forwards
PositionalAudioRingBuffer* audioBuffer = _ringBuffers[i];
PositionalAudioRingBuffer* audioBuffer = *i;
if (audioBuffer->willBeAddedToMix()) {
audioBuffer->shiftReadPosition(audioBuffer->isStereo()
? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
audioBuffer->shiftReadPosition(audioBuffer->getSamplesPerFrame());
audioBuffer->setWillBeAddedToMix(false);
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
// this is an empty audio buffer that has starved, safe to delete
delete audioBuffer;
_ringBuffers.erase(_ringBuffers.begin() + i);
i = _ringBuffers.erase(i);
continue;
}
i++;
}
}
QString AudioMixerClientData::getJitterBufferStats() const {
QString result;
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
if (avatarRingBuffer) {
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
int resetCount = avatarRingBuffer->getResetCount();
int samplesAvailable = avatarRingBuffer->samplesAvailable();
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
result += "mic.desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " resets:" + QString::number(resetCount);
} else {
result = "mic unknown";
}
for (int i = 0; i < _ringBuffers.size(); i++) {
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
int resetCount = _ringBuffers[i]->getResetCount();
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
result += "| injected["+QString::number(i)+"].desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " resets:" + QString::number(resetCount);
}
}
return result;
}

View file

@ -27,9 +27,11 @@ public:
AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
int parseData(const QByteArray& packet);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
void pushBuffersAfterFrameSend();
QString getJitterBufferStats() const;
private:
QList<PositionalAudioRingBuffer*> _ringBuffers;
};

View file

@ -13,12 +13,15 @@
#include "AvatarAudioRingBuffer.h"
AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo) :
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Microphone, isStereo) {
AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBuffer) :
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Microphone, isStereo, dynamicJitterBuffer) {
}
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
_interframeTimeGapStats.frameReceived();
updateDesiredJitterBufferFrames();
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
return PositionalAudioRingBuffer::parseData(packet);
}

View file

@ -18,7 +18,7 @@
class AvatarAudioRingBuffer : public PositionalAudioRingBuffer {
public:
AvatarAudioRingBuffer(bool isStereo = false);
AvatarAudioRingBuffer(bool isStereo = false, bool dynamicJitterBuffer = false);
int parseData(const QByteArray& packet);
private:

View file

@ -3189,7 +3189,7 @@ void Application::updateWindowTitle(){
float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT;
QString creditBalanceString;
creditBalanceString.sprintf("%.8f", creditBalance);
creditBalanceString.sprintf("%.8f", floor(creditBalance + 0.5));
title += " - ₵" + creditBalanceString;
}

View file

@ -218,9 +218,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
pPropertyStore->Release();
pPropertyStore = NULL;
//QAudio devices seems to only take the 31 first characters of the Friendly Device Name.
const DWORD QT_WIN_MAX_AUDIO_DEVICENAME_LEN = 31;
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal).left(QT_WIN_MAX_AUDIO_DEVICENAME_LEN);
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
const DWORD WINDOWS7_MAJOR_VERSION = 6;
const DWORD WINDOWS7_MINOR_VERSION = 1;
if (osvi.dwMajorVersion <= WINDOWS7_MAJOR_VERSION && osvi.dwMinorVersion <= WINDOWS7_MINOR_VERSION) {
// Windows 7 provides only the 31 first characters of the device name.
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
}
qDebug() << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
PropVariantClear(&pv);
}
@ -461,8 +466,8 @@ void Audio::handleAudioInput() {
int16_t* inputAudioSamples = new int16_t[inputSamplesRequired];
_inputRingBuffer.readSamples(inputAudioSamples, inputSamplesRequired);
int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
const int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
const int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
// zero out the monoAudioSamples array and the locally injected audio
memset(networkAudioSamples, 0, numNetworkBytes);
@ -634,12 +639,10 @@ void Audio::handleAudioInput() {
packetType = PacketTypeSilentAudioFrame;
// we need to indicate how many silent samples this is to the audio mixer
audioDataPacket[0] = _isStereoInput
? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
: NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
networkAudioSamples[0] = numNetworkSamples;
numAudioBytes = sizeof(int16_t);
} else {
numAudioBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
numAudioBytes = numNetworkBytes;
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)) {
packetType = PacketTypeMicrophoneAudioWithEcho;

View file

@ -249,12 +249,21 @@ Menu::Menu() :
QMenu* viewMenu = addMenu("View");
#ifdef Q_OS_MAC
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::Fullscreen,
Qt::CTRL | Qt::META | Qt::Key_F,
false,
appInstance,
SLOT(setFullscreen(bool)));
#else
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::Fullscreen,
Qt::CTRL | Qt::Key_F,
false,
appInstance,
SLOT(setFullscreen(bool)));
#endif
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
appInstance,SLOT(cameraMenuChanged()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
@ -1293,6 +1302,7 @@ void Menu::showChat() {
if (_chatWindow->isHidden()) {
_chatWindow->show();
}
_chatWindow->activateWindow();
} else {
Application::getInstance()->getTrayIcon()->showMessage("Interface", "You need to login to be able to chat with others on this domain.");
}

View file

@ -30,6 +30,9 @@
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const float OPACITY_ACTIVE = 1.0;
const float OPACITY_INACTIVE = 0.8;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)");
const QRegularExpression regexHifiLinks("([#@]\\S+)");
const QString mentionSoundsPath("/mention-sounds/");
@ -108,7 +111,7 @@ ChatWindow::~ChatWindow() {
void ChatWindow::keyPressEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Escape) {
hide();
Application::getInstance()->getWindow()->activateWindow();
} else {
FramelessDialog::keyPressEvent(event);
}
@ -383,3 +386,12 @@ void ChatWindow::scrollToBottom() {
QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
verticalScrollBar->setValue(verticalScrollBar->maximum());
}
bool ChatWindow::event(QEvent* event) {
if (event->type() == QEvent::WindowActivate) {
setWindowOpacity(OPACITY_ACTIVE);
} else if (event->type() == QEvent::WindowDeactivate) {
setWindowOpacity(OPACITY_INACTIVE);
}
return FramelessDialog::event(event);
}

View file

@ -50,6 +50,7 @@ protected:
virtual void keyPressEvent(QKeyEvent *event);
virtual void showEvent(QShowEvent* event);
virtual bool event(QEvent* event);
private:
#ifdef HAVE_QXMPP

View file

@ -21,6 +21,7 @@
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
NodeData(),
_resetCount(0),
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
_numFrameSamples(numFrameSamples),
_isStarved(true),
@ -122,19 +123,15 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
std::less<int16_t*> less;
std::less_equal<int16_t*> lessEqual;
if (_hasStarted
&& (less(_endOfLastWrite, _nextOutput)
&& lessEqual(_nextOutput, shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy)))) {
if (_hasStarted && samplesToCopy > _sampleCapacity - samplesAvailable()) {
// this read will cross the next output, so call us starved and reset the buffer
qDebug() << "Filled the ring buffer. Resetting.";
_endOfLastWrite = _buffer;
_nextOutput = _buffer;
_isStarved = true;
_resetCount++;
}
if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t));
} else {
@ -144,7 +141,7 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
}
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
return samplesToCopy * sizeof(int16_t);
}

View file

@ -31,7 +31,7 @@ const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTE
const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
/ (float) SAMPLE_RATE) * 1000 * 1000);
const short RING_BUFFER_LENGTH_FRAMES = 10;
const short RING_BUFFER_LENGTH_FRAMES = 100;
const int MAX_SAMPLE_VALUE = std::numeric_limits<int16_t>::max();
const int MIN_SAMPLE_VALUE = std::numeric_limits<int16_t>::min();
@ -71,6 +71,7 @@ public:
bool isStarved() const { return _isStarved; }
void setIsStarved(bool isStarved) { _isStarved = isStarved; }
int getResetCount() const { return _resetCount; } /// how many times has the ring buffer written past the end and reset
bool hasStarted() const { return _hasStarted; }
void addSilentFrame(int numSilentSamples);
@ -80,6 +81,8 @@ protected:
AudioRingBuffer& operator= (const AudioRingBuffer&);
int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
int _resetCount; /// how many times has the ring buffer written past the end and done a reset
int _sampleCapacity;
int _numFrameSamples;

View file

@ -19,8 +19,8 @@
#include "InjectedAudioRingBuffer.h"
InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier) :
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector),
InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, bool dynamicJitterBuffer) :
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector, /* isStereo=*/ false , dynamicJitterBuffer),
_streamIdentifier(streamIdentifier),
_radius(0.0f),
_attenuationRatio(0)
@ -31,6 +31,9 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier)
const uchar MAX_INJECTOR_VOLUME = 255;
int InjectedAudioRingBuffer::parseData(const QByteArray& packet) {
_interframeTimeGapStats.frameReceived();
updateDesiredJitterBufferFrames();
// setup a data stream to read from this packet
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));

View file

@ -18,7 +18,7 @@
class InjectedAudioRingBuffer : public PositionalAudioRingBuffer {
public:
InjectedAudioRingBuffer(const QUuid& streamIdentifier = QUuid());
InjectedAudioRingBuffer(const QUuid& streamIdentifier = QUuid(), bool dynamicJitterBuffer = false);
int parseData(const QByteArray& packet);

View file

@ -19,8 +19,75 @@
#include <UUID.h>
#include "PositionalAudioRingBuffer.h"
#include "SharedUtil.h"
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo) :
InterframeTimeGapStats::InterframeTimeGapStats()
: _lastFrameReceivedTime(0),
_numSamplesInCurrentInterval(0),
_currentIntervalMaxGap(0),
_newestIntervalMaxGapAt(0),
_windowMaxGap(0),
_newWindowMaxGapAvailable(false)
{
memset(_intervalMaxGaps, 0, TIME_GAP_NUM_INTERVALS_IN_WINDOW * sizeof(quint64));
}
void InterframeTimeGapStats::frameReceived() {
quint64 now = usecTimestampNow();
// make sure this isn't the first time frameReceived() is called so can actually calculate a gap.
if (_lastFrameReceivedTime != 0) {
quint64 gap = now - _lastFrameReceivedTime;
// update the current interval max
if (gap > _currentIntervalMaxGap) {
_currentIntervalMaxGap = gap;
// keep the window max gap at least as large as the current interval max
// this allows the window max gap to respond immediately to a sudden spike in gap times
// also, this prevents the window max gap from staying at 0 until the first interval of samples filled up
if (_currentIntervalMaxGap > _windowMaxGap) {
_windowMaxGap = _currentIntervalMaxGap;
_newWindowMaxGapAvailable = true;
}
}
_numSamplesInCurrentInterval++;
// if the current interval of samples is now full, record it in our interval maxes
if (_numSamplesInCurrentInterval == TIME_GAP_NUM_SAMPLES_IN_INTERVAL) {
// find location to insert this interval's max (increment index cyclically)
_newestIntervalMaxGapAt = _newestIntervalMaxGapAt == TIME_GAP_NUM_INTERVALS_IN_WINDOW - 1 ? 0 : _newestIntervalMaxGapAt + 1;
// record the current interval's max gap as the newest
_intervalMaxGaps[_newestIntervalMaxGapAt] = _currentIntervalMaxGap;
// update the window max gap, which is the max out of all the past intervals' max gaps
_windowMaxGap = 0;
for (int i = 0; i < TIME_GAP_NUM_INTERVALS_IN_WINDOW; i++) {
if (_intervalMaxGaps[i] > _windowMaxGap) {
_windowMaxGap = _intervalMaxGaps[i];
}
}
_newWindowMaxGapAvailable = true;
// reset the current interval
_numSamplesInCurrentInterval = 0;
_currentIntervalMaxGap = 0;
}
}
_lastFrameReceivedTime = now;
}
quint64 InterframeTimeGapStats::getWindowMaxGap() {
_newWindowMaxGapAvailable = false;
return _windowMaxGap;
}
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type,
bool isStereo, bool dynamicJitterBuffers) :
AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL),
_type(type),
_position(0.0f, 0.0f, 0.0f),
@ -29,9 +96,11 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
_shouldLoopbackForNode(false),
_shouldOutputStarveDebug(true),
_isStereo(isStereo),
_listenerUnattenuatedZone(NULL)
_listenerUnattenuatedZone(NULL),
_desiredJitterBufferFrames(1),
_currentJitterBufferFrames(0),
_dynamicJitterBuffers(dynamicJitterBuffers)
{
}
int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
@ -53,14 +122,35 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
readBytes += sizeof(int16_t);
// NOTE: fixes a bug in old clients that would send garbage for their number of silentSamples
numSilentSamples = getSamplesPerFrame();
if (numSilentSamples > 0) {
addSilentFrame(numSilentSamples);
if (_currentJitterBufferFrames > _desiredJitterBufferFrames) {
// our current jitter buffer size exceeds its desired value, so ignore some silent
// frames to get that size as close to desired as possible
int samplesPerFrame = getSamplesPerFrame();
int numSilentFrames = numSilentSamples / samplesPerFrame;
int numFramesToDropDesired = _currentJitterBufferFrames - _desiredJitterBufferFrames;
if (numSilentFrames > numFramesToDropDesired) {
// we have more than enough frames to drop to get the jitter buffer to its desired length
int numSilentFramesToAdd = numSilentFrames - numFramesToDropDesired;
addSilentFrame(numSilentFramesToAdd * samplesPerFrame);
_currentJitterBufferFrames = _desiredJitterBufferFrames;
} else {
// we need to drop all frames to get the jitter buffer close as possible to its desired length
_currentJitterBufferFrames -= numSilentFrames;
}
} else {
addSilentFrame(numSilentSamples);
}
}
} else {
// there is audio data to read
readBytes += writeData(packet.data() + readBytes, packet.size() - readBytes);
}
return readBytes;
}
@ -106,29 +196,72 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() {
}
}
bool PositionalAudioRingBuffer::shouldBeAddedToMix(int numJitterBufferSamples) {
if (!isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL + numJitterBufferSamples)) {
bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
int samplesPerFrame = getSamplesPerFrame();
int desiredJitterBufferSamples = _desiredJitterBufferFrames * samplesPerFrame;
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
// if the buffer was starved, allow it to accrue at least the desired number of
// jitter buffer frames before we start taking frames from it for mixing
if (_shouldOutputStarveDebug) {
_shouldOutputStarveDebug = false;
}
return false;
} else if (samplesAvailable() < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) {
return false;
} else if (samplesAvailable() < samplesPerFrame) {
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
_isStarved = true;
// set to 0 to indicate the jitter buffer is starved
_currentJitterBufferFrames = 0;
// reset our _shouldOutputStarveDebug to true so the next is printed
_shouldOutputStarveDebug = true;
return false;
} else {
// good buffer, add this to the mix
}
// good buffer, add this to the mix
if (_isStarved) {
// if this buffer has just finished replenishing after being starved, the number of frames in it now
// minus one (since a frame will be read immediately after this) is the length of the jitter buffer
_currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1;
_isStarved = false;
// since we've read data from ring buffer at least once - we've started
_hasStarted = true;
return true;
}
return false;
// since we've read data from ring buffer at least once - we've started
_hasStarted = true;
return true;
}
int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const {
int calculatedDesiredJitterBufferFrames = 1;
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.peekWindowMaxGap() / USECS_PER_FRAME);
if (calculatedDesiredJitterBufferFrames < 1) {
calculatedDesiredJitterBufferFrames = 1;
}
return calculatedDesiredJitterBufferFrames;
}
void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
if (_interframeTimeGapStats.hasNewWindowMaxGapAvailable()) {
if (!_dynamicJitterBuffers) {
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
} else {
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
if (_desiredJitterBufferFrames < 1) {
_desiredJitterBufferFrames = 1;
}
const int maxDesired = RING_BUFFER_LENGTH_FRAMES - 1;
if (_desiredJitterBufferFrames > maxDesired) {
_desiredJitterBufferFrames = maxDesired;
}
}
}
}

View file

@ -18,6 +18,31 @@
#include "AudioRingBuffer.h"
// this means that every 500 samples, the max for the past 10*500 samples will be calculated
const int TIME_GAP_NUM_SAMPLES_IN_INTERVAL = 500;
const int TIME_GAP_NUM_INTERVALS_IN_WINDOW = 10;
// class used to track time between incoming frames for the purpose of varying the jitter buffer length
class InterframeTimeGapStats {
public:
InterframeTimeGapStats();
void frameReceived();
bool hasNewWindowMaxGapAvailable() const { return _newWindowMaxGapAvailable; }
quint64 peekWindowMaxGap() const { return _windowMaxGap; }
quint64 getWindowMaxGap();
private:
quint64 _lastFrameReceivedTime;
int _numSamplesInCurrentInterval;
quint64 _currentIntervalMaxGap;
quint64 _intervalMaxGaps[TIME_GAP_NUM_INTERVALS_IN_WINDOW];
int _newestIntervalMaxGapAt;
quint64 _windowMaxGap;
bool _newWindowMaxGapAvailable;
};
class PositionalAudioRingBuffer : public AudioRingBuffer {
public:
enum Type {
@ -25,7 +50,7 @@ public:
Injector
};
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo = false);
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo = false, bool dynamicJitterBuffers = false);
int parseData(const QByteArray& packet);
int parsePositionalData(const QByteArray& positionalByteArray);
@ -34,7 +59,7 @@ public:
void updateNextOutputTrailingLoudness();
float getNextOutputTrailingLoudness() const { return _nextOutputTrailingLoudness; }
bool shouldBeAddedToMix(int numJitterBufferSamples);
bool shouldBeAddedToMix();
bool willBeAddedToMix() const { return _willBeAddedToMix; }
void setWillBeAddedToMix(bool willBeAddedToMix) { _willBeAddedToMix = willBeAddedToMix; }
@ -50,10 +75,18 @@ public:
AABox* getListenerUnattenuatedZone() const { return _listenerUnattenuatedZone; }
void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; }
int getSamplesPerFrame() const { return _isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; }
int getCalculatedDesiredJitterBufferFrames() const; /// returns what we would calculate our desired as if asked
int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; }
int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; }
protected:
// disallow copying of PositionalAudioRingBuffer objects
PositionalAudioRingBuffer(const PositionalAudioRingBuffer&);
PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&);
void updateDesiredJitterBufferFrames();
PositionalAudioRingBuffer::Type _type;
glm::vec3 _position;
@ -65,6 +98,11 @@ protected:
float _nextOutputTrailingLoudness;
AABox* _listenerUnattenuatedZone;
InterframeTimeGapStats _interframeTimeGapStats;
int _desiredJitterBufferFrames;
int _currentJitterBufferFrames;
bool _dynamicJitterBuffers;
};
#endif // hifi_PositionalAudioRingBuffer_h

View file

@ -211,6 +211,11 @@ void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelSt
root.writeSubdivision(state);
}
bool Attribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
return firstRoot.deepEquals(this, secondRoot, minimum, size, lod);
}
FloatAttribute::FloatAttribute(const QString& name, float defaultValue) :
SimpleInlineAttribute<float>(name, defaultValue) {
}
@ -449,6 +454,12 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons
}
}
bool SharedObjectAttribute::deepEqual(void* first, void* second) const {
SharedObjectPointer firstObject = decodeInline<SharedObjectPointer>(first);
SharedObjectPointer secondObject = decodeInline<SharedObjectPointer>(second);
return firstObject ? firstObject->equals(secondObject) : !secondObject;
}
bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const {
SharedObjectPointer firstChild = decodeInline<SharedObjectPointer>(children[0]);
for (int i = 1; i < MERGE_COUNT; i++) {
@ -489,6 +500,35 @@ MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode(
return new MetavoxelNode(value, original);
}
static bool setsEqual(const SharedObjectSet& firstSet, const SharedObjectSet& secondSet) {
if (firstSet.size() != secondSet.size()) {
return false;
}
// some hackiness here: we assume that the local ids of the first set correspond to the remote ids of the second,
// so that this will work with the tests
foreach (const SharedObjectPointer& firstObject, firstSet) {
int id = firstObject->getID();
bool found = false;
foreach (const SharedObjectPointer& secondObject, secondSet) {
if (secondObject->getRemoteID() == id) {
if (!firstObject->equals(secondObject)) {
return false;
}
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
bool SharedObjectSetAttribute::deepEqual(void* first, void* second) const {
return setsEqual(decodeInline<SharedObjectSet>(first), decodeInline<SharedObjectSet>(second));
}
bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const {
for (int i = 0; i < MERGE_COUNT; i++) {
if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
@ -563,3 +603,12 @@ void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, M
state.stream << SharedObjectPointer();
}
bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
SharedObjectSet firstSet;
firstRoot.getSpanners(this, minimum, size, lod, firstSet);
SharedObjectSet secondSet;
secondRoot.getSpanners(this, minimum, size, lod, secondSet);
return setsEqual(firstSet, secondSet);
}

View file

@ -27,6 +27,7 @@ class QScriptValue;
class Attribute;
class MetavoxelData;
class MetavoxelLOD;
class MetavoxelNode;
class MetavoxelStreamState;
@ -213,6 +214,11 @@ public:
virtual bool equal(void* first, void* second) const = 0;
virtual bool deepEqual(void* first, void* second) const { return equal(first, second); }
virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
/// Merges the value of a parent and its children.
/// \param postRead whether or not the merge is happening after a read
/// \return whether or not the children and parent values are all equal
@ -406,6 +412,8 @@ public:
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
virtual bool deepEqual(void* first, void* second) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
virtual void* createFromVariant(const QVariant& value) const;
@ -434,6 +442,8 @@ public:
virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
virtual bool deepEqual(void* first, void* second) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
@ -462,6 +472,9 @@ public:
virtual void readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state);
virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state);
virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
};
#endif // hifi_AttributeRegistry_h

View file

@ -110,6 +110,10 @@ const TypeStreamer* Bitstream::getTypeStreamer(int type) {
return getTypeStreamers().value(type);
}
const ObjectStreamer* Bitstream::getObjectStreamer(const QMetaObject* metaObject) {
return getObjectStreamers().value(metaObject);
}
const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
return getMetaObjects().value(className);
}
@ -2316,6 +2320,15 @@ QObject* MappedObjectStreamer::putJSONData(JSONReader& reader, const QJsonObject
return object;
}
bool MappedObjectStreamer::equal(const QObject* first, const QObject* second) const {
foreach (const StreamerPropertyPair& property, _properties) {
if (!property.first->equal(property.second.read(first), property.second.read(second))) {
return false;
}
}
return true;
}
void MappedObjectStreamer::write(Bitstream& out, const QObject* object) const {
foreach (const StreamerPropertyPair& property, _properties) {
property.first->write(out, property.second.read(object));
@ -2433,6 +2446,17 @@ QObject* GenericObjectStreamer::putJSONData(JSONReader& reader, const QJsonObjec
return object;
}
bool GenericObjectStreamer::equal(const QObject* first, const QObject* second) const {
const QVariantList& firstValues = static_cast<const GenericSharedObject*>(first)->getValues();
const QVariantList& secondValues = static_cast<const GenericSharedObject*>(second)->getValues();
for (int i = 0; i < _properties.size(); i++) {
if (!_properties.at(i).first->equal(firstValues.at(i), secondValues.at(i))) {
return false;
}
}
return true;
}
void GenericObjectStreamer::write(Bitstream& out, const QObject* object) const {
const QVariantList& values = static_cast<const GenericSharedObject*>(object)->getValues();
for (int i = 0; i < _properties.size(); i++) {

View file

@ -278,6 +278,9 @@ public:
/// Returns the streamer registered for the supplied type, if any.
static const TypeStreamer* getTypeStreamer(int type);
/// Returns the streamer registered for the supplied object, if any.
static const ObjectStreamer* getObjectStreamer(const QMetaObject* metaObject);
/// Returns the meta-object registered under the supplied class name, if any.
static const QMetaObject* getMetaObject(const QByteArray& className);
@ -1022,6 +1025,7 @@ public:
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const = 0;
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const = 0;
virtual bool equal(const QObject* first, const QObject* second) const = 0;
virtual void write(Bitstream& out, const QObject* object) const = 0;
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const = 0;
virtual QObject* read(Bitstream& in, QObject* object = NULL) const = 0;
@ -1047,6 +1051,7 @@ public:
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
virtual bool equal(const QObject* first, const QObject* second) const;
virtual void write(Bitstream& out, const QObject* object) const;
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
@ -1070,6 +1075,7 @@ public:
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
virtual bool equal(const QObject* first, const QObject* second) const;
virtual void write(Bitstream& out, const QObject* object) const;
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
@ -1104,7 +1110,7 @@ private:
Q_DECLARE_METATYPE(const QMetaObject*)
/// Macro for registering streamable meta-objects. Typically, one would use this macro at the top level of the source file
/// associated with the class.
/// associated with the class. The class should have a no-argument constructor flagged with Q_INVOKABLE.
#define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject);
/// Contains a value along with a pointer to its streamer. This is stored in QVariants when using fallback generics and
@ -1563,8 +1569,8 @@ public:
Bitstream::registerTypeStreamer(qMetaTypeId<X>(), new CollectionTypeStreamer<X>());
/// Declares the metatype and the streaming operators. Typically, one would use this immediately after the definition of a
/// type flagged as STREAMABLE in its header file. The last lines ensure that the generated file will be included in the link
/// phase.
/// type flagged as STREAMABLE in its header file. The type should have a no-argument constructor. The last lines of this
/// macro ensure that the generated file will be included in the link phase.
#ifdef _WIN32
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
Bitstream& operator<<(Bitstream& out, const X& obj); \

View file

@ -610,6 +610,23 @@ MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) {
return root = new MetavoxelNode(attribute);
}
bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod) const {
if (_size != other._size) {
return false;
}
if (_roots.size() != other._roots.size()) {
return false;
}
glm::vec3 minimum = getMinimum();
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
MetavoxelNode* otherNode = other._roots.value(it.key());
if (!(otherNode && it.key()->metavoxelRootsEqual(*it.value(), *otherNode, minimum, _size, lod))) {
return false;
}
}
return true;
}
bool MetavoxelData::operator==(const MetavoxelData& other) const {
return _size == other._size && _roots == other._roots;
}
@ -1006,6 +1023,44 @@ void MetavoxelNode::clearChildren(const AttributePointer& attribute) {
}
}
bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const {
if (!attribute->deepEqual(_attributeValue, other._attributeValue)) {
return false;
}
if (!lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
return true;
}
bool leaf = isLeaf(), otherLeaf = other.isLeaf();
if (leaf && otherLeaf) {
return true;
}
if (leaf || otherLeaf) {
return false;
}
float nextSize = size * 0.5f;
for (int i = 0; i < CHILD_COUNT; i++) {
glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
if (!_children[i]->deepEquals(attribute, *(other._children[i]), nextMinimum, nextSize, lod)) {
return false;
}
}
return true;
}
void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const {
results.unite(decodeInline<SharedObjectSet>(_attributeValue));
if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
return;
}
float nextSize = size * 0.5f;
for (int i = 0; i < CHILD_COUNT; i++) {
glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
_children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results);
}
}
int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth,
int fifth, int sixth, int seventh, int eighth) {
return first | (second << 3) | (third << 6) | (fourth << 9) |
@ -1034,6 +1089,25 @@ int MetavoxelVisitor::encodeOrder(const glm::vec3& direction) {
indexDistances.at(6).index, indexDistances.at(7).index);
}
const int ORDER_ELEMENT_BITS = 3;
const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
int MetavoxelVisitor::encodeRandomOrder() {
// see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm
int order;
int randomValues = rand();
for (int i = 0, iShift = 0; i < MetavoxelNode::CHILD_COUNT; i++, iShift += ORDER_ELEMENT_BITS) {
int j = (randomValues >> iShift) % (i + 1);
int jShift = j * ORDER_ELEMENT_BITS;
if (j != i) {
int jValue = (order >> jShift) & ORDER_ELEMENT_MASK;
order = (order & ~(ORDER_ELEMENT_MASK << iShift)) | (jValue << iShift);
}
order = (order & ~(ORDER_ELEMENT_MASK << jShift)) | (i << jShift);
}
return order;
}
const int MetavoxelVisitor::DEFAULT_ORDER = encodeOrder(0, 1, 2, 3, 4, 5, 6, 7);
const int MetavoxelVisitor::STOP_RECURSION = 0;
const int MetavoxelVisitor::SHORT_CIRCUIT = -1;
@ -1227,8 +1301,6 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
// the encoded order tells us the child indices for each iteration
const int ORDER_ELEMENT_BITS = 3;
const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
int index = encodedOrder & ORDER_ELEMENT_MASK;
encodedOrder >>= ORDER_ELEMENT_BITS;
for (int j = 0; j < visitation.inputNodes.size(); j++) {
@ -1269,7 +1341,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
}
}
MetavoxelNode* node = visitation.outputNodes.at(j);
MetavoxelNode* child = node->getChild(i);
MetavoxelNode* child = node->getChild(index);
if (child) {
child->decrementReferenceCount(value.getAttribute());
} else {

View file

@ -34,7 +34,8 @@ class NetworkValue;
class Spanner;
class SpannerRenderer;
/// Determines whether to subdivide each node when traversing.
/// Determines whether to subdivide each node when traversing. Contains the position (presumed to be of the viewer) and a
/// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided.
class MetavoxelLOD {
STREAMABLE
@ -46,6 +47,7 @@ public:
bool isValid() const { return threshold > 0.0f; }
/// Checks whether, according to this LOD, we should subdivide the described voxel.
bool shouldSubdivide(const glm::vec3& minimum, float size, float multiplier = 1.0f) const;
/// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference.
@ -54,7 +56,8 @@ public:
DECLARE_STREAMABLE_METATYPE(MetavoxelLOD)
/// The base metavoxel representation shared between server and client.
/// The base metavoxel representation shared between server and client. Contains a size (for all dimensions) and a set of
/// octrees for different attributes.
class MetavoxelData {
public:
@ -64,30 +67,38 @@ public:
MetavoxelData& operator=(const MetavoxelData& other);
/// Sets the size in all dimensions.
void setSize(float size) { _size = size; }
float getSize() const { return _size; }
/// Returns the minimum extent of the octrees (which are centered about the origin).
glm::vec3 getMinimum() const { return glm::vec3(_size, _size, _size) * -0.5f; }
/// Returns the bounds of the octrees.
Box getBounds() const;
/// Applies the specified visitor to the contained voxels.
void guide(MetavoxelVisitor& visitor);
/// Inserts a spanner into the specified attribute layer.
void insert(const AttributePointer& attribute, const SharedObjectPointer& object);
void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
/// Removes a spanner from the specified attribute layer.
void remove(const AttributePointer& attribute, const SharedObjectPointer& object);
void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
/// Toggles the existence of a spanner in the specified attribute layer (removes if present, adds if not).
void toggle(const AttributePointer& attribute, const SharedObjectPointer& object);
void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
/// Replaces a spanner in the specified attribute layer.
void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject,
const SharedObjectPointer& newObject);
void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject,
const SharedObjectPointer& newObject);
/// Clears all data in the specified attribute layer.
void clear(const AttributePointer& attribute);
/// Convenience function that finds the first spanner intersecting the provided ray.
@ -97,7 +108,7 @@ public:
/// Sets part of the data.
void set(const glm::vec3& minimum, const MetavoxelData& data, bool blend = false);
/// Expands the tree, increasing its capacity in all dimensions.
/// Expands the tree, doubling its size in all dimensions (that is, increasing its volume eightfold).
void expand();
void read(Bitstream& in, const MetavoxelLOD& lod = MetavoxelLOD());
@ -110,6 +121,10 @@ public:
MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); }
MetavoxelNode* createRoot(const AttributePointer& attribute);
/// Performs a deep comparison between this data and the specified other (as opposed to the == operator, which does a
/// shallow comparison).
bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const;
bool operator==(const MetavoxelData& other) const;
bool operator!=(const MetavoxelData& other) const;
@ -198,6 +213,14 @@ public:
void clearChildren(const AttributePointer& attribute);
/// Performs a deep comparison between this and the specified other node.
bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const;
/// Retrieves all spanners satisfying the LOD constraint, placing them in the provided set.
void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const;
private:
Q_DISABLE_COPY(MetavoxelNode)
@ -234,6 +257,9 @@ public:
/// Encodes a visitation order sequence that visits each child as sorted along the specified direction.
static int encodeOrder(const glm::vec3& direction);
/// Returns a random visitation order sequence.
static int encodeRandomOrder();
/// The default visitation order.
static const int DEFAULT_ORDER;

View file

@ -84,11 +84,19 @@ bool SharedObject::equals(const SharedObject* other, bool sharedAncestry) const
if (metaObject != other->metaObject() && !sharedAncestry) {
return false;
}
for (int i = 0; i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (property.isStored() && property.read(this) != property.read(other)) {
// use the streamer, if we have one
const ObjectStreamer* streamer = Bitstream::getObjectStreamer(metaObject);
if (streamer) {
if (!streamer->equal(this, other)) {
return false;
}
} else {
for (int i = 0; i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (property.isStored() && property.read(this) != property.read(other)) {
return false;
}
}
}
QList<QByteArray> dynamicPropertyNames = this->dynamicPropertyNames();
if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) {

View file

@ -354,7 +354,7 @@ void OctreeEditPacketSender::processNackPacket(const QByteArray& packet) {
// read number of sequence numbers
uint16_t numSequenceNumbers = (*(uint16_t*)dataAt);
dataAt += sizeof(uint16_t);
// read sequence numbers and queue packets for resend
for (int i = 0; i < numSequenceNumbers; i++) {
unsigned short int sequenceNumber = (*(unsigned short int*)dataAt);

View file

@ -68,7 +68,7 @@ float randFloat();
int randIntInRange (int min, int max);
float randFloatInRange (float min,float max);
float randomSign(); /// \return -1.0 or 1.0
unsigned char randomColorValue(int minimum);
unsigned char randomColorValue(int minimum = 0);
bool randomBoolean();
glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);

View file

@ -32,6 +32,8 @@ static int datagramsSent = 0;
static int datagramsReceived = 0;
static int bytesSent = 0;
static int bytesReceived = 0;
static int maxDatagramsPerPacket = 0;
static int maxBytesPerPacket = 0;
static int highPriorityMessagesSent = 0;
static int highPriorityMessagesReceived = 0;
static int unreliableMessagesSent = 0;
@ -45,6 +47,8 @@ static int sharedObjectsDestroyed = 0;
static int objectMutationsPerformed = 0;
static int scriptObjectsCreated = 0;
static int scriptMutationsPerformed = 0;
static int metavoxelMutationsPerformed = 0;
static int spannerMutationsPerformed = 0;
static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
@ -321,44 +325,80 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
}
bool MetavoxelTests::run() {
qDebug() << "Running transmission tests...";
qDebug();
// seed the random number generator so that our tests are reproducible
srand(0xBAAAAABE);
// create two endpoints with the same header
// check for an optional command line argument specifying a single test
QStringList arguments = this->arguments();
int test = (arguments.size() > 1) ? arguments.at(1).toInt() : 0;
QByteArray datagramHeader("testheader");
Endpoint alice(datagramHeader), bob(datagramHeader);
alice.setOther(&bob);
bob.setOther(&alice);
// perform a large number of simulation iterations
const int SIMULATION_ITERATIONS = 10000;
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
if (alice.simulate(i) || bob.simulate(i)) {
if (test == 0 || test == 1) {
qDebug() << "Running transmission tests...";
qDebug();
// create two endpoints with the same header
Endpoint alice(datagramHeader), bob(datagramHeader);
alice.setOther(&bob);
bob.setOther(&alice);
// perform a large number of simulation iterations
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
if (alice.simulate(i) || bob.simulate(i)) {
return true;
}
}
qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
datagramsReceived << "with" << bytesReceived << "bytes";
qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
qDebug() << "Performed" << objectMutationsPerformed << "object mutations";
qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed;
qDebug();
}
if (test == 0 || test == 2) {
qDebug() << "Running serialization tests...";
qDebug();
if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
return true;
}
}
qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
datagramsReceived << "with" << bytesReceived << "bytes";
qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
qDebug() << "Performed" << objectMutationsPerformed << "object mutations";
qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed;
qDebug();
if (test == 0 || test == 3) {
qDebug() << "Running metavoxel data tests...";
qDebug();
qDebug() << "Running serialization tests...";
qDebug();
// clear the stats
datagramsSent = bytesSent = datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
return true;
// create client and server endpoints
Endpoint client(datagramHeader, Endpoint::METAVOXEL_CLIENT_MODE);
Endpoint server(datagramHeader, Endpoint::METAVOXEL_SERVER_MODE);
client.setOther(&server);
server.setOther(&client);
// simulate
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
if (client.simulate(i) || server.simulate(i)) {
return true;
}
}
qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
datagramsReceived << "with" << bytesReceived << "bytes";
qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
qDebug() << "Performed" << metavoxelMutationsPerformed << "metavoxel mutations," << spannerMutationsPerformed <<
"spanner mutations";
}
qDebug() << "All tests passed!";
@ -375,7 +415,36 @@ static SharedObjectPointer createRandomSharedObject() {
}
}
Endpoint::Endpoint(const QByteArray& datagramHeader) :
class RandomVisitor : public MetavoxelVisitor {
public:
int leafCount;
RandomVisitor();
virtual int visit(MetavoxelInfo& info);
};
RandomVisitor::RandomVisitor() :
MetavoxelVisitor(QVector<AttributePointer>(),
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute()),
leafCount(0) {
}
const float MAXIMUM_LEAF_SIZE = 0.5f;
const float MINIMUM_LEAF_SIZE = 0.25f;
int RandomVisitor::visit(MetavoxelInfo& info) {
if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) {
return DEFAULT_ORDER;
}
info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline<QRgb>(qRgb(randomColorValue(),
randomColorValue(), randomColorValue())));
leafCount++;
return STOP_RECURSION;
}
Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) :
_mode(mode),
_sequencer(new DatagramSequencer(datagramHeader, this)),
_highPriorityMessagesToSend(0.0f),
_reliableMessagesToSend(0.0f) {
@ -396,6 +465,25 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) :
ReceiveRecord receiveRecord = { 0 };
_receiveRecords.append(receiveRecord);
if (mode == METAVOXEL_CLIENT_MODE) {
_lod = MetavoxelLOD(glm::vec3(), 0.01f);
return;
}
if (mode == METAVOXEL_SERVER_MODE) {
_data.expand();
_data.expand();
RandomVisitor visitor;
_data.guide(visitor);
qDebug() << "Created" << visitor.leafCount << "base leaves";
_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere());
_sphere = new Sphere();
static_cast<Transformable*>(_sphere.data())->setScale(0.01f);
_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere);
return;
}
// create the object that represents out delta-encoded state
_localState = new TestSharedObjectA();
@ -415,7 +503,7 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) :
QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
_dataStreamed.append(bytes);
output->getBuffer().write(bytes);
streamedBytesSent += bytes.size();
streamedBytesSent += bytes.size();
}
static QVariant createRandomMessage() {
@ -512,6 +600,37 @@ static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMe
}
}
class MutateVisitor : public MetavoxelVisitor {
public:
MutateVisitor();
virtual int visit(MetavoxelInfo& info);
private:
int _mutationsRemaining;
};
MutateVisitor::MutateVisitor() :
MetavoxelVisitor(QVector<AttributePointer>(),
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute()),
_mutationsRemaining(randIntInRange(2, 4)) {
}
int MutateVisitor::visit(MetavoxelInfo& info) {
if (_mutationsRemaining <= 0) {
return STOP_RECURSION;
}
if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) {
return encodeRandomOrder();
}
info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline<QRgb>(qRgb(randomColorValue(),
randomColorValue(), randomColorValue())));
_mutationsRemaining--;
metavoxelMutationsPerformed++;
return STOP_RECURSION;
}
bool Endpoint::simulate(int iterationNumber) {
// update/send our delayed datagrams
for (QList<QPair<QByteArray, int> >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
@ -525,51 +644,101 @@ bool Endpoint::simulate(int iterationNumber) {
}
}
// enqueue some number of high priority messages
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
_highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);
while (_highPriorityMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_highPriorityMessagesSent.append(message);
_sequencer->sendHighPriorityMessage(message);
highPriorityMessagesSent++;
_highPriorityMessagesToSend -= 1.0f;
}
// and some number of reliable messages
const float MIN_RELIABLE_MESSAGES = 0.0f;
const float MAX_RELIABLE_MESSAGES = 4.0f;
_reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);
while (_reliableMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_reliableMessagesSent.append(message);
_sequencer->getReliableOutputChannel()->sendMessage(message);
reliableMessagesSent++;
_reliableMessagesToSend -= 1.0f;
}
// tweak the local state
_localState = mutate(_localState);
// send a packet
try {
int oldDatagramsSent = datagramsSent;
int oldBytesSent = bytesSent;
if (_mode == METAVOXEL_CLIENT_MODE) {
Bitstream& out = _sequencer->startPacket();
SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
_unreliableMessagesSent.append(message);
unreliableMessagesSent++;
out << message;
ClientStateMessage state = { _lod };
out << QVariant::fromValue(state);
_sequencer->endPacket();
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber(), SharedObjectPointer(), MetavoxelData(), _lod };
_sendRecords.append(record);
} else if (_mode == METAVOXEL_SERVER_MODE) {
// make a random change
MutateVisitor visitor;
_data.guide(visitor);
// perhaps mutate the spanner
if (randomBoolean()) {
SharedObjectPointer oldSphere = _sphere;
_sphere = _sphere->clone(true);
Sphere* newSphere = static_cast<Sphere*>(_sphere.data());
if (randomBoolean()) {
newSphere->setColor(QColor(randomColorValue(), randomColorValue(), randomColorValue()));
} else {
newSphere->setTranslation(newSphere->getTranslation() + glm::vec3(randFloatInRange(-0.01f, 0.01f),
randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f)));
}
_data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere);
spannerMutationsPerformed++;
}
// wait until we have a valid lod before sending
if (!_lod.isValid()) {
return false;
}
Bitstream& out = _sequencer->startPacket();
out << QVariant::fromValue(MetavoxelDeltaMessage());
_data.writeDelta(_sendRecords.first().data, _sendRecords.first().lod, out, _lod);
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber() + 1, SharedObjectPointer(), _data, _lod };
_sendRecords.append(record);
_sequencer->endPacket();
} else {
// enqueue some number of high priority messages
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
_highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);
while (_highPriorityMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_highPriorityMessagesSent.append(message);
_sequencer->sendHighPriorityMessage(message);
highPriorityMessagesSent++;
_highPriorityMessagesToSend -= 1.0f;
}
// and some number of reliable messages
const float MIN_RELIABLE_MESSAGES = 0.0f;
const float MAX_RELIABLE_MESSAGES = 4.0f;
_reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);
while (_reliableMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_reliableMessagesSent.append(message);
_sequencer->getReliableOutputChannel()->sendMessage(message);
reliableMessagesSent++;
_reliableMessagesToSend -= 1.0f;
}
// tweak the local state
_localState = mutate(_localState);
// send a packet
try {
Bitstream& out = _sequencer->startPacket();
SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
_unreliableMessagesSent.append(message);
unreliableMessagesSent++;
out << message;
_sequencer->endPacket();
} catch (const QString& message) {
qDebug() << message;
return true;
}
} catch (const QString& message) {
qDebug() << message;
return true;
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState };
_sendRecords.append(record);
}
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState };
_sendRecords.append(record);
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
return false;
}
@ -619,6 +788,39 @@ void Endpoint::handleHighPriorityMessage(const QVariant& message) {
}
void Endpoint::readMessage(Bitstream& in) {
if (_mode == METAVOXEL_CLIENT_MODE) {
QVariant message;
in >> message;
handleMessage(message, in);
// deep-compare data to sent version
int packetNumber = _sequencer->getIncomingPacketNumber();
foreach (const SendRecord& sendRecord, _other->_sendRecords) {
if (sendRecord.packetNumber == packetNumber) {
if (!sendRecord.data.deepEquals(_data, _sendRecords.first().lod)) {
qDebug() << "Sent/received metavoxel data mismatch.";
exit(true);
}
break;
}
}
// record the receipt
ReceiveRecord record = { packetNumber, SharedObjectPointer(), _data, _sendRecords.first().lod };
_receiveRecords.append(record);
return;
}
if (_mode == METAVOXEL_SERVER_MODE) {
QVariant message;
in >> message;
handleMessage(message, in);
// record the receipt
ReceiveRecord record = { _sequencer->getIncomingPacketNumber() };
_receiveRecords.append(record);
return;
}
SequencedTestMessage message;
in >> message;
@ -632,17 +834,20 @@ void Endpoint::readMessage(Bitstream& in) {
it != _other->_unreliableMessagesSent.end(); it++) {
if (it->sequenceNumber == message.sequenceNumber) {
if (!messagesEqual(it->submessage, message.submessage)) {
throw QString("Sent/received unreliable message mismatch.");
qDebug() << "Sent/received unreliable message mismatch.";
exit(true);
}
if (!it->state->equals(message.state)) {
throw QString("Delta-encoded object mismatch.");
qDebug() << "Delta-encoded object mismatch.";
exit(true);
}
_other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
unreliableMessagesReceived++;
return;
}
}
throw QString("Received unsent/already sent unreliable message.");
qDebug() << "Received unsent/already sent unreliable message.";
exit(true);
}
void Endpoint::handleReliableMessage(const QVariant& message) {
@ -682,6 +887,22 @@ void Endpoint::clearReceiveRecordsBefore(int index) {
_receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
}
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
int userType = message.userType();
if (userType == ClientStateMessage::Type) {
ClientStateMessage state = message.value<ClientStateMessage>();
_lod = state.lod;
} else if (userType == MetavoxelDeltaMessage::Type) {
_data.readDelta(_receiveRecords.first().data, _receiveRecords.first().lod, in, _sendRecords.first().lod);
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) :
_foo(foo),
_baz(baz),

View file

@ -16,6 +16,7 @@
#include <QVariantList>
#include <DatagramSequencer.h>
#include <MetavoxelData.h>
#include <ScriptCache.h>
class SequencedTestMessage;
@ -39,7 +40,9 @@ class Endpoint : public QObject {
public:
Endpoint(const QByteArray& datagramHeader);
enum Mode { BASIC_PEER_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
Endpoint(const QByteArray& datagramHeader, Mode mode = BASIC_PEER_MODE);
void setOther(Endpoint* other) { _other = other; }
@ -60,18 +63,26 @@ private slots:
private:
void handleMessage(const QVariant& message, Bitstream& in);
class SendRecord {
public:
int packetNumber;
SharedObjectPointer localState;
MetavoxelData data;
MetavoxelLOD lod;
};
class ReceiveRecord {
public:
int packetNumber;
SharedObjectPointer remoteState;
MetavoxelData data;
MetavoxelLOD lod;
};
Mode _mode;
DatagramSequencer* _sequencer;
QList<SendRecord> _sendRecords;
QList<ReceiveRecord> _receiveRecords;
@ -79,6 +90,11 @@ private:
SharedObjectPointer _localState;
SharedObjectPointer _remoteState;
MetavoxelData _data;
MetavoxelLOD _lod;
SharedObjectPointer _sphere;
Endpoint* _other;
QList<QPair<QByteArray, int> > _delayedDatagrams;
float _highPriorityMessagesToSend;