mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 20:54:25 +02:00
Merge branch 'master' into 4869-Entity-resize-issue
This commit is contained in:
commit
967c50ab93
71 changed files with 2210 additions and 325 deletions
|
@ -11,6 +11,9 @@
|
|||
|
||||
#include "Application.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <gl/Config.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/component_wise.hpp>
|
||||
|
@ -144,6 +147,7 @@
|
|||
#include "InterfaceLogging.h"
|
||||
#include "LODManager.h"
|
||||
#include "ModelPackager.h"
|
||||
#include "networking/CloseEventSender.h"
|
||||
#include "networking/HFWebEngineProfile.h"
|
||||
#include "networking/HFTabletWebEngineProfile.h"
|
||||
#include "networking/FileTypeProfile.h"
|
||||
|
@ -534,6 +538,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<AvatarBookmarks>();
|
||||
DependencyManager::set<LocationBookmarks>();
|
||||
DependencyManager::set<Snapshot>();
|
||||
DependencyManager::set<CloseEventSender>();
|
||||
|
||||
return previousSessionCrashed;
|
||||
}
|
||||
|
@ -1570,6 +1575,14 @@ void Application::aboutToQuit() {
|
|||
|
||||
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.
|
||||
DependencyManager::get<OffscreenUi>()->hide("RunningScripts");
|
||||
|
||||
|
@ -1683,6 +1696,10 @@ Application::~Application() {
|
|||
|
||||
_physicsEngine->setCharacterController(nullptr);
|
||||
|
||||
// the _shapeManager should have zero references
|
||||
_shapeManager.collectGarbage();
|
||||
assert(_shapeManager.getNumShapes() == 0);
|
||||
|
||||
// shutdown render engine
|
||||
_main3DScene = nullptr;
|
||||
_renderEngine = nullptr;
|
||||
|
@ -1734,6 +1751,15 @@ Application::~Application() {
|
|||
|
||||
_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
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
}
|
||||
|
@ -2384,15 +2410,16 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
|
||||
// Check HMD use (may be technically available without being in use)
|
||||
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> firstRun{ Settings::firstRun, true };
|
||||
|
||||
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 <<
|
||||
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
|
||||
|
||||
|
@ -2406,10 +2433,18 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
|
||||
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 (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||
sentTo = SENT_TO_TUTORIAL;
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
if (firstRun.get()) {
|
||||
|
@ -2417,8 +2452,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
}
|
||||
if (addressLookupString.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
sentTo = SENT_TO_PREVIOUS_LOCATION;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -2431,23 +2468,40 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (isUsingHMD) {
|
||||
if (isUsingHMDAndHandControllers) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
sentTo = SENT_TO_SANDBOX;
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
}
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : 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();
|
||||
|
||||
// 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) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
|
||||
} 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);
|
||||
}
|
||||
break;
|
||||
|
@ -4490,12 +4555,13 @@ void Application::update(float deltaTime) {
|
|||
|
||||
getEntities()->getTree()->withWriteLock([&] {
|
||||
PerformanceTimer perfTimer("handleOutgoingChanges");
|
||||
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
|
||||
_entitySimulation->handleDeactivatedMotionStates(deactivations);
|
||||
|
||||
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
|
||||
_entitySimulation->handleChangedMotionStates(outgoingChanges);
|
||||
avatarManager->handleChangedMotionStates(outgoingChanges);
|
||||
|
||||
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
|
||||
_entitySimulation->handleDeactivatedMotionStates(deactivations);
|
||||
});
|
||||
|
||||
if (!_aboutToQuit) {
|
||||
|
@ -6307,21 +6373,6 @@ void Application::loadAddAvatarBookmarkDialog() const {
|
|||
}
|
||||
|
||||
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] {
|
||||
// Get a screenshot and save it
|
||||
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
|
||||
|
|
|
@ -407,6 +407,12 @@ Menu::Menu() {
|
|||
#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
|
||||
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0,
|
||||
|
|
|
@ -145,6 +145,7 @@ namespace MenuOption {
|
|||
const QString Quit = "Quit";
|
||||
const QString ReloadAllScripts = "Reload All Scripts";
|
||||
const QString ReloadContent = "Reload Content (Clears all caches)";
|
||||
const QString RenderClearKtxCache = "Clear KTX Cache (requires restart)";
|
||||
const QString RenderMaxTextureMemory = "Maximum Texture Memory";
|
||||
const QString RenderMaxTextureAutomatic = "Automatic Texture Memory";
|
||||
const QString RenderMaxTexture4MB = "4 MB";
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
#include <SandboxUtils.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
|
||||
#include "AddressManager.h"
|
||||
#include "Application.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
@ -191,7 +190,7 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
int exitCode;
|
||||
{
|
||||
RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME);
|
||||
RunningMarker runningMarker(RUNNING_MARKER_FILENAME);
|
||||
bool runningMarkerExisted = runningMarker.fileExists();
|
||||
runningMarker.writeRunningMarkerFile();
|
||||
|
||||
|
@ -200,14 +199,11 @@ int main(int argc, const char* argv[]) {
|
|||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||
QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||
if (runServer) {
|
||||
SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
|
||||
SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater);
|
||||
}
|
||||
|
||||
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 (override) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
|
90
interface/src/networking/CloseEventSender.cpp
Normal file
90
interface/src/networking/CloseEventSender.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
41
interface/src/networking/CloseEventSender.h
Normal file
41
interface/src/networking/CloseEventSender.h
Normal 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
|
|
@ -19,6 +19,7 @@
|
|||
#include <QStandardPaths>
|
||||
#include <PathUtils.h>
|
||||
#include <QUrl>
|
||||
#include <Gzip.h>
|
||||
|
||||
#include <BuildInfo.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 FILE_PREFIX_NAME = "input-recording-";
|
||||
QString COMPRESS_EXTENSION = "json.gz";
|
||||
QString COMPRESS_EXTENSION = ".json.gz";
|
||||
namespace controller {
|
||||
|
||||
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()) {
|
||||
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);
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
qWarning() << "could not open file: " << fileName;
|
||||
return;
|
||||
}
|
||||
QJsonDocument saveData(object);
|
||||
QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact));
|
||||
saveFile.write(compressedData);
|
||||
QByteArray jsonData = saveData.toJson(QJsonDocument::Indented);
|
||||
QByteArray jsonDataForFile;
|
||||
|
||||
if (!gzip(jsonData, jsonDataForFile, -1)) {
|
||||
qCritical("unable to gzip while saving to json.");
|
||||
return;
|
||||
}
|
||||
|
||||
saveFile.write(jsonDataForFile);
|
||||
saveFile.close();
|
||||
}
|
||||
|
||||
|
@ -121,8 +125,16 @@ namespace controller {
|
|||
status = false;
|
||||
return object;
|
||||
}
|
||||
QByteArray compressedData = qUncompress(openFile.readAll());
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData);
|
||||
QByteArray compressedData = openFile.readAll();
|
||||
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();
|
||||
status = true;
|
||||
openFile.close();
|
||||
|
@ -153,7 +165,7 @@ namespace controller {
|
|||
QJsonObject InputRecorder::recordDataToJson() {
|
||||
QJsonObject data;
|
||||
data["frameCount"] = _framesRecorded;
|
||||
data["version"] = "1.0";
|
||||
data["version"] = "0.0";
|
||||
|
||||
QJsonArray actionArrayList;
|
||||
QJsonArray poseArrayList;
|
||||
|
@ -187,7 +199,10 @@ namespace controller {
|
|||
|
||||
void InputRecorder::saveRecording() {
|
||||
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) {
|
||||
|
@ -202,10 +217,12 @@ namespace controller {
|
|||
QString filePath = urlPath.toLocalFile();
|
||||
QFileInfo info(filePath);
|
||||
QString extension = info.suffix();
|
||||
|
||||
if (extension != "gz") {
|
||||
qWarning() << "can not load file with exentsion of " << extension;
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
QJsonObject data = openFile(filePath, success);
|
||||
auto keyValue = data.find("version");
|
||||
|
@ -233,34 +250,7 @@ namespace controller {
|
|||
_poseStateList.push_back(_currentFramePoses);
|
||||
_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;
|
||||
}
|
||||
|
||||
|
|
|
@ -329,6 +329,16 @@ QString UserInputMapper::getActionName(Action action) const {
|
|||
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 {
|
||||
Locker locker(_lock);
|
||||
QVector<QString> result;
|
||||
|
|
|
@ -80,6 +80,7 @@ namespace controller {
|
|||
|
||||
QVector<Action> getAllActions() const;
|
||||
QString getActionName(Action action) const;
|
||||
QString getStandardPoseName(uint16_t pose);
|
||||
float getActionState(Action action) const { return _actionStates[toInt(action)]; }
|
||||
Pose getPoseState(Action action) const;
|
||||
int findAction(const QString& actionName) const;
|
||||
|
|
|
@ -36,9 +36,6 @@ void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
|
|||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
inputRecorder->setActionState(actionName, _currentPose);
|
||||
if (inputRecorder->isPlayingback()) {
|
||||
_currentPose = inputRecorder->getPoseState(actionName);
|
||||
}
|
||||
|
||||
if (!_currentPose.isValid()) {
|
||||
return;
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
|
||||
#include "../Endpoint.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "../../InputRecorder.h"
|
||||
#include "../../UserInputMapper.h"
|
||||
|
||||
namespace controller {
|
||||
|
||||
class StandardEndpoint : public VirtualEndpoint {
|
||||
|
@ -40,6 +45,12 @@ public:
|
|||
|
||||
virtual Pose pose() override {
|
||||
_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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
// 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)) {
|
||||
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
|
||||
somethingChanged = true;
|
||||
|
@ -1293,27 +1295,15 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c
|
|||
properties._accelerationChanged = true;
|
||||
}
|
||||
|
||||
void EntityItem::pokeSimulationOwnership() {
|
||||
markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE);
|
||||
void EntityItem::flagForOwnershipBid(uint8_t priority) {
|
||||
markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) {
|
||||
// we already own it
|
||||
_simulationOwner.promotePriority(SCRIPT_POKE_SIMULATION_PRIORITY);
|
||||
_simulationOwner.promotePriority(priority);
|
||||
} else {
|
||||
// we don't own it yet
|
||||
_simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_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());
|
||||
_simulationOwner.setPendingPriority(priority, usecTimestampNow());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -321,6 +321,7 @@ public:
|
|||
void updateSimulationOwner(const SimulationOwner& owner);
|
||||
void clearSimulationOwnership();
|
||||
void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp);
|
||||
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
|
||||
void rememberHasSimulationOwnershipBid() const;
|
||||
|
||||
QString getMarketplaceID() const;
|
||||
|
@ -394,8 +395,7 @@ public:
|
|||
|
||||
void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
|
||||
|
||||
void pokeSimulationOwnership();
|
||||
void grabSimulationOwnership();
|
||||
void flagForOwnershipBid(uint8_t priority);
|
||||
void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; }
|
||||
|
||||
QString actionsToDebugString();
|
||||
|
|
|
@ -230,6 +230,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
}
|
||||
|
||||
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());
|
||||
} else {
|
||||
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
||||
|
@ -440,7 +442,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
} else {
|
||||
// we make a bid for simulation ownership
|
||||
properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
|
||||
entity->pokeSimulationOwnership();
|
||||
entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY);
|
||||
entity->rememberHasSimulationOwnershipBid();
|
||||
}
|
||||
}
|
||||
|
@ -1194,7 +1196,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
|||
}
|
||||
action->setIsMine(true);
|
||||
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.
|
||||
});
|
||||
if (success) {
|
||||
|
@ -1210,7 +1212,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
|
|||
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
bool success = entity->updateAction(simulation, actionID, arguments);
|
||||
if (success) {
|
||||
entity->grabSimulationOwnership();
|
||||
entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY);
|
||||
}
|
||||
return success;
|
||||
});
|
||||
|
@ -1224,7 +1226,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
|
|||
success = entity->removeAction(simulation, actionID);
|
||||
if (success) {
|
||||
// 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.
|
||||
});
|
||||
|
|
|
@ -26,12 +26,10 @@ namespace Simulation {
|
|||
const uint32_t DIRTY_MATERIAL = 0x00400;
|
||||
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_SIMULATION_OWNERSHIP_FOR_POKE = 0x2000; // bid for simulation ownership at "poke"
|
||||
const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB = 0x4000; // bid for simulation ownership at "grab"
|
||||
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
|
||||
|
||||
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
|
||||
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
|
||||
|
|
|
@ -26,9 +26,9 @@ const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
|
|||
SimulationOwner::SimulationOwner() :
|
||||
_id(),
|
||||
_expiry(0),
|
||||
_pendingTimestamp(0),
|
||||
_pendingBidTimestamp(0),
|
||||
_priority(0),
|
||||
_pendingPriority(0),
|
||||
_pendingBidPriority(0),
|
||||
_pendingState(PENDING_STATE_NOTHING)
|
||||
{
|
||||
}
|
||||
|
@ -36,9 +36,9 @@ SimulationOwner::SimulationOwner() :
|
|||
SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) :
|
||||
_id(id),
|
||||
_expiry(0),
|
||||
_pendingTimestamp(0),
|
||||
_pendingBidTimestamp(0),
|
||||
_priority(priority),
|
||||
_pendingPriority(0)
|
||||
_pendingBidPriority(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -61,9 +61,9 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) {
|
|||
void SimulationOwner::clear() {
|
||||
_id = QUuid();
|
||||
_expiry = 0;
|
||||
_pendingTimestamp = 0;
|
||||
_pendingBidTimestamp = 0;
|
||||
_priority = 0;
|
||||
_pendingPriority = 0;
|
||||
_pendingBidPriority = 0;
|
||||
_pendingState = PENDING_STATE_NOTHING;
|
||||
}
|
||||
|
||||
|
@ -102,9 +102,9 @@ bool SimulationOwner::set(const SimulationOwner& owner) {
|
|||
}
|
||||
|
||||
void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) {
|
||||
_pendingPriority = priority;
|
||||
_pendingTimestamp = timestamp;
|
||||
_pendingState = (_pendingPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
|
||||
_pendingBidPriority = priority;
|
||||
_pendingBidTimestamp = timestamp;
|
||||
_pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
|
||||
}
|
||||
|
||||
void SimulationOwner::updateExpiry() {
|
||||
|
@ -113,11 +113,11 @@ void SimulationOwner::updateExpiry() {
|
|||
}
|
||||
|
||||
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) {
|
||||
return _pendingPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingTimestamp >= timestamp;
|
||||
return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp;
|
||||
}
|
||||
|
||||
void SimulationOwner::clearCurrentOwner() {
|
||||
|
|
|
@ -66,6 +66,7 @@ public:
|
|||
|
||||
bool hasExpired() const { return usecTimestampNow() > _expiry; }
|
||||
|
||||
uint8_t getPendingPriority() const { return _pendingBidPriority; }
|
||||
bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE
|
||||
bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE
|
||||
void clearCurrentOwner();
|
||||
|
@ -84,9 +85,9 @@ public:
|
|||
private:
|
||||
QUuid _id; // owner
|
||||
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 _pendingPriority; // priority of pendingTake
|
||||
quint8 _pendingBidPriority; // priority at which we'd like to own it
|
||||
quint8 _pendingState; // NOTHING, TAKE, or RELEASE
|
||||
};
|
||||
|
||||
|
|
|
@ -11,14 +11,28 @@
|
|||
|
||||
#include "KTXCache.h"
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include <ktx/KTX.h>
|
||||
|
||||
using File = cache::File;
|
||||
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) :
|
||||
FileCache(dir, ext) {
|
||||
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) {
|
||||
|
|
|
@ -27,6 +27,12 @@ class KTXCache : public cache::FileCache {
|
|||
Q_OBJECT
|
||||
|
||||
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);
|
||||
|
||||
KTXFilePointer writeFile(const char* data, Metadata&& metadata);
|
||||
|
|
|
@ -45,7 +45,6 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
|
|||
Q_DECLARE_METATYPE(JSONCallbackParameters)
|
||||
|
||||
const QString ACCOUNTS_GROUP = "accounts";
|
||||
static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
|
||||
|
||||
JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod,
|
||||
QObject* errorCallbackReceiver, const QString& errorCallbackMethod,
|
||||
|
|
|
@ -52,6 +52,7 @@ namespace AccountManagerAuth {
|
|||
Q_DECLARE_METATYPE(AccountManagerAuth::Type);
|
||||
|
||||
const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
|
||||
const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
|
||||
|
||||
using UserAgentGetter = std::function<QString()>;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
size_t overbudgetAmount = getOverbudgetAmount();
|
||||
|
||||
|
@ -250,28 +272,23 @@ void FileCache::clean() {
|
|||
for (const auto& file : _unusedFiles) {
|
||||
queue.push(file);
|
||||
}
|
||||
|
||||
while (!queue.empty() && overbudgetAmount > 0) {
|
||||
auto file = queue.top();
|
||||
queue.pop();
|
||||
eject(file);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void FileCache::wipe() {
|
||||
Lock unusedFilesLock(_unusedFilesMutex);
|
||||
while (!_unusedFiles.empty()) {
|
||||
eject(*_unusedFiles.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void FileCache::clear() {
|
||||
// Eliminate any overbudget files
|
||||
clean();
|
||||
|
|
|
@ -46,6 +46,9 @@ public:
|
|||
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
|
||||
virtual ~FileCache();
|
||||
|
||||
// Remove all unlocked items from the cache
|
||||
void wipe();
|
||||
|
||||
size_t getNumTotalFiles() const { return _numTotalFiles; }
|
||||
size_t getNumCachedFiles() const { return _numUnusedFiles; }
|
||||
size_t getSizeTotalFiles() const { return _totalFilesSize; }
|
||||
|
@ -95,6 +98,9 @@ public:
|
|||
private:
|
||||
using Mutex = std::recursive_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;
|
||||
|
||||
|
@ -105,6 +111,8 @@ private:
|
|||
void removeUnusedFile(const FilePointer& file);
|
||||
void clean();
|
||||
void clear();
|
||||
// Remove a file from the cache
|
||||
void eject(const FilePointer& file);
|
||||
|
||||
size_t getOverbudgetAmount() const;
|
||||
|
||||
|
@ -122,10 +130,10 @@ private:
|
|||
std::string _dirpath;
|
||||
bool _initialized { false };
|
||||
|
||||
std::unordered_map<Key, std::weak_ptr<File>> _files;
|
||||
Map _files;
|
||||
Mutex _filesMutex;
|
||||
|
||||
std::unordered_set<FilePointer> _unusedFiles;
|
||||
Set _unusedFiles;
|
||||
Mutex _unusedFilesMutex;
|
||||
};
|
||||
|
||||
|
@ -136,8 +144,8 @@ public:
|
|||
using Key = FileCache::Key;
|
||||
using Metadata = FileCache::Metadata;
|
||||
|
||||
Key getKey() const { return _key; }
|
||||
size_t getLength() const { return _length; }
|
||||
const Key& getKey() const { return _key; }
|
||||
const size_t& getLength() const { return _length; }
|
||||
std::string getFilepath() const { return _filepath; }
|
||||
|
||||
virtual ~File();
|
||||
|
|
|
@ -52,9 +52,8 @@ bool readStatus(QByteArray statusData) {
|
|||
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";
|
||||
qCDebug(networking) << "Running marker path is: " << runningMarkerName;
|
||||
qCDebug(networking) << "Server path is: " << serverPath;
|
||||
qCDebug(networking) << "autoShutdown: " << autoShutdown;
|
||||
qCDebug(networking) << "noUpdater: " << noUpdater;
|
||||
|
@ -74,8 +73,8 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMark
|
|||
}
|
||||
|
||||
if (autoShutdown) {
|
||||
QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName);
|
||||
args << "--shutdownWatcher" << interfaceRunningStateFile;
|
||||
auto pid = QCoreApplication::applicationPid();
|
||||
args << "--shutdownWith" << QString::number(pid);
|
||||
}
|
||||
|
||||
if (noUpdater) {
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace SandboxUtils {
|
|||
|
||||
QNetworkReply* getStatus();
|
||||
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
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
#include <DependencyManager.h>
|
||||
#include "AddressManager.h"
|
||||
|
||||
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
|
||||
|
||||
UserActivityLogger& UserActivityLogger::getInstance() {
|
||||
static UserActivityLogger sharedInstance;
|
||||
return sharedInstance;
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include <SettingHandle.h>
|
||||
#include "AddressManager.h"
|
||||
|
||||
const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
|
||||
|
||||
class UserActivityLogger : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ void UserActivityLoggerScriptingInterface::palAction(QString action, QString tar
|
|||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) {
|
||||
doLogAction("pal_opened", {
|
||||
doLogAction("pal_opened", {
|
||||
{ "seconds_opened", secondsOpened }
|
||||
});
|
||||
}
|
||||
|
@ -71,6 +71,14 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b
|
|||
doLogAction("makeUserConnection", payload);
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) {
|
||||
doLogAction(newValue ? "bubbleOn" : "bubbleOff");
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::bubbleActivated() {
|
||||
doLogAction("bubbleActivated");
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) {
|
||||
doLogAction(action, QJsonObject::fromVariantMap(details));
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ public:
|
|||
Q_INVOKABLE void palAction(QString action, QString target);
|
||||
Q_INVOKABLE void palOpened(float secondsOpen);
|
||||
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{});
|
||||
private:
|
||||
void doLogAction(QString action, QJsonObject details = {});
|
||||
|
|
|
@ -65,8 +65,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
|||
_lastStep(0),
|
||||
_loopsWithoutOwner(0),
|
||||
_accelerationNearlyGravityCount(0),
|
||||
_numInactiveUpdates(1),
|
||||
_outgoingPriority(0)
|
||||
_numInactiveUpdates(1)
|
||||
{
|
||||
_type = MOTIONSTATE_TYPE_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
|
||||
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
|
||||
setShape(shape);
|
||||
|
||||
_outgoingPriority = _entity->getPendingOwnershipPriority();
|
||||
}
|
||||
|
||||
EntityMotionState::~EntityMotionState() {
|
||||
|
@ -84,7 +85,7 @@ EntityMotionState::~EntityMotionState() {
|
|||
|
||||
void EntityMotionState::updateServerPhysicsVariables() {
|
||||
assert(entityTreeIsLocked());
|
||||
if (_entity->getSimulatorID() == Physics::getSessionUUID()) {
|
||||
if (isLocallyOwned()) {
|
||||
// don't slam these values if we are the simulation owner
|
||||
return;
|
||||
}
|
||||
|
@ -114,6 +115,7 @@ void EntityMotionState::handleDeactivation() {
|
|||
|
||||
// virtual
|
||||
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
updateServerPhysicsVariables();
|
||||
ObjectMotionState::handleEasyChanges(flags);
|
||||
|
@ -135,23 +137,23 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
}
|
||||
_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
|
||||
upgradeOutgoingPriority(_entity->getSimulationPriority());
|
||||
} else {
|
||||
_outgoingPriority = 0;
|
||||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
_numInactiveUpdates = 0;
|
||||
}
|
||||
}
|
||||
if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) {
|
||||
// The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bits really mean "we should bid for ownership because
|
||||
// a local script has been changing physics properties, or we should adjust our own ownership priority".
|
||||
// The desired priority is determined by which bits were set.
|
||||
if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB) {
|
||||
_outgoingPriority = SCRIPT_GRAB_SIMULATION_PRIORITY;
|
||||
} else {
|
||||
_outgoingPriority = SCRIPT_POKE_SIMULATION_PRIORITY;
|
||||
}
|
||||
// The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following:
|
||||
// (1) we own it but may need to change the priority OR...
|
||||
// (2) we don't own it but should bid (because a local script has been changing physics properties)
|
||||
uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
|
||||
_outgoingPriority = glm::max(_outgoingPriority, newPriority);
|
||||
|
||||
// reset bid expiry so that we bid ASAP
|
||||
_nextOwnershipBid = 0;
|
||||
}
|
||||
|
@ -170,6 +172,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
|
||||
// virtual
|
||||
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
assert(_entity);
|
||||
updateServerPhysicsVariables();
|
||||
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
@ -315,7 +318,7 @@ bool EntityMotionState::isCandidateForOwnership() const {
|
|||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
return _outgoingPriority != 0
|
||||
|| Physics::getSessionUUID() == _entity->getSimulatorID()
|
||||
|| isLocallyOwned()
|
||||
|| _entity->dynamicDataNeedsTransmit();
|
||||
}
|
||||
|
||||
|
@ -489,7 +492,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (_entity->getSimulatorID() != Physics::getSessionUUID()) {
|
||||
if (!isLocallyOwned()) {
|
||||
// we don't own the simulation
|
||||
|
||||
// NOTE: we do not volunteer to own kinematic or static objects
|
||||
|
@ -597,7 +600,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
properties.clearSimulationOwner();
|
||||
_outgoingPriority = 0;
|
||||
_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
|
||||
quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY);
|
||||
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
|
||||
|
@ -786,6 +789,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
|
|||
_entity->computeCollisionGroupAndFinalMask(group, mask);
|
||||
}
|
||||
|
||||
bool EntityMotionState::isLocallyOwned() const {
|
||||
return _entity->getSimulatorID() == Physics::getSessionUUID();
|
||||
}
|
||||
|
||||
bool EntityMotionState::shouldBeLocallyOwned() const {
|
||||
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
|
||||
_entity->getSimulatorID() == Physics::getSessionUUID();
|
||||
|
|
|
@ -79,6 +79,7 @@ public:
|
|||
|
||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||
|
||||
bool isLocallyOwned() const override;
|
||||
bool shouldBeLocallyOwned() const override;
|
||||
|
||||
friend class PhysicalEntitySimulation;
|
||||
|
|
|
@ -202,6 +202,7 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) {
|
|||
}
|
||||
|
||||
void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
assert(_body && _shape);
|
||||
if (flags & Simulation::DIRTY_POSITION) {
|
||||
btTransform worldTrans = _body->getWorldTransform();
|
||||
btVector3 newPosition = glmToBullet(getObjectPosition());
|
||||
|
@ -282,6 +283,7 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
}
|
||||
|
||||
bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
assert(_body && _shape);
|
||||
if (flags & Simulation::DIRTY_SHAPE) {
|
||||
// make sure the new shape is valid
|
||||
if (!isReadyToComputeShape()) {
|
||||
|
|
|
@ -79,7 +79,7 @@ public:
|
|||
static ShapeManager* getShapeManager();
|
||||
|
||||
ObjectMotionState(const btCollisionShape* shape);
|
||||
~ObjectMotionState();
|
||||
virtual ~ObjectMotionState();
|
||||
|
||||
virtual void handleEasyChanges(uint32_t& flags);
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine);
|
||||
|
@ -146,6 +146,7 @@ public:
|
|||
void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
|
||||
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
|
||||
|
||||
virtual bool isLocallyOwned() const { return false; }
|
||||
virtual bool shouldBeLocallyOwned() const { return false; }
|
||||
|
||||
friend class PhysicsEngine;
|
||||
|
|
|
@ -130,7 +130,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
|
|||
}
|
||||
|
||||
// then remove the objects (aka MotionStates) from physics
|
||||
_physicsEngine->removeObjects(_physicalObjects);
|
||||
_physicsEngine->removeSetOfObjects(_physicalObjects);
|
||||
|
||||
// delete the MotionStates
|
||||
// TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete
|
||||
|
|
|
@ -129,6 +129,9 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
|
|||
}
|
||||
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
if (motionState->isLocallyOwned()) {
|
||||
_activeStaticBodies.insert(body);
|
||||
}
|
||||
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
|
||||
// _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer.
|
||||
for (auto object : objects) {
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
|
||||
std::vector<btRigidBody*>::reverse_iterator itr = _activeStaticBodies.rbegin();
|
||||
while (itr != _activeStaticBodies.rend()) {
|
||||
if (body == *itr) {
|
||||
if (*itr != *(_activeStaticBodies.rbegin())) {
|
||||
// swap with rbegin
|
||||
*itr = *(_activeStaticBodies.rbegin());
|
||||
}
|
||||
_activeStaticBodies.pop_back();
|
||||
break;
|
||||
}
|
||||
++itr;
|
||||
std::set<btRigidBody*>::iterator itr = _activeStaticBodies.find(object->getRigidBody());
|
||||
if (itr != _activeStaticBodies.end()) {
|
||||
_activeStaticBodies.erase(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.
|
||||
void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) {
|
||||
void PhysicsEngine::removeSetOfObjects(const SetOfMotionStates& objects) {
|
||||
_contactMap.clear();
|
||||
for (auto object : objects) {
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
|
@ -245,14 +238,16 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob
|
|||
object->clearIncomingDirtyFlags();
|
||||
}
|
||||
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
|
||||
// but we've configured Bullet to NOT update them automatically (for improved performance)
|
||||
// so we must do it ourselves
|
||||
for (size_t i = 0; i < _activeStaticBodies.size(); ++i) {
|
||||
_dynamicsWorld->updateSingleAabb(_activeStaticBodies[i]);
|
||||
std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin();
|
||||
while (itr != _activeStaticBodies.end()) {
|
||||
_dynamicsWorld->updateSingleAabb(*itr);
|
||||
++itr;
|
||||
}
|
||||
return stillNeedChange;
|
||||
}
|
||||
|
@ -496,13 +491,23 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
|||
|
||||
const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() {
|
||||
BT_PROFILE("copyOutgoingChanges");
|
||||
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
|
||||
// Bullet will not deactivate static objects (it doesn't expect them to be active)
|
||||
// so we must deactivate them ourselves
|
||||
for (size_t i = 0; i < _activeStaticBodies.size(); ++i) {
|
||||
_activeStaticBodies[i]->forceActivationState(ISLAND_SLEEPING);
|
||||
std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin();
|
||||
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();
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
|
||||
_hasOutgoingChanges = false;
|
||||
return _dynamicsWorld->getChangedMotionStates();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_PhysicsEngine_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <QUuid>
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
uint32_t getNumSubsteps();
|
||||
|
||||
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);
|
||||
VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects);
|
||||
|
@ -114,7 +115,7 @@ private:
|
|||
CollisionEvents _collisionEvents;
|
||||
QHash<QUuid, EntityDynamicPointer> _objectDynamics;
|
||||
QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody;
|
||||
std::vector<btRigidBody*> _activeStaticBodies;
|
||||
std::set<btRigidBody*> _activeStaticBodies;
|
||||
|
||||
glm::vec3 _originOffset;
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; }
|
||||
const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; }
|
||||
|
||||
void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); }
|
||||
|
||||
private:
|
||||
// call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState()
|
||||
void synchronizeMotionState(btRigidBody* body);
|
||||
|
|
|
@ -69,7 +69,7 @@ public:
|
|||
glm::vec3 size() const { return maximum - minimum; }
|
||||
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 temp(minimum, maximum);
|
||||
temp.rotate(rotation);
|
||||
|
|
|
@ -13,44 +13,16 @@
|
|||
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
#include "NumericalConstants.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
RunningMarker::RunningMarker(QObject* parent, QString name) :
|
||||
_parent(parent),
|
||||
RunningMarker::RunningMarker(QString 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() {
|
||||
deleteRunningMarkerFile();
|
||||
QMetaObject::invokeMethod(_runningMarkerTimer, "stop", Qt::BlockingQueuedConnection);
|
||||
_runningMarkerThread->quit();
|
||||
_runningMarkerTimer->deleteLater();
|
||||
_runningMarkerThread->deleteLater();
|
||||
}
|
||||
|
||||
bool RunningMarker::fileExists() const {
|
||||
|
@ -77,8 +49,3 @@ void RunningMarker::deleteRunningMarkerFile() {
|
|||
QString RunningMarker::getFilePath() const {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name;
|
||||
}
|
||||
|
||||
QString RunningMarker::getMarkerFilePath(QString name) {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,21 +12,14 @@
|
|||
#ifndef hifi_RunningMarker_h
|
||||
#define hifi_RunningMarker_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QThread;
|
||||
class QTimer;
|
||||
|
||||
class RunningMarker {
|
||||
public:
|
||||
RunningMarker(QObject* parent, QString name);
|
||||
RunningMarker(QString name);
|
||||
~RunningMarker();
|
||||
|
||||
void startRunningMarker();
|
||||
|
||||
QString getFilePath() const;
|
||||
static QString getMarkerFilePath(QString name);
|
||||
|
||||
bool fileExists() const;
|
||||
|
||||
|
@ -34,10 +27,7 @@ public:
|
|||
void deleteRunningMarkerFile();
|
||||
|
||||
private:
|
||||
QObject* _parent { nullptr };
|
||||
QString _name;
|
||||
QThread* _runningMarkerThread { nullptr };
|
||||
QTimer* _runningMarkerTimer { nullptr };
|
||||
};
|
||||
|
||||
#endif // hifi_RunningMarker_h
|
||||
|
|
Binary file not shown.
|
@ -10,7 +10,7 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// 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
|
||||
var button;
|
||||
|
@ -76,6 +76,7 @@
|
|||
// Called from the C++ scripting interface to show the bubble overlay
|
||||
function enteredIgnoreRadius() {
|
||||
createOverlays();
|
||||
UserActivityLogger.bubbleActivated();
|
||||
}
|
||||
|
||||
// Used to set the state of the bubble HUD button
|
||||
|
@ -139,10 +140,14 @@
|
|||
}
|
||||
|
||||
// When the space bubble is toggled...
|
||||
function onBubbleToggled() {
|
||||
var bubbleActive = Users.getIgnoreRadiusEnabled();
|
||||
writeButtonProperties(bubbleActive);
|
||||
if (bubbleActive) {
|
||||
// NOTE: the c++ calls this with just the first param -- we added a second
|
||||
// just for not logging the initial state of the bubble when we startup.
|
||||
function onBubbleToggled(enabled, doNotLog) {
|
||||
writeButtonProperties(enabled);
|
||||
if (doNotLog !== true) {
|
||||
UserActivityLogger.bubbleToggled(enabled);
|
||||
}
|
||||
if (enabled) {
|
||||
createOverlays();
|
||||
} else {
|
||||
hideOverlays();
|
||||
|
@ -163,7 +168,7 @@
|
|||
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);
|
||||
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
|
||||
|
|
|
@ -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 GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
|
||||
var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable";
|
||||
|
@ -234,6 +260,32 @@ var toolBar = (function () {
|
|||
var position = getPositionToCreateEntity();
|
||||
var entityID = null;
|
||||
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);
|
||||
properties.position = position;
|
||||
if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) {
|
||||
|
@ -243,6 +295,32 @@ var toolBar = (function () {
|
|||
if (properties.type == "ParticleEffect") {
|
||||
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 {
|
||||
Window.notifyEditError("Can't create " + properties.type + ": " +
|
||||
properties.type + " would be out of bounds.");
|
||||
|
@ -1407,40 +1485,26 @@ function handeMenuEvent(menuItem) {
|
|||
}
|
||||
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") {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
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) {
|
||||
if (!Entities.canRez() && !Entities.canRezTmp()) {
|
||||
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
|
||||
|
@ -1458,22 +1522,73 @@ function importSVO(importURL) {
|
|||
|
||||
if (success) {
|
||||
var VERY_LARGE = 10000;
|
||||
var position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
|
||||
position = getPositionToImportEntity();
|
||||
var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
|
||||
var position = Vec3.ZERO;
|
||||
if (!isLargeImport) {
|
||||
position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
|
||||
}
|
||||
if (position !== null && position !== undefined) {
|
||||
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) {
|
||||
selectionManager.setSelections(pastedEntityIDs);
|
||||
}
|
||||
} 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 {
|
||||
Window.notifyEditError("There was an error importing the entity file.");
|
||||
|
|
|
@ -79,7 +79,7 @@ Grid = function(opts) {
|
|||
}
|
||||
}
|
||||
|
||||
that.snapToSurface = function(position, dimensions) {
|
||||
that.snapToSurface = function(position, dimensions, registration) {
|
||||
if (!snapToGrid) {
|
||||
return position;
|
||||
}
|
||||
|
@ -88,14 +88,18 @@ Grid = function(opts) {
|
|||
dimensions = { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
|
||||
if (registration === undefined) {
|
||||
registration = { x: 0.5, y: 0.5, z: 0.5 };
|
||||
}
|
||||
|
||||
return {
|
||||
x: position.x,
|
||||
y: origin.y + (dimensions.y / 2),
|
||||
y: origin.y + (registration.y * dimensions.y),
|
||||
z: position.z
|
||||
};
|
||||
}
|
||||
|
||||
that.snapToGrid = function(position, majorOnly, dimensions) {
|
||||
that.snapToGrid = function(position, majorOnly, dimensions, registration) {
|
||||
if (!snapToGrid) {
|
||||
return position;
|
||||
}
|
||||
|
@ -104,6 +108,10 @@ Grid = function(opts) {
|
|||
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;
|
||||
|
||||
position = Vec3.subtract(position, origin);
|
||||
|
@ -112,7 +120,7 @@ Grid = function(opts) {
|
|||
position.y = Math.round(position.y / 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) {
|
||||
|
@ -161,9 +169,9 @@ Grid = function(opts) {
|
|||
|
||||
if (data.origin) {
|
||||
var pos = data.origin;
|
||||
pos.x = pos.x === undefined ? origin.x : pos.x;
|
||||
pos.y = pos.y === undefined ? origin.y : pos.y;
|
||||
pos.z = pos.z === undefined ? origin.z : pos.z;
|
||||
pos.x = pos.x === undefined ? origin.x : parseFloat(pos.x);
|
||||
pos.y = pos.y === undefined ? origin.y : parseFloat(pos.y);
|
||||
pos.z = pos.z === undefined ? origin.z : parseFloat(pos.z);
|
||||
that.setPosition(pos, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -410,8 +410,15 @@ function takeSnapshot() {
|
|||
Menu.setIsOptionChecked("Overlays", false);
|
||||
}
|
||||
|
||||
var snapActivateSound = SoundCache.getSound(Script.resolvePath("../../resources/sounds/snap.wav"));
|
||||
|
||||
// take snapshot (with no notification)
|
||||
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();
|
||||
Script.setTimeout(function () {
|
||||
Window.takeSnapshot(false, includeAnimated, 1.91);
|
||||
|
|
|
@ -821,6 +821,17 @@ for (var key in trayIcons) {
|
|||
|
||||
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() {
|
||||
// Disable splash window for now.
|
||||
// maybeShowSplash();
|
||||
|
@ -882,31 +893,18 @@ function onContentLoaded() {
|
|||
startInterface();
|
||||
}
|
||||
|
||||
// If we were launched with the shutdownWatcher option, then we need to watch for the interface app
|
||||
// shutting down. The interface app will regularly update a running state file which we will check.
|
||||
// If the file doesn't exist or stops updating for a significant amount of time, we will shut down.
|
||||
if (argv.shutdownWatcher) {
|
||||
log.debug("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher);
|
||||
var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates
|
||||
var firstAttemptToCheck = new Date().getTime();
|
||||
var shutdownWatchInterval = setInterval(function(){
|
||||
var stats = fs.stat(argv.shutdownWatcher, function(err, stats) {
|
||||
if (err) {
|
||||
var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck;
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
// If we were launched with the shutdownWith option, then we need to shutdown when that process (pid)
|
||||
// is no longer running.
|
||||
if (argv.shutdownWith) {
|
||||
let pid = argv.shutdownWith;
|
||||
console.log("Shutting down with process: ", pid);
|
||||
let checkProcessInterval = setInterval(function() {
|
||||
let isRunning = isProcessRunning(pid);
|
||||
if (!isRunning) {
|
||||
log.debug("Watched process is no longer running, shutting down");
|
||||
clearTimeout(checkProcessInterval);
|
||||
forcedShutdown();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,18 +113,21 @@ void FileCacheTests::testUnusedFiles() {
|
|||
QVERIFY(!file.get());
|
||||
}
|
||||
|
||||
QThread::msleep(1000);
|
||||
// Test files 90 to 99 are present
|
||||
for (int i = 90; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(file.get());
|
||||
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
|
||||
// for cache ejection
|
||||
QThread::msleep(1000);
|
||||
|
||||
if (i == 94) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ private slots:
|
|||
void testUnusedFiles();
|
||||
void testFreeSpacePreservation();
|
||||
void cleanupTestCase();
|
||||
void testWipe();
|
||||
|
||||
private:
|
||||
size_t getFreeSpace() const;
|
||||
|
|
1262
unpublishedScripts/marketplace/record/html/css/edit-style.css
Normal file
1262
unpublishedScripts/marketplace/record/html/css/edit-style.css
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -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.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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.
|
BIN
unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf
Normal file
BIN
unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf
Normal file
Binary file not shown.
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
@ -43,13 +43,47 @@ var minuteHandID = Entities.addEntity({
|
|||
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, {
|
||||
userData: JSON.stringify({
|
||||
secondHandID: secondHandID,
|
||||
minuteHandID: minuteHandID,
|
||||
minuteHandID: minuteHandID
|
||||
}),
|
||||
script: Script.resolvePath("stopwatchClient.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()
|
||||
|
|
22
unpublishedScripts/marketplace/stopwatch/stopwatchReset.js
Normal file
22
unpublishedScripts/marketplace/stopwatch/stopwatchReset.js
Normal 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;
|
||||
});
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
self.equipped = false;
|
||||
self.isActive = false;
|
||||
self.seconds = 0;
|
||||
|
||||
self.secondHandID = null;
|
||||
self.minuteHandID = null;
|
||||
|
@ -46,11 +47,19 @@
|
|||
};
|
||||
self.messageReceived = function(channel, message, sender) {
|
||||
print("Message received", channel, sender, message);
|
||||
if (channel === self.messageChannel && message === 'click') {
|
||||
if (self.isActive) {
|
||||
self.resetTimer();
|
||||
} else {
|
||||
self.startTimer();
|
||||
if (channel === self.messageChannel) {
|
||||
switch (message) {
|
||||
case "startStop":
|
||||
if (self.isActive) {
|
||||
self.stopTimer();
|
||||
} else {
|
||||
self.startTimer();
|
||||
}
|
||||
break;
|
||||
case "reset":
|
||||
self.stopTimer();
|
||||
self.resetTimer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -58,14 +67,7 @@
|
|||
return Entities.getEntityProperties(self.entityID, "position").position;
|
||||
};
|
||||
self.resetTimer = function() {
|
||||
print("Stopping stopwatch");
|
||||
if (self.tickInjector) {
|
||||
self.tickInjector.stop();
|
||||
}
|
||||
if (self.tickIntervalID !== null) {
|
||||
Script.clearInterval(self.tickIntervalID);
|
||||
self.tickIntervalID = null;
|
||||
}
|
||||
print("Resetting stopwatch");
|
||||
Entities.editEntity(self.secondHandID, {
|
||||
localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
|
||||
angularVelocity: { x: 0, y: 0, z: 0 },
|
||||
|
@ -74,7 +76,7 @@
|
|||
localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
|
||||
angularVelocity: { x: 0, y: 0, z: 0 },
|
||||
});
|
||||
self.isActive = false;
|
||||
self.seconds = 0;
|
||||
};
|
||||
self.startTimer = function() {
|
||||
print("Starting stopwatch");
|
||||
|
@ -88,7 +90,6 @@
|
|||
self.tickInjector.restart();
|
||||
}
|
||||
|
||||
var seconds = 0;
|
||||
self.tickIntervalID = Script.setInterval(function() {
|
||||
if (self.tickInjector) {
|
||||
self.tickInjector.setOptions({
|
||||
|
@ -97,15 +98,15 @@
|
|||
loop: true
|
||||
});
|
||||
}
|
||||
seconds++;
|
||||
self.seconds++;
|
||||
const degreesPerTick = -360 / 60;
|
||||
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, {
|
||||
localRotation: Quat.fromPitchYawRollDegrees(0, (seconds / 60) * degreesPerTick, 0),
|
||||
localRotation: Quat.fromPitchYawRollDegrees(0, (self.seconds / 60) * degreesPerTick, 0),
|
||||
});
|
||||
Audio.playSound(self.chimeSound, {
|
||||
position: self.getStopwatchPosition(),
|
||||
|
@ -117,4 +118,15 @@
|
|||
|
||||
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;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
var messageChannel;
|
||||
this.preload = function(entityID) {
|
||||
this.messageChannel = "STOPWATCH-" + entityID;
|
||||
this.preload = function (entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID, "userData");
|
||||
this.messageChannel = "STOPWATCH-" + JSON.parse(properties.userData).stopwatchID;
|
||||
};
|
||||
function click() {
|
||||
Messages.sendMessage(this.messageChannel, 'click');
|
||||
Messages.sendMessage(this.messageChannel, "startStop");
|
||||
}
|
||||
this.startNearTrigger = click;
|
||||
this.startFarTrigger = click;
|
Loading…
Reference in a new issue