Merge branch 'master' into 4869-Entity-resize-issue

This commit is contained in:
NeetBhagat 2017-06-05 11:08:51 +05:30
commit 967c50ab93
71 changed files with 2210 additions and 325 deletions

View file

@ -11,6 +11,9 @@
#include "Application.h" #include "Application.h"
#include <chrono>
#include <thread>
#include <gl/Config.h> #include <gl/Config.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/component_wise.hpp> #include <glm/gtx/component_wise.hpp>
@ -144,6 +147,7 @@
#include "InterfaceLogging.h" #include "InterfaceLogging.h"
#include "LODManager.h" #include "LODManager.h"
#include "ModelPackager.h" #include "ModelPackager.h"
#include "networking/CloseEventSender.h"
#include "networking/HFWebEngineProfile.h" #include "networking/HFWebEngineProfile.h"
#include "networking/HFTabletWebEngineProfile.h" #include "networking/HFTabletWebEngineProfile.h"
#include "networking/FileTypeProfile.h" #include "networking/FileTypeProfile.h"
@ -534,6 +538,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<AvatarBookmarks>(); DependencyManager::set<AvatarBookmarks>();
DependencyManager::set<LocationBookmarks>(); DependencyManager::set<LocationBookmarks>();
DependencyManager::set<Snapshot>(); DependencyManager::set<Snapshot>();
DependencyManager::set<CloseEventSender>();
return previousSessionCrashed; return previousSessionCrashed;
} }
@ -1570,6 +1575,14 @@ void Application::aboutToQuit() {
getActiveDisplayPlugin()->deactivate(); getActiveDisplayPlugin()->deactivate();
// use the CloseEventSender via a QThread to send an event that says the user asked for the app to close
auto closeEventSender = DependencyManager::get<CloseEventSender>();
QThread* closureEventThread = new QThread(this);
closeEventSender->moveToThread(closureEventThread);
// sendQuitEventAsync will bail immediately if the UserActivityLogger is not enabled
connect(closureEventThread, &QThread::started, closeEventSender.data(), &CloseEventSender::sendQuitEventAsync);
closureEventThread->start();
// Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown.
DependencyManager::get<OffscreenUi>()->hide("RunningScripts"); DependencyManager::get<OffscreenUi>()->hide("RunningScripts");
@ -1683,6 +1696,10 @@ Application::~Application() {
_physicsEngine->setCharacterController(nullptr); _physicsEngine->setCharacterController(nullptr);
// the _shapeManager should have zero references
_shapeManager.collectGarbage();
assert(_shapeManager.getNumShapes() == 0);
// shutdown render engine // shutdown render engine
_main3DScene = nullptr; _main3DScene = nullptr;
_renderEngine = nullptr; _renderEngine = nullptr;
@ -1734,6 +1751,15 @@ Application::~Application() {
_window->deleteLater(); _window->deleteLater();
// make sure that the quit event has finished sending before we take the application down
auto closeEventSender = DependencyManager::get<CloseEventSender>();
while (!closeEventSender->hasFinishedQuitEvent() && !closeEventSender->hasTimedOutQuitEvent()) {
// sleep a little so we're not spinning at 100%
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// quit the thread used by the closure event sender
closeEventSender->thread()->quit();
// Can't log to file passed this point, FileLogger about to be deleted // Can't log to file passed this point, FileLogger about to be deleted
qInstallMessageHandler(LogHandler::verboseMessageHandler); qInstallMessageHandler(LogHandler::verboseMessageHandler);
} }
@ -2384,15 +2410,16 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
// Check HMD use (may be technically available without being in use) // Check HMD use (may be technically available without being in use)
bool hasHMD = PluginUtils::isHMDAvailable(); bool hasHMD = PluginUtils::isHMDAvailable();
bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd(); bool isUsingHMD = _displayPlugin->isHmd();
bool isUsingHMDAndHandControllers = hasHMD && hasHandControllers && isUsingHMD;
Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false }; Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false };
Setting::Handle<bool> firstRun{ Settings::firstRun, true }; Setting::Handle<bool> firstRun{ Settings::firstRun, true };
bool isTutorialComplete = tutorialComplete.get(); bool isTutorialComplete = tutorialComplete.get();
bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete; bool shouldGoToTutorial = isUsingHMDAndHandControllers && hasTutorialContent && !isTutorialComplete;
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD; qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMDAndHandControllers;
qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent << qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial; ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
@ -2406,10 +2433,18 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
const QString TUTORIAL_PATH = "/tutorial_begin"; const QString TUTORIAL_PATH = "/tutorial_begin";
static const QString SENT_TO_TUTORIAL = "tutorial";
static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location";
static const QString SENT_TO_ENTRY = "entry";
static const QString SENT_TO_SANDBOX = "sandbox";
QString sentTo;
if (shouldGoToTutorial) { if (shouldGoToTutorial) {
if (sandboxIsRunning) { if (sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH); DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
sentTo = SENT_TO_TUTORIAL;
} else { } else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
if (firstRun.get()) { if (firstRun.get()) {
@ -2417,8 +2452,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
} }
if (addressLookupString.isEmpty()) { if (addressLookupString.isEmpty()) {
DependencyManager::get<AddressManager>()->goToEntry(); DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
} else { } else {
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
sentTo = SENT_TO_PREVIOUS_LOCATION;
} }
} }
} else { } else {
@ -2431,23 +2468,40 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
// If this is a first run we short-circuit the address passed in // If this is a first run we short-circuit the address passed in
if (isFirstRun) { if (isFirstRun) {
if (isUsingHMD) { if (isUsingHMDAndHandControllers) {
if (sandboxIsRunning) { if (sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox(); DependencyManager::get<AddressManager>()->goToLocalSandbox();
sentTo = SENT_TO_SANDBOX;
} else { } else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry(); DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
} }
} else { } else {
DependencyManager::get<AddressManager>()->goToEntry(); DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
} }
} else { } else {
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
sentTo = SENT_TO_PREVIOUS_LOCATION;
} }
} }
UserActivityLogger::getInstance().logAction("startup_sent_to", {
{ "sent_to", sentTo },
{ "sandbox_is_running", sandboxIsRunning },
{ "has_hmd", hasHMD },
{ "has_hand_controllers", hasHandControllers },
{ "is_using_hmd", isUsingHMD },
{ "is_using_hmd_and_hand_controllers", isUsingHMDAndHandControllers },
{ "content_version", contentVersion },
{ "is_tutorial_complete", isTutorialComplete },
{ "has_tutorial_content", hasTutorialContent },
{ "should_go_to_tutorial", shouldGoToTutorial }
});
_connectionMonitor.init(); _connectionMonitor.init();
// After all of the constructor is completed, then set firstRun to false. // After all of the constructor is completed, then set firstRun to false.
@ -2776,6 +2830,17 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (isShifted && isMeta && !isOption) { if (isShifted && isMeta && !isOption) {
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
} else if (!isOption && !isShifted && isMeta) { } else if (!isOption && !isShifted && isMeta) {
AudioInjectorOptions options;
options.localOnly = true;
options.stereo = true;
if (_snapshotSoundInjector) {
_snapshotSoundInjector->setOptions(options);
_snapshotSoundInjector->restart();
} else {
QByteArray samples = _snapshotSound->getByteArray();
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
}
takeSnapshot(true); takeSnapshot(true);
} }
break; break;
@ -4490,12 +4555,13 @@ void Application::update(float deltaTime) {
getEntities()->getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
PerformanceTimer perfTimer("handleOutgoingChanges"); PerformanceTimer perfTimer("handleOutgoingChanges");
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
_entitySimulation->handleDeactivatedMotionStates(deactivations);
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
_entitySimulation->handleChangedMotionStates(outgoingChanges); _entitySimulation->handleChangedMotionStates(outgoingChanges);
avatarManager->handleChangedMotionStates(outgoingChanges); avatarManager->handleChangedMotionStates(outgoingChanges);
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
_entitySimulation->handleDeactivatedMotionStates(deactivations);
}); });
if (!_aboutToQuit) { if (!_aboutToQuit) {
@ -6307,21 +6373,6 @@ void Application::loadAddAvatarBookmarkDialog() const {
} }
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
//keep sound thread out of event loop scope
AudioInjectorOptions options;
options.localOnly = true;
options.stereo = true;
if (_snapshotSoundInjector) {
_snapshotSoundInjector->setOptions(options);
_snapshotSoundInjector->restart();
} else {
QByteArray samples = _snapshotSound->getByteArray();
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
}
postLambdaEvent([notify, includeAnimated, aspectRatio, this] { postLambdaEvent([notify, includeAnimated, aspectRatio, this] {
// Get a screenshot and save it // Get a screenshot and save it
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));

View file

@ -407,6 +407,12 @@ Menu::Menu() {
#endif #endif
{
auto action = addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderClearKtxCache);
connect(action, &QAction::triggered, []{
Setting::Handle<int>(KTXCache::SETTING_VERSION_NAME, KTXCache::INVALID_VERSION).set(KTXCache::INVALID_VERSION);
});
}
// Developer > Render > LOD Tools // Developer > Render > LOD Tools
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0,

View file

@ -145,6 +145,7 @@ namespace MenuOption {
const QString Quit = "Quit"; const QString Quit = "Quit";
const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadAllScripts = "Reload All Scripts";
const QString ReloadContent = "Reload Content (Clears all caches)"; const QString ReloadContent = "Reload Content (Clears all caches)";
const QString RenderClearKtxCache = "Clear KTX Cache (requires restart)";
const QString RenderMaxTextureMemory = "Maximum Texture Memory"; const QString RenderMaxTextureMemory = "Maximum Texture Memory";
const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; const QString RenderMaxTextureAutomatic = "Automatic Texture Memory";
const QString RenderMaxTexture4MB = "4 MB"; const QString RenderMaxTexture4MB = "4 MB";

View file

@ -24,7 +24,6 @@
#include <SandboxUtils.h> #include <SandboxUtils.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include "AddressManager.h" #include "AddressManager.h"
#include "Application.h" #include "Application.h"
#include "InterfaceLogging.h" #include "InterfaceLogging.h"
@ -191,7 +190,7 @@ int main(int argc, const char* argv[]) {
int exitCode; int exitCode;
{ {
RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME); RunningMarker runningMarker(RUNNING_MARKER_FILENAME);
bool runningMarkerExisted = runningMarker.fileExists(); bool runningMarkerExisted = runningMarker.fileExists();
runningMarker.writeRunningMarkerFile(); runningMarker.writeRunningMarkerFile();
@ -200,14 +199,11 @@ int main(int argc, const char* argv[]) {
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
if (runServer) { if (runServer) {
SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater); SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater);
} }
Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted); Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted);
// Now that the main event loop is setup, launch running marker thread
runningMarker.startRunningMarker();
// If we failed the OpenGLVersion check, log it. // If we failed the OpenGLVersion check, log it.
if (override) { if (override) {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();

View file

@ -0,0 +1,90 @@
//
// CloseEventSender.cpp
// interface/src/networking
//
// Created by Stephen Birarda on 5/31/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDateTime>
#include <QtCore/QEventLoop>
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply>
#include <AccountManager.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <NetworkLogging.h>
#include <UserActivityLogger.h>
#include <UUID.h>
#include "CloseEventSender.h"
QNetworkRequest createNetworkRequest() {
QNetworkRequest request;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
requestURL.setPath(USER_ACTIVITY_URL);
request.setUrl(requestURL);
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->hasValidAccessToken()) {
request.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue());
}
request.setRawHeader(METAVERSE_SESSION_ID_HEADER,
uuidStringWithoutCurlyBraces(accountManager->getSessionID()).toLocal8Bit());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setPriority(QNetworkRequest::HighPriority);
return request;
}
QByteArray postDataForAction(QString action) {
return QString("{\"action_name\": \"" + action + "\"}").toUtf8();
}
QNetworkReply* replyForAction(QString action) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
return networkAccessManager.post(createNetworkRequest(), postDataForAction(action));
}
void CloseEventSender::sendQuitEventAsync() {
if (UserActivityLogger::getInstance().isEnabled()) {
QNetworkReply* reply = replyForAction("quit");
connect(reply, &QNetworkReply::finished, this, &CloseEventSender::handleQuitEventFinished);
_quitEventStartTimestamp = QDateTime::currentMSecsSinceEpoch();
} else {
_hasFinishedQuitEvent = true;
}
}
void CloseEventSender::handleQuitEventFinished() {
_hasFinishedQuitEvent = true;
auto reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error() == QNetworkReply::NoError) {
qCDebug(networking) << "Quit event sent successfully";
} else {
qCDebug(networking) << "Failed to send quit event -" << reply->errorString();
}
reply->deleteLater();
}
bool CloseEventSender::hasTimedOutQuitEvent() {
const int CLOSURE_EVENT_TIMEOUT_MS = 5000;
return _quitEventStartTimestamp != 0
&& QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS;
}

View file

@ -0,0 +1,41 @@
//
// CloseEventSender.h
// interface/src/networking
//
// Created by Stephen Birarda on 5/31/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CloseEventSender_h
#define hifi_CloseEventSender_h
#include <atomic>
#include <QtCore/QString>
#include <QtCore/QUuid>
#include <DependencyManager.h>
class CloseEventSender : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
bool hasTimedOutQuitEvent();
bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; }
public slots:
void sendQuitEventAsync();
private slots:
void handleQuitEventFinished();
private:
std::atomic<bool> _hasFinishedQuitEvent { false };
std::atomic<int64_t> _quitEventStartTimestamp;
};
#endif // hifi_CloseEventSender_h

View file

@ -19,6 +19,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <PathUtils.h> #include <PathUtils.h>
#include <QUrl> #include <QUrl>
#include <Gzip.h>
#include <BuildInfo.h> #include <BuildInfo.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
@ -27,7 +28,7 @@
QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/"; QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
QString FILE_PREFIX_NAME = "input-recording-"; QString FILE_PREFIX_NAME = "input-recording-";
QString COMPRESS_EXTENSION = "json.gz"; QString COMPRESS_EXTENSION = ".json.gz";
namespace controller { namespace controller {
QJsonObject poseToJsonObject(const Pose pose) { QJsonObject poseToJsonObject(const Pose pose) {
@ -93,23 +94,26 @@ namespace controller {
} }
void exportToFile(const QJsonObject& object) { void exportToFile(const QJsonObject& object, const QString& fileName) {
if (!QDir(SAVE_DIRECTORY).exists()) { if (!QDir(SAVE_DIRECTORY).exists()) {
QDir().mkdir(SAVE_DIRECTORY); QDir().mkdir(SAVE_DIRECTORY);
} }
QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate);
timeStamp.replace(":", "-");
QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION;
qDebug() << fileName;
QFile saveFile (fileName); QFile saveFile (fileName);
if (!saveFile.open(QIODevice::WriteOnly)) { if (!saveFile.open(QIODevice::WriteOnly)) {
qWarning() << "could not open file: " << fileName; qWarning() << "could not open file: " << fileName;
return; return;
} }
QJsonDocument saveData(object); QJsonDocument saveData(object);
QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact)); QByteArray jsonData = saveData.toJson(QJsonDocument::Indented);
saveFile.write(compressedData); QByteArray jsonDataForFile;
if (!gzip(jsonData, jsonDataForFile, -1)) {
qCritical("unable to gzip while saving to json.");
return;
}
saveFile.write(jsonDataForFile);
saveFile.close(); saveFile.close();
} }
@ -121,8 +125,16 @@ namespace controller {
status = false; status = false;
return object; return object;
} }
QByteArray compressedData = qUncompress(openFile.readAll()); QByteArray compressedData = openFile.readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData); QByteArray jsonData;
if (!gunzip(compressedData, jsonData)) {
qCritical() << "json file not in gzip format: " << file;
status = false;
return object;
}
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
object = jsonDoc.object(); object = jsonDoc.object();
status = true; status = true;
openFile.close(); openFile.close();
@ -153,7 +165,7 @@ namespace controller {
QJsonObject InputRecorder::recordDataToJson() { QJsonObject InputRecorder::recordDataToJson() {
QJsonObject data; QJsonObject data;
data["frameCount"] = _framesRecorded; data["frameCount"] = _framesRecorded;
data["version"] = "1.0"; data["version"] = "0.0";
QJsonArray actionArrayList; QJsonArray actionArrayList;
QJsonArray poseArrayList; QJsonArray poseArrayList;
@ -187,7 +199,10 @@ namespace controller {
void InputRecorder::saveRecording() { void InputRecorder::saveRecording() {
QJsonObject jsonData = recordDataToJson(); QJsonObject jsonData = recordDataToJson();
exportToFile(jsonData); QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate);
timeStamp.replace(":", "-");
QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION;
exportToFile(jsonData, fileName);
} }
void InputRecorder::loadRecording(const QString& path) { void InputRecorder::loadRecording(const QString& path) {
@ -202,10 +217,12 @@ namespace controller {
QString filePath = urlPath.toLocalFile(); QString filePath = urlPath.toLocalFile();
QFileInfo info(filePath); QFileInfo info(filePath);
QString extension = info.suffix(); QString extension = info.suffix();
if (extension != "gz") { if (extension != "gz") {
qWarning() << "can not load file with exentsion of " << extension; qWarning() << "can not load file with exentsion of " << extension;
return; return;
} }
bool success = false; bool success = false;
QJsonObject data = openFile(filePath, success); QJsonObject data = openFile(filePath, success);
auto keyValue = data.find("version"); auto keyValue = data.find("version");
@ -233,34 +250,7 @@ namespace controller {
_poseStateList.push_back(_currentFramePoses); _poseStateList.push_back(_currentFramePoses);
_currentFramePoses.clear(); _currentFramePoses.clear();
} }
} else if (success) { }
//convert recording to new reacording standard and rewrite file
auto userInputMapper = DependencyManager::get<UserInputMapper>();
_framesRecorded = data["frameCount"].toInt();
QJsonArray actionArrayList = data["actionList"].toArray();
QJsonArray poseArrayList = data["poseList"].toArray();
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
QJsonArray actionState = actionArrayList[actionIndex].toArray();
for (int index = 0; index < actionState.size(); index++) {
QString actionName = userInputMapper->getActionName(Action(index));
_currentFrameActions[actionName] = actionState[index].toDouble();
}
_actionStateList.push_back(_currentFrameActions);
_currentFrameActions.clear();
}
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
QJsonArray poseState = poseArrayList[poseIndex].toArray();
for (int index = 0; index < poseState.size(); index++) {
QString actionName = userInputMapper->getActionName(Action(index));
_currentFramePoses[actionName] = jsonObjectToPose(poseState[index].toObject());
}
_poseStateList.push_back(_currentFramePoses);
_currentFramePoses.clear();
}
}
_loading = false; _loading = false;
} }

View file

@ -329,6 +329,16 @@ QString UserInputMapper::getActionName(Action action) const {
return QString(); return QString();
} }
QString UserInputMapper::getStandardPoseName(uint16_t pose) {
Locker locker(_lock);
for (auto posePair : getStandardInputs()) {
if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) {
return posePair.second;
}
}
return QString();
}
QVector<QString> UserInputMapper::getActionNames() const { QVector<QString> UserInputMapper::getActionNames() const {
Locker locker(_lock); Locker locker(_lock);
QVector<QString> result; QVector<QString> result;

View file

@ -80,6 +80,7 @@ namespace controller {
QVector<Action> getAllActions() const; QVector<Action> getAllActions() const;
QString getActionName(Action action) const; QString getActionName(Action action) const;
QString getStandardPoseName(uint16_t pose);
float getActionState(Action action) const { return _actionStates[toInt(action)]; } float getActionState(Action action) const { return _actionStates[toInt(action)]; }
Pose getPoseState(Action action) const; Pose getPoseState(Action action) const;
int findAction(const QString& actionName) const; int findAction(const QString& actionName) const;

View file

@ -36,9 +36,6 @@ void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
auto userInputMapper = DependencyManager::get<UserInputMapper>(); auto userInputMapper = DependencyManager::get<UserInputMapper>();
QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
inputRecorder->setActionState(actionName, _currentPose); inputRecorder->setActionState(actionName, _currentPose);
if (inputRecorder->isPlayingback()) {
_currentPose = inputRecorder->getPoseState(actionName);
}
if (!_currentPose.isValid()) { if (!_currentPose.isValid()) {
return; return;

View file

@ -12,6 +12,11 @@
#include "../Endpoint.h" #include "../Endpoint.h"
#include <DependencyManager.h>
#include "../../InputRecorder.h"
#include "../../UserInputMapper.h"
namespace controller { namespace controller {
class StandardEndpoint : public VirtualEndpoint { class StandardEndpoint : public VirtualEndpoint {
@ -40,6 +45,12 @@ public:
virtual Pose pose() override { virtual Pose pose() override {
_read = true; _read = true;
InputRecorder* inputRecorder = InputRecorder::getInstance();
if (inputRecorder->isPlayingback()) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
QString actionName = userInputMapper->getStandardPoseName(_input.getChannel());
return inputRecorder->getPoseState(actionName);
}
return VirtualEndpoint::pose(); return VirtualEndpoint::pose();
} }

View file

@ -681,7 +681,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// and pretend that we own it (we assume we'll recover it soon) // and pretend that we own it (we assume we'll recover it soon)
// However, for now, when the server uses a newer time than what we sent, listen to what we're told. // However, for now, when the server uses a newer time than what we sent, listen to what we're told.
if (overwriteLocalData) weOwnSimulation = false; if (overwriteLocalData) {
weOwnSimulation = false;
}
} else if (_simulationOwner.set(newSimOwner)) { } else if (_simulationOwner.set(newSimOwner)) {
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true; somethingChanged = true;
@ -1293,27 +1295,15 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c
properties._accelerationChanged = true; properties._accelerationChanged = true;
} }
void EntityItem::pokeSimulationOwnership() { void EntityItem::flagForOwnershipBid(uint8_t priority) {
markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE); markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY);
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) {
// we already own it // we already own it
_simulationOwner.promotePriority(SCRIPT_POKE_SIMULATION_PRIORITY); _simulationOwner.promotePriority(priority);
} else { } else {
// we don't own it yet // we don't own it yet
_simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_PRIORITY, usecTimestampNow()); _simulationOwner.setPendingPriority(priority, usecTimestampNow());
}
}
void EntityItem::grabSimulationOwnership() {
markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB);
auto nodeList = DependencyManager::get<NodeList>();
if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) {
// we already own it
_simulationOwner.promotePriority(SCRIPT_GRAB_SIMULATION_PRIORITY);
} else {
// we don't own it yet
_simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow());
} }
} }

View file

@ -321,6 +321,7 @@ public:
void updateSimulationOwner(const SimulationOwner& owner); void updateSimulationOwner(const SimulationOwner& owner);
void clearSimulationOwnership(); void clearSimulationOwnership();
void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp);
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
void rememberHasSimulationOwnershipBid() const; void rememberHasSimulationOwnershipBid() const;
QString getMarketplaceID() const; QString getMarketplaceID() const;
@ -394,8 +395,7 @@ public:
void getAllTerseUpdateProperties(EntityItemProperties& properties) const; void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
void pokeSimulationOwnership(); void flagForOwnershipBid(uint8_t priority);
void grabSimulationOwnership();
void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; } void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; }
QString actionsToDebugString(); QString actionsToDebugString();

View file

@ -230,6 +230,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
} }
entity->setLastBroadcast(usecTimestampNow()); entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
propertiesWithSimID.setLastEdited(entity->getLastEdited()); propertiesWithSimID.setLastEdited(entity->getLastEdited());
} else { } else {
qCDebug(entities) << "script failed to add new Entity to local Octree"; qCDebug(entities) << "script failed to add new Entity to local Octree";
@ -440,7 +442,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
} else { } else {
// we make a bid for simulation ownership // we make a bid for simulation ownership
properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
entity->pokeSimulationOwnership(); entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY);
entity->rememberHasSimulationOwnershipBid(); entity->rememberHasSimulationOwnershipBid();
} }
} }
@ -1194,7 +1196,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
} }
action->setIsMine(true); action->setIsMine(true);
success = entity->addAction(simulation, action); success = entity->addAction(simulation, action);
entity->grabSimulationOwnership(); entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY);
return false; // Physics will cause a packet to be sent, so don't send from here. return false; // Physics will cause a packet to be sent, so don't send from here.
}); });
if (success) { if (success) {
@ -1210,7 +1212,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
bool success = entity->updateAction(simulation, actionID, arguments); bool success = entity->updateAction(simulation, actionID, arguments);
if (success) { if (success) {
entity->grabSimulationOwnership(); entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY);
} }
return success; return success;
}); });
@ -1224,7 +1226,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
success = entity->removeAction(simulation, actionID); success = entity->removeAction(simulation, actionID);
if (success) { if (success) {
// reduce from grab to poke // reduce from grab to poke
entity->pokeSimulationOwnership(); entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY);
} }
return false; // Physics will cause a packet to be sent, so don't send from here. return false; // Physics will cause a packet to be sent, so don't send from here.
}); });

View file

@ -26,12 +26,10 @@ namespace Simulation {
const uint32_t DIRTY_MATERIAL = 0x00400; const uint32_t DIRTY_MATERIAL = 0x00400;
const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine
const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed
const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_POKE = 0x2000; // bid for simulation ownership at "poke" const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB = 0x4000; // bid for simulation ownership at "grab"
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = DIRTY_SIMULATION_OWNERSHIP_FOR_POKE | DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB;
}; };
#endif // hifi_SimulationFlags_h #endif // hifi_SimulationFlags_h

View file

@ -26,9 +26,9 @@ const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
SimulationOwner::SimulationOwner() : SimulationOwner::SimulationOwner() :
_id(), _id(),
_expiry(0), _expiry(0),
_pendingTimestamp(0), _pendingBidTimestamp(0),
_priority(0), _priority(0),
_pendingPriority(0), _pendingBidPriority(0),
_pendingState(PENDING_STATE_NOTHING) _pendingState(PENDING_STATE_NOTHING)
{ {
} }
@ -36,9 +36,9 @@ SimulationOwner::SimulationOwner() :
SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) :
_id(id), _id(id),
_expiry(0), _expiry(0),
_pendingTimestamp(0), _pendingBidTimestamp(0),
_priority(priority), _priority(priority),
_pendingPriority(0) _pendingBidPriority(0)
{ {
} }
@ -61,9 +61,9 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) {
void SimulationOwner::clear() { void SimulationOwner::clear() {
_id = QUuid(); _id = QUuid();
_expiry = 0; _expiry = 0;
_pendingTimestamp = 0; _pendingBidTimestamp = 0;
_priority = 0; _priority = 0;
_pendingPriority = 0; _pendingBidPriority = 0;
_pendingState = PENDING_STATE_NOTHING; _pendingState = PENDING_STATE_NOTHING;
} }
@ -102,9 +102,9 @@ bool SimulationOwner::set(const SimulationOwner& owner) {
} }
void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) {
_pendingPriority = priority; _pendingBidPriority = priority;
_pendingTimestamp = timestamp; _pendingBidTimestamp = timestamp;
_pendingState = (_pendingPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
} }
void SimulationOwner::updateExpiry() { void SimulationOwner::updateExpiry() {
@ -113,11 +113,11 @@ void SimulationOwner::updateExpiry() {
} }
bool SimulationOwner::pendingRelease(const quint64& timestamp) { bool SimulationOwner::pendingRelease(const quint64& timestamp) {
return _pendingPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingTimestamp >= timestamp; return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp;
} }
bool SimulationOwner::pendingTake(const quint64& timestamp) { bool SimulationOwner::pendingTake(const quint64& timestamp) {
return _pendingPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingTimestamp >= timestamp; return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp;
} }
void SimulationOwner::clearCurrentOwner() { void SimulationOwner::clearCurrentOwner() {

View file

@ -66,6 +66,7 @@ public:
bool hasExpired() const { return usecTimestampNow() > _expiry; } bool hasExpired() const { return usecTimestampNow() > _expiry; }
uint8_t getPendingPriority() const { return _pendingBidPriority; }
bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE
bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE
void clearCurrentOwner(); void clearCurrentOwner();
@ -84,9 +85,9 @@ public:
private: private:
QUuid _id; // owner QUuid _id; // owner
quint64 _expiry; // time when ownership can transition at equal priority quint64 _expiry; // time when ownership can transition at equal priority
quint64 _pendingTimestamp; // time when pending update was set quint64 _pendingBidTimestamp; // time when pending bid was set
quint8 _priority; // priority of current owner quint8 _priority; // priority of current owner
quint8 _pendingPriority; // priority of pendingTake quint8 _pendingBidPriority; // priority at which we'd like to own it
quint8 _pendingState; // NOTHING, TAKE, or RELEASE quint8 _pendingState; // NOTHING, TAKE, or RELEASE
}; };

View file

@ -11,14 +11,28 @@
#include "KTXCache.h" #include "KTXCache.h"
#include <SettingHandle.h>
#include <ktx/KTX.h> #include <ktx/KTX.h>
using File = cache::File; using File = cache::File;
using FilePointer = cache::FilePointer; using FilePointer = cache::FilePointer;
// Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible,
// this value should be incremented. This will force the KTX cache to be wiped
const int KTXCache::CURRENT_VERSION = 0x01;
const int KTXCache::INVALID_VERSION = 0x00;
const char* KTXCache::SETTING_VERSION_NAME = "hifi.ktx.cache_version";
KTXCache::KTXCache(const std::string& dir, const std::string& ext) : KTXCache::KTXCache(const std::string& dir, const std::string& ext) :
FileCache(dir, ext) { FileCache(dir, ext) {
initialize(); initialize();
Setting::Handle<int> cacheVersionHandle(SETTING_VERSION_NAME, INVALID_VERSION);
auto cacheVersion = cacheVersionHandle.get();
if (cacheVersion != CURRENT_VERSION) {
wipe();
cacheVersionHandle.set(CURRENT_VERSION);
}
} }
KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) { KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) {

View file

@ -27,6 +27,12 @@ class KTXCache : public cache::FileCache {
Q_OBJECT Q_OBJECT
public: public:
// Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible,
// this value should be incremented. This will force the KTX cache to be wiped
static const int CURRENT_VERSION;
static const int INVALID_VERSION;
static const char* SETTING_VERSION_NAME;
KTXCache(const std::string& dir, const std::string& ext); KTXCache(const std::string& dir, const std::string& ext);
KTXFilePointer writeFile(const char* data, Metadata&& metadata); KTXFilePointer writeFile(const char* data, Metadata&& metadata);

View file

@ -45,7 +45,6 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
Q_DECLARE_METATYPE(JSONCallbackParameters) Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNTS_GROUP = "accounts"; const QString ACCOUNTS_GROUP = "accounts";
static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod,
QObject* errorCallbackReceiver, const QString& errorCallbackMethod, QObject* errorCallbackReceiver, const QString& errorCallbackMethod,

View file

@ -52,6 +52,7 @@ namespace AccountManagerAuth {
Q_DECLARE_METATYPE(AccountManagerAuth::Type); Q_DECLARE_METATYPE(AccountManagerAuth::Type);
const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
using UserAgentGetter = std::function<QString()>; using UserAgentGetter = std::function<QString()>;

View file

@ -236,6 +236,28 @@ namespace cache {
}; };
} }
void FileCache::eject(const FilePointer& file) {
file->_cache = nullptr;
const auto& length = file->getLength();
const auto& key = file->getKey();
{
Lock lock(_filesMutex);
if (0 != _files.erase(key)) {
_numTotalFiles -= 1;
_totalFilesSize -= length;
}
}
{
Lock unusedLock(_unusedFilesMutex);
if (0 != _unusedFiles.erase(file)) {
_numUnusedFiles -= 1;
_unusedFilesSize -= length;
}
}
}
void FileCache::clean() { void FileCache::clean() {
size_t overbudgetAmount = getOverbudgetAmount(); size_t overbudgetAmount = getOverbudgetAmount();
@ -250,28 +272,23 @@ void FileCache::clean() {
for (const auto& file : _unusedFiles) { for (const auto& file : _unusedFiles) {
queue.push(file); queue.push(file);
} }
while (!queue.empty() && overbudgetAmount > 0) { while (!queue.empty() && overbudgetAmount > 0) {
auto file = queue.top(); auto file = queue.top();
queue.pop(); queue.pop();
eject(file);
auto length = file->getLength(); auto length = file->getLength();
unusedLock.unlock();
{
file->_cache = nullptr;
Lock lock(_filesMutex);
_files.erase(file->getKey());
}
unusedLock.lock();
_unusedFiles.erase(file);
_numTotalFiles -= 1;
_numUnusedFiles -= 1;
_totalFilesSize -= length;
_unusedFilesSize -= length;
overbudgetAmount -= std::min(length, overbudgetAmount); overbudgetAmount -= std::min(length, overbudgetAmount);
} }
} }
void FileCache::wipe() {
Lock unusedFilesLock(_unusedFilesMutex);
while (!_unusedFiles.empty()) {
eject(*_unusedFiles.begin());
}
}
void FileCache::clear() { void FileCache::clear() {
// Eliminate any overbudget files // Eliminate any overbudget files
clean(); clean();

View file

@ -46,6 +46,9 @@ public:
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr); FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
virtual ~FileCache(); virtual ~FileCache();
// Remove all unlocked items from the cache
void wipe();
size_t getNumTotalFiles() const { return _numTotalFiles; } size_t getNumTotalFiles() const { return _numTotalFiles; }
size_t getNumCachedFiles() const { return _numUnusedFiles; } size_t getNumCachedFiles() const { return _numUnusedFiles; }
size_t getSizeTotalFiles() const { return _totalFilesSize; } size_t getSizeTotalFiles() const { return _totalFilesSize; }
@ -95,6 +98,9 @@ public:
private: private:
using Mutex = std::recursive_mutex; using Mutex = std::recursive_mutex;
using Lock = std::unique_lock<Mutex>; using Lock = std::unique_lock<Mutex>;
using Map = std::unordered_map<Key, std::weak_ptr<File>>;
using Set = std::unordered_set<FilePointer>;
using KeySet = std::unordered_set<Key>;
friend class File; friend class File;
@ -105,6 +111,8 @@ private:
void removeUnusedFile(const FilePointer& file); void removeUnusedFile(const FilePointer& file);
void clean(); void clean();
void clear(); void clear();
// Remove a file from the cache
void eject(const FilePointer& file);
size_t getOverbudgetAmount() const; size_t getOverbudgetAmount() const;
@ -122,10 +130,10 @@ private:
std::string _dirpath; std::string _dirpath;
bool _initialized { false }; bool _initialized { false };
std::unordered_map<Key, std::weak_ptr<File>> _files; Map _files;
Mutex _filesMutex; Mutex _filesMutex;
std::unordered_set<FilePointer> _unusedFiles; Set _unusedFiles;
Mutex _unusedFilesMutex; Mutex _unusedFilesMutex;
}; };
@ -136,8 +144,8 @@ public:
using Key = FileCache::Key; using Key = FileCache::Key;
using Metadata = FileCache::Metadata; using Metadata = FileCache::Metadata;
Key getKey() const { return _key; } const Key& getKey() const { return _key; }
size_t getLength() const { return _length; } const size_t& getLength() const { return _length; }
std::string getFilepath() const { return _filepath; } std::string getFilepath() const { return _filepath; }
virtual ~File(); virtual ~File();

View file

@ -52,9 +52,8 @@ bool readStatus(QByteArray statusData) {
return false; return false;
} }
void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) { void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) {
QString serverPath = "./server-console/server-console.exe"; QString serverPath = "./server-console/server-console.exe";
qCDebug(networking) << "Running marker path is: " << runningMarkerName;
qCDebug(networking) << "Server path is: " << serverPath; qCDebug(networking) << "Server path is: " << serverPath;
qCDebug(networking) << "autoShutdown: " << autoShutdown; qCDebug(networking) << "autoShutdown: " << autoShutdown;
qCDebug(networking) << "noUpdater: " << noUpdater; qCDebug(networking) << "noUpdater: " << noUpdater;
@ -74,8 +73,8 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMark
} }
if (autoShutdown) { if (autoShutdown) {
QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName); auto pid = QCoreApplication::applicationPid();
args << "--shutdownWatcher" << interfaceRunningStateFile; args << "--shutdownWith" << QString::number(pid);
} }
if (noUpdater) { if (noUpdater) {

View file

@ -21,7 +21,7 @@ namespace SandboxUtils {
QNetworkReply* getStatus(); QNetworkReply* getStatus();
bool readStatus(QByteArray statusData); bool readStatus(QByteArray statusData);
void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater); void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater);
}; };
#endif // hifi_SandboxUtils_h #endif // hifi_SandboxUtils_h

View file

@ -20,8 +20,6 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include "AddressManager.h" #include "AddressManager.h"
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
UserActivityLogger& UserActivityLogger::getInstance() { UserActivityLogger& UserActivityLogger::getInstance() {
static UserActivityLogger sharedInstance; static UserActivityLogger sharedInstance;
return sharedInstance; return sharedInstance;

View file

@ -22,6 +22,8 @@
#include <SettingHandle.h> #include <SettingHandle.h>
#include "AddressManager.h" #include "AddressManager.h"
const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
class UserActivityLogger : public QObject { class UserActivityLogger : public QObject {
Q_OBJECT Q_OBJECT

View file

@ -56,7 +56,7 @@ void UserActivityLoggerScriptingInterface::palAction(QString action, QString tar
} }
void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) { void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) {
doLogAction("pal_opened", { doLogAction("pal_opened", {
{ "seconds_opened", secondsOpened } { "seconds_opened", secondsOpened }
}); });
} }
@ -71,6 +71,14 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b
doLogAction("makeUserConnection", payload); doLogAction("makeUserConnection", payload);
} }
void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) {
doLogAction(newValue ? "bubbleOn" : "bubbleOff");
}
void UserActivityLoggerScriptingInterface::bubbleActivated() {
doLogAction("bubbleActivated");
}
void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) { void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) {
doLogAction(action, QJsonObject::fromVariantMap(details)); doLogAction(action, QJsonObject::fromVariantMap(details));
} }

View file

@ -30,6 +30,8 @@ public:
Q_INVOKABLE void palAction(QString action, QString target); Q_INVOKABLE void palAction(QString action, QString target);
Q_INVOKABLE void palOpened(float secondsOpen); Q_INVOKABLE void palOpened(float secondsOpen);
Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = ""); Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = "");
Q_INVOKABLE void bubbleToggled(bool newValue);
Q_INVOKABLE void bubbleActivated();
Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{});
private: private:
void doLogAction(QString action, QJsonObject details = {}); void doLogAction(QString action, QJsonObject details = {});

View file

@ -65,8 +65,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_lastStep(0), _lastStep(0),
_loopsWithoutOwner(0), _loopsWithoutOwner(0),
_accelerationNearlyGravityCount(0), _accelerationNearlyGravityCount(0),
_numInactiveUpdates(1), _numInactiveUpdates(1)
_outgoingPriority(0)
{ {
_type = MOTIONSTATE_TYPE_ENTITY; _type = MOTIONSTATE_TYPE_ENTITY;
assert(_entity); assert(_entity);
@ -75,6 +74,8 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
// we need the side-effects of EntityMotionState::setShape() so we call it explicitly here // we need the side-effects of EntityMotionState::setShape() so we call it explicitly here
// rather than pass the legit shape pointer to the ObjectMotionState ctor above. // rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape); setShape(shape);
_outgoingPriority = _entity->getPendingOwnershipPriority();
} }
EntityMotionState::~EntityMotionState() { EntityMotionState::~EntityMotionState() {
@ -84,7 +85,7 @@ EntityMotionState::~EntityMotionState() {
void EntityMotionState::updateServerPhysicsVariables() { void EntityMotionState::updateServerPhysicsVariables() {
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
if (_entity->getSimulatorID() == Physics::getSessionUUID()) { if (isLocallyOwned()) {
// don't slam these values if we are the simulation owner // don't slam these values if we are the simulation owner
return; return;
} }
@ -114,6 +115,7 @@ void EntityMotionState::handleDeactivation() {
// virtual // virtual
void EntityMotionState::handleEasyChanges(uint32_t& flags) { void EntityMotionState::handleEasyChanges(uint32_t& flags) {
assert(_entity);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
updateServerPhysicsVariables(); updateServerPhysicsVariables();
ObjectMotionState::handleEasyChanges(flags); ObjectMotionState::handleEasyChanges(flags);
@ -135,23 +137,23 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
} }
_loopsWithoutOwner = 0; _loopsWithoutOwner = 0;
} else if (_entity->getSimulatorID() == Physics::getSessionUUID()) { _numInactiveUpdates = 0;
} else if (isLocallyOwned()) {
// we just inherited ownership, make sure our desired priority matches what we have // we just inherited ownership, make sure our desired priority matches what we have
upgradeOutgoingPriority(_entity->getSimulationPriority()); upgradeOutgoingPriority(_entity->getSimulationPriority());
} else { } else {
_outgoingPriority = 0; _outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
_numInactiveUpdates = 0;
} }
} }
if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) { if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) {
// The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bits really mean "we should bid for ownership because // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following:
// a local script has been changing physics properties, or we should adjust our own ownership priority". // (1) we own it but may need to change the priority OR...
// The desired priority is determined by which bits were set. // (2) we don't own it but should bid (because a local script has been changing physics properties)
if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB) { uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
_outgoingPriority = SCRIPT_GRAB_SIMULATION_PRIORITY; _outgoingPriority = glm::max(_outgoingPriority, newPriority);
} else {
_outgoingPriority = SCRIPT_POKE_SIMULATION_PRIORITY;
}
// reset bid expiry so that we bid ASAP // reset bid expiry so that we bid ASAP
_nextOwnershipBid = 0; _nextOwnershipBid = 0;
} }
@ -170,6 +172,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// virtual // virtual
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
assert(_entity);
updateServerPhysicsVariables(); updateServerPhysicsVariables();
return ObjectMotionState::handleHardAndEasyChanges(flags, engine); return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
} }
@ -315,7 +318,7 @@ bool EntityMotionState::isCandidateForOwnership() const {
assert(_entity); assert(_entity);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
return _outgoingPriority != 0 return _outgoingPriority != 0
|| Physics::getSessionUUID() == _entity->getSimulatorID() || isLocallyOwned()
|| _entity->dynamicDataNeedsTransmit(); || _entity->dynamicDataNeedsTransmit();
} }
@ -489,7 +492,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
return true; return true;
} }
if (_entity->getSimulatorID() != Physics::getSessionUUID()) { if (!isLocallyOwned()) {
// we don't own the simulation // we don't own the simulation
// NOTE: we do not volunteer to own kinematic or static objects // NOTE: we do not volunteer to own kinematic or static objects
@ -597,7 +600,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.clearSimulationOwner(); properties.clearSimulationOwner();
_outgoingPriority = 0; _outgoingPriority = 0;
_entity->setPendingOwnershipPriority(_outgoingPriority, now); _entity->setPendingOwnershipPriority(_outgoingPriority, now);
} else if (Physics::getSessionUUID() != _entity->getSimulatorID()) { } else if (!isLocallyOwned()) {
// we don't own the simulation for this entity yet, but we're sending a bid for it // we don't own the simulation for this entity yet, but we're sending a bid for it
quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY);
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
@ -786,6 +789,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
_entity->computeCollisionGroupAndFinalMask(group, mask); _entity->computeCollisionGroupAndFinalMask(group, mask);
} }
bool EntityMotionState::isLocallyOwned() const {
return _entity->getSimulatorID() == Physics::getSessionUUID();
}
bool EntityMotionState::shouldBeLocallyOwned() const { bool EntityMotionState::shouldBeLocallyOwned() const {
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
_entity->getSimulatorID() == Physics::getSessionUUID(); _entity->getSimulatorID() == Physics::getSessionUUID();

View file

@ -79,6 +79,7 @@ public:
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
bool isLocallyOwned() const override;
bool shouldBeLocallyOwned() const override; bool shouldBeLocallyOwned() const override;
friend class PhysicalEntitySimulation; friend class PhysicalEntitySimulation;

View file

@ -202,6 +202,7 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) {
} }
void ObjectMotionState::handleEasyChanges(uint32_t& flags) { void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
assert(_body && _shape);
if (flags & Simulation::DIRTY_POSITION) { if (flags & Simulation::DIRTY_POSITION) {
btTransform worldTrans = _body->getWorldTransform(); btTransform worldTrans = _body->getWorldTransform();
btVector3 newPosition = glmToBullet(getObjectPosition()); btVector3 newPosition = glmToBullet(getObjectPosition());
@ -282,6 +283,7 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
} }
bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
assert(_body && _shape);
if (flags & Simulation::DIRTY_SHAPE) { if (flags & Simulation::DIRTY_SHAPE) {
// make sure the new shape is valid // make sure the new shape is valid
if (!isReadyToComputeShape()) { if (!isReadyToComputeShape()) {

View file

@ -79,7 +79,7 @@ public:
static ShapeManager* getShapeManager(); static ShapeManager* getShapeManager();
ObjectMotionState(const btCollisionShape* shape); ObjectMotionState(const btCollisionShape* shape);
~ObjectMotionState(); virtual ~ObjectMotionState();
virtual void handleEasyChanges(uint32_t& flags); virtual void handleEasyChanges(uint32_t& flags);
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine);
@ -146,6 +146,7 @@ public:
void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; } void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
virtual bool isLocallyOwned() const { return false; }
virtual bool shouldBeLocallyOwned() const { return false; } virtual bool shouldBeLocallyOwned() const { return false; }
friend class PhysicsEngine; friend class PhysicsEngine;

View file

@ -130,7 +130,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
} }
// then remove the objects (aka MotionStates) from physics // then remove the objects (aka MotionStates) from physics
_physicsEngine->removeObjects(_physicalObjects); _physicsEngine->removeSetOfObjects(_physicalObjects);
// delete the MotionStates // delete the MotionStates
// TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete

View file

@ -129,6 +129,9 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
} }
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor(); body->updateInertiaTensor();
if (motionState->isLocallyOwned()) {
_activeStaticBodies.insert(body);
}
break; break;
} }
} }
@ -174,19 +177,9 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
// frame (because the framerate is faster than our physics simulation rate). When this happens we must scan // frame (because the framerate is faster than our physics simulation rate). When this happens we must scan
// _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer. // _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer.
for (auto object : objects) { for (auto object : objects) {
btRigidBody* body = object->getRigidBody(); std::set<btRigidBody*>::iterator itr = _activeStaticBodies.find(object->getRigidBody());
if (itr != _activeStaticBodies.end()) {
std::vector<btRigidBody*>::reverse_iterator itr = _activeStaticBodies.rbegin(); _activeStaticBodies.erase(itr);
while (itr != _activeStaticBodies.rend()) {
if (body == *itr) {
if (*itr != *(_activeStaticBodies.rbegin())) {
// swap with rbegin
*itr = *(_activeStaticBodies.rbegin());
}
_activeStaticBodies.pop_back();
break;
}
++itr;
} }
} }
} }
@ -207,7 +200,7 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
} }
// Same as above, but takes a Set instead of a Vector. Should only be called during teardown. // Same as above, but takes a Set instead of a Vector. Should only be called during teardown.
void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) { void PhysicsEngine::removeSetOfObjects(const SetOfMotionStates& objects) {
_contactMap.clear(); _contactMap.clear();
for (auto object : objects) { for (auto object : objects) {
btRigidBody* body = object->getRigidBody(); btRigidBody* body = object->getRigidBody();
@ -245,14 +238,16 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob
object->clearIncomingDirtyFlags(); object->clearIncomingDirtyFlags();
} }
if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) {
_activeStaticBodies.push_back(object->getRigidBody()); _activeStaticBodies.insert(object->getRigidBody());
} }
} }
// active static bodies have changed (in an Easy way) and need their Aabbs updated // active static bodies have changed (in an Easy way) and need their Aabbs updated
// but we've configured Bullet to NOT update them automatically (for improved performance) // but we've configured Bullet to NOT update them automatically (for improved performance)
// so we must do it ourselves // so we must do it ourselves
for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin();
_dynamicsWorld->updateSingleAabb(_activeStaticBodies[i]); while (itr != _activeStaticBodies.end()) {
_dynamicsWorld->updateSingleAabb(*itr);
++itr;
} }
return stillNeedChange; return stillNeedChange;
} }
@ -496,13 +491,23 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() {
BT_PROFILE("copyOutgoingChanges"); BT_PROFILE("copyOutgoingChanges");
_dynamicsWorld->synchronizeMotionStates();
// Bullet will not deactivate static objects (it doesn't expect them to be active) // Bullet will not deactivate static objects (it doesn't expect them to be active)
// so we must deactivate them ourselves // so we must deactivate them ourselves
for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin();
_activeStaticBodies[i]->forceActivationState(ISLAND_SLEEPING); while (itr != _activeStaticBodies.end()) {
btRigidBody* body = *itr;
body->forceActivationState(ISLAND_SLEEPING);
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(body->getUserPointer());
if (motionState) {
_dynamicsWorld->addChangedMotionState(motionState);
}
++itr;
} }
_activeStaticBodies.clear(); _activeStaticBodies.clear();
_dynamicsWorld->synchronizeMotionStates();
_hasOutgoingChanges = false; _hasOutgoingChanges = false;
return _dynamicsWorld->getChangedMotionStates(); return _dynamicsWorld->getChangedMotionStates();
} }

View file

@ -13,6 +13,7 @@
#define hifi_PhysicsEngine_h #define hifi_PhysicsEngine_h
#include <stdint.h> #include <stdint.h>
#include <set>
#include <vector> #include <vector>
#include <QUuid> #include <QUuid>
@ -53,7 +54,7 @@ public:
uint32_t getNumSubsteps(); uint32_t getNumSubsteps();
void removeObjects(const VectorOfMotionStates& objects); void removeObjects(const VectorOfMotionStates& objects);
void removeObjects(const SetOfMotionStates& objects); // only called during teardown void removeSetOfObjects(const SetOfMotionStates& objects); // only called during teardown
void addObjects(const VectorOfMotionStates& objects); void addObjects(const VectorOfMotionStates& objects);
VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects);
@ -114,7 +115,7 @@ private:
CollisionEvents _collisionEvents; CollisionEvents _collisionEvents;
QHash<QUuid, EntityDynamicPointer> _objectDynamics; QHash<QUuid, EntityDynamicPointer> _objectDynamics;
QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody; QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody;
std::vector<btRigidBody*> _activeStaticBodies; std::set<btRigidBody*> _activeStaticBodies;
glm::vec3 _originOffset; glm::vec3 _originOffset;

View file

@ -51,6 +51,8 @@ public:
const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; } const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; }
const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; } const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; }
void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); }
private: private:
// call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState() // call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState()
void synchronizeMotionState(btRigidBody* body); void synchronizeMotionState(btRigidBody* body);

View file

@ -69,7 +69,7 @@ public:
glm::vec3 size() const { return maximum - minimum; } glm::vec3 size() const { return maximum - minimum; }
float largestDimension() const { return glm::compMax(size()); } float largestDimension() const { return glm::compMax(size()); }
/// \return new Extents which is original rotated around orign by rotation /// \return new Extents which is original rotated around origin by rotation
Extents getRotated(const glm::quat& rotation) const { Extents getRotated(const glm::quat& rotation) const {
Extents temp(minimum, maximum); Extents temp(minimum, maximum);
temp.rotate(rotation); temp.rotate(rotation);

View file

@ -13,44 +13,16 @@
#include <QFile> #include <QFile>
#include <QStandardPaths> #include <QStandardPaths>
#include <QThread>
#include <QTimer>
#include "NumericalConstants.h"
#include "PathUtils.h" #include "PathUtils.h"
RunningMarker::RunningMarker(QObject* parent, QString name) : RunningMarker::RunningMarker(QString name) :
_parent(parent),
_name(name) _name(name)
{ {
} }
void RunningMarker::startRunningMarker() {
static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND;
// start the nodeThread so its event loop is running
_runningMarkerThread = new QThread(_parent);
_runningMarkerThread->setObjectName("Running Marker Thread");
_runningMarkerThread->start();
writeRunningMarkerFile(); // write the first file, even before timer
_runningMarkerTimer = new QTimer();
QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){
writeRunningMarkerFile();
});
_runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS);
// put the time on the thread
_runningMarkerTimer->moveToThread(_runningMarkerThread);
}
RunningMarker::~RunningMarker() { RunningMarker::~RunningMarker() {
deleteRunningMarkerFile(); deleteRunningMarkerFile();
QMetaObject::invokeMethod(_runningMarkerTimer, "stop", Qt::BlockingQueuedConnection);
_runningMarkerThread->quit();
_runningMarkerTimer->deleteLater();
_runningMarkerThread->deleteLater();
} }
bool RunningMarker::fileExists() const { bool RunningMarker::fileExists() const {
@ -77,8 +49,3 @@ void RunningMarker::deleteRunningMarkerFile() {
QString RunningMarker::getFilePath() const { QString RunningMarker::getFilePath() const {
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name;
} }
QString RunningMarker::getMarkerFilePath(QString name) {
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name;
}

View file

@ -12,21 +12,14 @@
#ifndef hifi_RunningMarker_h #ifndef hifi_RunningMarker_h
#define hifi_RunningMarker_h #define hifi_RunningMarker_h
#include <QObject>
#include <QString> #include <QString>
class QThread;
class QTimer;
class RunningMarker { class RunningMarker {
public: public:
RunningMarker(QObject* parent, QString name); RunningMarker(QString name);
~RunningMarker(); ~RunningMarker();
void startRunningMarker();
QString getFilePath() const; QString getFilePath() const;
static QString getMarkerFilePath(QString name);
bool fileExists() const; bool fileExists() const;
@ -34,10 +27,7 @@ public:
void deleteRunningMarkerFile(); void deleteRunningMarkerFile();
private: private:
QObject* _parent { nullptr };
QString _name; QString _name;
QThread* _runningMarkerThread { nullptr };
QTimer* _runningMarkerTimer { nullptr };
}; };
#endif // hifi_RunningMarker_h #endif // hifi_RunningMarker_h

View file

@ -10,7 +10,7 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ /* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */
(function () { // BEGIN LOCAL_SCOPE (function () { // BEGIN LOCAL_SCOPE
var button; var button;
@ -76,6 +76,7 @@
// Called from the C++ scripting interface to show the bubble overlay // Called from the C++ scripting interface to show the bubble overlay
function enteredIgnoreRadius() { function enteredIgnoreRadius() {
createOverlays(); createOverlays();
UserActivityLogger.bubbleActivated();
} }
// Used to set the state of the bubble HUD button // Used to set the state of the bubble HUD button
@ -139,10 +140,14 @@
} }
// When the space bubble is toggled... // When the space bubble is toggled...
function onBubbleToggled() { // NOTE: the c++ calls this with just the first param -- we added a second
var bubbleActive = Users.getIgnoreRadiusEnabled(); // just for not logging the initial state of the bubble when we startup.
writeButtonProperties(bubbleActive); function onBubbleToggled(enabled, doNotLog) {
if (bubbleActive) { writeButtonProperties(enabled);
if (doNotLog !== true) {
UserActivityLogger.bubbleToggled(enabled);
}
if (enabled) {
createOverlays(); createOverlays();
} else { } else {
hideOverlays(); hideOverlays();
@ -163,7 +168,7 @@
sortOrder: 4 sortOrder: 4
}); });
onBubbleToggled(); onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table
button.clicked.connect(Users.toggleIgnoreRadius); button.clicked.connect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);

View file

@ -217,6 +217,32 @@ function hideMarketplace() {
// } // }
// } // }
function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
// Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
// position in the given direction.
var CORNERS = [
{ x: 0, y: 0, z: 0 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 1, z: 0 },
{ x: 0, y: 1, z: 1 },
{ x: 1, y: 0, z: 0 },
{ x: 1, y: 0, z: 1 },
{ x: 1, y: 1, z: 0 },
{ x: 1, y: 1, z: 1 },
];
// Go through all corners and find least (most negative) distance in front of position.
var distance = 0;
for (var i = 0, length = CORNERS.length; i < length; i++) {
var cornerVector =
Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
var cornerDistance = Vec3.dot(cornerVector, direction);
distance = Math.min(cornerDistance, distance);
}
position = Vec3.sum(Vec3.multiply(distance, direction), position);
return position;
}
var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable";
@ -234,6 +260,32 @@ var toolBar = (function () {
var position = getPositionToCreateEntity(); var position = getPositionToCreateEntity();
var entityID = null; var entityID = null;
if (position !== null && position !== undefined) { if (position !== null && position !== undefined) {
var direction;
if (Camera.mode === "entity" || Camera.mode === "independent") {
direction = Camera.orientation;
} else {
direction = MyAvatar.orientation;
}
direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z);
var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"];
if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
// Adjust position of entity per bounding box prior to creating it.
var registration = properties.registration;
if (registration === undefined) {
var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 };
registration = DEFAULT_REGISTRATION;
}
var orientation = properties.orientation;
if (orientation === undefined) {
var DEFAULT_ORIENTATION = Quat.fromPitchYawRollDegrees(0, 0, 0);
orientation = DEFAULT_ORIENTATION;
}
position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation);
}
position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions);
properties.position = position; properties.position = position;
if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) { if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) {
@ -243,6 +295,32 @@ var toolBar = (function () {
if (properties.type == "ParticleEffect") { if (properties.type == "ParticleEffect") {
selectParticleEntity(entityID); selectParticleEntity(entityID);
} }
var POST_ADJUST_ENTITY_TYPES = ["Model"];
if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
// Adjust position of entity per bounding box after it has been created and auto-resized.
var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions;
var DIMENSIONS_CHECK_INTERVAL = 200;
var MAX_DIMENSIONS_CHECKS = 10;
var dimensionsCheckCount = 0;
var dimensionsCheckFunction = function () {
dimensionsCheckCount++;
var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]);
if (!Vec3.equal(properties.dimensions, initialDimensions)) {
position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint,
properties.dimensions, properties.rotation);
position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions),
properties.dimensions);
Entities.editEntity(entityID, {
position: position
});
selectionManager._update();
} else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) {
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
}
};
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
}
} else { } else {
Window.notifyEditError("Can't create " + properties.type + ": " + Window.notifyEditError("Can't create " + properties.type + ": " +
properties.type + " would be out of bounds."); properties.type + " would be out of bounds.");
@ -1407,40 +1485,26 @@ function handeMenuEvent(menuItem) {
} }
tooltip.show(false); tooltip.show(false);
} }
function getPositionToCreateEntity() {
var HALF_TREE_SCALE = 16384;
var direction = Quat.getForward(MyAvatar.orientation);
var distance = 1;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance));
var HALF_TREE_SCALE = 16384;
function getPositionToCreateEntity(extra) {
var CREATE_DISTANCE = 2;
var position;
var delta = extra !== undefined ? extra : 0;
if (Camera.mode === "entity" || Camera.mode === "independent") { if (Camera.mode === "entity" || Camera.mode === "independent") {
position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), distance)); position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta));
} else {
position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta));
position.y += 0.5;
} }
position.y += 0.5;
if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
return null; return null;
} }
return position; return position;
} }
function getPositionToImportEntity() {
var dimensions = Clipboard.getContentsDimensions();
var HALF_TREE_SCALE = 16384;
var direction = Quat.getForward(MyAvatar.orientation);
var longest = 1;
longest = Math.sqrt(Math.pow(dimensions.x, 2) + Math.pow(dimensions.z, 2));
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, longest));
if (Camera.mode === "entity" || Camera.mode === "independent") {
position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), longest));
}
if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
return null;
}
return position;
}
function importSVO(importURL) { function importSVO(importURL) {
if (!Entities.canRez() && !Entities.canRezTmp()) { if (!Entities.canRez() && !Entities.canRezTmp()) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
@ -1458,22 +1522,73 @@ function importSVO(importURL) {
if (success) { if (success) {
var VERY_LARGE = 10000; var VERY_LARGE = 10000;
var position = { var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
x: 0, var position = Vec3.ZERO;
y: 0, if (!isLargeImport) {
z: 0 position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
};
if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
position = getPositionToImportEntity();
} }
if (position !== null && position !== undefined) { if (position !== null && position !== undefined) {
var pastedEntityIDs = Clipboard.pasteEntities(position); var pastedEntityIDs = Clipboard.pasteEntities(position);
if (!isLargeImport) {
// The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
// entities after they're imported so that they're all the correct distance in front of and with geometric mean
// centered on the avatar/camera direction.
var deltaPosition = Vec3.ZERO;
var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]);
var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
if (NO_ADJUST_ENTITY_TYPES.indexOf(properties.type) === -1) {
var targetDirection;
if (Camera.mode === "entity" || Camera.mode === "independent") {
targetDirection = Camera.orientation;
} else {
targetDirection = MyAvatar.orientation;
}
targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z);
var targetPosition = getPositionToCreateEntity();
var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
var entityPositions = [];
for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
"registrationPoint", "rotation"]);
var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
properties.registrationPoint, properties.dimensions, properties.rotation);
var delta = Vec3.subtract(adjustedPosition, properties.position);
var distance = Vec3.dot(delta, targetDirection);
deltaParallel = Math.min(distance, deltaParallel);
deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
deltaPerpendicular);
entityPositions[i] = properties.position;
}
deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
}
if (grid.getSnapToGrid()) {
var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions",
"registrationPoint"]);
var position = Vec3.sum(deltaPosition, properties.position);
position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions,
properties.registrationPoint), properties.dimensions, properties.registrationPoint);
deltaPosition = Vec3.subtract(position, properties.position);
}
if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
Entities.editEntity(pastedEntityIDs[i], {
position: Vec3.sum(deltaPosition, entityPositions[i])
});
}
}
}
if (isActive) { if (isActive) {
selectionManager.setSelections(pastedEntityIDs); selectionManager.setSelections(pastedEntityIDs);
} }
} else { } else {
Window.notifyEditError("Can't import objects: objects would be out of bounds."); Window.notifyEditError("Can't import entities: entities would be out of bounds.");
} }
} else { } else {
Window.notifyEditError("There was an error importing the entity file."); Window.notifyEditError("There was an error importing the entity file.");

View file

@ -79,7 +79,7 @@ Grid = function(opts) {
} }
} }
that.snapToSurface = function(position, dimensions) { that.snapToSurface = function(position, dimensions, registration) {
if (!snapToGrid) { if (!snapToGrid) {
return position; return position;
} }
@ -88,14 +88,18 @@ Grid = function(opts) {
dimensions = { x: 0, y: 0, z: 0 }; dimensions = { x: 0, y: 0, z: 0 };
} }
if (registration === undefined) {
registration = { x: 0.5, y: 0.5, z: 0.5 };
}
return { return {
x: position.x, x: position.x,
y: origin.y + (dimensions.y / 2), y: origin.y + (registration.y * dimensions.y),
z: position.z z: position.z
}; };
} }
that.snapToGrid = function(position, majorOnly, dimensions) { that.snapToGrid = function(position, majorOnly, dimensions, registration) {
if (!snapToGrid) { if (!snapToGrid) {
return position; return position;
} }
@ -104,6 +108,10 @@ Grid = function(opts) {
dimensions = { x: 0, y: 0, z: 0 }; dimensions = { x: 0, y: 0, z: 0 };
} }
if (registration === undefined) {
registration = { x: 0.5, y: 0.5, z: 0.5 };
}
var spacing = majorOnly ? majorGridEvery : minorGridEvery; var spacing = majorOnly ? majorGridEvery : minorGridEvery;
position = Vec3.subtract(position, origin); position = Vec3.subtract(position, origin);
@ -112,7 +120,7 @@ Grid = function(opts) {
position.y = Math.round(position.y / spacing) * spacing; position.y = Math.round(position.y / spacing) * spacing;
position.z = Math.round(position.z / spacing) * spacing; position.z = Math.round(position.z / spacing) * spacing;
return Vec3.sum(Vec3.sum(position, Vec3.multiply(0.5, dimensions)), origin); return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin);
} }
that.snapToSpacing = function(delta, majorOnly) { that.snapToSpacing = function(delta, majorOnly) {
@ -161,9 +169,9 @@ Grid = function(opts) {
if (data.origin) { if (data.origin) {
var pos = data.origin; var pos = data.origin;
pos.x = pos.x === undefined ? origin.x : pos.x; pos.x = pos.x === undefined ? origin.x : parseFloat(pos.x);
pos.y = pos.y === undefined ? origin.y : pos.y; pos.y = pos.y === undefined ? origin.y : parseFloat(pos.y);
pos.z = pos.z === undefined ? origin.z : pos.z; pos.z = pos.z === undefined ? origin.z : parseFloat(pos.z);
that.setPosition(pos, true); that.setPosition(pos, true);
} }

View file

@ -410,8 +410,15 @@ function takeSnapshot() {
Menu.setIsOptionChecked("Overlays", false); Menu.setIsOptionChecked("Overlays", false);
} }
var snapActivateSound = SoundCache.getSound(Script.resolvePath("../../resources/sounds/snap.wav"));
// take snapshot (with no notification) // take snapshot (with no notification)
Script.setTimeout(function () { Script.setTimeout(function () {
Audio.playSound(snapActivateSound, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 1.0
});
HMD.closeTablet(); HMD.closeTablet();
Script.setTimeout(function () { Script.setTimeout(function () {
Window.takeSnapshot(false, includeAnimated, 1.91); Window.takeSnapshot(false, includeAnimated, 1.91);

View file

@ -821,6 +821,17 @@ for (var key in trayIcons) {
const notificationIcon = path.join(__dirname, '../resources/console-notification.png'); const notificationIcon = path.join(__dirname, '../resources/console-notification.png');
function isProcessRunning(pid) {
try {
// Sending a signal of 0 is effectively a NOOP.
// If sending the signal is successful, kill will return true.
// If the process is not running, an exception will be thrown.
return process.kill(pid, 0);
} catch (e) {
}
return false;
}
function onContentLoaded() { function onContentLoaded() {
// Disable splash window for now. // Disable splash window for now.
// maybeShowSplash(); // maybeShowSplash();
@ -882,31 +893,18 @@ function onContentLoaded() {
startInterface(); startInterface();
} }
// If we were launched with the shutdownWatcher option, then we need to watch for the interface app // If we were launched with the shutdownWith option, then we need to shutdown when that process (pid)
// shutting down. The interface app will regularly update a running state file which we will check. // is no longer running.
// If the file doesn't exist or stops updating for a significant amount of time, we will shut down. if (argv.shutdownWith) {
if (argv.shutdownWatcher) { let pid = argv.shutdownWith;
log.debug("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher); console.log("Shutting down with process: ", pid);
var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates let checkProcessInterval = setInterval(function() {
var firstAttemptToCheck = new Date().getTime(); let isRunning = isProcessRunning(pid);
var shutdownWatchInterval = setInterval(function(){ if (!isRunning) {
var stats = fs.stat(argv.shutdownWatcher, function(err, stats) { log.debug("Watched process is no longer running, shutting down");
if (err) { clearTimeout(checkProcessInterval);
var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck; forcedShutdown();
if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) { }
log.debug("Running state file is missing, assume interface has shutdown... shutting down snadbox.");
forcedShutdown();
clearTimeout(shutdownWatchInterval);
}
} else {
var sinceEdit = new Date().getTime() - stats.mtime.getTime();
if (sinceEdit > MAX_TIME_SINCE_EDIT) {
log.debug("Running state of interface hasn't updated in MAX time... shutting down.");
forcedShutdown();
clearTimeout(shutdownWatchInterval);
}
}
});
}, 1000); }, 1000);
} }
} }

View file

@ -113,18 +113,21 @@ void FileCacheTests::testUnusedFiles() {
QVERIFY(!file.get()); QVERIFY(!file.get());
} }
QThread::msleep(1000);
// Test files 90 to 99 are present // Test files 90 to 99 are present
for (int i = 90; i < 100; ++i) { for (int i = 90; i < 100; ++i) {
std::string key = getFileKey(i); std::string key = getFileKey(i);
auto file = cache->getFile(key); auto file = cache->getFile(key);
QVERIFY(file.get()); QVERIFY(file.get());
inUseFiles.push_back(file); inUseFiles.push_back(file);
// Each access touches the file, so we need to sleep here to ensure that the files are
// spaced out in numeric order, otherwise later tests can't reliably determine the order if (i == 94) {
// for cache ejection // Each access touches the file, so we need to sleep here to ensure that the the last 5 files
QThread::msleep(1000); // have later times for cache ejection priority, otherwise the test runs too fast to reliably
// differentiate
QThread::msleep(1000);
}
} }
QCOMPARE(cache->getNumCachedFiles(), (size_t)0); QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
QCOMPARE(cache->getNumTotalFiles(), (size_t)10); QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
inUseFiles.clear(); inUseFiles.clear();
@ -165,6 +168,20 @@ void FileCacheTests::testFreeSpacePreservation() {
} }
} }
void FileCacheTests::testWipe() {
// Reset the cache
auto cache = makeFileCache(_testDir.path());
QCOMPARE(cache->getNumCachedFiles(), (size_t)5);
QCOMPARE(cache->getNumTotalFiles(), (size_t)5);
cache->wipe();
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
QCOMPARE(cache->getNumTotalFiles(), (size_t)0);
QVERIFY(getCacheDirectorySize() > 0);
forceDeletes();
QCOMPARE(getCacheDirectorySize(), (size_t)0);
}
void FileCacheTests::cleanupTestCase() { void FileCacheTests::cleanupTestCase() {
} }

View file

@ -20,6 +20,7 @@ private slots:
void testUnusedFiles(); void testUnusedFiles();
void testFreeSpacePreservation(); void testFreeSpacePreservation();
void cleanupTestCase(); void cleanupTestCase();
void testWipe();
private: private:
size_t getFreeSpace() const; size_t getFreeSpace() const;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,94 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
with Reserved Font Name < Fira >,
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,94 @@
Copyright (c) 2010, Matt McInerney (matt@pixelspread.com),
Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com),
Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -43,13 +43,47 @@ var minuteHandID = Entities.addEntity({
modelURL: Script.resolvePath("models/Stopwatch-min-hand.fbx"), modelURL: Script.resolvePath("models/Stopwatch-min-hand.fbx"),
}); });
var startStopButtonID = Entities.addEntity({
type: "Model",
name: "stopwatch/startStop",
parentID: stopwatchID,
dimensions: Vec3.multiply(scale, { x: 0.8, y: 0.8, z: 1.0 }),
localPosition: Vec3.multiply(scale, { x: 0, y: -0.1, z: -2.06 }),
modelURL: Script.resolvePath("models/transparent-box.fbx")
});
var resetButtonID = Entities.addEntity({
type: "Model",
name: "stopwatch/startStop",
parentID: stopwatchID,
dimensions: Vec3.multiply(scale, { x: 0.6, y: 0.6, z: 0.8 }),
localPosition: Vec3.multiply(scale, { x: -1.5, y: -0.1, z: -1.2 }),
localRotation: Quat.fromVec3Degrees({ x: 0, y: 36, z: 0 }),
modelURL: Script.resolvePath("models/transparent-box.fbx")
});
Entities.editEntity(stopwatchID, { Entities.editEntity(stopwatchID, {
userData: JSON.stringify({ userData: JSON.stringify({
secondHandID: secondHandID, secondHandID: secondHandID,
minuteHandID: minuteHandID, minuteHandID: minuteHandID
}), }),
script: Script.resolvePath("stopwatchClient.js"),
serverScripts: Script.resolvePath("stopwatchServer.js") serverScripts: Script.resolvePath("stopwatchServer.js")
}); });
Entities.editEntity(startStopButtonID, {
userData: JSON.stringify({
stopwatchID: stopwatchID,
grabbableKey: { wantsTrigger: true }
}),
script: Script.resolvePath("stopwatchStartStop.js")
});
Entities.editEntity(resetButtonID, {
userData: JSON.stringify({
stopwatchID: stopwatchID,
grabbableKey: { wantsTrigger: true }
}),
script: Script.resolvePath("stopwatchReset.js")
});
Script.stop() Script.stop()

View file

@ -0,0 +1,22 @@
//
// stopwatchReset.js
//
// Created by David Rowe on 26 May 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
this.preload = function (entityID) {
var properties = Entities.getEntityProperties(entityID, "userData");
this.messageChannel = "STOPWATCH-" + JSON.parse(properties.userData).stopwatchID;
};
function click() {
Messages.sendMessage(this.messageChannel, "reset");
}
this.startNearTrigger = click;
this.startFarTrigger = click;
this.clickDownOnEntity = click;
});

View file

@ -13,6 +13,7 @@
self.equipped = false; self.equipped = false;
self.isActive = false; self.isActive = false;
self.seconds = 0;
self.secondHandID = null; self.secondHandID = null;
self.minuteHandID = null; self.minuteHandID = null;
@ -46,11 +47,19 @@
}; };
self.messageReceived = function(channel, message, sender) { self.messageReceived = function(channel, message, sender) {
print("Message received", channel, sender, message); print("Message received", channel, sender, message);
if (channel === self.messageChannel && message === 'click') { if (channel === self.messageChannel) {
if (self.isActive) { switch (message) {
self.resetTimer(); case "startStop":
} else { if (self.isActive) {
self.startTimer(); self.stopTimer();
} else {
self.startTimer();
}
break;
case "reset":
self.stopTimer();
self.resetTimer();
break;
} }
} }
}; };
@ -58,14 +67,7 @@
return Entities.getEntityProperties(self.entityID, "position").position; return Entities.getEntityProperties(self.entityID, "position").position;
}; };
self.resetTimer = function() { self.resetTimer = function() {
print("Stopping stopwatch"); print("Resetting stopwatch");
if (self.tickInjector) {
self.tickInjector.stop();
}
if (self.tickIntervalID !== null) {
Script.clearInterval(self.tickIntervalID);
self.tickIntervalID = null;
}
Entities.editEntity(self.secondHandID, { Entities.editEntity(self.secondHandID, {
localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
angularVelocity: { x: 0, y: 0, z: 0 }, angularVelocity: { x: 0, y: 0, z: 0 },
@ -74,7 +76,7 @@
localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
angularVelocity: { x: 0, y: 0, z: 0 }, angularVelocity: { x: 0, y: 0, z: 0 },
}); });
self.isActive = false; self.seconds = 0;
}; };
self.startTimer = function() { self.startTimer = function() {
print("Starting stopwatch"); print("Starting stopwatch");
@ -88,7 +90,6 @@
self.tickInjector.restart(); self.tickInjector.restart();
} }
var seconds = 0;
self.tickIntervalID = Script.setInterval(function() { self.tickIntervalID = Script.setInterval(function() {
if (self.tickInjector) { if (self.tickInjector) {
self.tickInjector.setOptions({ self.tickInjector.setOptions({
@ -97,15 +98,15 @@
loop: true loop: true
}); });
} }
seconds++; self.seconds++;
const degreesPerTick = -360 / 60; const degreesPerTick = -360 / 60;
Entities.editEntity(self.secondHandID, { Entities.editEntity(self.secondHandID, {
localRotation: Quat.fromPitchYawRollDegrees(0, seconds * degreesPerTick, 0), localRotation: Quat.fromPitchYawRollDegrees(0, self.seconds * degreesPerTick, 0),
}); });
if (seconds % 60 == 0) { if (self.seconds % 60 == 0) {
Entities.editEntity(self.minuteHandID, { Entities.editEntity(self.minuteHandID, {
localRotation: Quat.fromPitchYawRollDegrees(0, (seconds / 60) * degreesPerTick, 0), localRotation: Quat.fromPitchYawRollDegrees(0, (self.seconds / 60) * degreesPerTick, 0),
}); });
Audio.playSound(self.chimeSound, { Audio.playSound(self.chimeSound, {
position: self.getStopwatchPosition(), position: self.getStopwatchPosition(),
@ -117,4 +118,15 @@
self.isActive = true; self.isActive = true;
}; };
self.stopTimer = function () {
print("Stopping stopwatch");
if (self.tickInjector) {
self.tickInjector.stop();
}
if (self.tickIntervalID !== null) {
Script.clearInterval(self.tickIntervalID);
self.tickIntervalID = null;
}
self.isActive = false;
};
}); });

View file

@ -1,20 +1,21 @@
// //
// stopwatchServer.js // stopwatchStartStop.js
// //
// Created by Ryan Huffman on 1/20/17. // Created by David Rowe on 26 May 2017.
// Copyright 2017 High Fidelity, Inc. // Copyright 2017 High Fidelity, Inc.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
(function() { (function () {
var messageChannel; var messageChannel;
this.preload = function(entityID) { this.preload = function (entityID) {
this.messageChannel = "STOPWATCH-" + entityID; var properties = Entities.getEntityProperties(entityID, "userData");
this.messageChannel = "STOPWATCH-" + JSON.parse(properties.userData).stopwatchID;
}; };
function click() { function click() {
Messages.sendMessage(this.messageChannel, 'click'); Messages.sendMessage(this.messageChannel, "startStop");
} }
this.startNearTrigger = click; this.startNearTrigger = click;
this.startFarTrigger = click; this.startFarTrigger = click;