diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 148e6819be..6f90a67f28 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -70,7 +70,7 @@ include(${QT_USE_FILE}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") # run qt moc on qt-enabled headers -qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h src/Webcam.h) +qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h src/Webcam.h src/ui/BandwidthDialog.h) # create the executable, make it a bundle on OS X add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS}) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6954339cee..728bea8b04 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -131,6 +131,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), + _bandwidthDialog(NULL), _displayLevels(false), _frameCount(0), _fps(120.0f), @@ -423,11 +424,30 @@ void Application::resizeGL(int width, int height) { glLoadIdentity(); } -static void sendVoxelServerAddScene() { +void Application::broadcastToAgents(unsigned char* data, size_t bytes, const char type) { + + int n = AgentList::getInstance()->broadcastToAgents(data, bytes, &type, 1); + + unsigned channel; + switch (type) { + case AGENT_TYPE_AVATAR: + case AGENT_TYPE_AVATAR_MIXER: + channel = 1; + break; + case AGENT_TYPE_VOXEL_SERVER: + channel = 2; + break; + default: + return; + } + getInstance()->_bandwidthMeter.outputStream(channel).updateValue(n * bytes); +} + +void Application::sendVoxelServerAddScene() { char message[100]; sprintf(message,"%c%s",'Z',"add scene"); int messageSize = strlen(message) + 1; - AgentList::getInstance()->broadcastToAgents((unsigned char*)message, messageSize, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents((unsigned char*)message, messageSize, AGENT_TYPE_VOXEL_SERVER); } void Application::keyPressEvent(QKeyEvent* event) { @@ -833,7 +853,7 @@ void Application::terminate() { } } -static void sendAvatarVoxelURLMessage(const QUrl& url) { +void Application::sendAvatarVoxelURLMessage(const QUrl& url) { uint16_t ownerID = AgentList::getInstance()->getOwnerID(); if (ownerID == UNKNOWN_AGENT_ID) { @@ -844,10 +864,10 @@ static void sendAvatarVoxelURLMessage(const QUrl& url) { message.append((const char*)&ownerID, sizeof(ownerID)); message.append(url.toEncoded()); - AgentList::getInstance()->broadcastToAgents((unsigned char*)message.data(), message.size(), &AGENT_TYPE_AVATAR_MIXER, 1); + broadcastToAgents((unsigned char*)message.data(), message.size(), AGENT_TYPE_AVATAR_MIXER); } -static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes) { +void Application::processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes) { // skip the header packetData++; dataBytes--; @@ -872,6 +892,24 @@ static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataB QMetaObject::invokeMethod(avatar->getVoxels(), "setVoxelURL", Q_ARG(QUrl, url)); } +void Application::bandwidthDetails() { + + if (! _bandwidthDialog) { + _bandwidthDialog = new BandwidthDialog(_glWidget, getBandwidthMeter()); + _bandwidthDialog->show(); + } + _bandwidthDialog->raise(); + + connect(_bandwidthDialog, SIGNAL(closed()), SLOT(bandwidthDetailsClosed())); +} + +void Application::bandwidthDetailsClosed() { + + QDialog* dlg = _bandwidthDialog; + _bandwidthDialog = NULL; + delete dlg; +} + void Application::editPreferences() { QDialog dialog(_glWidget); dialog.setWindowTitle("Interface Preferences"); @@ -1024,12 +1062,12 @@ void Application::updateVoxelModeActions() { } } -static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { +void Application::sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { unsigned char* bufferOut; int sizeOut; if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ - AgentList::getInstance()->broadcastToAgents(bufferOut, sizeOut, &AGENT_TYPE_VOXEL_SERVER, 1); + Application::broadcastToAgents(bufferOut, sizeOut, AGENT_TYPE_VOXEL_SERVER); delete[] bufferOut; } } @@ -1104,7 +1142,7 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { // if we have room don't have room in the buffer, then send the previously generated message first if (args->bufferInUse + codeAndColorLength > MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE) { - AgentList::getInstance()->broadcastToAgents(args->messageBuffer, args->bufferInUse, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents(args->messageBuffer, args->bufferInUse, AGENT_TYPE_VOXEL_SERVER); args->bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // reset } @@ -1168,7 +1206,7 @@ void Application::importVoxels() { // If we have voxels left in the packet, then send the packet if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { - AgentList::getInstance()->broadcastToAgents(args.messageBuffer, args.bufferInUse, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents(args.messageBuffer, args.bufferInUse, AGENT_TYPE_VOXEL_SERVER); } if (calculatedOctCode) { @@ -1220,7 +1258,7 @@ void Application::pasteVoxels() { // If we have voxels left in the packet, then send the packet if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { - AgentList::getInstance()->broadcastToAgents(args.messageBuffer, args.bufferInUse, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents(args.messageBuffer, args.bufferInUse, AGENT_TYPE_VOXEL_SERVER); } if (calculatedOctCode) { @@ -1290,7 +1328,9 @@ void Application::initMenu() { (_logOn = toolsMenu->addAction("Log"))->setCheckable(true); _logOn->setChecked(false); _logOn->setShortcut(Qt::CTRL | Qt::Key_L); - + toolsMenu->addAction("Bandwidth Details", this, SLOT(bandwidthDetails())); + + QMenu* voxelMenu = menuBar->addMenu("Voxels"); _voxelModeActions = new QActionGroup(this); _voxelModeActions->setExclusive(false); // exclusivity implies one is always checked @@ -1643,7 +1683,12 @@ void Application::update(float deltaTime) { } } } - + + // Update bandwidth dialog, if any + if (_bandwidthDialog) { + _bandwidthDialog->update(); + } + // Update audio stats for procedural sounds #ifndef _WIN32 _audio.setLastAcceleration(_myAvatar.getThrust()); @@ -1727,8 +1772,8 @@ void Application::updateAvatar(float deltaTime) { endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite); - const char broadcastReceivers[2] = {AGENT_TYPE_VOXEL_SERVER, AGENT_TYPE_AVATAR_MIXER}; - AgentList::getInstance()->broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, broadcastReceivers, sizeof(broadcastReceivers)); + broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, AGENT_TYPE_VOXEL_SERVER); + broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, AGENT_TYPE_AVATAR_MIXER); // once in a while, send my voxel url const float AVATAR_VOXEL_URL_SEND_INTERVAL = 1.0f; // seconds @@ -2116,6 +2161,8 @@ void Application::displayOverlay() { glPointSize(1.0f); if (_renderStatsOn->isChecked()) { displayStats(); } + + _bandwidthMeter.render(_glWidget->width() - 400, 40, 380, 42 * BandwidthMeter::N_CHANNELS); if (_logOn->isChecked()) { LogDisplay::instance.render(_glWidget->width(), _glWidget->height()); } // Show chat entry field @@ -2612,6 +2659,7 @@ void* Application::networkReceive(void* args) { AgentList::getInstance()->processBulkAgentData(&senderAddress, app->_incomingPacket, bytesReceived); + getInstance()->_bandwidthMeter.inputStream(1).updateValue(bytesReceived); break; case PACKET_HEADER_AVATAR_VOXEL_URL: processAvatarVoxelURLMessage(app->_incomingPacket, bytesReceived); diff --git a/interface/src/Application.h b/interface/src/Application.h index 54fdf1c347..b666bb06a0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -20,6 +20,9 @@ #include +#include "BandwidthMeter.h" +#include "ui/BandwidthDialog.h" + #ifndef _WIN32 #include "Audio.h" #endif @@ -31,6 +34,7 @@ #include "Stars.h" #include "ViewFrustum.h" #include "VoxelSystem.h" +#include "PacketHeaders.h" #include "Webcam.h" #include "ui/ChatEntry.h" @@ -77,6 +81,7 @@ public: QSettings* getSettings() { return _settings; } Environment* getEnvironment() { return &_environment; } Webcam* getWebcam() { return &_webcam; } + BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } bool shouldEchoAudio() { return _echoAudioMode->isChecked(); } QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; } @@ -86,8 +91,10 @@ private slots: void timer(); void idle(); void terminate(); - + + void bandwidthDetails(); void editPreferences(); + void bandwidthDetailsClosed(); void pair(); @@ -133,7 +140,12 @@ private slots: void runTests(); private: + static void broadcastToAgents(unsigned char* data, size_t bytes, const char type); + static void sendVoxelServerAddScene(); static bool sendVoxelsOperation(VoxelNode* node, void* extraData); + static void sendAvatarVoxelURLMessage(const QUrl& url); + static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes); + static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail); void initMenu(); void updateFrustumRenderModeAction(); @@ -210,6 +222,9 @@ private: QAction* _frustumRenderModeAction; QAction* _settingsAutosave; // Whether settings are saved automatically + BandwidthMeter _bandwidthMeter; + BandwidthDialog* _bandwidthDialog; + SerialInterface _serialHeadSensor; QNetworkAccessManager* _networkAccessManager; QSettings* _settings; @@ -224,7 +239,7 @@ private: timeval _timerStart, _timerEnd; timeval _lastTimeIdle; bool _justStarted; - + Stars _stars; VoxelSystem _voxels; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 533b6e9e3b..fd44b930ac 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -143,10 +143,12 @@ inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* o // copy the audio data to the last BUFFER_LENGTH_BYTES bytes of the data packet memcpy(currentPacketPtr, inputLeft, BUFFER_LENGTH_BYTES_PER_CHANNEL); - agentList->getAgentSocket()->send(audioMixer->getActiveSocket(), dataPacket, BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); + + interface->getBandwidthMeter()->outputStream(0) + .updateValue(BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); } } @@ -423,9 +425,12 @@ void Audio::addReceivedAudioToBuffer(unsigned char* receivedData, int receivedBy if (_packetsReceivedThisPlayback == 1) { gettimeofday(&_firstPlaybackTime, NULL); } - + _ringBuffer.parseData((unsigned char*) receivedData, PACKET_LENGTH_BYTES + sizeof(PACKET_HEADER)); - + + Application::getInstance()->getBandwidthMeter()->inputStream(0) + .updateValue(PACKET_LENGTH_BYTES + sizeof(PACKET_HEADER)); + _lastReceiveTime = currentReceiveTime; } diff --git a/interface/src/BandwidthMeter.cpp b/interface/src/BandwidthMeter.cpp new file mode 100644 index 0000000000..d044fe5e37 --- /dev/null +++ b/interface/src/BandwidthMeter.cpp @@ -0,0 +1,217 @@ +// +// BandwidthMeter.h +// interface +// +// Created by Tobias Schwinger on 6/20/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "BandwidthMeter.h" +#include "InterfaceConfig.h" + +#include "Log.h" +#include "Util.h" + +// --- Configuration + +// Layout: +// +// +--- unit label width (e) +// | +-- unit label horiz. spacing (f) +// V V +// | | | +// +--------+ \ \ Channel +// Unit +-------+ | Total channel height (a) / height (d) +// | / ] Channel spacing (b) +// +----+ +// Unit +------+ +// | +// ... +// + +namespace { // .cpp-local + + float const CHANNEL_SPACING = 0.125f; // (b) fractional in respect to total channel height + float const STREAM_SPACING = 0.0625f; // (c) fractional in respect to total channel height + float const LABEL_CHAR_WIDTH = 0.30f; // (e) fractional in respect to total channel height + float const LABEL_HORIZ_SPACING = 0.25f; // (f) fractional in respect to total channel height + float const LABEL_VERT_PADDING = 0.035f; // fractional in respect to total channel height / 2 + + unsigned const FRAME_COLOR = 0xe0e0e0b0; + unsigned const INDICATOR_COLOR = 0xc0c0c0b0; + unsigned const VALUE_COLOR = 0xa0a0a0e0; + float const INDICATOR_BASE = 10.0f; + float const INDICATOR_LOG_BASE = log(INDICATOR_BASE); +} + +BandwidthMeter::ChannelInfo BandwidthMeter::_DEFAULT_CHANNELS[] = { + { "Audio" , "Mbps", 1024.0 * 1024.0, 2.5, 0x40ff40d0 }, + { "Avatars" , "KBps", 1.0 * 1024.0, 20.0, 0xffef40c0 }, + { "Voxels" , "Mbps", 1024.0 * 1024.0, 0.5, 0xd0d0d0a0 } +}; + +// --- + +BandwidthMeter::BandwidthMeter() : + _textRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT) { + + memcpy(_channels, _DEFAULT_CHANNELS, sizeof(_DEFAULT_CHANNELS)); +} + +BandwidthMeter::Stream::Stream(float secondsToAverage) : + _value(0.0f), + _secsToAverage(secondsToAverage) { + + gettimeofday(& _prevTime, NULL); +} + +void BandwidthMeter::Stream::updateValue(double amount) { + + // Determine elapsed time + timeval now; + gettimeofday(& now, NULL); + double dt = diffclock(& _prevTime, & now) / 1000.0; + memcpy(& _prevTime, & now, sizeof(timeval)); + + // Compute approximate average + _value = glm::mix(_value, amount / dt, + glm::clamp(dt / _secsToAverage, 0.0, 1.0)); +} + +static void setColorRGBA(unsigned c) { + + glColor4ub(GLubyte( c >> 24), + GLubyte((c >> 16) & 0xff), + GLubyte((c >> 8) & 0xff), + GLubyte( c & 0xff)); +} + +static void renderBox(float w, float h) { + glBegin(GL_QUADS); + glVertex2f(0.0f, 0.0f); + glVertex2f(w, 0.0f); + glVertex2f(w, h); + glVertex2f(0.0f, h); + glEnd(); +} + +void BandwidthMeter::render(int x, int y, unsigned w, unsigned h) { + + float channelTotalHeight = float(h) / N_CHANNELS; + float channelSpacing = CHANNEL_SPACING * channelTotalHeight; + float channelHeight = channelTotalHeight - channelSpacing; + float streamSpacing = STREAM_SPACING * channelTotalHeight; + float streamHeight = (channelHeight - streamSpacing) * 0.5; + + QFontMetrics const& fontMetrics = _textRenderer.metrics(); + int fontDescent = fontMetrics.descent(); + int labelRenderWidthCaption = 0; + int labelRenderWidthUnit = 0; + for (int i = 0; i < N_CHANNELS; ++i) { + labelRenderWidthCaption = glm::max(labelRenderWidthCaption, fontMetrics.width(_channels[i].caption)); + labelRenderWidthUnit = glm::max(labelRenderWidthUnit, fontMetrics.width(_channels[i].unitCaption)); + } + int labelRenderWidth = glm::max(labelRenderWidthCaption, labelRenderWidthUnit); + int labelRenderHeight = fontMetrics.lineSpacing(); + float labelCharWSpaces = float(labelRenderWidth) / float(fontMetrics.width("M")); + float labelWidth = channelTotalHeight * LABEL_CHAR_WIDTH * labelCharWSpaces; + float labelHeight = channelTotalHeight * (1.0f - LABEL_VERT_PADDING); + float labelTextHeight = labelHeight * 0.5f; + + float labelScaleX = labelWidth / float(labelRenderWidth); + float labelScaleY = labelTextHeight / float(labelRenderHeight); + float labelHorizSpacing = channelTotalHeight * LABEL_HORIZ_SPACING; + + + glPushMatrix(); + + int xMin = x + int(labelWidth + labelHorizSpacing); + + // Render vertical lines for the frame + setColorRGBA(FRAME_COLOR); + glBegin(GL_LINES); + glVertex2i(xMin - 1, y); + glVertex2i(xMin - 1, y + h); + glVertex2i(x + w - 1, y); + glVertex2i(x + w - 1, y + h); + glEnd(); + + // Set coordinate center to right edge of bars / top + glTranslatef(float(xMin), float(y) + channelSpacing * 0.5f, 0.0f); + + // Determine maximum horizontal length for bars + float xMax = float(w) - labelWidth - labelHorizSpacing; + + char fmtBuf[64]; + + for (int i = 0; i < N_CHANNELS; ++i) { + // ...each channel + + ChannelInfo& c = channelInfo(i); + float scaleStep = powf(INDICATOR_BASE, ceil(logf(c.unitsMax) / INDICATOR_LOG_BASE) - 1.0f); + + float unitsIn = inputStream(i).getValue() / c.unitScale; + if (unitsIn > c.unitsMax) { + c.unitsMax += scaleStep; + } + float barPosIn = xMax * fmin(unitsIn / c.unitsMax, 1.0f); + float unitsOut = outputStream(i).getValue() / c.unitScale; + float barPosOut = xMax * fmin(unitsOut / c.unitsMax, 1.0f); + + // Render scale indicators + setColorRGBA(INDICATOR_COLOR); + for (float meas = scaleStep; meas < c.unitsMax - 0.01f; meas += scaleStep) { + float pixX = xMax * meas / c.unitsMax; + glBegin(GL_LINES); + glVertex2f(pixX, 0); + glVertex2f(pixX, channelHeight); + glEnd(); + } + + // Render captions + setColorRGBA(c.colorRGBA); + glPushMatrix(); + glTranslatef(-labelHorizSpacing, channelHeight * 0.5f, 0.0f); + glScalef(labelScaleX, labelScaleY, 1.0f); + _textRenderer.draw(-labelRenderWidth, -fontDescent, c.caption); + _textRenderer.draw(-fontMetrics.width(c.unitCaption), labelRenderHeight - fontDescent, c.unitCaption); + glPopMatrix(); + + // Render input bar + renderBox(barPosIn, streamHeight); + + // Render output value + glPushMatrix(); + setColorRGBA(VALUE_COLOR); + glTranslatef(0.0f, streamHeight, 0.0f); + glScalef(labelScaleX, labelScaleY, 1.0f); + + sprintf(fmtBuf, "%0.2f in", unitsIn); + _textRenderer.draw(glm::max(int(barPosIn) - fontMetrics.width(fmtBuf), 0), -fontDescent, fmtBuf); + glPopMatrix(); + + // Advance to next stream + glTranslatef(0.0f, streamHeight + streamSpacing, 0.0f); + + // Render output bar + setColorRGBA(c.colorRGBA); + renderBox(barPosOut, streamHeight); + + // Render output value + glPushMatrix(); + setColorRGBA(VALUE_COLOR); + glTranslatef(0.0f, streamHeight, 0.0f); + glScalef(labelScaleX, labelScaleY, 1.0f); + + sprintf(fmtBuf, "%0.2f out", unitsOut); + _textRenderer.draw(glm::max(int(barPosOut) - fontMetrics.width(fmtBuf), 0), -fontDescent, fmtBuf); + glPopMatrix(); + + // Advance to next channel + glTranslatef(0.0f, streamHeight + channelSpacing, 0.0f); + } + + glPopMatrix(); +} + + diff --git a/interface/src/BandwidthMeter.h b/interface/src/BandwidthMeter.h new file mode 100644 index 0000000000..3da26d680a --- /dev/null +++ b/interface/src/BandwidthMeter.h @@ -0,0 +1,72 @@ +// +// BandwidthMeter.h +// interface +// +// Created by Tobias Schwinger on 6/20/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__BandwidthMeter__ +#define __interface__BandwidthMeter__ + +#include + +#include "ui/TextRenderer.h" + + +class BandwidthMeter { + +public: + + BandwidthMeter(); + + void render(int x, int y, unsigned w, unsigned h); + + // Number of channels / streams. + static size_t const N_CHANNELS = 3; + static size_t const N_STREAMS = N_CHANNELS * 2; + + // Meta information held for a communication channel (bidirectional). + struct ChannelInfo { + + char const* caption; + char const* unitCaption; + double unitScale; + double unitsMax; + unsigned colorRGBA; + }; + + // Representation of a data stream (unidirectional; input or output). + class Stream { + + public: + + Stream(float secondsToAverage = 3.0f); + void updateValue(double amount); + double getValue() const { return _value; } + + private: + double _value; // Current value. + timeval _prevTime; // Time of last feed. + float _secsToAverage; // Seconds to average. + }; + + // Data model accessors + Stream& inputStream(unsigned channelIndex) { return _streams[channelIndex * 2]; } + Stream const& inputStream(unsigned channelIndex) const { return _streams[channelIndex * 2]; } + Stream& outputStream(unsigned channelIndex) { return _streams[channelIndex * 2 + 1]; } + Stream const& outputStream(unsigned channelIndex) const { return _streams[channelIndex * 2 + 1]; } + ChannelInfo& channelInfo(unsigned index) { return _channels[index]; } + ChannelInfo const& channelInfo(unsigned index) const { return _channels[index]; } + +private: + TextRenderer _textRenderer; + ChannelInfo _channels[N_CHANNELS]; + Stream _streams[N_STREAMS]; + + static ChannelInfo _DEFAULT_CHANNELS[]; + static Stream _DEFAULT_STREAMS[]; +}; + +#endif /* defined(__interface__BandwidthMeter__) */ + diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 69edf376ee..5de9b89594 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -160,7 +160,9 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { setupNewVoxelsForDrawing(); pthread_mutex_unlock(&_treeLock); - + + Application::getInstance()->getBandwidthMeter()->inputStream(2).updateValue(numBytes); + return numBytes; } diff --git a/interface/src/ui/BandwidthDialog.cpp b/interface/src/ui/BandwidthDialog.cpp new file mode 100644 index 0000000000..fcbab5816b --- /dev/null +++ b/interface/src/ui/BandwidthDialog.cpp @@ -0,0 +1,91 @@ + +#include "ui/BandwidthDialog.h" + +#include +#include + +#include "Log.h" + +BandwidthDialog::BandwidthDialog(QWidget* parent, BandwidthMeter* model) : + QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint), + _model(model) { + + char strBuf[64]; + + this->setWindowTitle("Bandwidth Details"); + + // Create layouter + QFormLayout* form = new QFormLayout(); + this->QDialog::setLayout(form); + + // Setup labels + for (int i = 0; i < BandwidthMeter::N_STREAMS; ++i) { + int chIdx = i / 2; + bool input = i % 2 == 0; + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(chIdx); + QLabel* label = _labels[i] = new QLabel(); + snprintf(strBuf, sizeof(strBuf), "%s %s Bandwidth:", input ? "Input" : "Output", ch.caption); + form->addRow(strBuf, label); + } + + // Setup spinners + for (int i = 0; i < BandwidthMeter::N_CHANNELS; ++i) { + + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(i); + QDoubleSpinBox* spinner = _spinners[i] = new QDoubleSpinBox(); + spinner->setDecimals(3); + spinner->setMinimum(0.001); + spinner->setMaximum(1000.0); + spinner->setSuffix(ch.unitCaption); + snprintf(strBuf, sizeof(strBuf), "Maximum of %s Bandwidth Meter:", ch.caption); + form->addRow(strBuf, spinner); + connect(spinner, SIGNAL(valueChanged(double)), SLOT(applySettings(double))); + } + +} + +void BandwidthDialog::paintEvent(QPaintEvent* event) { + + // Update labels + char strBuf[64]; + for (int i = 0; i < BandwidthMeter::N_STREAMS; ++i) { + int chIdx = i / 2; + bool input = i % 2 == 0; + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(chIdx); + BandwidthMeter::Stream& s = input ? _model->inputStream(chIdx) : _model->outputStream(chIdx); + QLabel* label = _labels[i]; + snprintf(strBuf, sizeof(strBuf), "%010.6f%s", s.getValue() / ch.unitScale, ch.unitCaption); + label->setText(strBuf); + } + // Update spinners (only when the value has been changed) + for (int i = 0; i < BandwidthMeter::N_CHANNELS; ++i) { + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(i); + if (_spinners[i]->value() != double(ch.unitsMax)) { + _spinners[i]->setValue(ch.unitsMax); + } + } + + this->QDialog::paintEvent(event); + this->setFixedSize(this->width(), this->height()); +} + +void BandwidthDialog::closeEvent(QCloseEvent* event) { + + this->QDialog::closeEvent(event); + emit closed(); +} + +void BandwidthDialog::applySettings(double value) { + + // Update model from spinner value (only the one that's been changed) + for (int i = 0; i < BandwidthMeter::N_CHANNELS; ++i) { + float v = _spinners[i]->value(); + if (v == float(value)) { + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(i); + if (ch.unitsMax != v) { + ch.unitsMax = v; + } + } + } +} + diff --git a/interface/src/ui/BandwidthDialog.h b/interface/src/ui/BandwidthDialog.h new file mode 100644 index 0000000000..e2d12cc976 --- /dev/null +++ b/interface/src/ui/BandwidthDialog.h @@ -0,0 +1,51 @@ +// +// BandwidthDialog.h +// interface +// +// Created by Tobias Schwinger on 6/21/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__BandwidthDialog__ +#define __hifi__BandwidthDialog__ + +#include + +#include +#include + +#include "BandwidthMeter.h" + + +class BandwidthDialog : public QDialog { + Q_OBJECT +public: + + // Sets up the UI based on the configuration of the BandwidthMeter + BandwidthDialog(QWidget* parent, BandwidthMeter* model); + +signals: + + void closed(); + +protected: + + // State <- data model held by BandwidthMeter + void paintEvent(QPaintEvent*); + + // Emits a 'closed' signal when this dialog is closed. + void closeEvent(QCloseEvent*); + +private slots: + + // State -> data model held by BandwidthMeter + void applySettings(double); + +private: + BandwidthMeter* _model; + QLabel* _labels[BandwidthMeter::N_STREAMS]; + QDoubleSpinBox* _spinners[BandwidthMeter::N_CHANNELS]; +}; + +#endif /* defined(__interface__BandwidthDialog__) */ + diff --git a/libraries/shared/src/AgentList.cpp b/libraries/shared/src/AgentList.cpp index 7a4803586e..4b112c5477 100644 --- a/libraries/shared/src/AgentList.cpp +++ b/libraries/shared/src/AgentList.cpp @@ -336,14 +336,17 @@ void AgentList::addAgentToList(Agent* newAgent) { Agent::printLog(*newAgent); } -void AgentList::broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes) { +unsigned AgentList::broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes) { + unsigned n = 0; for(AgentList::iterator agent = begin(); agent != end(); agent++) { // only send to the AgentTypes we are asked to send to. if (agent->getActiveSocket() != NULL && memchr(agentTypes, agent->getType(), numAgentTypes)) { // we know which socket is good for this agent, send there _agentSocket.send(agent->getActiveSocket(), broadcastData, dataBytes); + ++n; } } + return n; } void AgentList::handlePingReply(sockaddr *agentAddress) { diff --git a/libraries/shared/src/AgentList.h b/libraries/shared/src/AgentList.h index 1b51913928..b50b3fbff7 100644 --- a/libraries/shared/src/AgentList.h +++ b/libraries/shared/src/AgentList.h @@ -82,7 +82,7 @@ public: int updateAgentWithData(sockaddr *senderAddress, unsigned char *packetData, size_t dataBytes); int updateAgentWithData(Agent *agent, unsigned char *packetData, int dataBytes); - void broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes); + unsigned broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes); Agent* soloAgentOfType(char agentType);