Merge branch 'master' into Anim_memory_reduction

This commit is contained in:
dooglifeSF 2019-08-27 09:52:16 -07:00
commit c6f72e0e77
58 changed files with 995 additions and 227 deletions

View file

@ -34,7 +34,7 @@ if (WIN32)
list(APPEND CMAKE_PREFIX_PATH "${WINDOW_SDK_PATH}")
# /wd4351 disables warning C4351: new behavior: elements of array will be default initialized
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP${HIFI_MAX_BUILD_CORES} /wd4351")
# /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory.
# Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables
# TODO: Remove when building 64-bit.

View file

@ -58,6 +58,7 @@
#include <shared/FileUtils.h>
#include <shared/QtHelpers.h>
#include <shared/PlatformHelper.h>
#include <shared/GlobalAppProperties.h>
#include <StatTracker.h>
#include <Trace.h>
@ -257,10 +258,6 @@ extern "C" {
}
#endif
#ifdef Q_OS_MAC
#include "MacHelper.h"
#endif
#if defined(Q_OS_ANDROID)
#include <android/log.h>
#include "AndroidHelper.h"
@ -552,13 +549,6 @@ public:
return true;
}
if (message->message == WM_POWERBROADCAST) {
if (message->wParam == PBT_APMRESUMEAUTOMATIC) {
qCInfo(interfaceapp) << "Waking up from sleep or hybernation.";
QMetaObject::invokeMethod(DependencyManager::get<NodeList>().data(), "noteAwakening", Qt::QueuedConnection);
}
}
if (message->message == WM_COPYDATA) {
COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)(message->lParam);
QUrl url = QUrl((const char*)(pcds->lpData));
@ -964,9 +954,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<KeyboardScriptingInterface>();
DependencyManager::set<GrabManager>();
DependencyManager::set<AvatarPackager>();
#ifdef Q_OS_MAC
DependencyManager::set<MacHelper>();
#endif
PlatformHelper::setup();
QObject::connect(PlatformHelper::instance(), &PlatformHelper::systemWillWake, [] {
QMetaObject::invokeMethod(DependencyManager::get<NodeList>().data(), "noteAwakening", Qt::QueuedConnection);
});
QString setBookmarkValue = getCmdOption(argc, constArgv, "--setBookmark");
if (!setBookmarkValue.isEmpty()) {
@ -1172,6 +1165,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId());
deadlockWatchdogThread->start();
// Pause the deadlock watchdog when we sleep, or it might
// trigger a false positive when we wake back up
auto platformHelper = PlatformHelper::instance();
connect(platformHelper, &PlatformHelper::systemWillSleep, [] {
DeadlockWatchdogThread::pause();
});
connect(platformHelper, &PlatformHelper::systemWillWake, [] {
DeadlockWatchdogThread::resume();
});
// Main thread timer to keep the watchdog updated
QTimer* watchdogUpdateTimer = new QTimer(this);
@ -2868,9 +2872,7 @@ Application::~Application() {
_gameWorkload.shutdown();
DependencyManager::destroy<Preferences>();
#ifdef Q_OS_MAC
DependencyManager::destroy<MacHelper>();
#endif
PlatformHelper::shutdown();
_entityClipboard->eraseAllOctreeElements();
_entityClipboard.reset();
@ -3562,6 +3564,9 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance());
// This `module` context property is blank for the QML scripting interface so that we don't get log errors when importing
// certain JS files from both scripts (in the JS context) and QML (in the QML context).
surfaceContext->setContextProperty("module", "");
}
}

View file

@ -1,58 +0,0 @@
//
// MacHelper.h
// interface/src
//
// Created by Howard Stearns
// Copyright 2019 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 "InterfaceLogging.h"
#include "MacHelper.h"
#include <NodeList.h>
#ifdef Q_OS_MAC
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
// The type definitions in these variables come from IOKit, which includes a definition of Duration that conflicts with ours.
// So... we include these definitions here rather than in the .h, as the .h is included in Application.cpp which
// uses Duration.
static io_connect_t root_port;
static IONotificationPortRef notifyPortRef;
static io_object_t notifierObject;
static void* refCon;
static void sleepHandler(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) {
if (messageType == kIOMessageSystemHasPoweredOn) {
qCInfo(interfaceapp) << "Waking up from sleep or hybernation.";
QMetaObject::invokeMethod(DependencyManager::get<NodeList>().data(), "noteAwakening", Qt::QueuedConnection);
}
}
#endif
MacHelper::MacHelper() {
#ifdef Q_OS_MAC
root_port = IORegisterForSystemPower(refCon, &notifyPortRef, sleepHandler, &notifierObject);
if (root_port == 0) {
qCWarning(interfaceapp) << "IORegisterForSystemPower failed";
return;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes);
#endif
}
MacHelper::~MacHelper() {
#ifdef Q_OS_MAC
CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes);
IODeregisterForSystemPower(&notifierObject);
IOServiceClose(root_port);
IONotificationPortDestroy(notifyPortRef);
#endif
}

View file

@ -1,21 +0,0 @@
//
// MacHelper.h
// interface/src
//
// Created by Howard Stearns
// Copyright 2019 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
//
#pragma once
#include "DependencyManager.h"
class MacHelper : public Dependency {
public:
MacHelper();
~MacHelper();
};

View file

@ -101,8 +101,8 @@ void Audio::setMutedDesktop(bool isMuted) {
}
}
});
if (!isMuted && _settingsLoaded) {
// Disable Push-To-Talk if muted is changed to false. Settings also need to be loaded.
if (!isMuted && _settingsLoaded && !_pushingToTalk) {
// If the user is not pushing to talk and muted is changed to false, disable Push-To-Talk. Settings also need to be loaded.
setPTTDesktop(isMuted);
}
if (changed) {
@ -132,8 +132,8 @@ void Audio::setMutedHMD(bool isMuted) {
}
}
});
if (!isMuted && _settingsLoaded) {
// Disable Push-To-Talk if muted is changed to false. Settings also need to be loaded.
if (!isMuted && _settingsLoaded && !_pushingToTalk) {
// If the user is not pushing to talk and muted is changed to false, disable Push-To-Talk. Settings also need to be loaded.
setPTTHMD(isMuted);
}
if (changed) {

View file

@ -225,6 +225,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
QObject::connect(_dockWidget.get(), SIGNAL(onResizeEvent()), this, SLOT(emitMainWindowResizeEvent()));
_dockWidget->setSource(QUrl(sourceUrl));
_dockWidget->setObjectName("DockedWidget");
mainWindow->addDockWidget(dockArea, _dockWidget.get());
} else {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
@ -283,6 +284,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString();
}
object->setObjectName("InteractiveWindow");
object->setProperty(SOURCE_PROPERTY, sourceURL);
});
}

View file

@ -45,6 +45,8 @@ set(src_files
src/CustomUI.m
src/NSTask+NSTaskExecveAdditions.h
src/NSTask+NSTaskExecveAdditions.m
src/HQDefaults.h
src/HQDefaults.m
src/main.mm
nib/Window.xib
nib/SplashScreen.xib
@ -118,6 +120,10 @@ add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/")
add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/data/HQDefaults.plist "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND updater
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/updater" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/")
@ -146,3 +152,17 @@ set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc"
set(CPACK_GENERATOR "DragNDrop")
include(CPack)
include(FindXCTest)
include_directories(${CMAKE_SOURCE_DIR}/src)
xctest_add_bundle(HQLauncherTests HQLauncher
${CMAKE_SOURCE_DIR}/src/HQDefaults.m
${CMAKE_SOURCE_DIR}/tests/HQDefaultsTests.m
${CMAKE_SOURCE_DIR}/tests/Info.plist
)
set_target_properties(HQLauncherTests PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/tests/Info.plist
)

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>name</key>
<string>thunderURL</string>
<key>defaultValue</key>
<string>https://thunder.highfidelity.com</string>
<key>environmentVariable</key>
<string>HIFI_THUNDER_URL</string>
</dict>
</array>
</plist>

View file

@ -81,7 +81,7 @@
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xeX-qc-ccB" customClass="HFButton">
<rect key="frame" x="205" y="111" width="104" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="square" title="LOG IN" bezelStyle="shadowlessSquare" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Ba-S9-5qW">
<buttonCell key="cell" type="square" title="NEXT" bezelStyle="shadowlessSquare" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Ba-S9-5qW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="systemBold" size="18"/>
</buttonCell>

View file

@ -76,7 +76,7 @@
<button focusRingType="exterior" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jKE-fV-Tjv" customClass="HFButton">
<rect key="frame" x="197" y="57" width="110" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="square" title="NEXT" bezelStyle="shadowlessSquare" alignment="center" state="on" borderStyle="border" focusRingType="exterior" imageScaling="proportionallyUpOrDown" inset="2" id="RZM-vz-5eS">
<buttonCell key="cell" type="square" title="LOG IN" bezelStyle="shadowlessSquare" alignment="center" state="on" borderStyle="border" focusRingType="exterior" imageScaling="proportionallyUpOrDown" inset="2" id="RZM-vz-5eS">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="systemBold" size="18"/>
</buttonCell>

View file

@ -0,0 +1,46 @@
//
// Created by Matt Hardcastle <m@hardcastle.com>
// Copyright 2018 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
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/*
* `HQDefaults` loads defaults from the `HQDefaults.plist` and allows
* defaults in that plist to be overwritten using an environment variable.
*
* For example, the following `HQDefaults.plist` will set a default "foo"
* with a value "bar". The "bar" value can be overwritten with the value of
* the FOO environment variables, if the FOO environment variable is set.
*
* <?xml version="1.0" encoding="UTF-8"?>
* <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
* <plist version="1.0">
* <array>
* <dict>
* <key>name</key>
* <string>foo</string>
* <key>defaultValue</key>
* <string>bar</string>
* <key>environmentVariable</key>
* <string>FOO</string>
* </dict>
* </array>
* </plist>
*/
@interface HQDefaults : NSObject
-(NSString *)defaultNamed:(NSString *)name;
+(HQDefaults *)sharedDefaults;
@property (strong, readonly) NSDictionary<NSString *, NSString *> *defaults;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,74 @@
//
// Created by Matt Hardcastle <m@hardcastle.com>
// Copyright 2018 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
//
#import "HQDefaults.h"
@implementation HQDefaults;
// This initializer is for testing purposes. See `init()` for normal use.
-(id)initWithArray:(NSArray *)array environment:(NSDictionary<NSString *, NSString *> *)environment
{
self = [super init];
if (self) {
NSMutableDictionary<NSString *, NSString *> *__defaults = [[NSMutableDictionary alloc] init];
NSString *name, *defaultValue, *environmentVariable;
for (NSDictionary *obj in array) {
NSMutableArray<NSString *> *missingKeys = [[NSMutableArray alloc] init];
if ((name = [obj objectForKey:@"name"]) == nil) {
[missingKeys addObject:@"name"];
}
if ((defaultValue = [obj objectForKey:@"defaultValue"]) == nil) {
[missingKeys addObject:@"defaultValue"];
}
if ([missingKeys count] > 0) {
@throw [NSException exceptionWithName:@"InvalidHQDefaults"
reason:@"A required key is missing"
userInfo:@{@"missingKeys": missingKeys}];
}
environmentVariable = [obj objectForKey:@"environmentVariable"];
if (environmentVariable == nil) {
__defaults[name] = defaultValue;
continue;
}
NSString *value = environment[environmentVariable];
__defaults[name] = value == nil ? defaultValue : value;
}
// Make the dictionary immutable.
_defaults = __defaults;
}
return self;
}
// Initialize an `HQLauncher` object using the bundles "HQDefaults.plist" and the current process's environment.
-(id)init {
NSBundle *bundle = [NSBundle mainBundle];
NSString *defaultsPath = [bundle pathForResource:@"HQDefaults" ofType:@"plist"];
NSArray *array = [NSArray arrayWithContentsOfFile:defaultsPath];
return [self initWithArray:array environment:NSProcessInfo.processInfo.environment];
}
// Retrieve a default.
-(NSString *)defaultNamed:(NSString *)name {
return _defaults[name];
}
// A singleton HQDefaults using the mainBundle's "HQDefaults.plist" and the environment.
+(HQDefaults *)sharedDefaults {
static HQDefaults *defaults = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaults = [[HQDefaults alloc] init];
});
return defaults;
}
@end

View file

@ -1,6 +1,7 @@
#import "LatestBuildRequest.h"
#import "Launcher.h"
#import "Settings.h"
#import "HQDefaults.h"
@implementation LatestBuildRequest
@ -8,7 +9,13 @@
NSString* buildsURL = [[[NSProcessInfo processInfo] environment] objectForKey:@"HQ_LAUNCHER_BUILDS_URL"];
if ([buildsURL length] == 0) {
buildsURL = @"https://thunder.highfidelity.com/builds/api/tags/latest?format=json";
NSString *thunderURL = [[HQDefaults sharedDefaults] defaultNamed:@"thunderURL"];
if (thunderURL == nil) {
@throw [NSException exceptionWithName:@"DefaultMissing"
reason:@"The thunderURL default is missing"
userInfo:nil];
}
buildsURL = [NSString stringWithFormat:@"%@/builds/api/tags/latest?format=json", thunderURL];
}
NSLog(@"Making request for builds to: %@", buildsURL);

View file

@ -9,6 +9,7 @@
#import "Settings.h"
#import "NSTask+NSTaskExecveAdditions.h"
#import "Interface.h"
#import "HQDefaults.h"
@interface Launcher ()
@ -483,6 +484,17 @@ static BOOL const DELETE_ZIP_FILES = TRUE;
[self.window makeKeyAndOrderFront:self];
}
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
// Sanity check the HQDefaults so we fail early if there's an issue.
HQDefaults *defaults = [HQDefaults sharedDefaults];
if ([defaults defaultNamed:@"thunderURL"] == nil) {
@throw [NSException exceptionWithName:@"DefaultsNotConfigured"
reason:@"thunderURL is not configured"
userInfo:nil];
}
}
- (void) setDownloadFilename:(NSString *)aFilename
{
self.filename = aFilename;

View file

@ -0,0 +1,78 @@
//
// Created by Matt Hardcastle <m@hardcastle.com>
// Copyright 2018 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
//
#import <XCTest/XCTest.h>
#import "HQDefaults.h"
// Expose the `initWithArray:environment` initializer for testing.
@interface HQDefaults(HQDefaultsTesting)
-(id)initWithArray:(NSArray *)array environment:(NSDictionary<NSString *, NSString *> *)environment;
@end
@interface HQDefaultsTests : XCTestCase
@end
@implementation HQDefaultsTests
// If there is not environment the default value should be used.
- (void)testNoEnvironmentReturnsDefaultValue {
NSArray *array = @[@{@"name":@"foo",
@"environmentVariable":@"FOO",
@"defaultValue":@"bar"}];
HQDefaults *defaults = [[HQDefaults alloc] initWithArray:array
environment:@{}];
XCTAssertEqual([defaults defaultNamed:@"foo"], @"bar");
}
// The value from the environment should overwrite the default value.
- (void)testEnvironmentOverWritesDefaultValue {
NSArray *array = @[@{@"name":@"foo",
@"environmentVariable":@"FOO",
@"defaultValue":@"bar"}];
NSDictionary *environment = @{@"FOO":@"FOO_VALUE_FROM_ENVIRONMENT"};
HQDefaults *defaults = [[HQDefaults alloc] initWithArray:array environment:environment];
NSString *value = [defaults defaultNamed:@"foo"];
XCTAssertEqual(value, @"FOO_VALUE_FROM_ENVIRONMENT");
}
// An exception should be thrown if a defaults object is missing `name`.
- (void)testMissingNameThrowsException {
NSArray *array = @[@{@"defaultValue":@"bar"}];
XCTAssertThrowsSpecificNamed([[HQDefaults alloc] initWithArray:array environment:@{}],
NSException,
@"InvalidHQDefaults");
}
// An exception should be thrown if a defaults object is missing `defaultValue`.
- (void)testMissingDefaultValueThrowsException {
NSArray *array = @[@{@"name":@"foo" }];
XCTAssertThrowsSpecificNamed([[HQDefaults alloc] initWithArray:array environment:@{}],
NSException,
@"InvalidHQDefaults");
}
// An exception should **NOT** be thrown if a defaults object is missing `environmentVariable`.
- (void)testMissingEnvironmentVariableDoesNotThrowException {
NSArray *array = @[@{@"name":@"foo", @"defaultValue":@"bar"}];
XCTAssertNoThrow([[HQDefaults alloc] initWithArray:array environment:@{}]);
}
// A `nil` should be returned if a default is missing.
- (void)testEmptyDefaultIsNil {
HQDefaults *defaults = [[HQDefaults alloc] initWithArray:@[] environment:@{}];
XCTAssertNil([defaults defaultNamed:@"foo"]);
}
@end

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -581,7 +581,7 @@ void CLauncherDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
int xpan = 0;
if (nIDCtl == IDC_BUTTON_NEXT) {
if (_drawStep == DrawStep::DrawChoose || _drawStep == DrawStep::DrawLoginLogin) {
btnName += _drawStep == DrawStep::DrawLoginLogin ? _T("NEXT") : _T("LOG IN");
btnName += _drawStep == DrawStep::DrawLoginLogin ? _T("LOG IN") : _T("NEXT");
int xpan = -20;
defrect = CRect(rect.left - xpan, rect.top, rect.right + xpan, rect.bottom);
} else if (_drawStep == DrawStep::DrawError) {

View file

@ -1225,8 +1225,8 @@ void LimitedNodeList::connectedForLocalSocketTest() {
auto localHostAddress = localIPTestSocket->localAddress();
if (localHostAddress.protocol() == QAbstractSocket::IPv4Protocol) {
_hasTCPCheckedLocalSocket = true;
setLocalSocket(HifiSockAddr { localHostAddress, _nodeSocket.localPort() });
_hasTCPCheckedLocalSocket = true;
}
localIPTestSocket->deleteLater();
@ -1244,7 +1244,7 @@ void LimitedNodeList::errorTestingLocalSocket() {
setLocalSocket(HifiSockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
}
localIPTestSocket->deleteLater();;
localIPTestSocket->deleteLater();
}
}

View file

@ -140,7 +140,7 @@ void NetworkPeer::activatePublicSocket() {
void NetworkPeer::activateSymmetricSocket() {
if (_activeSocket != &_symmetricSocket) {
qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid);
qCDebug(networking) << "Activating symmetric socket (" << _symmetricSocket << ") for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid);
setActiveSocket(&_symmetricSocket);
}
}

View file

@ -263,7 +263,7 @@ Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr, bool fi
if (filterCreate && _connectionCreationFilterOperator && !_connectionCreationFilterOperator(sockAddr)) {
// the connection creation filter did not allow us to create a new connection
#ifdef UDT_CONNECTION_DEBUG
qCDebug(networking) << "Socket::findOrCreateConnection refusing to create connection for" << sockAddr
qCDebug(networking) << "Socket::findOrCreateConnection refusing to create Connection class for" << sockAddr
<< "due to connection creation filter";
#endif // UDT_CONNECTION_DEBUG
return nullptr;
@ -279,7 +279,7 @@ Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr, bool fi
QObject::connect(connection.get(), &Connection::receiverHandshakeRequestComplete,
this, &Socket::clientHandshakeRequestComplete);
qCDebug(networking) << "Creating new connection to" << sockAddr;
qCDebug(networking) << "Creating new Connection class for" << sockAddr;
it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection)));
}

View file

@ -37,7 +37,11 @@ OctreeEditPacketSender::~OctreeEditPacketSender() {
bool OctreeEditPacketSender::serversExist() const {
auto node = DependencyManager::get<NodeList>()->soloNodeOfType(getMyNodeType());
auto nodeList = DependencyManager::get<NodeList>();
if (!nodeList) {
return false;
}
auto node = nodeList->soloNodeOfType(getMyNodeType());
return node && node->getActiveSocket();
}

View file

@ -129,7 +129,7 @@ CharacterController::CharacterController() {
// ATM CharacterController is a singleton. When we want more we'll have to
// overhaul the applyPairwiseFilter() logic to handle multiple instances.
++_numCharacterControllers;
assert(numCharacterControllers == 1);
assert(_numCharacterControllers == 1);
}
CharacterController::~CharacterController() {
@ -189,7 +189,7 @@ void CharacterController::addToWorld() {
_rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
// enable CCD
_rigidBody->setCcdSweptSphereRadius(_radius);
_rigidBody->setCcdSweptSphereRadius(2.0f * (_radius + _halfHeight));
_rigidBody->setCcdMotionThreshold(_radius);
btCollisionShape* shape = _rigidBody->getCollisionShape();
@ -225,6 +225,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
float deepestDistance = 0.0f;
float strongestImpulse = 0.0f;
_netCollisionImpulse = btVector3(0.0f, 0.0f, 0.0f);
for (int i = 0; i < numManifolds; i++) {
btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i);
if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) {
@ -245,6 +246,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
deepestDistance = distance;
}
float impulse = contact.getAppliedImpulse();
_netCollisionImpulse += impulse * normal;
if (impulse > strongestImpulse) {
strongestImpulse = impulse;
}
@ -554,7 +556,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
if (_rigidBody) {
// update CCD with new _radius
_rigidBody->setCcdSweptSphereRadius(_radius);
_rigidBody->setCcdSweptSphereRadius(2.0f * (_radius + _halfHeight));
_rigidBody->setCcdMotionThreshold(_radius);
}
}
@ -777,14 +779,50 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
velocity += dt * _linearAcceleration;
// Note the differences between these two variables:
// _targetVelocity = ideal final velocity according to input
// velocity = real final velocity after motors are applied to current velocity
// velocity = new final velocity after motors are applied to currentVelocity
bool gettingStuck = !_isStuck && _stuckTransitionCount > 1 && _state == State::Hover;
if (gettingStuck && velocity.length2() > currentVelocity.length2()) {
// we are probably trying to fly fast into a mesh obstacle
// which is causing us to tickle the "stuck" detection code
// so we average our new velocity with currentVeocity to prevent a "safe landing" response
velocity = 0.5f * (velocity + currentVelocity);
// but we want to avoid getting stuck and tunelling through geometry so we perform
// further checks and modify/abandon our velocity calculations
if (_isStuck || _stuckTransitionCount == 0) {
// we are either definitely stuck, or definitely not --> nothing to do
return;
}
const float SAFE_COLLISION_SPEED = glm::abs(STUCK_PENETRATION) * (float)NUM_SUBSTEPS_PER_SECOND;
const float SAFE_COLLISION_SPEED_SQUARED = SAFE_COLLISION_SPEED * SAFE_COLLISION_SPEED;
bool fast = velocity.length2() > SAFE_COLLISION_SPEED_SQUARED;
const float STRONG_IMPACT_IMPULSE_DOT = -1000.0f; // this tuned manually
bool strongImpact = velocity.dot(_netCollisionImpulse) < STRONG_IMPACT_IMPULSE_DOT;
if (fast && strongImpact) {
const float REFLECTION_COEFFICIENT = 1.5f;
if (velocity.dot(currentVelocity) > 0.0f) {
// our new velocity points in the same direction as our currentVelocity
// but strongImpact means new velocity points against netImpulse
if (currentVelocity.dot(_netCollisionImpulse) > 0.0f) {
// currentVelocity points positively with netImpulse
// so we will assume collisions will save us and use it for our new velocity
velocity = currentVelocity;
} else {
// can't trust physical simulation --> reflect velocity against netImpulse
btVector3 impulseDirection = _netCollisionImpulse.normalized();
velocity -= (REFLECTION_COEFFICIENT * velocity.dot(impulseDirection)) * impulseDirection;
// also attenuate the velocity to help slow down the character before its penetration gets worse
const float ATTENUATION_COEFFICIENT = 0.8f;
velocity *= ATTENUATION_COEFFICIENT;
}
} else {
// currentVelocity points against new velocity, which means it is probably better but...
// this doesn't mean it points in a good direction yet, so we must check
if (currentVelocity.dot(_netCollisionImpulse) < 0.0f) {
// currentVelocity points against netImpulse, so we reflect it
btVector3 impulseDirection = _netCollisionImpulse.normalized();
currentVelocity -= (REFLECTION_COEFFICIENT * currentVelocity.dot(impulseDirection)) * impulseDirection;
}
velocity = currentVelocity;
}
}
}

View file

@ -215,6 +215,7 @@ protected:
btVector3 _followLinearDisplacement;
btQuaternion _followAngularDisplacement;
btVector3 _linearAcceleration;
btVector3 _netCollisionImpulse;
State _state;
bool _isPushingUp;

View file

@ -56,7 +56,6 @@ static FilePersistThread* _persistThreadInstance;
QString getLogRollerFilename() {
QString result = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = getGuessedLocalAddress();
QDateTime now = QDateTime::currentDateTime();
QString fileSessionID;

View file

@ -9,9 +9,19 @@
#include "NetworkUtils.h"
#include <QtNetwork/QNetworkInterface>
namespace {
const QString LINK_LOCAL_SUBNET {"169.254."};
// Is address local-subnet valid only (rfc 3927):
bool isLinkLocalAddress(const QHostAddress& ip4Addr) {
return ip4Addr.toString().startsWith(LINK_LOCAL_SUBNET);
}
}
QHostAddress getGuessedLocalAddress() {
QHostAddress localAddress;
QHostAddress linkLocalAddress;
foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
if (networkInterface.flags() & QNetworkInterface::IsUp
@ -20,12 +30,16 @@ QHostAddress getGuessedLocalAddress() {
// we've decided that this is the active NIC
// enumerate it's addresses to grab the IPv4 address
foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
const auto& addressCandidate = entry.ip();
// make sure it's an IPv4 address that isn't the loopback
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) {
// set our localAddress and break out
localAddress = entry.ip();
break;
if (addressCandidate.protocol() == QAbstractSocket::IPv4Protocol && !addressCandidate.isLoopback()) {
if (isLinkLocalAddress(addressCandidate)) {
linkLocalAddress = addressCandidate; // Last resort
} else {
// set our localAddress and break out
localAddress = addressCandidate;
break;
}
}
}
}
@ -36,7 +50,5 @@ QHostAddress getGuessedLocalAddress() {
}
// return the looked up local address
return localAddress;
return localAddress.isNull() ? linkLocalAddress : localAddress;
}

View file

@ -0,0 +1,31 @@
//
// Created by Bradley Austin Davis on 2019/08/22
// Copyright 2013-2019 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 "PlatformHelper.h"
void PlatformHelper::onSleep() {
if (_awake.exchange(false)) {
qInfo() << "Entering sleep or hibernation.";
emit systemWillSleep();
}
}
void PlatformHelper::onWake() {
if (!_awake.exchange(true)) {
qInfo() << "Waking up from sleep or hibernation.";
emit systemWillWake();
}
}
void PlatformHelper::shutdown() {
DependencyManager::destroy<PlatformHelper>();
}
PlatformHelper* PlatformHelper::instance() {
return DependencyManager::get<PlatformHelper>().get();
}

View file

@ -0,0 +1,43 @@
//
// Created by Bradley Austin Davis on 2019/08/22
// Copyright 2013-2019 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
//
#pragma once
#ifndef hifi_shared_PlatformHelper_h
#define hifi_shared_PlatformHelper_h
#include <atomic>
#include <QtCore/QtGlobal>
#include "../DependencyManager.h"
class PlatformHelper : public QObject, public Dependency {
Q_OBJECT
public:
PlatformHelper() {}
virtual ~PlatformHelper() {}
void onSleep();
void onWake();
signals:
void systemWillSleep();
void systemWillWake();
public:
// Run the per-platform code to instantiate a platform-dependent PlatformHelper dependency object
static void setup();
// Run the per-platform code to cleanly shutdown a platform-dependent PlatformHelper dependency object
static void shutdown();
// Fetch the platform specific instance of the helper
static PlatformHelper* instance();
std::atomic<bool> _awake{ true };
};
#endif

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis on 2019/08/22
// Copyright 2013-2019 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 "../PlatformHelper.h"
#if defined(Q_OS_ANDROID)
// FIXME support sleep/wake notifications
class AndroidHelper : public PlatformHelper {
public:
AndroidHelper() {
}
~AndroidHelper() {
}
};
void PlatformHelper::setup() {
DependencyManager::set<PlatformHelper, AndroidHelper>();
}
#endif

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis on 2019/08/22
// Copyright 2013-2019 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 "../PlatformHelper.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
// FIXME support sleep/wake notifications
class LinuxHelper : public PlatformHelper {
public:
LinuxHelper() {
}
~LinuxHelper() {
}
};
void PlatformHelper::setup() {
DependencyManager::set<PlatformHelper, LinuxHelper>();
}
#endif

View file

@ -0,0 +1,70 @@
//
// Created by Bradley Austin Davis on 2019/08/22
// Based on interface/src/MacHelper.cpp, created by Howard Stearns
// Copyright 2013-2019 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 "../PlatformHelper.h"
#if defined(Q_OS_MAC)
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
class MacHelper : public PlatformHelper {
public:
MacHelper() {
_rootPort = IORegisterForSystemPower(this, &_notifyPortRef, serviceInterestCallback, &_notifierObject);
if (_rootPort == 0) {
qWarning() << "IORegisterForSystemPower failed";
return;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(_notifyPortRef), kCFRunLoopCommonModes);
}
~MacHelper() {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(_notifyPortRef), kCFRunLoopCommonModes);
IODeregisterForSystemPower(&_notifierObject);
IOServiceClose(_rootPort);
IONotificationPortDestroy(_notifyPortRef);
}
private:
void onServiceMessage(io_service_t, natural_t messageType, void* messageArgument) {
switch (messageType) {
case kIOMessageSystemHasPoweredOn:
onWake();
break;
case kIOMessageSystemWillSleep:
onSleep();
// explicit fallthrough
// Per the documentation for kIOMessageSystemWillSleep and kIOMessageCanSystemSleep, the receiver MUST respond
// https://developer.apple.com/documentation/iokit/1557114-ioregisterforsystempower?language=objc
case kIOMessageCanSystemSleep:
IOAllowPowerChange(_rootPort, (long)messageArgument);
break;
default:
break;
}
}
static void serviceInterestCallback(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) {
static_cast<MacHelper*>(refCon)->onServiceMessage(service, messageType, messageArgument);
}
io_connect_t _rootPort{ 0 };
IONotificationPortRef _notifyPortRef{};
io_object_t _notifierObject{};
};
void PlatformHelper::setup() {
DependencyManager::set<PlatformHelper, MacHelper>();
}
#endif

View file

@ -0,0 +1,52 @@
//
// Created by Bradley Austin Davis on 2019/08/22
// Copyright 2013-2019 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 "../PlatformHelper.h"
#if !defined(Q_OS_ANDROID) && defined(Q_OS_WIN)
#include <QtCore/QAbstractNativeEventFilter>
#include <QtCore/QAbstractEventDispatcher>
#include <Windows.h>
class WinHelper : public PlatformHelper, public QAbstractNativeEventFilter {
public:
WinHelper() {
QAbstractEventDispatcher::instance()->installNativeEventFilter(this);
}
~WinHelper() {
auto eventDispatcher = QAbstractEventDispatcher::instance();
if (eventDispatcher) {
eventDispatcher->removeNativeEventFilter(this);
}
}
bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override {
MSG* msg = static_cast<MSG*>(message);
if (msg->message == WM_POWERBROADCAST) {
switch (msg->wParam) {
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMESUSPEND:
onWake();
break;
case PBT_APMSUSPEND:
onSleep();
break;
}
}
return false;
}
};
void PlatformHelper::setup() {
DependencyManager::set<PlatformHelper, WinHelper>();
}
#endif

View file

@ -8,10 +8,7 @@ var customEmojiList = [
]
}
]
try {
if (module) {
module.exports = customEmojiList;
}
} catch (e) {
console.log("error exporting:\n", e);
if (module.exports) {
module.exports = customEmojiList;
}

View file

@ -2987,8 +2987,7 @@ var emojiList = [
"shortName": "unamused face",
"keywords": [
"face",
"unamused",
"unhappy"
"unamused"
],
"mainCategory": "Smileys & Emotion",
"subCategory": "face-neutral-skeptical",
@ -38734,10 +38733,7 @@ var emojiList = [
}
}
]
try {
if (module) {
module.exports = emojiList;
}
} catch (e) {
console.log("error exporting:\n", e);
if (module.exports) {
module.exports = emojiList;
}

View file

@ -102,7 +102,6 @@ function startTimeoutDelete() {
// The QML has a property called archEnd on the pie chart that controls how much pie is showing
var COUNT_DOWN_INTERVAL_MS = 100;
function beginCountDownTimer() {
_this._avimojiQMLWindow.sendToQml({
"source": "simplifiedEmoji.js",
@ -128,9 +127,9 @@ function resetEmojis() {
clearCountDownTimerHandler();
if (currentEmoji) {
Entities.deleteEntity(currentEmoji);
currentEmoji = false;
selectedEmojiFilename = null;
}
currentEmoji = false;
selectedEmojiFilename = null;
}
@ -315,7 +314,7 @@ function playPopAnimation() {
} else {
// Start with the pop sound on the out
currentPopScale = finalInPopScale ? finalInPopScale : MAX_POP_SCALE;
playSound(emojiDestroySound);
playSound(emojiDestroySound, DEFAULT_VOLUME, MyAvatar.position, true);
}
}
@ -360,11 +359,11 @@ function playPopAnimation() {
Entities.editEntity(currentEmoji, {"dimensions": dimensions});
}
} else {
// make sure there is a currentEmoji entity before trying to delete
pruneOldAvimojis();
if (currentEmoji) {
Entities.deleteEntity(currentEmoji);
currentEmoji = false;
}
currentEmoji = false;
finalInPopScale = null;
selectedEmojiFilename = null;
clearCountDownTimerHandler();

View file

@ -31,6 +31,9 @@ Rectangle {
// if this is true, then hovering doesn't allow showing other icons
property bool isSelected: false
KeyNavigation.backtab: emojiSearchTextField
KeyNavigation.tab: emojiSearchTextField
// Update the selected emoji image whenever the code property is changed.
onCurrentCodeChanged: {
mainEmojiImage.source = emojiBaseURL + currentCode;
@ -209,6 +212,18 @@ Rectangle {
}
}
}
// If this MouseArea is hit, the user is clicking on an area not handled
// by any other MouseArea
MouseArea {
anchors.fill: parent
onClicked: {
// The grid will forward keypresses to the main Interface window
grid.forceActiveFocus();
// Necessary to get keyboard keyPress/keyRelease forwarding to work. See `Application::hasFocus()`;
Window.setFocus();
}
}
Rectangle {
id: emojiIndicatorContainer
@ -302,6 +317,7 @@ Rectangle {
MouseArea {
hoverEnabled: enabled
anchors.fill: parent
propagateComposedEvents: false
onEntered: {
grid.currentIndex = index
// don't allow a hover image change of the main emoji image
@ -313,6 +329,10 @@ Rectangle {
}
onClicked: {
root.selectEmoji(model.code.utf);
// The grid will forward keypresses to the main Interface window
grid.forceActiveFocus();
// Necessary to get keyboard keyPress/keyRelease forwarding to work. See `Application::hasFocus()`;
Window.setFocus();
}
}
}
@ -330,6 +350,10 @@ Rectangle {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.selectEmoji(grid.model.get(grid.currentIndex).code.utf);
}
root.keyPressEvent(event.key, event.modifiers);
}
Keys.onReleased: {
root.keyReleaseEvent(event.key, event.modifiers);
}
}
@ -429,6 +453,7 @@ Rectangle {
id: attributionMouseArea
hoverEnabled: enabled
anchors.fill: parent
propagateComposedEvents: false
onClicked: {
popupContainer.visible = true;
}
@ -458,8 +483,15 @@ Rectangle {
}
}
signal sendToScript(var message);
signal sendToScript(var message)
signal keyPressEvent(int key, int modifiers)
signal keyReleaseEvent(int key, int modifiers)
Keys.onPressed: {
root.keyPressEvent(event.key, event.modifiers);
}
Keys.onReleased: {
root.keyReleaseEvent(event.key, event.modifiers);
}
function fromScript(message) {
if (message.source !== "simplifiedEmoji.js") {

View file

@ -127,14 +127,51 @@ function getSounds() {
}
// Returns the first valid joint position from the list of supplied test joint positions.
// If none are valid, returns MyAvatar.position.
function getValidJointPosition(jointsToTest) {
var currentJointIndex;
for (var i = 0; i < jointsToTest.length; i++) {
currentJointIndex = MyAvatar.getJointIndex(jointsToTest[i]);
if (currentJointIndex > -1) {
return MyAvatar.getJointPosition(jointsToTest[i]);
}
}
return Vec3.sum(MyAvatar.position, Vec3.multiply(0.25, Quat.getForward(MyAvatar.orientation)));
}
// Returns the world position halfway between the user's hands
var HALF = 0.5;
function getClapPosition() {
var validLeftJoints = ["LeftHandMiddle2", "LeftHand", "LeftArm"];
var leftPosition = getValidJointPosition(validLeftJoints);
var validRightJoints = ["RightHandMiddle2", "RightHand", "RightArm"];
var rightPosition = getValidJointPosition(validRightJoints);
var centerPosition = Vec3.sum(leftPosition, rightPosition);
centerPosition = Vec3.multiply(centerPosition, HALF);
return centerPosition;
}
var clapSoundInterval = false;
var CLAP_SOUND_INTERVAL_MS = 260; // Must match the clap animation interval
var CLAP_SOUND_INTERVAL_MS_FLOOR = 260;
var CLAP_SOUND_INTERVAL_MS_CEIL = 320;
function startClappingSounds() {
maybeClearClapSoundInterval();
// Compute a random clap sound interval to avoid strange echos between many people clapping simultaneously
var clapSoundIntervalMS = Math.floor(randomFloat(CLAP_SOUND_INTERVAL_MS_FLOOR, CLAP_SOUND_INTERVAL_MS_CEIL));
clapSoundInterval = Script.setInterval(function() {
playSound(clapSounds[Math.floor(Math.random() * clapSounds.length)], MyAvatar.position, true);
}, CLAP_SOUND_INTERVAL_MS);
playSound(clapSounds[Math.floor(Math.random() * clapSounds.length)], getClapPosition(), true);
}, clapSoundIntervalMS);
}
@ -151,10 +188,15 @@ function toggleReaction(reaction) {
if (reactionEnding) {
endReactionWrapper(reaction);
updateEmoteIndicatorIcon("images/emote_Icon.svg");
} else {
beginReactionWrapper(reaction);
updateEmoteIndicatorIcon("images/" + reaction + "_Icon.svg");
}
}
function maybeDeleteRemoteIndicatorTimeout() {
if (restoreEmoteIndicatorTimeout) {
Script.clearTimeout(restoreEmoteIndicatorTimeout);
restoreEmoteIndicatorTimeout = null;
}
}
@ -162,6 +204,8 @@ var reactionsBegun = [];
var pointReticle = null;
var mouseMoveEventsConnected = false;
function beginReactionWrapper(reaction) {
maybeDeleteRemoteIndicatorTimeout();
reactionsBegun.forEach(function(react) {
endReactionWrapper(react);
});
@ -169,6 +213,8 @@ function beginReactionWrapper(reaction) {
if (MyAvatar.beginReaction(reaction)) {
reactionsBegun.push(reaction);
}
updateEmoteIndicatorIcon("images/" + reaction + "_Icon.svg");
// Insert reaction-specific logic here:
switch (reaction) {
@ -235,10 +281,10 @@ function mouseMoveEvent(event) {
Entities.editEntity(pointReticle, { position: reticlePosition });
} else if (reticlePosition) {
pointReticle = Entities.addEntity({
type: "Sphere",
type: "Box",
name: "Point Reticle",
position: reticlePosition,
dimensions: { x: 0.1, y: 0.1, z: 0.1 },
dimensions: { x: 0.075, y: 0.075, z: 0.075 },
color: { red: 255, green: 0, blue: 0 },
collisionless: true,
ignorePickIntersection: true,
@ -250,13 +296,22 @@ function mouseMoveEvent(event) {
}
var WAIT_TO_RESTORE_EMOTE_INDICATOR_ICON_MS = 2000;
var restoreEmoteIndicatorTimeout;
function triggerReactionWrapper(reaction) {
maybeDeleteRemoteIndicatorTimeout();
reactionsBegun.forEach(function(react) {
endReactionWrapper(react);
});
MyAvatar.triggerReaction(reaction);
updateEmoteIndicatorIcon("images/" + reaction + "_Icon.svg");
restoreEmoteIndicatorTimeout = Script.setTimeout(function() {
updateEmoteIndicatorIcon("images/emote_Icon.svg");
restoreEmoteIndicatorTimeout = null;
}, WAIT_TO_RESTORE_EMOTE_INDICATOR_ICON_MS);
}
function maybeClearReticleUpdateLimiterTimeout() {
@ -275,6 +330,8 @@ function endReactionWrapper(reaction) {
reactionsBegun.splice(reactionsBegunIndex, 1);
}
}
updateEmoteIndicatorIcon("images/emote_Icon.svg");
// Insert reaction-specific logic here:
switch (reaction) {
@ -287,7 +344,6 @@ function endReactionWrapper(reaction) {
mouseMoveEventsConnected = false;
}
maybeClearReticleUpdateLimiterTimeout();
intersectedEntityOrAvatarID = null;
deleteOldReticles();
break;
}
@ -301,19 +357,35 @@ function onMessageFromEmoteAppBar(message) {
}
switch (message.method) {
case "positive":
if (!message.data.isPressingAndHolding) {
return;
}
triggerReactionWrapper("positive");
updateEmoteIndicatorIcon("images/" + message.method + "_Icon.svg");
break;
case "negative":
if (!message.data.isPressingAndHolding) {
return;
}
triggerReactionWrapper("negative");
updateEmoteIndicatorIcon("images/" + message.method + "_Icon.svg");
break;
case "raiseHand":
case "applaud":
if (message.data.isPressingAndHolding) {
beginReactionWrapper(message.method);
} else {
endReactionWrapper(message.method);
}
break;
case "point":
case "raiseHand":
if (!message.data.isPressingAndHolding) {
return;
}
toggleReaction(message.method);
break;
case "toggleEmojiApp":
if (!message.data.isPressingAndHolding) {
return;
}
toggleEmojiApp();
break;
default:
@ -363,8 +435,8 @@ function onWindowMinimizedChanged(isMinimized) {
// for the tooltips to match the actual keys.
var POSITIVE_KEY = "z";
var NEGATIVE_KEY = "x";
var RAISE_HAND_KEY = "c";
var APPLAUD_KEY = "v";
var APPLAUD_KEY = "c";
var RAISE_HAND_KEY = "v";
var POINT_KEY = "b";
var EMOTE_WINDOW = "f";
function keyPressHandler(event) {
@ -383,7 +455,7 @@ function keyPressHandler(event) {
toggleReaction("applaud");
} else if (event.text === POINT_KEY) {
toggleReaction("point");
} else if (event.text === EMOTE_WINDOW) {
} else if (event.text === EMOTE_WINDOW && !(Settings.getValue("io.highfidelity.isEditing", false))) {
toggleEmojiApp();
}
}
@ -392,18 +464,10 @@ function keyPressHandler(event) {
function keyReleaseHandler(event) {
if (!event.isAutoRepeat) {
if (event.text === RAISE_HAND_KEY) {
if (reactionsBegun.indexOf("raiseHand") > -1) {
toggleReaction("raiseHand");
}
} else if (event.text === APPLAUD_KEY) {
if (event.text === APPLAUD_KEY) {
if (reactionsBegun.indexOf("applaud") > -1) {
toggleReaction("applaud");
}
} else if (event.text === POINT_KEY) {
if (reactionsBegun.indexOf("point") > -1) {
toggleReaction("point");
}
}
}
}
@ -491,6 +555,7 @@ var EmojiAPI = Script.require("./emojiApp/simplifiedEmoji.js");
var emojiAPI = new EmojiAPI();
var keyPressSignalsConnected = false;
var emojiCodeMap;
var customEmojiCodeMap;
function init() {
deleteOldReticles();
@ -552,6 +617,7 @@ function shutdown() {
emojiAPI.unload();
maybeClearClapSoundInterval();
maybeClearReticleUpdateLimiterTimeout();
maybeDeleteRemoteIndicatorTimeout();
Window.minimizedChanged.disconnect(onWindowMinimizedChanged);
Window.geometryChanged.disconnect(onGeometryChanged);
@ -657,7 +723,6 @@ var EMOJI_APP_WINDOW_FLAGS = 0x00000001 | // Qt::Window
0x00008000 | // Qt::WindowMaximizeButtonHint
0x00004000; // Qt::WindowMinimizeButtonHint
var emojiAppWindow = false;
var POPOUT_SAFE_MARGIN_X = 30;
var POPOUT_SAFE_MARGIN_Y = 30;
var emojiAppWindowSignalsConnected = false;
function toggleEmojiApp() {
@ -678,7 +743,7 @@ function toggleEmojiApp() {
y: EMOJI_APP_HEIGHT_PX
},
position: {
x: Math.max(Window.x + POPOUT_SAFE_MARGIN_X, Window.x + Window.innerWidth / 2 - EMOJI_APP_WIDTH_PX / 2),
x: Window.x + EMOTE_APP_BAR_LEFT_MARGIN,
y: Math.max(Window.y + POPOUT_SAFE_MARGIN_Y, Window.y + Window.innerHeight / 2 - EMOJI_APP_HEIGHT_PX / 2)
},
overrideFlags: EMOJI_APP_WINDOW_FLAGS

View file

@ -171,10 +171,10 @@ Rectangle {
id: buttonsModel
ListElement { imageURL: "images/positive_Icon.svg"; hotkey: "Z"; method: "positive" }
ListElement { imageURL: "images/negative_Icon.svg"; hotkey: "X"; method: "negative" }
ListElement { imageURL: "images/raiseHand_Icon.svg"; hotkey: "C"; method: "raiseHand" }
ListElement { imageURL: "images/applaud_Icon.svg"; hotkey: "V"; method: "applaud" }
ListElement { imageURL: "images/applaud_Icon.svg"; hotkey: "C"; method: "applaud" }
ListElement { imageURL: "images/raiseHand_Icon.svg"; hotkey: "V"; method: "raiseHand" }
ListElement { imageURL: "images/point_Icon.svg"; hotkey: "B"; method: "point" }
ListElement { imageURL: "images/emote_Icon.svg"; hotkey: "F"; method: "toggleEmojiApp" }
ListElement { imageURL: "images/emoji_Icon.svg"; hotkey: "F"; method: "toggleEmojiApp" }
}
Rectangle {
@ -211,7 +211,7 @@ Rectangle {
opacity: 0.8
radius: 4
HifiStylesUit.GraphikRegular {
HifiStylesUit.GraphikSemiBold {
id: toolTipText
anchors.left: parent.left
anchors.leftMargin: 2
@ -222,7 +222,7 @@ Rectangle {
verticalAlignment: TextInput.AlignBottom
horizontalAlignment: TextInput.AlignLeft
color: simplifiedUI.colors.text.white
size: 22
size: 20
}
}
@ -230,17 +230,37 @@ Rectangle {
id: emoteTrayMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
sendToScript({
"source": "EmoteAppBar.qml",
"method": model.method
});
}
onEntered: {
Tablet.playSound(TabletEnums.ButtonHover);
}
onPressed: {
Tablet.playSound(TabletEnums.ButtonClick);
sendToScript({
"source": "EmoteAppBar.qml",
"method": model.method,
"data": { "isPressingAndHolding": true }
});
}
onReleased: {
sendToScript({
"source": "EmoteAppBar.qml",
"method": model.method,
"data": { "isPressingAndHolding": false }
});
}
onExited: {
if (pressed) {
sendToScript({
"source": "EmoteAppBar.qml",
"method": model.method,
"data": { "isPressingAndHolding": false }
});
}
}
}
}
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
<path d="M64,0C28.7,0,0,28.7,0,64s28.7,64,64,64s64-28.7,64-64S99.3,0,64,0z M64,124.1C30.8,124.1,3.9,97.2,3.9,64S30.8,3.9,64,3.9
s60.1,26.9,60.1,60.1S97.2,124.1,64,124.1z M88.8,63.5c-3.8,0-7,3.2-7,7c0,9.5-7.6,5.7-17.8,5.7c-10.2,0-17.8,3.8-17.8-5.7
c0-3.8-3.2-7-7-7s-7,3.2-7,7c0,17.8,14,32.4,31.7,32.4s31.7-14.6,31.7-32.4C95.7,66.7,92.6,63.5,88.8,63.5z M40.5,41.9
c5.1,0,9.5-1.9,9.5,3.2c0,1.9,1.9,3.8,3.8,3.8c2.5,0,4.4-1.9,4.4-3.8c0-9.5-7.6-17.1-17.1-17.1S24,35.6,24,45.1
c0,1.9,1.3,3.8,3.8,3.8c1.9,0,3.8-1.9,3.8-3.8C31.6,40,35.4,41.9,40.5,41.9z M86.9,28c-9.5,0-17.1,7.6-17.1,17.1
c0,1.9,1.9,3.8,3.8,3.8s3.8-1.9,3.8-3.8c0-5.1,4.4-3.2,9.5-3.2c5.1,0,9.5-1.9,9.5,3.2c0,1.9,1.9,3.8,3.8,3.8c2.5,0,3.8-1.9,3.8-3.8
C104,35.6,96.4,28,86.9,28z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,29 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 94 128" style="enable-background:new 0 0 94 128;" xml:space="preserve">
<path d="M47,74.3c25.3,0,45.9-16.6,45.9-37.2S72.3,0,47,0S1.1,16.6,1.1,37.2S21.6,74.3,47,74.3z M65.3,24.7c2.7,0,4.9,1.8,4.9,4
s-2.2,4-4.9,4c-2.7,0-4.9-1.8-4.9-4S62.6,24.7,65.3,24.7z M36.4,43.5c2.6,2.6,6.5,4,10.6,4c4.1,0,8-1.5,10.6-4
c0.9-0.9,2.6-1,3.7-0.3c1.1,0.7,1.3,2.1,0.3,3c-3.6,3.5-8.9,5.5-14.6,5.5c-5.7,0-11-2-14.6-5.5c-0.9-0.9-0.8-2.2,0.3-3
C33.8,42.5,35.4,42.6,36.4,43.5z M28.6,24.7c2.7,0,4.9,1.8,4.9,4c0,2.2-2.2,4-4.9,4c-2.7,0-4.9-1.8-4.9-4
C23.7,26.5,25.9,24.7,28.6,24.7z M47,101.1c21.2,0,39.4-10.9,46.8-26.4C83.2,87.1,66.2,95.1,47,95.1S10.7,87.1,0.2,74.7
C7.6,90.2,25.7,101.1,47,101.1z M47,108.6c-19.2,0-36.3-8.1-46.8-20.4c7.4,15.5,25.5,26.4,46.8,26.4s39.4-10.9,46.8-26.4
C83.2,100.5,66.2,108.6,47,108.6z M47,122c-19.2,0-36.3-8.1-46.8-20.4c1.8,3.9,4.4,7.4,7.4,10.6c0,0,0,0,0,0
c0.4,0.4,0.7,0.7,1.1,1.1c0.1,0.1,0.1,0.1,0.2,0.2c1.1,1.1,2.3,2.1,3.5,3.1c0.1,0.1,0.1,0.1,0.2,0.2c0.4,0.3,0.7,0.6,1.1,0.8
c0.2,0.1,0.3,0.2,0.5,0.3c0.4,0.3,0.7,0.5,1.1,0.8c0.1,0.1,0.3,0.2,0.5,0.3c0.7,0.4,1.4,0.9,2,1.3c0.2,0.1,0.4,0.2,0.6,0.4
c0.3,0.2,0.7,0.4,1,0.6c0.3,0.1,0.5,0.3,0.8,0.4c0.3,0.2,0.7,0.3,1,0.5c0.3,0.2,0.6,0.3,1,0.5c0.3,0.2,0.7,0.3,1,0.5
c0.5,0.2,1.1,0.5,1.6,0.7c0.3,0.1,0.6,0.2,0.9,0.3c0.4,0.1,0.8,0.3,1.2,0.4c0.3,0.1,0.6,0.2,0.9,0.3c0.4,0.1,0.9,0.3,1.3,0.4
c0.2,0.1,0.5,0.2,0.7,0.2c0.7,0.2,1.3,0.4,2,0.6c0.2,0.1,0.4,0.1,0.6,0.2c0.5,0.1,1,0.2,1.5,0.4c0.3,0.1,0.5,0.1,0.8,0.2
c0.5,0.1,1,0.2,1.5,0.3c0.2,0,0.5,0.1,0.7,0.1c0.7,0.1,1.5,0.2,2.2,0.3c0.1,0,0.3,0,0.4,0c0.6,0.1,1.3,0.1,1.9,0.2
c0.2,0,0.5,0,0.7,0.1c0.6,0,1.1,0.1,1.7,0.1c0.2,0,0.5,0,0.7,0c0.8,0,1.6,0,2.3,0c0.8,0,1.6,0,2.3,0c0.2,0,0.5,0,0.7,0
c0.6,0,1.1-0.1,1.7-0.1c0.2,0,0.5,0,0.7-0.1c0.6-0.1,1.3-0.1,1.9-0.2c0.1,0,0.3,0,0.4,0c0.7-0.1,1.5-0.2,2.2-0.3
c0.2,0,0.5-0.1,0.7-0.1c0.5-0.1,1-0.2,1.5-0.3c0.3-0.1,0.5-0.1,0.8-0.2c0.5-0.1,1-0.2,1.5-0.4c0.2-0.1,0.4-0.1,0.6-0.2
c0.7-0.2,1.3-0.4,2-0.6c0.2-0.1,0.5-0.2,0.7-0.2c0.4-0.1,0.9-0.3,1.3-0.4c0.3-0.1,0.6-0.2,0.9-0.3c0.4-0.1,0.8-0.3,1.2-0.4
c0.3-0.1,0.6-0.2,0.9-0.3c0.6-0.2,1.1-0.5,1.6-0.7c0.3-0.2,0.7-0.3,1-0.5c0.3-0.2,0.6-0.3,1-0.5c0.3-0.2,0.7-0.3,1-0.5
c0.3-0.1,0.5-0.3,0.8-0.4c0.3-0.2,0.7-0.4,1-0.6c0.2-0.1,0.4-0.2,0.6-0.4c0.7-0.4,1.4-0.8,2.1-1.3c0.1-0.1,0.3-0.2,0.4-0.3
c0.4-0.2,0.7-0.5,1.1-0.8c0.2-0.1,0.3-0.2,0.5-0.3c0.4-0.3,0.7-0.5,1.1-0.8c0.1-0.1,0.1-0.1,0.2-0.2c1.2-1,2.4-2,3.5-3.1
c0.1-0.1,0.1-0.1,0.2-0.2c0.4-0.4,0.7-0.7,1.1-1.1c0,0,0,0,0,0c3.1-3.2,5.6-6.8,7.4-10.6c0,0,0,0,0,0C83.2,113.9,66.2,122,47,122z
M62,80c-0.3,0.1-0.7,0.2-1,0.2c-4.5,1-9.2,1.6-14,1.6c-4.8,0-9.5-0.5-14-1.5c-0.4-0.1-0.8-0.2-1.2-0.3c0,0-0.1,0-0.1,0
c-11-2.6-20.8-7.9-28.1-15c-0.3,0.4-0.5,0.7-0.8,1.1c8.6,13,25.1,21.7,44.1,21.7s35.6-8.8,44.1-21.7c-0.2-0.4-0.5-0.7-0.8-1.1
c-7.3,7.1-17,12.4-28,15C62.2,79.9,62.1,80,62,80z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 93.7 127.8" style="enable-background:new 0 0 93.7 127.8;" xml:space="preserve">
<path d="M91.9,97c-7.6,9.6-17.8,16.7-29.3,20.3h-0.1c-0.4,0.1-0.8,0.3-1.3,0.4c-4.6,1.3-9.5,2-14.4,2c-4.9,0-9.8-0.7-14.4-2
c-0.4-0.1-0.8-0.3-1.3-0.4h-0.1C19.6,113.8,9.4,106.6,1.8,97c0,0-0.5-0.5-0.9,0.1C0.6,97.6,1,98.5,1,98.5
C9.9,116,27,127.8,46.8,127.8l0,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0l0,0c19.7-0.1,36.8-11.8,45.8-29.3c0,0,0.4-0.9,0.1-1.4
C92.4,96.6,91.9,97,91.9,97z M91.9,78.8C84.3,88.4,74.1,95.5,62.6,99h-0.1c-0.4,0.1-0.8,0.3-1.3,0.4c-4.6,1.3-9.5,2-14.4,2
c-4.9,0-9.8-0.7-14.4-2c-0.4-0.1-0.8-0.3-1.3-0.4h-0.1C19.6,95.5,9.4,88.4,1.8,78.8c0,0-0.5-0.5-0.9,0.1C0.6,79.3,1,80.2,1,80.2
c8.9,17.5,26.1,29.2,45.8,29.3l0,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0l0,0c19.7-0.1,36.8-11.8,45.8-29.3c0,0,0.4-0.9,0.1-1.4
C92.4,78.3,91.9,78.8,91.9,78.8z M46.9,92.7c25.8,0,46.9-20.7,46.9-46.4S72.7,0,46.9,0S0,20.7,0,46.4S20.9,92.7,46.9,92.7z
M65.5,30.8c2.8,0,5,2.2,5,5s-2.2,5-5,5c-2.8,0-5-2.2-5-5S62.8,30.8,65.5,30.8z M36,54.3c2.7,3.2,6.6,5,10.8,5s8.2-1.9,10.8-5
c0.9-1.1,2.7-1.2,3.8-0.4c1.1,0.9,1.3,2.6,0.3,3.7c-3.7,4.4-9.1,6.9-14.9,6.9c-5.8,0-11.2-2.5-14.9-6.9c-0.9-1.1-0.8-2.7,0.3-3.7
C33.4,53.1,35,53.2,36,54.3z M28.1,30.8c2.8,0,5,2.2,5,5s-2.2,5-5,5c-2.8,0-5-2.2-5-5S25.3,30.8,28.1,30.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -93,7 +93,7 @@ if (WIN32)
set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF")
endif()
link_hifi_libraries(entities-renderer)
link_hifi_libraries(entities-renderer platform)
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})

View file

@ -323,9 +323,11 @@ void TestCreator::startTestsEvaluation(
QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename);
// Images are stored on GitHub as ExpectedImage_ddddd.png
// Extract the digits at the end of the filename (excluding the file extension)
QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS);
// Images are stored on GitHub as ExpectedImage_ddddd.png or ExpectedImage_some_metadata_ddddd.png
// Extract the part of the filename after "ExpectedImage_" and excluding the file extension
QString expectedImageFilenameTail = currentFilename.left(currentFilename.lastIndexOf("."));
int expectedImageStart = expectedImageFilenameTail.lastIndexOf(".") + 1;
expectedImageFilenameTail.remove(0, expectedImageStart);
QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png";
QString imageURLString("https://raw.githubusercontent.com/" + user + "/" + GIT_HUB_REPOSITORY + "/" + branch + "/" +
@ -495,6 +497,93 @@ void TestCreator::createTests(const QString& clientProfile) {
QMessageBox::information(0, "Success", "Test images have been created");
}
namespace TestProfile {
std::vector<QString> tiers = [](){
std::vector<QString> toReturn;
for (int tier = (int)platform::Profiler::Tier::LOW; tier < (int)platform::Profiler::Tier::NumTiers; ++tier) {
QString tierStringUpper = platform::Profiler::TierNames[tier];
toReturn.push_back(tierStringUpper.toLower());
}
return toReturn;
}();
std::vector<QString> operatingSystems = { "windows", "mac", "linux", "android" };
std::vector<QString> gpus = { "amd", "nvidia", "intel" };
};
enum class ProfileCategory {
TIER,
OS,
GPU
};
const std::map<QString, ProfileCategory> propertyToProfileCategory = [](){
std::map<QString, ProfileCategory> toReturn;
for (const auto& tier : TestProfile::tiers) {
toReturn[tier] = ProfileCategory::TIER;
}
for (const auto& os : TestProfile::operatingSystems) {
toReturn[os] = ProfileCategory::OS;
}
for (const auto& gpu : TestProfile::gpus) {
toReturn[gpu] = ProfileCategory::GPU;
}
return toReturn;
}();
TestFilter::TestFilter(const QString& filterString) {
auto filterParts = filterString.split(".", QString::SkipEmptyParts);
for (const auto& filterPart : filterParts) {
QList<QString> allowedVariants = filterPart.split(",", QString::SkipEmptyParts);
if (allowedVariants.empty()) {
continue;
}
auto& referenceVariant = allowedVariants[0];
auto foundCategoryIt = propertyToProfileCategory.find(referenceVariant);
if (foundCategoryIt == propertyToProfileCategory.cend()) {
error = "Invalid test filter property '" + referenceVariant + "'";
return;
}
ProfileCategory selectedFilterCategory = foundCategoryIt->second;
for (auto allowedVariantIt = ++(allowedVariants.cbegin()); allowedVariantIt != allowedVariants.cend(); ++allowedVariantIt) {
auto& currentVariant = *allowedVariantIt;
auto nextCategoryIt = propertyToProfileCategory.find(currentVariant);
if (nextCategoryIt == propertyToProfileCategory.cend()) {
error = "Invalid test filter property '" + referenceVariant + "'";
return;
}
auto& currentCategory = nextCategoryIt->second;
if (currentCategory != selectedFilterCategory) {
error = "Mismatched comma-separated test filter properties '" + referenceVariant + "' and '" + currentVariant + "'";
return;
}
// List of comma-separated test property variants is consistent so far
}
switch (selectedFilterCategory) {
case ProfileCategory::TIER:
allowedTiers.insert(allowedTiers.cend(), allowedVariants.cbegin(), allowedVariants.cend());
break;
case ProfileCategory::OS:
allowedOperatingSystems.insert(allowedOperatingSystems.cend(), allowedVariants.cbegin(), allowedVariants.cend());
break;
case ProfileCategory::GPU:
allowedGPUs.insert(allowedGPUs.cend(), allowedVariants.cbegin(), allowedVariants.cend());
break;
}
}
}
bool TestFilter::isValid() const {
return error.isEmpty();
}
QString TestFilter::getError() const {
return error;
}
ExtractedText TestCreator::getTestScriptLines(QString testFileName) {
ExtractedText relevantTextFromTest;
@ -511,7 +600,7 @@ ExtractedText TestCreator::getTestScriptLines(QString testFileName) {
QString line = stream.readLine();
// Name of test is the string in the following line:
// nitpick.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {...
// nitpick.perform("Apply Material Entities to Avatars", Script.resolvePath("."), "secondary", undefined, function(testType) {...
const QString ws("\\h*"); //white-space character
const QString functionPerformName(ws + "nitpick" + ws + "\\." + ws + "perform");
const QString quotedString("\\\".+\\\"");
@ -644,6 +733,17 @@ void TestCreator::createAllMDFiles() {
QMessageBox::information(0, "Success", "MD files have been created");
}
QString joinVector(const std::vector<QString>& qStringVector, const char* separator) {
if (qStringVector.empty()) {
return QString("");
}
QString joined = qStringVector[0];
for (std::size_t i = 1; i < qStringVector.size(); ++i) {
joined += separator + qStringVector[i];
}
return joined;
}
bool TestCreator::createMDFile(const QString& directory) {
// Verify folder contains test.js file
QString testFileName(directory + "/" + TEST_FILENAME);
@ -655,33 +755,73 @@ bool TestCreator::createMDFile(const QString& directory) {
ExtractedText testScriptLines = getTestScriptLines(testFileName);
QDir qDirectory(directory);
QString mdFilename(directory + "/" + "test.md");
QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::WriteOnly)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
// TODO: Don't just exit
exit(-1);
}
QTextStream stream(&mdFile);
//TestCreator title
QString testName = testScriptLines.title;
stream << "# " << testName << "\n";
stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n";
stream << "## Preconditions" << "\n";
stream << "- In an empty region of a domain with editing rights." << "\n\n";
stream << "- In an empty region of a domain with editing rights." << "\n";
stream << "\n";
// ExpectedImage_00000.png OR ExpectedImage_some_stu-ff_00000.png
const QRegularExpression firstExpectedImage("^ExpectedImage(_[-_\\w]*)?_00000\\.png$");
std::vector<QString> testDescriptors;
std::vector<TestFilter> testFilters;
for (const auto& potentialImageFile : qDirectory.entryInfoList()) {
if (potentialImageFile.isDir()) {
continue;
}
auto firstExpectedImageMatch = firstExpectedImage.match(potentialImageFile.fileName());
if (!firstExpectedImageMatch.hasMatch()) {
continue;
}
QString testDescriptor = firstExpectedImageMatch.captured(1);
auto filterString = QString(testDescriptor).replace("_", ".").replace("-", ",");
TestFilter descriptorAsFilter(filterString);
testDescriptors.push_back(testDescriptor);
testFilters.push_back(descriptorAsFilter);
}
stream << "## Steps\n";
stream << "Press '" + ADVANCE_KEY + "' key to advance step by step\n\n"; // note apostrophes surrounding 'ADVANCE_KEY'
int snapShotIndex { 0 };
for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) {
stream << "### Step " << QString::number(i + 1) << "\n";
stream << "- " << testScriptLines.stepList[i]->text << "\n";
if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i]->takeSnapshot) {
stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n";
for (int i = 0; i < (int)testDescriptors.size(); ++i) {
const auto& testDescriptor = testDescriptors[i];
const auto& descriptorAsFilter = testFilters[i];
if (descriptorAsFilter.isValid()) {
stream << "- Expected image on ";
stream << (descriptorAsFilter.allowedTiers.empty() ? "any" : joinVector(descriptorAsFilter.allowedTiers, "/")) << " tier, ";
stream << (descriptorAsFilter.allowedOperatingSystems.empty() ? "any" : joinVector(descriptorAsFilter.allowedOperatingSystems, "/")) << " OS, ";
stream << (descriptorAsFilter.allowedGPUs.empty() ? "any" : joinVector(descriptorAsFilter.allowedGPUs, "/")) << " GPU";
stream << ":";
} else {
// Fall back to displaying file name
stream << "- ExpectedImage" << testDescriptor << "_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png";
}
stream << "\n";
stream << "- ![](./ExpectedImage" << testDescriptor << "_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n";
}
++snapShotIndex;
}
}

View file

@ -16,6 +16,8 @@
#include <QtCore/QRegularExpression>
#include <QProgressBar>
#include <platform/Profiler.h>
#include "AWSInterface.h"
#include "ImageComparer.h"
#include "Downloader.h"
@ -30,6 +32,20 @@ public:
using StepList = std::vector<Step*>;
class TestFilter {
public:
TestFilter(const QString& filterString);
bool isValid() const;
QString getError() const;
std::vector<QString> allowedTiers;
std::vector<QString> allowedOperatingSystems;
std::vector<QString> allowedGPUs;
protected:
QString error;
};
class ExtractedText {
public:
QString title;