Merge branch 'master' of github.com:highfidelity/hifi into head-controller

This commit is contained in:
Seth Alves 2017-06-02 13:58:00 -07:00
commit c40a76f320
21 changed files with 377 additions and 58 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");
@ -1738,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);
} }
@ -2388,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;
@ -2410,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()) {
@ -2421,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 {
@ -2435,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.

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"

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

@ -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

@ -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

@ -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
// have later times for cache ejection priority, otherwise the test runs too fast to reliably
// differentiate
QThread::msleep(1000); 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;

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,26 +47,27 @@
}; };
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) {
switch (message) {
case "startStop":
if (self.isActive) { if (self.isActive) {
self.resetTimer(); self.stopTimer();
} else { } else {
self.startTimer(); self.startTimer();
} }
break;
case "reset":
self.stopTimer();
self.resetTimer();
break;
}
} }
}; };
self.getStopwatchPosition = function() { self.getStopwatchPosition = function() {
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;