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

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

View file

@ -11,6 +11,9 @@
#include "Application.h"
#include <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));

View file

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

View file

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

View file

@ -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>();

View file

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

View file

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

View file

@ -19,6 +19,7 @@
#include <QStandardPaths>
#include <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;
}

View file

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

View file

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

View file

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

View file

@ -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();
}

View file

@ -681,7 +681,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// and pretend that we own it (we assume we'll recover it soon)
// 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());
}
}

View file

@ -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();

View file

@ -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.
});

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

@ -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()>;

View file

@ -236,6 +236,28 @@ namespace cache {
};
}
void FileCache::eject(const FilePointer& file) {
file->_cache = nullptr;
const auto& length = file->getLength();
const auto& key = file->getKey();
{
Lock lock(_filesMutex);
if (0 != _files.erase(key)) {
_numTotalFiles -= 1;
_totalFilesSize -= length;
}
}
{
Lock unusedLock(_unusedFilesMutex);
if (0 != _unusedFiles.erase(file)) {
_numUnusedFiles -= 1;
_unusedFilesSize -= length;
}
}
}
void FileCache::clean() {
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();

View file

@ -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();

View 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) {

View file

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

View file

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

View file

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

View file

@ -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));
}

View file

@ -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 = {});

View file

@ -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();

View file

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

View file

@ -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()) {

View file

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

View file

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

View file

@ -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();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -217,6 +217,32 @@ function hideMarketplace() {
// }
// }
function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
// Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
// position in the given direction.
var CORNERS = [
{ x: 0, y: 0, z: 0 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 1, z: 0 },
{ x: 0, y: 1, z: 1 },
{ x: 1, y: 0, z: 0 },
{ x: 1, y: 0, z: 1 },
{ x: 1, y: 1, z: 0 },
{ x: 1, y: 1, z: 1 },
];
// Go through all corners and find least (most negative) distance in front of position.
var distance = 0;
for (var i = 0, length = CORNERS.length; i < length; i++) {
var cornerVector =
Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
var cornerDistance = Vec3.dot(cornerVector, direction);
distance = Math.min(cornerDistance, distance);
}
position = Vec3.sum(Vec3.multiply(distance, direction), position);
return position;
}
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
var 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.");

View 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);
}

View file

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

View file

@ -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);
}
}

View file

@ -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() {
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -43,13 +43,47 @@ var minuteHandID = Entities.addEntity({
modelURL: Script.resolvePath("models/Stopwatch-min-hand.fbx"),
});
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()

View file

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

View file

@ -13,6 +13,7 @@
self.equipped = false;
self.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;
};
});

View file

@ -1,20 +1,21 @@
//
// stopwatchServer.js
// stopwatchStartStop.js
//
// Created by Ryan Huffman on 1/20/17.
// Created by David Rowe on 26 May 2017.
// Copyright 2017 High Fidelity, Inc.
//
// 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;