diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 28edf6b6a1..3e60d1509c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -22,6 +23,10 @@ const int RESTART_HOLD_TIME_MSECS = 5 * 1000; +const char* VOXEL_SERVER_CONFIG = "voxelServerConfig"; +const char* PARTICLE_SERVER_CONFIG = "particleServerConfig"; +const char* METAVOXEL_SERVER_CONFIG = "metavoxelServerConfig"; + void signalhandler(int sig){ if (sig == SIGINT) { qApp->quit(); @@ -47,17 +52,22 @@ DomainServer::DomainServer(int argc, char* argv[]) : const char* customPortString = getCmdOption(argc, (const char**) argv, CUSTOM_PORT_OPTION); unsigned short domainServerPort = customPortString ? atoi(customPortString) : DEFAULT_DOMAIN_SERVER_PORT; + const char CONFIG_FILE_OPTION[] = "-c"; + const char* configFilePath = getCmdOption(argc, (const char**) argv, CONFIG_FILE_OPTION); + + if (!readConfigFile(configFilePath)) { + QByteArray voxelConfigOption = QString("--%1").arg(VOXEL_SERVER_CONFIG).toLocal8Bit(); + _voxelServerConfig = getCmdOption(argc, (const char**) argv, voxelConfigOption.constData()); + + QByteArray particleConfigOption = QString("--%1").arg(PARTICLE_SERVER_CONFIG).toLocal8Bit(); + _particleServerConfig = getCmdOption(argc, (const char**) argv, particleConfigOption.constData()); + + QByteArray metavoxelConfigOption = QString("--%1").arg(METAVOXEL_SERVER_CONFIG).toLocal8Bit(); + _metavoxelServerConfig = getCmdOption(argc, (const char**) argv, metavoxelConfigOption.constData()); + } + NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, domainServerPort); - const char VOXEL_CONFIG_OPTION[] = "--voxelServerConfig"; - _voxelServerConfig = getCmdOption(argc, (const char**) argv, VOXEL_CONFIG_OPTION); - - const char PARTICLE_CONFIG_OPTION[] = "--particleServerConfig"; - _particleServerConfig = getCmdOption(argc, (const char**) argv, PARTICLE_CONFIG_OPTION); - - const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig"; - _metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION); - connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer))); if (!_staticAssignmentFile.exists() || _voxelServerConfig) { @@ -275,6 +285,70 @@ QJsonObject jsonObjectForNode(Node* node) { return nodeJson; } +// Attempts to read configuration from specified path +// returns true on success, false otherwise +bool DomainServer::readConfigFile(const char* path) { + if (!path) { + // config file not specified + return false; + } + + if (!QFile::exists(path)) { + qWarning("Specified configuration file does not exist!\n"); + return false; + } + + QFile configFile(path); + if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Can't open specified configuration file!\n"); + return false; + } else { + qDebug("Reading configuration from %s\n", path); + } + QTextStream configStream(&configFile); + QByteArray configStringByteArray = configStream.readAll().toUtf8(); + QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); + configFile.close(); + + QString voxelServerConfig = readServerAssignmentConfig(configDocObject, VOXEL_SERVER_CONFIG); + _voxelServerConfig = new char[strlen(voxelServerConfig.toLocal8Bit().constData()) +1]; + _voxelServerConfig = strcpy((char *) _voxelServerConfig, voxelServerConfig.toLocal8Bit().constData() + '\0'); + + QString particleServerConfig = readServerAssignmentConfig(configDocObject, PARTICLE_SERVER_CONFIG); + _particleServerConfig = new char[strlen(particleServerConfig.toLocal8Bit().constData()) +1]; + _particleServerConfig = strcpy((char *) _particleServerConfig, particleServerConfig.toLocal8Bit().constData() + '\0'); + + QString metavoxelServerConfig = readServerAssignmentConfig(configDocObject, METAVOXEL_SERVER_CONFIG); + _metavoxelServerConfig = new char[strlen(metavoxelServerConfig.toLocal8Bit().constData()) +1]; + _metavoxelServerConfig = strcpy((char *) _metavoxelServerConfig, metavoxelServerConfig.toLocal8Bit().constData() + '\0'); + + return true; +} + +// find assignment configurations on the specified node name and json object +// returns a string in the form of its equivalent cmd line params +QString DomainServer::readServerAssignmentConfig(QJsonObject jsonObject, const char* nodeName) { + QJsonArray nodeArray = jsonObject[nodeName].toArray(); + + QStringList serverConfig; + foreach (const QJsonValue & childValue, nodeArray) { + QString cmdParams; + QJsonObject childObject = childValue.toObject(); + QStringList keys = childObject.keys(); + for (int i = 0; i < keys.size(); i++) { + QString key = keys[i]; + QString value = childObject[key].toString(); + // both cmd line params and json keys are the same + cmdParams += QString("--%1 %2 ").arg(key, value); + } + serverConfig << cmdParams; + } + + // according to split() calls from DomainServer::prepopulateStaticAssignmentFile + // we shold simply join them with semicolons + return serverConfig.join(';'); +} + bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { const QString JSON_MIME_TYPE = "application/json"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c9c4f0e2d7..fed5aaaa43 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -35,6 +35,9 @@ public slots: void nodeKilled(SharedNodePointer node); private: + bool readConfigFile(const char* path); + QString readServerAssignmentConfig(QJsonObject jsonObj, const char* nodeName); + void prepopulateStaticAssignmentFile(); Assignment* matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType); Assignment* deployableAssignmentForRequest(Assignment& requestAssignment); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index a0fc3f332f..d05ea7a7c0 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -15,7 +15,7 @@ set(SIXENSE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense) if (DEFINED ENV{JOB_ID}) set(BUILD_SEQ $ENV{JOB_ID}) else () - set(BUILD_SEQ "0") + set(BUILD_SEQ "dev") endif () if (APPLE) @@ -80,6 +80,8 @@ find_package(Qt5OpenGL REQUIRED) find_package(Qt5Svg REQUIRED) find_package(Qt5WebKit REQUIRED) find_package(Qt5WebKitWidgets REQUIRED) +find_package(Qt5Xml REQUIRED) +find_package(Qt5UiTools REQUIRED) if (APPLE) set(MACOSX_BUNDLE_BUNDLE_NAME Interface) @@ -146,7 +148,7 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) -qt5_use_modules(${TARGET_NAME} Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets) +qt5_use_modules(${TARGET_NAME} Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) # include headers for interface and InterfaceConfig. include_directories( diff --git a/interface/InterfaceVersion.h.in b/interface/InterfaceVersion.h.in index 26b2260965..60e51b4ab6 100644 --- a/interface/InterfaceVersion.h.in +++ b/interface/InterfaceVersion.h.in @@ -6,4 +6,4 @@ // Copyright (c) 2013 High Fidelity, Inc.. All rights reserved. // -const int BUILD_VERSION = @BUILD_SEQ@; +const QString BUILD_VERSION = "@BUILD_SEQ@"; diff --git a/interface/resources/info/ApplicationInfo.ini b/interface/resources/info/ApplicationInfo.ini index a1d1603172..35a6c71eb8 100644 --- a/interface/resources/info/ApplicationInfo.ini +++ b/interface/resources/info/ApplicationInfo.ini @@ -1,5 +1,4 @@ [INFO] name=Interface -version=0.0.1 organizationName=High Fidelity -organizationDomain=highfidelity.io \ No newline at end of file +organizationDomain=highfidelity.io diff --git a/interface/resources/ui/updateDialog.ui b/interface/resources/ui/updateDialog.ui new file mode 100644 index 0000000000..b0f1e63cb9 --- /dev/null +++ b/interface/resources/ui/updateDialog.ui @@ -0,0 +1,182 @@ + + + Dialog + + + Qt::NonModal + + + + 0 + 0 + 750 + 300 + + + + PointingHandCursor + + + Update Required + + + background-color: rgb(255, 255, 255); + + + + + 0 + 0 + 751 + 71 + + + + Qt::LeftToRight + + + false + + + background-color: rgb(236, 236, 236); + + + QFrame::StyledPanel + + + QFrame::Plain + + + 0 + + + + + 240 + 10 + 271 + 41 + + + + + Arial + 36 + PreferAntialias + false + + + + color: rgb(51, 51, 51); + + + Update Required + + + + + + + 100 + 110 + 561 + 61 + + + + + Arial + 18 + 50 + false + + + + + + + true + + + + + + 360 + 240 + 364 + 40 + + + + + + + PointingHandCursor + + + background-color: #333333; + border-width: 0; + border-radius: 9px; + border-radius: 9px; + font-family: Arial; + font-size: 18px; + font-weight: 100; + color: #b7b7b7; + width: 120px; + height: 40px; + + + Download + + + + + + + PointingHandCursor + + + background-color: #333333; + border-width: 0; + border-radius: 9px; + border-radius: 9px; + font-family: Arial; + font-size: 18px; + font-weight: 100; + color: #b7b7b7; + width: 120px; + height: 40px; + + + Skip Version + + + + + + + PointingHandCursor + + + background-color: #333333; + border-width: 0; + border-radius: 9px; + border-radius: 9px; + font-family: Arial; + font-size: 18px; + font-weight: 100; + color: #b7b7b7; + width: 120px; + height: 40px; + + + Close + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 54c097e8d6..fde531d410 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -33,8 +33,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -43,6 +45,8 @@ #include #include #include +#include +#include #include #include @@ -91,6 +95,9 @@ const float MIRROR_FULLSCREEN_DISTANCE = 0.35f; const float MIRROR_REARVIEW_DISTANCE = 0.65f; const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; +const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml"; +const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; + void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) { QString messageWithNewLine = message + "\n"; fprintf(stdout, "%s", messageWithNewLine.toLocal8Bit().constData()); @@ -160,8 +167,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // call Menu getInstance static method to set up the menu _window->setMenuBar(Menu::getInstance()); - qDebug("[VERSION] Build sequence: %i", BUILD_VERSION); - unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); @@ -195,9 +200,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : applicationInfo.beginGroup("INFO"); setApplicationName(applicationInfo.value("name").toString()); - setApplicationVersion(applicationInfo.value("version").toString()); + setApplicationVersion(BUILD_VERSION); setOrganizationName(applicationInfo.value("organizationName").toString()); setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); + + qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); _settings = new QSettings(this); @@ -232,6 +239,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); + loadScripts(); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); @@ -255,7 +263,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // Set the sixense filtering _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); - + + checkVersion(); } Application::~Application() { @@ -270,7 +279,7 @@ Application::~Application() { _audio.thread()->wait(); storeSizeAndPosition(); - + saveScripts(); _sharedVoxelSystem.changeTree(new VoxelTree); VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown @@ -1384,6 +1393,7 @@ void Application::idle() { } } } + void Application::terminate() { // Close serial port // close(serial_fd); @@ -3959,7 +3969,7 @@ void Application::attachNewHeadToNode(Node* newNode) { void Application::updateWindowTitle(){ QString title = ""; - QString buildVersion = " (build " + QString::number(BUILD_VERSION) + ")"; + QString buildVersion = " (build " + applicationVersion() + ")"; QString username = _profile.getUsername(); if(!username.isEmpty()){ title += _profile.getUsername(); @@ -4237,13 +4247,38 @@ void Application::packetSentNotification(ssize_t length) { _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length); } -void Application::loadScript() { - // shut down and stop any existing script - QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); - QString suggestedName = desktopLocation.append("/script.js"); +void Application::loadScripts(){ + // loads all saved scripts + QSettings* settings = new QSettings(this); + int size = settings->beginReadArray("Settings"); + for(int i=0; isetArrayIndex(i); + QString string = settings->value("script").toString(); + loadScript(string); + } + settings->endArray(); - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, - tr("JavaScript Files (*.js)")); +} + +void Application::saveScripts(){ + // saves all current running scripts + QSettings* settings = new QSettings(this); + settings->beginWriteArray("Settings"); + for(int i=0; i<_activeScripts.size(); ++i){ + settings->setArrayIndex(i); + settings->setValue("script", _activeScripts.at(i)); + } + settings->endArray(); + +} + +void Application::removeScriptName(const QString& fileNameString) +{ + _activeScripts.removeOne(fileNameString); +} + +void Application::loadScript(const QString& fileNameString){ + _activeScripts.append(fileNameString); QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); @@ -4270,9 +4305,7 @@ void Application::loadScript() { // start the script on a new thread... bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself - - ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), - &_controllerScriptingInterface); + ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), &_controllerScriptingInterface); scriptEngine->setupMenuItems(); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so @@ -4286,8 +4319,9 @@ void Application::loadScript() { connect(workerThread, SIGNAL(started()), scriptEngine, SLOT(run())); // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue - connect(scriptEngine, SIGNAL(finished()), scriptEngine, SLOT(deleteLater())); + connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater())); connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); + connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(removeScriptName(const QString&))); // when the application is about to quit, stop our script engine so it unwinds properly connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop())); @@ -4301,6 +4335,17 @@ void Application::loadScript() { _window->activateWindow(); } +void Application::loadDialog() { + // shut down and stop any existing script + QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QString suggestedName = desktopLocation.append("/script.js"); + + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, + tr("JavaScript Files (*.js)")); + + loadScript(fileNameString); +} + void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(_glWidget, getLogger()); @@ -4310,7 +4355,6 @@ void Application::toggleLogDialog() { } } - void Application::initAvatarAndViewFrustum() { updateAvatar(0.f); } @@ -4357,3 +4401,72 @@ void Application::updateLocalOctreeCache(bool firstTime) { } } } + +void Application::checkVersion() { + QNetworkRequest latestVersionRequest((QUrl(CHECK_VERSION_URL))); + latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + connect(Application::getInstance()->getNetworkAccessManager()->get(latestVersionRequest), SIGNAL(finished()), SLOT(parseVersionXml())); +} + +void Application::parseVersionXml() { + + #ifdef Q_OS_WIN32 + QString operatingSystem("win"); + #endif + + #ifdef Q_OS_MAC + QString operatingSystem("mac"); + #endif + + #ifdef Q_OS_LINUX + QString operatingSystem("ubuntu"); + #endif + + QString releaseDate; + QString releaseNotes; + QString latestVersion; + QUrl downloadUrl; + QObject* sender = QObject::sender(); + + QXmlStreamReader xml(qobject_cast(sender)); + while (!xml.atEnd() && !xml.hasError()) { + QXmlStreamReader::TokenType token = xml.readNext(); + + if (token == QXmlStreamReader::StartElement) { + if (xml.name() == "ReleaseDate") { + xml.readNext(); + releaseDate = xml.text().toString(); + } + if (xml.name() == "ReleaseNotes") { + xml.readNext(); + releaseNotes = xml.text().toString(); + } + if (xml.name() == "Version") { + xml.readNext(); + latestVersion = xml.text().toString(); + } + if (xml.name() == operatingSystem) { + xml.readNext(); + downloadUrl = QUrl(xml.text().toString()); + } + } + } + if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) { + new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl); + } + sender->deleteLater(); +} + +bool Application::shouldSkipVersion(QString latestVersion) { + QFile skipFile(SKIP_FILENAME); + skipFile.open(QIODevice::ReadWrite); + QString skipVersion(skipFile.readAll()); + return (skipVersion == latestVersion || applicationVersion() == "dev"); +} + +void Application::skipVersion(QString latestVersion) { + QFile skipFile(SKIP_FILENAME); + skipFile.open(QIODevice::WriteOnly | QIODevice::Truncate); + skipFile.seek(0); + skipFile.write(latestVersion.toStdString().c_str()); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 7e78cced4c..ae2638c230 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -64,6 +65,7 @@ #include "ui/RearMirrorTools.h" #include "ui/LodToolsDialog.h" #include "ui/LogDialog.h" +#include "ui/UpdateDialog.h" #include "FileLogger.h" #include "ParticleTreeRenderer.h" #include "ParticleEditHandle.h" @@ -103,7 +105,10 @@ public: ~Application(); void restoreSizeAndPosition(); + void loadScript(const QString& fileNameString); + void loadScripts(); void storeSizeAndPosition(); + void saveScripts(); void initializeGL(); void paintGL(); void resizeGL(int width, int height); @@ -202,6 +207,8 @@ public: /// set a voxel which is to be rendered with a highlight void setHighlightVoxel(const VoxelDetail& highlightVoxel) { _highlightVoxel = highlightVoxel; } void setIsHighlightVoxel(bool isHighlightVoxel) { _isHighlightVoxel = isHighlightVoxel; } + + void skipVersion(QString latestVersion); public slots: void domainChanged(const QString& domainHostname); @@ -221,7 +228,7 @@ public slots: void doKillLocalVoxels(); void decreaseVoxelSize(); void increaseVoxelSize(); - void loadScript(); + void loadDialog(); void toggleLogDialog(); void initAvatarAndViewFrustum(); @@ -249,6 +256,10 @@ private slots: void restoreMirrorView(); void shrinkMirrorView(); void resetSensors(); + + void parseVersionXml(); + + void removeScriptName(const QString& fileNameString); private: void resetCamerasOnResizeGL(Camera& camera, int width, int height); @@ -373,6 +384,7 @@ private: Faceshift _faceshift; SixenseManager _sixenseManager; + QStringList _activeScripts; Camera _myCamera; // My view onto the world Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode @@ -495,6 +507,10 @@ private: QString getLocalVoxelCacheFileName(); void updateLocalOctreeCache(bool firstTime = false); + + void checkVersion(); + void displayUpdateDialog(); + bool shouldSkipVersion(QString latestVersion); }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h index d0e032d52f..69daefa3fb 100644 --- a/interface/src/ControllerScriptingInterface.h +++ b/interface/src/ControllerScriptingInterface.h @@ -12,6 +12,7 @@ #include #include +class PalmData; /// handles scripting of input controller commands from JS class ControllerScriptingInterface : public AbstractControllerScriptingInterface { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7299615ecc..87edfa82a2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -91,7 +91,7 @@ Menu::Menu() : SLOT(login()))); addDisabledActionAndSeparator(fileMenu, "Scripts"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadScript())); + addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog())); _activeScriptsMenu = fileMenu->addMenu("Running Scripts"); addDisabledActionAndSeparator(fileMenu, "Voxels"); diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp index c6ed1488e4..81ef7f1221 100644 --- a/interface/src/ParticleTreeRenderer.cpp +++ b/interface/src/ParticleTreeRenderer.cpp @@ -32,7 +32,7 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg // we need to iterate the actual particles of the element ParticleTreeElement* particleTreeElement = (ParticleTreeElement*)element; - const std::vector& particles = particleTreeElement->getParticles(); + const QList& particles = particleTreeElement->getParticles(); uint16_t numberOfParticles = particles.size(); diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp new file mode 100644 index 0000000000..12c5cd83e0 --- /dev/null +++ b/interface/src/ui/UpdateDialog.cpp @@ -0,0 +1,61 @@ +// +// UpdateDialog.cpp +// interface +// +// Created by Leonardo Murillo on 1/8/14. +// Copyright (c) 2013, 2014 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "SharedUtil.h" +#include "UpdateDialog.h" + +UpdateDialog::UpdateDialog(QWidget *parent, const QString& releaseNotes, const QString& latestVersion, const QUrl& downloadURL) : + QWidget(parent, Qt::Widget), + _latestVersion(latestVersion), + _downloadUrl(downloadURL) { + + QUiLoader updateDialogLoader; + QWidget* updateDialog; + QFile updateDialogUi("resources/ui/updateDialog.ui"); + updateDialogUi.open(QFile::ReadOnly); + updateDialog = updateDialogLoader.load(&updateDialogUi, this); + + QString updateRequired = QString("You are currently running build %1, the latest build released is %2. \ + Please download and install the most recent release to access the latest features and bug fixes.") + .arg(Application::getInstance()->applicationVersion(), latestVersion); + + + updateDialog->setAttribute(Qt::WA_DeleteOnClose); + + QPushButton* downloadButton = updateDialog->findChild("downloadButton"); + QPushButton* skipButton = updateDialog->findChild("skipButton"); + QPushButton* closeButton = updateDialog->findChild("closeButton"); + QLabel* updateContent = updateDialog->findChild("updateContent"); + + updateContent->setText(updateRequired); + + connect(downloadButton, SIGNAL(released()), this, SLOT(handleDownload())); + connect(skipButton, SIGNAL(released()), this, SLOT(handleSkip())); + connect(closeButton, SIGNAL(released()), this, SLOT(close())); + updateDialog->show(); +} + +void UpdateDialog::handleDownload() { + QDesktopServices::openUrl(_downloadUrl); + Application::getInstance()->quit(); +} + +void UpdateDialog::handleSkip() { + Application::getInstance()->skipVersion(_latestVersion); + this->close(); +} \ No newline at end of file diff --git a/interface/src/ui/UpdateDialog.h b/interface/src/ui/UpdateDialog.h new file mode 100644 index 0000000000..1c4f5de9d0 --- /dev/null +++ b/interface/src/ui/UpdateDialog.h @@ -0,0 +1,29 @@ +// +// UpdateDialog.h +// interface +// +// Created by Leonardo Murillo on 1/8/14. +// Copyright (c) 2013, 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__UpdateDialog__ +#define __hifi__UpdateDialog__ + +#include + +class UpdateDialog : public QWidget { + Q_OBJECT + +public: + UpdateDialog(QWidget* parent, const QString& releaseNotes, const QString& latestVersion, const QUrl& downloadURL); + +private: + QString _latestVersion; + QUrl _downloadUrl; + +private slots: + void handleDownload(); + void handleSkip(); +}; + +#endif /* defined(__hifi__UpdateDialog__) */ diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index dcb344f164..e395996c45 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -474,21 +474,21 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz } } +// MIN_VALID_SPEED is obtained by computing speed gained at one gravity during the shortest expected frame period +const float MIN_EXPECTED_FRAME_PERIOD = 0.005f; // 1/200th of a second +const float MIN_VALID_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); -void Particle::update() { - uint64_t now = usecTimestampNow(); - float elapsed = static_cast(now - _lastUpdated); +void Particle::update(const uint64_t& now) { + float timeElapsed = (float)(now - _lastUpdated) / (float)(USECS_PER_SECOND); _lastUpdated = now; - float timeElapsed = elapsed / static_cast(USECS_PER_SECOND); // calculate our default shouldDie state... then allow script to change it if it wants... - float velocityScalar = glm::length(getVelocity()); - const float STILL_MOVING = 0.05f / static_cast(TREE_SCALE); - bool isStillMoving = (velocityScalar > STILL_MOVING); - const float REALLY_OLD = 30.0f; // 30 seconds - bool isReallyOld = (getLifetime() > REALLY_OLD); + float speed = glm::length(_velocity); + bool isStopped = (speed < MIN_VALID_SPEED); + const uint64_t REALLY_OLD = 30 * USECS_PER_SECOND; // 30 seconds + bool isReallyOld = ((now - _created) > REALLY_OLD); bool isInHand = getInHand(); - bool shouldDie = getShouldDie() || (!isInHand && !isStillMoving && isReallyOld); + bool shouldDie = getShouldDie() || (!isInHand && isStopped && isReallyOld); setShouldDie(shouldDie); runUpdateScript(); // allow the javascript to alter our state diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index 2c112daeeb..d53c7b9c40 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -117,7 +117,7 @@ public: static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); - void update(); + void update(const uint64_t& now); void collisionWithParticle(Particle* other); void collisionWithVoxel(VoxelDetail* voxel); diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 8544932181..dee0259eb7 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -43,7 +42,7 @@ bool ParticleCollisionSystem::updateOperation(OctreeElement* element, void* extr ParticleTreeElement* particleTreeElement = static_cast(element); // iterate the particles... - std::vector& particles = particleTreeElement->getParticles(); + QList& particles = particleTreeElement->getParticles(); uint16_t numberOfParticles = particles.size(); for (uint16_t i = 0; i < numberOfParticles; i++) { Particle* particle = &particles[i]; @@ -72,18 +71,18 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE); float radius = particle->getRadius() * (float)(TREE_SCALE); const float ELASTICITY = 0.4f; - const float DAMPING = 0.0f; + const float DAMPING = 0.05f; const float COLLISION_FREQUENCY = 0.5f; - glm::vec3 penetration; + CollisionInfo collisionInfo; VoxelDetail* voxelDetails = NULL; - if (_voxels->findSpherePenetration(center, radius, penetration, (void**)&voxelDetails)) { + if (_voxels->findSpherePenetration(center, radius, collisionInfo._penetration, (void**)&voxelDetails)) { // let the particles run their collision scripts if they have them particle->collisionWithVoxel(voxelDetails); - penetration /= (float)(TREE_SCALE); - updateCollisionSound(particle, penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, penetration, ELASTICITY, DAMPING); + collisionInfo._penetration /= (float)(TREE_SCALE); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + applyHardCollision(particle, ELASTICITY, DAMPING, collisionInfo); delete voxelDetails; // cleanup returned details } @@ -136,6 +135,10 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) } } +// MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame +const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second +const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); + void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // particles that are in hand, don't collide with avatars @@ -146,18 +149,18 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE); float radius = particle->getRadius() * (float)(TREE_SCALE); const float ELASTICITY = 0.9f; - const float DAMPING = 0.0f; + const float DAMPING = 0.1f; const float COLLISION_FREQUENCY = 0.5f; glm::vec3 penetration; // first check the selfAvatar if set... if (_selfAvatar) { AvatarData* avatar = (AvatarData*)_selfAvatar; - CollisionInfo collision; - if (avatar->findSphereCollision(center, radius, collision)) { - collision._addedVelocity /= (float)(TREE_SCALE); - glm::vec3 relativeVelocity = collision._addedVelocity - particle->getVelocity(); - if (glm::dot(relativeVelocity, collision._penetration) < 0.f) { + CollisionInfo collisionInfo; + if (avatar->findSphereCollision(center, radius, collisionInfo)) { + collisionInfo._addedVelocity /= (float)(TREE_SCALE); + glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity(); + if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) { // only collide when particle and collision point are moving toward each other // (doing this prevents some "collision snagging" when particle penetrates the object) @@ -165,17 +168,20 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. // TODO: make this less hacky when we have more per-collision details float elasticity = ELASTICITY; - float SLOW_PADDLE_SPEED = 5.0e-5f; - float attenuationFactor = glm::length(collision._addedVelocity) / SLOW_PADDLE_SPEED; + float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED; + float damping = DAMPING; if (attenuationFactor < 1.f) { - collision._addedVelocity *= attenuationFactor; + collisionInfo._addedVelocity *= attenuationFactor; elasticity *= attenuationFactor; + // NOTE: the math below keeps the damping piecewise continuous, + // while ramping it up to 1.0 when attenuationFactor = 0 + damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); } // HACK END - collision._penetration /= (float)(TREE_SCALE); - updateCollisionSound(particle, collision._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, collision._penetration, elasticity, DAMPING, collision._addedVelocity); + collisionInfo._penetration /= (float)(TREE_SCALE); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + applyHardCollision(particle, elasticity, damping, collisionInfo); } } } @@ -185,36 +191,37 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { //qDebug() << "updateCollisionWithAvatars()... node:" << *node << "\n"; if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT) { AvatarData* avatar = static_cast(node->getLinkedData()); - CollisionInfo collision; - if (avatar->findSphereCollision(center, radius, collision)) { - collision._addedVelocity /= (float)(TREE_SCALE); - glm::vec3 relativeVelocity = collision._addedVelocity - particle->getVelocity(); - if (glm::dot(relativeVelocity, collision._penetration) < 0.f) { + CollisionInfo collisionInfo; + if (avatar->findSphereCollision(center, radius, collisionInfo)) { + collisionInfo._addedVelocity /= (float)(TREE_SCALE); + glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity(); + if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) { // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. // TODO: make this less hacky when we have more per-collision details float elasticity = ELASTICITY; - float SLOW_PADDLE_SPEED = 5.0e-5f; - float attenuationFactor = glm::length(collision._addedVelocity) / SLOW_PADDLE_SPEED; + float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED; + float damping = DAMPING; if (attenuationFactor < 1.f) { - collision._addedVelocity *= attenuationFactor; + collisionInfo._addedVelocity *= attenuationFactor; elasticity *= attenuationFactor; + // NOTE: the math below keeps the damping piecewise continuous, + // while ramping it up to 1.0 when attenuationFactor = 0 + damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); } // HACK END - collision._penetration /= (float)(TREE_SCALE); - updateCollisionSound(particle, collision._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, collision._penetration, ELASTICITY, DAMPING, collision._addedVelocity); + collisionInfo._penetration /= (float)(TREE_SCALE); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + applyHardCollision(particle, ELASTICITY, damping, collisionInfo); } } } } } - // TODO: convert applyHardCollision() to take a CollisionInfo& instead of penetration + addedVelocity -void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm::vec3& penetration, - float elasticity, float damping, const glm::vec3& addedVelocity) { +void ParticleCollisionSystem::applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo) { // // Update the particle in response to a hard collision. Position will be reset exactly // to outside the colliding surface. Velocity will be modified according to elasticity. @@ -226,20 +233,19 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm:: glm::vec3 velocity = particle->getVelocity(); const float EPSILON = 0.0f; - float velocityDotPenetration = glm::dot(velocity, penetration); - if (velocityDotPenetration > EPSILON) { - position -= penetration; - static float HALTING_VELOCITY = 0.2f / (float)(TREE_SCALE); - // cancel out the velocity component in the direction of penetration + glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity; + float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration); + if (velocityDotPenetration < EPSILON) { + // particle is moving into collision surface + position -= collisionInfo._penetration; - float penetrationLength = glm::length(penetration); - glm::vec3 direction = penetration / penetrationLength; - velocity -= (glm::dot(velocity, direction) * (1.0f + elasticity)) * direction; - velocity += addedVelocity; - velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f); - if (glm::length(velocity) < HALTING_VELOCITY) { - // If moving really slowly after a collision, and not applying forces, stop altogether - velocity *= 0.f; + if (glm::length(relativeVelocity) < HALTING_SPEED) { + // static friction kicks in and particle moves with colliding object + velocity = collisionInfo._addedVelocity; + } else { + glm::vec3 direction = glm::normalize(collisionInfo._penetration); + velocity += glm::dot(relativeVelocity, direction) * (1.0f + elasticity) * direction; // dynamic reflection + velocity += glm::clamp(damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction } } const bool wantDebug = false; diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index 13766a0264..cf52d01a7a 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -46,8 +47,7 @@ public: void updateCollisionWithVoxels(Particle* particle); void updateCollisionWithParticles(Particle* particle); void updateCollisionWithAvatars(Particle* particle); - void applyHardCollision(Particle* particle, const glm::vec3& penetration, float elasticity, float damping, - const glm::vec3& addedVelocity = NO_ADDED_VELOCITY); + void applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo); void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency); private: diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 44cb8db008..2be0c330d2 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -11,12 +11,14 @@ #include "ParticleTree.h" #include "ParticleTreeElement.h" -ParticleTreeElement::ParticleTreeElement(unsigned char* octalCode) : OctreeElement() { +ParticleTreeElement::ParticleTreeElement(unsigned char* octalCode) : OctreeElement(), _particles(NULL) { init(octalCode); }; ParticleTreeElement::~ParticleTreeElement() { _voxelMemoryUsage -= sizeof(ParticleTreeElement); + delete _particles; + _particles = NULL; } // This will be called primarily on addChildAt(), which means we're adding a child of our @@ -31,6 +33,7 @@ OctreeElement* ParticleTreeElement::createNewElement(unsigned char* octalCode) c void ParticleTreeElement::init(unsigned char* octalCode) { OctreeElement::init(octalCode); + _particles = new QList; _voxelMemoryUsage += sizeof(ParticleTreeElement); } @@ -45,12 +48,12 @@ bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const bool success = true; // assume the best... // write our particles out... - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); success = packetData->appendValue(numberOfParticles); if (success) { for (uint16_t i = 0; i < numberOfParticles; i++) { - const Particle& particle = _particles[i]; + const Particle& particle = (*_particles)[i]; success = particle.appendParticleData(packetData); if (!success) { break; @@ -62,35 +65,40 @@ bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) { markWithChangedTime(); + // TODO: early exit when _particles is empty // update our contained particles - uint16_t numberOfParticles = _particles.size(); - - for (uint16_t i = 0; i < numberOfParticles; i++) { - _particles[i].update(); + QList::iterator particleItr = _particles->begin(); + while(particleItr != _particles->end()) { + Particle& particle = (*particleItr); + particle.update(_lastChanged); // If the particle wants to die, or if it's left our bounding box, then move it // into the arguments moving particles. These will be added back or deleted completely - if (_particles[i].getShouldDie() || !_box.contains(_particles[i].getPosition())) { - args._movingParticles.push_back(_particles[i]); + if (particle.getShouldDie() || !_box.contains(particle.getPosition())) { + args._movingParticles.push_back(particle); // erase this particle - _particles.erase(_particles.begin()+i); - - // reduce our index since we just removed this item - i--; - numberOfParticles--; + particleItr = _particles->erase(particleItr); + } else { + ++particleItr; } } + // TODO: if _particles is empty after while loop consider freeing memory in _particles if + // internal array is too big (QList internal array does not decrease size except in dtor and + // assignment operator). Otherwise _particles could become a "resource leak" for large + // roaming piles of particles. } bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { - uint16_t numberOfParticles = _particles.size(); - for (uint16_t i = 0; i < numberOfParticles; i++) { - glm::vec3 particleCenter = _particles[i].getPosition(); - float particleRadius = _particles[i].getRadius(); + QList::iterator particleItr = _particles->begin(); + QList::const_iterator particleEnd = _particles->end(); + while(particleItr != particleEnd) { + Particle& particle = (*particleItr); + glm::vec3 particleCenter = particle.getPosition(); + float particleRadius = particle.getRadius(); // don't penetrate yourself if (particleCenter == center && particleRadius == radius) { @@ -102,23 +110,28 @@ bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float r const bool IN_HAND_PARTICLES_DONT_COLLIDE = false; if (IN_HAND_PARTICLES_DONT_COLLIDE) { // don't penetrate if the particle is "inHand" -- they don't collide - if (_particles[i].getInHand()) { - return false; + if (particle.getInHand()) { + ++particleItr; + continue; } } if (findSphereSpherePenetration(center, radius, particleCenter, particleRadius, penetration)) { - *penetratedObject = (void*)&_particles[i]; + // return true on first valid particle penetration + *penetratedObject = (void*)(&particle); return true; } + ++particleItr; } return false; } bool ParticleTreeElement::containsParticle(const Particle& particle) const { - uint16_t numberOfParticles = _particles.size(); + // TODO: remove this method and force callers to use getParticleWithID() instead + uint16_t numberOfParticles = _particles->size(); + uint32_t particleID = particle.getID(); for (uint16_t i = 0; i < numberOfParticles; i++) { - if (_particles[i].getID() == particle.getID()) { + if ((*_particles)[i].getID() == particleID) { return true; } } @@ -126,13 +139,17 @@ bool ParticleTreeElement::containsParticle(const Particle& particle) const { } bool ParticleTreeElement::updateParticle(const Particle& particle) { + // NOTE: this method must first lookup the particle by ID, hence it is O(N) + // and "particle is not found" is worst-case (full N) but maybe we don't care? + // (guaranteed that num particles per elemen is small?) const bool wantDebug = false; - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { - if (_particles[i].getID() == particle.getID()) { - int difference = _particles[i].getLastUpdated() - particle.getLastUpdated(); - bool changedOnServer = _particles[i].getLastEdited() < particle.getLastEdited(); - bool localOlder = _particles[i].getLastUpdated() < particle.getLastUpdated(); + Particle& thisParticle = (*_particles)[i]; + if (thisParticle.getID() == particle.getID()) { + int difference = thisParticle.getLastUpdated() - particle.getLastUpdated(); + bool changedOnServer = thisParticle.getLastEdited() < particle.getLastEdited(); + bool localOlder = thisParticle.getLastUpdated() < particle.getLastUpdated(); if (changedOnServer || localOlder) { if (wantDebug) { printf("local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", @@ -140,7 +157,7 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(particle.isNewlyCreated()) ); } - _particles[i].copyChangedProperties(particle); + thisParticle.copyChangedProperties(particle); } else { if (wantDebug) { printf(">>> IGNORING SERVER!!! Would've caused jutter! <<< " @@ -159,22 +176,23 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) const { const Particle* closestParticle = NULL; float closestParticleDistance = FLT_MAX; - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { - float distanceToParticle = glm::distance(position, _particles[i].getPosition()); + float distanceToParticle = glm::distance(position, (*_particles)[i].getPosition()); if (distanceToParticle < closestParticleDistance) { - closestParticle = &_particles[i]; + closestParticle = &(*_particles)[i]; } } return closestParticle; } const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const { + // NOTE: this lookup is O(N) but maybe we don't care? (guaranteed that num particles per elemen is small?) const Particle* foundParticle = NULL; - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { - if (_particles[i].getID() == id) { - foundParticle = &_particles[i]; + if ((*_particles)[i].getID() == id) { + foundParticle = &(*_particles)[i]; break; } } @@ -229,7 +247,7 @@ bool ParticleTreeElement::collapseChildren() { void ParticleTreeElement::storeParticle(const Particle& particle, Node* senderNode) { - _particles.push_back(particle); + _particles->push_back(particle); markWithChangedTime(); } diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 971f5f5481..ab697aed1b 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -10,9 +10,10 @@ #ifndef __hifi__ParticleTreeElement__ #define __hifi__ParticleTreeElement__ -#include +//#include #include +#include #include "Particle.h" #include "ParticleTree.h" @@ -22,7 +23,7 @@ class ParticleTreeElement; class ParticleTreeUpdateArgs { public: - std::vector _movingParticles; + QList _movingParticles; }; class ParticleTreeElement : public OctreeElement { @@ -34,7 +35,6 @@ class ParticleTreeElement : public OctreeElement { public: virtual ~ParticleTreeElement(); - virtual void init(unsigned char * octalCode); // type safe versions of OctreeElement methods ParticleTreeElement* getChildAtIndex(int index) { return (ParticleTreeElement*)OctreeElement::getChildAtIndex(index); } @@ -79,9 +79,9 @@ public: virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; - const std::vector& getParticles() const { return _particles; } - std::vector& getParticles() { return _particles; } - bool hasParticles() const { return _particles.size() > 0; } + const QList& getParticles() const { return *_particles; } + QList& getParticles() { return *_particles; } + bool hasParticles() const { return _particles->size() > 0; } void update(ParticleTreeUpdateArgs& args); void setTree(ParticleTree* tree) { _myTree = tree; } @@ -93,10 +93,12 @@ public: protected: + virtual void init(unsigned char * octalCode); + void storeParticle(const Particle& particle, Node* senderNode = NULL); ParticleTree* _myTree; - std::vector _particles; + QList* _particles; }; #endif /* defined(__hifi__ParticleTreeElement__) */ diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6b85cf33ad..e3f694f7cc 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -38,17 +38,20 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng return soundScriptValue; } -ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, - const char* scriptMenuName, AbstractMenuInterface* menu, +ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) { _scriptContents = scriptContents; _isFinished = false; _isRunning = false; _isInitialized = false; + _fileNameString = fileNameString; + + QByteArray fileNameAscii = fileNameString.toLocal8Bit(); + const char* scriptMenuName = fileNameAscii.data(); // some clients will use these menu features _wantMenuItems = wantMenuItems; - if (scriptMenuName) { + if (!fileNameString.isEmpty()) { _scriptMenuName = "Stop "; _scriptMenuName.append(scriptMenuName); _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); @@ -233,9 +236,11 @@ void ScriptEngine::run() { thread()->quit(); } - emit finished(); + emit finished(_fileNameString); + _isRunning = false; } + void ScriptEngine::stop() { _isFinished = true; } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 06597f82a8..d1a4d6cf51 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -29,7 +29,7 @@ class ScriptEngine : public QObject { Q_OBJECT public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const char* scriptMenuName = NULL, AbstractMenuInterface* menu = NULL, + const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); ~ScriptEngine(); @@ -58,7 +58,7 @@ signals: void willSendAudioDataCallback(); void willSendVisualDataCallback(); void scriptEnding(); - void finished(); + void finished(const QString& fileNameString); protected: QString _scriptContents; @@ -74,6 +74,7 @@ private: AudioScriptingInterface _audioScriptingInterface; bool _wantMenuItems; QString _scriptMenuName; + QString _fileNameString; AbstractMenuInterface* _menu; static int _scriptNumber; };