Merge branch 'master' of https://github.com/highfidelity/hifi into newEditGrab

This commit is contained in:
David Back 2018-01-31 11:03:11 -08:00
commit 09bcee7c2e
56 changed files with 983 additions and 471 deletions

35
cmake/externals/crashpad/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,35 @@
include(ExternalProject)
set(EXTERNAL_NAME crashpad)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://backtrace.io/download/crashpad_062317.zip
URL_MD5 65817e564b3628492abfc1dbd2a1e98b
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "List of Crashpad include directories")
set(LIB_EXT "lib")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad release library")
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base release library")
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad debug library")
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base debug library")
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util debug library")
set(CRASHPAD_HANDLER_EXE_PATH ${SOURCE_DIR}/out/Release_x64/crashpad_handler.exe CACHE FILEPATH "Path to the Crashpad handler executable")
endif ()
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")

View file

@ -1,34 +0,0 @@
#
# AddBugSplat.cmake
# cmake/macros
#
# Created by Ryan Huffman on 02/09/16.
# Copyright 2016 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
#
macro(add_bugsplat)
get_property(BUGSPLAT_CHECKED GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE)
if (NOT BUGSPLAT_CHECKED)
find_package(BugSplat)
set_property(GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE TRUE)
endif ()
if (BUGSPLAT_FOUND)
add_definitions(-DHAS_BUGSPLAT)
target_include_directories(${TARGET_NAME} PRIVATE ${BUGSPLAT_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${BUGSPLAT_LIBRARIES})
add_paths_to_fixup_libs(${BUGSPLAT_DLL_PATH})
add_custom_command(TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_RC_DLL_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
add_custom_command(TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
endif ()
endmacro()

View file

@ -0,0 +1,58 @@
#
# AddCrashpad.cmake
# cmake/macros
#
# Created by Clement Brisset on 01/19/18.
# 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
#
macro(add_crashpad)
set (USE_CRASHPAD TRUE)
if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "")
set(USE_CRASHPAD FALSE)
else()
set(CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL})
endif()
if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "")
set(USE_CRASHPAD FALSE)
else()
set(CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN})
endif()
if (WIN32 AND USE_CRASHPAD)
get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE)
if (NOT CRASHPAD_CHECKED)
add_dependency_external_projects(crashpad)
find_package(crashpad REQUIRED)
set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE)
endif()
add_definitions(-DHAS_CRASHPAD)
add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\")
add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\")
target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY})
if (WIN32)
set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099")
endif()
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
)
install(
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
DESTINATION ${INTERFACE_INSTALL_DIR}
COMPONENT ${CLIENT_COMPONENT}
)
endif ()
endmacro()

View file

@ -0,0 +1,7 @@
# adds a custom path and local path to the inserted CUSTOM_PATHS_VAR list which
# can be given to the GENERATE_QRC command
function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH)
list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}")
set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE)
endfunction()

View file

@ -0,0 +1,14 @@
#
# FindNPM.cmake
# cmake/macros
#
# Created by Thijs Wenker on 01/23/18.
# 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
#
macro(find_npm)
find_program(NPM_EXECUTABLE "npm")
endmacro()

View file

@ -1,7 +1,7 @@
function(GENERATE_QRC)
set(oneValueArgs OUTPUT PREFIX PATH)
set(multiValueArgs GLOBS)
set(multiValueArgs CUSTOM_PATHS GLOBS)
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
set(QRC_PREFIX_PATH /)
@ -20,6 +20,13 @@ function(GENERATE_QRC)
endforeach()
endforeach()
foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS})
string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH})
list(GET CUSTOM_PATH 0 IMPORT_PATH)
list(GET CUSTOM_PATH 1 LOCAL_PATH)
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${LOCAL_PATH}\">${IMPORT_PATH}</file>\n")
endforeach()
set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE)
configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
endfunction()

View file

@ -1,30 +0,0 @@
#
# FindBugSplat.cmake
# cmake/modules
#
# Created by Ryan Huffman on 02/09/16.
# Copyright 2016 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
#
if (WIN32)
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("BugSplat")
find_path(BUGSPLAT_INCLUDE_DIRS NAMES BugSplat.h PATH_SUFFIXES inc HINTS ${BUGSPLAT_SEARCH_DIRS})
find_library(BUGSPLAT_LIBRARY_RELEASE "BugSplat64.lib" PATH_SUFFIXES "lib64" HINTS ${BUGSPLAT_SEARCH_DIRS})
find_path(BUGSPLAT_DLL_PATH NAMES "BugSplat64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
find_file(BUGSPLAT_RC_DLL_PATH NAMES "BugSplatRc64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
find_file(BUGSPLAT_EXE_PATH NAMES "BsSndRpt64.exe" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
include(SelectLibraryConfigurations)
select_library_configurations(BUGSPLAT)
set(BUGSPLAT_LIBRARIES ${BUGSPLAT_LIBRARY_RELEASE})
endif ()
set(BUGSPLAT_REQUIREMENTS BUGSPLAT_INCLUDE_DIRS BUGSPLAT_LIBRARIES BUGSPLAT_DLL_PATH BUGSPLAT_RC_DLL_PATH BUGSPLAT_EXE_PATH)
find_package_handle_standard_args(BugSplat DEFAULT_MSG ${BUGSPLAT_REQUIREMENTS})

View file

@ -0,0 +1,41 @@
#
# FindCrashpad.cmake
#
# Try to find Crashpad libraries and include path.
# Once done this will define
#
# CRASHPAD_FOUND
# CRASHPAD_INCLUDE_DIRS
# CRASHPAD_LIBRARY
# CRASHPAD_BASE_LIBRARY
# CRASHPAD_UTIL_LIBRARY
#
# Created on 01/19/2018 by Clement Brisset
# 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
#
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("crashpad")
find_path(CRASHPAD_INCLUDE_DIRS base/macros.h PATH_SUFFIXES include HINTS ${CRASHPAD_SEARCH_DIRS})
find_library(CRASHPAD_LIBRARY_RELEASE crashpad PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
find_library(CRASHPAD_BASE_LIBRARY_RELEASE base PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
find_library(CRASHPAD_UTIL_LIBRARY_RELEASE util PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
find_library(CRASHPAD_LIBRARY_DEBUG crashpad PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
find_library(CRASHPAD_BASE_LIBRARY_DEBUG base PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
find_library(CRASHPAD_UTIL_LIBRARY_DEBUG util PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
find_file(CRASHPAD_HANDLER_EXE_PATH NAME "crashpad_handler.exe" PATH_SUFFIXES "Release_x64" HINTS ${CRASHPAD_SEARCH_DIRS})
include(SelectLibraryConfigurations)
select_library_configurations(CRASHPAD)
select_library_configurations(CRASHPAD_BASE)
select_library_configurations(CRASHPAD_UTIL)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CRASHPAD DEFAULT_MSG CRASHPAD_INCLUDE_DIRS CRASHPAD_LIBRARY CRASHPAD_BASE_LIBRARY CRASHPAD_UTIL_LIBRARY)

View file

@ -823,6 +823,7 @@ Section "-Core installation"
Delete "$INSTDIR\server-console.exe"
RMDir /r "$INSTDIR\locales"
RMDir /r "$INSTDIR\resources\app"
RMDir /r "$INSTDIR\client"
Delete "$INSTDIR\resources\atom.asar"
Delete "$INSTDIR\build-info.json"
Delete "$INSTDIR\content_resources_200_percent.pak"

View file

@ -157,7 +157,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
DependencyManager::set<StatTracker>();
LogUtils::init();
Setting::init();
qDebug() << "Setting up domain-server";
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());

View file

@ -29,6 +29,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
Setting::init();
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif

View file

@ -11,9 +11,17 @@ function(JOIN VALUES GLUE OUTPUT)
set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
endfunction()
set(CUSTOM_INTERFACE_QRC_PATHS "")
find_npm()
if (BUILD_TOOLS AND NPM_EXECUTABLE)
add_custom_qrc_path(CUSTOM_INTERFACE_QRC_PATHS "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "auto-complete/hifiJSDoc.json")
endif ()
set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc)
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *)
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *)
add_custom_command(
OUTPUT ${RESOURCES_RCC}
@ -163,6 +171,11 @@ else ()
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
endif ()
if (BUILD_TOOLS AND NPM_EXECUTABLE)
# require JSDoc to be build before interface is deployed (Console Auto-complete)
add_dependencies(resources jsdoc)
endif()
add_dependencies(${TARGET_NAME} resources)
if (WIN32)
@ -206,6 +219,7 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries
target_bullet()
target_opengl()
add_crashpad()
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
@ -289,6 +303,7 @@ if (APPLE)
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
# copy script files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
@ -350,8 +365,6 @@ if (SCRIPTS_INSTALL_DIR)
)
endif()
add_bugsplat()
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"")

View file

@ -33,6 +33,7 @@ Column {
property int labelSize: 20;
property string metaverseServerUrl: '';
property string protocol: '';
property string actions: 'snapshot';
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
property string labelText: actions;
@ -102,7 +103,7 @@ Column {
'include_actions=' + actions,
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
'require_online=true',
'protocol=' + encodeURIComponent(Window.protocolSignature()),
'protocol=' + protocol,
'page=' + pageNumber
];
var url = metaverseBase + 'user_stories?' + options.join('&');

View file

@ -12,7 +12,7 @@ StackView {
HifiConstants { id: hifi }
function pushSource(path) {
editRoot.push(Qt.resolvedUrl(path));
editRoot.push(Qt.resolvedUrl("../../" + path));
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
}

View file

@ -54,6 +54,7 @@ StackView {
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
feeds.forEach(function(feed) {
feed.protocol = encodeURIComponent(message.protocolSignature);
Qt.callLater(feed.fillDestinations);
});

View file

@ -127,7 +127,7 @@ Item {
GridView {
id: gridView
flickableDirection: Flickable.AutoFlickIfNeeded
keyNavigationEnabled: false
highlightFollowsCurrentItem: false
@ -144,23 +144,20 @@ Item {
bottomMargin: 0
}
function setButtonState(buttonIndex, buttonstate) {
if (buttonIndex < 0 || gridView.contentItem === undefined
|| gridView.contentItem.children.length - 1 < buttonIndex) {
return;
}
var itemat = gridView.contentItem.children[buttonIndex].children[0];
if (itemat.isActive) {
itemat.state = "active state";
} else {
itemat.state = buttonstate;
}
onCurrentIndexChanged: {
previousGridIndex = currentIndex
}
onCurrentIndexChanged: {
setButtonState(previousGridIndex, "base state");
setButtonState(currentIndex, "hover state");
previousGridIndex = currentIndex
onMovementStarted: {
if (currentIndex < 0 || gridView.currentItem === undefined || gridView.contentItem.children.length - 1 < currentIndex) {
return;
}
var button = gridView.contentItem.children[currentIndex].children[0];
if (button.isActive) {
button.state = "active state";
} else {
button.state = "base state";
}
}
cellWidth: width/3

View file

@ -149,6 +149,7 @@
#include "avatar/AvatarManager.h"
#include "avatar/MyHead.h"
#include "CrashHandler.h"
#include "Crashpad.h"
#include "devices/DdeFaceTracker.h"
#include "DiscoverabilityManager.h"
#include "GLCanvas.h"
@ -396,6 +397,7 @@ public:
setObjectName("Deadlock Watchdog");
// Give the heartbeat an initial value
_heartbeat = usecTimestampNow();
_paused = false;
connect(qApp, &QCoreApplication::aboutToQuit, [this] {
_quit = true;
});
@ -413,11 +415,20 @@ public:
*crashTrigger = 0xDEAD10CC;
}
static void pause() {
_paused = true;
}
static void resume() {
_paused = false;
updateHeartbeat();
}
void run() override {
while (!_quit) {
QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS);
// Don't do heartbeat detection under nsight
if (nsightActive()) {
if (nsightActive() || _paused) {
continue;
}
uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us
@ -473,6 +484,7 @@ public:
}
}
static std::atomic<bool> _paused;
static std::atomic<uint64_t> _heartbeat;
static std::atomic<uint64_t> _maxElapsed;
static std::atomic<int> _maxElapsedAverage;
@ -481,6 +493,7 @@ public:
bool _quit { false };
};
std::atomic<bool> DeadlockWatchdogThread::_paused;
std::atomic<uint64_t> DeadlockWatchdogThread::_heartbeat;
std::atomic<uint64_t> DeadlockWatchdogThread::_maxElapsed;
std::atomic<int> DeadlockWatchdogThread::_maxElapsedAverage;
@ -656,8 +669,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
}
#endif
Setting::init();
// Tell the plugin manager about our statically linked plugins
auto pluginManager = PluginManager::getInstance();
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
@ -909,6 +920,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_logger->setSessionID(accountManager->getSessionID());
setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString());
if (steamClient) {
qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID();
}
@ -2269,6 +2282,11 @@ void Application::initializeGL() {
initDisplay();
qCDebug(interfaceapp, "Initialized Display.");
#ifdef Q_OS_OSX
// FIXME: on mac os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
DeadlockWatchdogThread::pause();
#endif
// Set up the render engine
render::CullFunctor cullFunctor = LODManager::shouldRender;
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
@ -2277,6 +2295,11 @@ void Application::initializeGL() {
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED);
#endif
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, !DISABLE_DEFERRED);
#ifdef Q_OS_OSX
DeadlockWatchdogThread::resume();
#endif
_renderEngine->load();
_renderEngine->registerScene(_main3DScene);
@ -6272,7 +6295,7 @@ void Application::showAssetServerWidget(QString filePath) {
if (!hmd->getShouldShowTablet() && !isHMDMode()) {
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
} else {
static const QUrl url("../dialogs/TabletAssetServer.qml");
static const QUrl url("hifi/dialogs/TabletAssetServer.qml");
tablet->pushOntoStack(url);
}
}

View file

@ -1,166 +0,0 @@
//
// CrashReporter.cpp
// interface/src
//
// Created by Ryan Huffman on 11 April 2016.
// Copyright 2016 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 "Application.h"
#include "CrashReporter.h"
#ifdef _WIN32
#include <new.h>
#include <Windows.h>
#include <DbgHelp.h>
#include <csignal>
#include <QDebug>
#pragma comment(lib, "Dbghelp.lib")
// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information
// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so
// that the applicaiton can handle it itself.
// The CAPIHook class referenced in the above article is not openly available, but a similar implementation
// can be found here: http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/
// The below has been adapted to work with different library and functions.
BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn)
{
HMODULE lib = LoadLibrary(library);
if (lib == NULL) return FALSE;
void *pOrgEntry = GetProcAddress(lib, function);
if (pOrgEntry == NULL) return FALSE;
DWORD dwOldProtect = 0;
SIZE_T jmpSize = 5;
#ifdef _M_X64
jmpSize = 13;
#endif
BOOL bProt = VirtualProtect(pOrgEntry, jmpSize,
PAGE_EXECUTE_READWRITE, &dwOldProtect);
BYTE newJump[20];
void *pNewFunc = fn;
#ifdef _M_IX86
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32
DWORD dwNewEntryAddr = (DWORD)pNewFunc;
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
// JMP rel32: Jump near, relative, displacement relative to next instruction.
newJump[0] = 0xE9; // JMP rel32
memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
#elif _M_X64
// We must use R10 or R11, because these are "scratch" registers
// which need not to be preserved accross function calls
// For more info see: Register Usage for x64 64-Bit
// http://msdn.microsoft.com/en-us/library/ms794547.aspx
// Thanks to Matthew Smith!!!
newJump[0] = 0x49; // MOV R11, ...
newJump[1] = 0xBB; // ...
memcpy(&newJump[2], &pNewFunc, sizeof(pNewFunc));
//pCur += sizeof (ULONG_PTR);
newJump[10] = 0x41; // JMP R11, ...
newJump[11] = 0xFF; // ...
newJump[12] = 0xE3; // ...
#endif
SIZE_T bytesWritten;
BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
pOrgEntry, newJump, jmpSize, &bytesWritten);
if (bProt != FALSE)
{
DWORD dwBuf;
VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf);
}
return bRet;
}
void printStackTrace(ULONG framesToSkip = 1) {
HANDLE process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
void* stack[100];
uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL);
SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
for (uint16_t i = 0; i < frames; ++i) {
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16));
}
free(symbol);
// Try to force the log to sync to the filesystem
auto app = qApp;
if (app && app->getLogger()) {
app->getLogger()->sync();
}
}
void handleSignal(int signal) {
// Throw so BugSplat can handle
throw(signal);
}
void __cdecl handlePureVirtualCall() {
qWarning() << "Pure virtual function call detected";
printStackTrace(2);
// Throw so BugSplat can handle
throw("ERROR: Pure virtual call");
}
void handleInvalidParameter(const wchar_t * expression, const wchar_t * function, const wchar_t * file,
unsigned int line, uintptr_t pReserved ) {
// Throw so BugSplat can handle
throw("ERROR: Invalid parameter");
}
int handleNewError(size_t size) {
// Throw so BugSplat can handle
throw("ERROR: Errors calling new");
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI noop_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
return nullptr;
}
_purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) {
return nullptr;
}
#ifdef HAS_BUGSPLAT
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version)
: mpSender(qPrintable(bugSplatDatabase), qPrintable(bugSplatApplicationName), qPrintable(version), nullptr, BUG_SPLAT_FLAGS)
{
signal(SIGSEGV, handleSignal);
signal(SIGABRT, handleSignal);
_set_purecall_handler(handlePureVirtualCall);
_set_invalid_parameter_handler(handleInvalidParameter);
_set_new_mode(1);
_set_new_handler(handleNewError);
// Disable WER popup
//SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
//_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
// QtWebEngineCore internally sets its own purecall handler, overriding our own error handling. This disables that.
if (!redirectLibraryFunctionToFunction("msvcr120.dll", "_set_purecall_handler", &noop_set_purecall_handler)) {
qWarning() << "Failed to patch _set_purecall_handler";
}
// Patch SetUnhandledExceptionFilter to keep the CRT from overriding our own error handling.
if (!redirectLibraryFunctionToFunction("kernel32.dll", "SetUnhandledExceptionFilter", &noop_SetUnhandledExceptionFilter)) {
qWarning() << "Failed to patch setUnhandledExceptionFilter";
}
}
#endif
#endif

View file

@ -1,32 +0,0 @@
//
// CrashReporter.h
// interface/src
//
// Created by Ryan Huffman on 11 April 2016.
// Copyright 2016 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_CrashReporter_h
#define hifi_CrashReporter_h
#include <QString>
#ifdef HAS_BUGSPLAT
#include <BugSplat.h>
class CrashReporter {
public:
CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version);
MiniDmpSender mpSender;
};
#endif
#endif // hifi_CrashReporter_h

101
interface/src/Crashpad.cpp Normal file
View file

@ -0,0 +1,101 @@
//
// Crashpad.cpp
// interface/src
//
// Created by Clement Brisset on 01/19/18.
// 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
//
#include "Crashpad.h"
#include <QDebug>
#if HAS_CRASHPAD
#include <QStandardPaths>
#include <QDir>
#include <BuildInfo.h>
#include <client/crashpad_client.h>
#include <client/crash_report_database.h>
#include <client/settings.h>
// #include <client/annotation_list.h>
// #include <client/crashpad_info.h>
using namespace crashpad;
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
extern QString qAppFileName();
// crashpad::AnnotationList* crashpadAnnotations { nullptr };
bool startCrashHandler() {
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
return false;
}
CrashpadClient client;
std::vector<std::string> arguments;
std::map<std::string, std::string> annotations;
annotations["token"] = BACKTRACE_TOKEN;
annotations["format"] = "minidump";
annotations["version"] = BuildInfo::VERSION.toStdString();
arguments.push_back("--no-rate-limit");
// Setup Crashpad DB directory
const auto crashpadDbName = "crashpad-db";
const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists
const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName;
// Locate Crashpad handler
const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe";
// Setup different file paths
base::FilePath::StringType dbPath;
base::FilePath::StringType handlerPath;
dbPath.assign(crashpadDbPath.cbegin(), crashpadDbPath.cend());
handlerPath.assign(CRASHPAD_HANDLER_PATH.cbegin(), CRASHPAD_HANDLER_PATH.cend());
base::FilePath db(dbPath);
base::FilePath handler(handlerPath);
auto database = crashpad::CrashReportDatabase::Initialize(db);
if (database == nullptr || database->GetSettings() == nullptr) {
return false;
}
// Enable automated uploads.
database->GetSettings()->SetUploadsEnabled(true);
return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
}
void setCrashAnnotation(std::string name, std::string value) {
// if (!crashpadAnnotations) {
// crashpadAnnotations = new crashpad::AnnotationList(); // don't free this, let it leak
// crashpad::CrashpadInfo* crashpad_info = crashpad::GetCrashpadInfo();
// crashpad_info->set_simple_annotations(crashpadAnnotations);
// }
// crashpadAnnotations->SetKeyValue(name, value);
}
#else
bool startCrashHandler() {
qDebug() << "No crash handler available.";
return false;
}
void setCrashAnnotation(std::string name, std::string value) {
}
#endif

20
interface/src/Crashpad.h Normal file
View file

@ -0,0 +1,20 @@
//
// Crashpad.h
// interface/src
//
// Created by Clement Brisset on 01/19/18.
// 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
//
#ifndef hifi_Crashpad_h
#define hifi_Crashpad_h
#include <string>
bool startCrashHandler();
void setCrashAnnotation(std::string name, std::string value);
#endif // hifi_Crashpad_h

View file

@ -20,7 +20,7 @@
#include <render/Args.h>
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f;
const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f;
const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f;
const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec
const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec
const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps

View file

@ -81,7 +81,7 @@ class MyAvatar : public Avatar {
* and MyAvatar.customListenOrientation properties.
* @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position
* of audio spatialization listener.
* @property customListenOreintation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
* @property customListenOrientation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
* of the audio spatialization listener.
* @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener
* around the avatar's head.

View file

@ -26,15 +26,11 @@
#include "AddressManager.h"
#include "Application.h"
#include "Crashpad.h"
#include "InterfaceLogging.h"
#include "UserActivityLogger.h"
#include "MainWindow.h"
#ifdef HAS_BUGSPLAT
#include <BugSplat.h>
#include <CrashReporter.h>
#endif
#ifdef Q_OS_WIN
extern "C" {
typedef int(__stdcall * CHECKMINSPECPROC) ();
@ -42,11 +38,6 @@ extern "C" {
#endif
int main(int argc, const char* argv[]) {
#if HAS_BUGSPLAT
static QString BUG_SPLAT_DATABASE = "interface_alpha";
static QString BUG_SPLAT_APPLICATION_NAME = "Interface";
CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION };
#endif
#ifdef Q_OS_LINUX
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
@ -71,6 +62,17 @@ int main(int argc, const char* argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
Setting::init();
// Instance UserActivityLogger now that the settings are loaded
auto& ual = UserActivityLogger::getInstance();
qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled();
if (ual.isEnabled()) {
auto crashHandlerStarted = startCrashHandler();
qDebug() << "Crash handler started:" << crashHandlerStarted;
}
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
@ -259,7 +261,6 @@ int main(int argc, const char* argv[]) {
}
}
#endif
// Setup local server
QLocalServer server { &app };
@ -268,29 +269,8 @@ int main(int argc, const char* argv[]) {
server.removeServer(applicationName);
server.listen(applicationName);
QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection);
#ifdef HAS_BUGSPLAT
auto accountManager = DependencyManager::get<AccountManager>();
crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername()));
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) {
crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername));
});
// BugSplat WILL NOT work with file paths that do not use OS native separators.
auto logger = app.getLogger();
auto logPath = QDir::toNativeSeparators(logger->getFilename());
crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath));
QMetaObject::Connection connection;
connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&crashReporter, &connection](QString newFilename) {
// We only want to add the first rolled log file (the "beginning" of the log) to BugSplat to ensure we don't exceed the 2MB
// zipped limit, so we disconnect here.
QObject::disconnect(connection);
auto rolledLogPath = QDir::toNativeSeparators(newFilename);
crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath));
});
#endif
QObject::connect(&server, &QLocalServer::newConnection,
&app, &Application::handleLocalServerConnection, Qt::DirectConnection);
printSystemInformation();

View file

@ -12,10 +12,12 @@
#include "JSConsole.h"
#include <QFuture>
#include <QKeyEvent>
#include <QLabel>
#include <QScrollBar>
#include <QtConcurrent/QtConcurrentRun>
#include <QStringListModel>
#include <QListView>
#include <QToolTip>
#include <shared/QtHelpers.h>
#include <ScriptEngines.h>
@ -38,6 +40,21 @@ const QString RESULT_ERROR_STYLE = "color: #d13b22;";
const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\">&lt;</span>";
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
const QString JSDOC_LINE_SEPARATOR = "\r";
const QString JSDOC_STYLE =
"<style type=\"text/css\"> \
.code { \
font-family: Consolas, Monaco, 'Andale Mono', monospace \
} \
pre, code { \
display: inline; \
} \
.no-wrap { \
white-space: nowrap; \
} \
</style>";
const QString JSConsole::_consoleFileName { "about:console" };
const QString JSON_KEY = "entries";
@ -49,7 +66,7 @@ QList<QString> _readLines(const QString& filename) {
// TODO: check root["version"]
return root[JSON_KEY].toVariant().toStringList();
}
void _writeLines(const QString& filename, const QList<QString>& lines) {
QFile file(filename);
file.open(QFile::WriteOnly);
@ -61,12 +78,71 @@ void _writeLines(const QString& filename, const QList<QString>& lines) {
QTextStream(&file) << json;
}
QString _jsdocTypeToString(QJsonValue jsdocType) {
return jsdocType.toObject().value("names").toVariant().toStringList().join("/");
}
void JSConsole::readAPI() {
QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json");
file.open(QFile::ReadOnly);
auto json = QTextStream(&file).readAll().toUtf8();
_apiDocs = QJsonDocument::fromJson(json).array();
}
QStandardItem* getAutoCompleteItem(QJsonValue propertyObject) {
auto propertyItem = new QStandardItem(propertyObject.toObject().value("name").toString());
propertyItem->setData(propertyObject.toVariant());
return propertyItem;
}
QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) {
QString memberOfProperty = nullptr;
auto model = new QStandardItemModel(this);
if (memberOf != nullptr) {
foreach(auto doc, _apiDocs) {
auto object = doc.toObject();
if (object.value("name").toString() == memberOf && object.value("scope").toString() == "global" &&
object.value("kind").toString() == "namespace") {
memberOfProperty = object.value("longname").toString();
auto properties = doc.toObject().value("properties").toArray();
foreach(auto propertyObject, properties) {
model->appendRow(getAutoCompleteItem(propertyObject));
}
}
}
if (memberOfProperty == nullptr) {
return nullptr;
}
}
foreach(auto doc, _apiDocs) {
auto object = doc.toObject();
auto scope = object.value("scope");
if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") ||
(memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty &&
object.value("kind").toString() != "typedef")) {
model->appendRow(getAutoCompleteItem(doc));
}
}
model->sort(0);
return model;
}
JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) :
QWidget(parent),
_ui(new Ui::Console),
_currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND),
_savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME),
_commandHistory(_readLines(_savedHistoryFilename)) {
_commandHistory(_readLines(_savedHistoryFilename)),
_completer(new QCompleter(this)) {
readAPI();
_ui->setupUi(this);
_ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap);
_ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap);
@ -78,38 +154,174 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) :
setStyleSheet(styleSheet.readAll());
}
connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom()));
connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput()));
connect(_ui->scrollArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, &JSConsole::scrollToBottom);
connect(_ui->promptTextEdit, &QTextEdit::textChanged, this, &JSConsole::resizeTextInput);
_completer->setWidget(_ui->promptTextEdit);
_completer->setModel(getAutoCompleteModel(nullptr));
_completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
_completer->setMaxVisibleItems(12);
_completer->setFilterMode(Qt::MatchStartsWith);
_completer->setWrapAround(false);
_completer->setCompletionMode(QCompleter::PopupCompletion);
_completer->setCaseSensitivity(Qt::CaseSensitive);
QListView *listView = new QListView();
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
listView->setSelectionBehavior(QAbstractItemView::SelectRows);
listView->setSelectionMode(QAbstractItemView::SingleSelection);
listView->setModelColumn(_completer->completionColumn());
_completer->setPopup(listView);
_completer->popup()->installEventFilter(this);
QObject::connect(_completer, static_cast<void(QCompleter::*)(const QModelIndex&)>(&QCompleter::activated), this,
&JSConsole::insertCompletion);
QObject::connect(_completer, static_cast<void(QCompleter::*)(const QModelIndex&)>(&QCompleter::highlighted), this,
&JSConsole::highlightedCompletion);
setScriptEngine(scriptEngine);
resizeTextInput();
connect(&_executeWatcher, SIGNAL(finished()), this, SLOT(commandFinished()));
connect(&_executeWatcher, &QFutureWatcher<QScriptValue>::finished, this, &JSConsole::commandFinished);
}
void JSConsole::insertCompletion(const QModelIndex& completion) {
auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject();
auto kind = jsdocObject.value("kind").toString();
auto completionString = completion.data().toString();
if (kind == "function") {
auto params = jsdocObject.value("params").toArray();
// automatically add the parenthesis/parentheses for the functions
completionString += params.isEmpty() ? "()" : "(";
}
QTextCursor textCursor = _ui->promptTextEdit->textCursor();
int extra = completionString.length() - _completer->completionPrefix().length();
textCursor.movePosition(QTextCursor::Left);
textCursor.movePosition(QTextCursor::EndOfWord);
textCursor.insertText(completionString.right(extra));
_ui->promptTextEdit->setTextCursor(textCursor);
}
void JSConsole::highlightedCompletion(const QModelIndex& completion) {
auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject();
QString memberOf = "";
if (!_completerModule.isEmpty()) {
memberOf = _completerModule + ".";
}
auto name = memberOf + "<b>" + jsdocObject.value("name").toString() + "</b>";
auto description = jsdocObject.value("description").toString();
auto examples = jsdocObject.value("examples").toArray();
auto kind = jsdocObject.value("kind").toString();
QString returnTypeText = "";
QString paramsTable = "";
if (kind == "function") {
auto params = jsdocObject.value("params").toArray();
auto returns = jsdocObject.value("returns");
if (!returns.isUndefined()) {
returnTypeText = _jsdocTypeToString(jsdocObject.value("returns").toArray().at(0).toObject().value("type")) + " ";
}
name += "(";
if (!params.isEmpty()) {
bool hasDefaultParam = false;
bool hasOptionalParam = false;
bool firstItem = true;
foreach(auto param, params) {
auto paramObject = param.toObject();
if (!hasOptionalParam && paramObject.value("optional").toBool(false)) {
hasOptionalParam = true;
name += "<i>[";
}
if (!firstItem) {
name += ", ";
} else {
firstItem = false;
}
name += paramObject.value("name").toString();
if (!hasDefaultParam && !paramObject.value("defaultvalue").isUndefined()) {
hasDefaultParam = true;
}
}
if (hasOptionalParam) {
name += "]</i>";
}
paramsTable += "<table border=\"1\" cellpadding=\"10\"><thead><tr><th>Name</th><th>Type</th>";
if (hasDefaultParam) {
paramsTable += "<th>Default</th>";
}
paramsTable += "<th>Description</th></tr></thead><tbody>";
foreach(auto param, params) {
auto paramObject = param.toObject();
paramsTable += "<tr><td>" + paramObject.value("name").toString() + "</td><td>" +
_jsdocTypeToString(paramObject.value("type")) + "</td><td>";
if (hasDefaultParam) {
paramsTable += paramObject.value("defaultvalue").toVariant().toString() + "</td><td>";
}
paramsTable += paramObject.value("description").toString() + "</td></tr>";
}
paramsTable += "</tbody></table>";
}
name += ")";
} else if (!jsdocObject.value("type").isUndefined()){
returnTypeText = _jsdocTypeToString(jsdocObject.value("type")) + " ";
}
auto popupText = JSDOC_STYLE + "<span class=\"no-wrap\">" + returnTypeText + name + "</span>";
auto descriptionText = "<p>" + description.replace(JSDOC_LINE_SEPARATOR, "<br>") + "</p>";
popupText += descriptionText;
popupText += paramsTable;
auto returns = jsdocObject.value("returns");
if (!returns.isUndefined()) {
foreach(auto returnEntry, returns.toArray()) {
auto returnsObject = returnEntry.toObject();
auto returnsDescription = returnsObject.value("description").toString().replace(JSDOC_LINE_SEPARATOR, "<br>");
popupText += "<h4>Returns</h4><p>" + returnsDescription + "</p><h5>Type</h5><pre><code>" +
_jsdocTypeToString(returnsObject.value("type")) + "</code></pre>";
}
}
if (!examples.isEmpty()) {
popupText += "<h4>Examples</h4>";
foreach(auto example, examples) {
auto exampleText = example.toString();
auto exampleLines = exampleText.split(JSDOC_LINE_SEPARATOR);
foreach(auto exampleLine, exampleLines) {
if (exampleLine.contains("<caption>")) {
popupText += exampleLine.replace("caption>", "h5>");
} else {
popupText += "<pre><code>" + exampleLine + "\n</code></pre>";
}
}
}
}
QToolTip::showText(QPoint(_completer->popup()->pos().x() + _completer->popup()->width(), _completer->popup()->pos().y()),
popupText, _completer->popup());
}
JSConsole::~JSConsole() {
if (_scriptEngine) {
disconnect(_scriptEngine.data(), SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
disconnect(_scriptEngine.data(), SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
disconnect(_scriptEngine.data(), nullptr, this, nullptr);
_scriptEngine.reset();
}
delete _ui;
}
void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) {
if (_scriptEngine == scriptEngine && scriptEngine != NULL) {
if (_scriptEngine == scriptEngine && scriptEngine != nullptr) {
return;
}
if (_scriptEngine != NULL) {
disconnect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
disconnect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
disconnect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
disconnect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError);
if (_scriptEngine != nullptr) {
disconnect(_scriptEngine.data(), nullptr, this, nullptr);
_scriptEngine.reset();
}
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
// if scriptEngine is nullptr then create one and keep track of it using _ownScriptEngine
if (scriptEngine.isNull()) {
_scriptEngine = DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false);
} else {
@ -199,45 +411,121 @@ void JSConsole::showEvent(QShowEvent* event) {
}
bool JSConsole::eventFilter(QObject* sender, QEvent* event) {
if (sender == _ui->promptTextEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
int key = keyEvent->key();
if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
int key = keyEvent->key();
if ((key == Qt::Key_Return || key == Qt::Key_Enter)) {
if (keyEvent->modifiers() & Qt::ShiftModifier) {
// If the shift key is being used then treat it as a regular return/enter. If this isn't done,
// a new QTextBlock isn't created.
keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
} else {
QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed();
if (!command.isEmpty()) {
QTextCursor cursor = _ui->promptTextEdit->textCursor();
_ui->promptTextEdit->clear();
executeCommand(command);
}
return true;
}
} else if (key == Qt::Key_Down) {
// Go to the next command in history if the cursor is at the last line of the current command.
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
int blockCount = _ui->promptTextEdit->document()->blockCount();
if (blockNumber == blockCount - 1) {
setToNextCommandInHistory();
return true;
}
} else if (key == Qt::Key_Up) {
// Go to the previous command in history if the cursor is at the first line of the current command.
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
if (blockNumber == 0) {
setToPreviousCommandInHistory();
return true;
}
if (_completer->popup()->isVisible()) {
// The following keys are forwarded by the completer to the widget
switch (key) {
case Qt::Key_Space:
case Qt::Key_Enter:
case Qt::Key_Return:
insertCompletion(_completer->popup()->currentIndex());
_completer->popup()->hide();
return true;
default:
return false;
}
}
if (key == Qt::Key_Return || key == Qt::Key_Enter) {
if (keyEvent->modifiers() & Qt::ShiftModifier) {
// If the shift key is being used then treat it as a regular return/enter. If this isn't done,
// a new QTextBlock isn't created.
keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
} else {
QString command = _ui->promptTextEdit->toPlainText().replace("\r\n", "\n").trimmed();
if (!command.isEmpty()) {
QTextCursor cursor = _ui->promptTextEdit->textCursor();
_ui->promptTextEdit->clear();
executeCommand(command);
}
return true;
}
} else if (key == Qt::Key_Down) {
// Go to the next command in history if the cursor is at the last line of the current command.
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
int blockCount = _ui->promptTextEdit->document()->blockCount();
if (blockNumber == blockCount - 1) {
setToNextCommandInHistory();
return true;
}
} else if (key == Qt::Key_Up) {
// Go to the previous command in history if the cursor is at the first line of the current command.
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
if (blockNumber == 0) {
setToPreviousCommandInHistory();
return true;
}
}
} else if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyRelease) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
int key = keyEvent->key();
// completer shortcut (CTRL + SPACE)
bool isCompleterShortcut = ((keyEvent->modifiers() & Qt::ControlModifier) && key == Qt::Key_Space) ||
key == Qt::Key_Period;
if (_completer->popup()->isVisible() || isCompleterShortcut) {
const bool ctrlOrShift = keyEvent->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
if (ctrlOrShift && keyEvent->text().isEmpty()) {
return false;
}
static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word
if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) {
_completer->popup()->hide();
return false;
}
auto textCursor = _ui->promptTextEdit->textCursor();
textCursor.select(QTextCursor::WordUnderCursor);
QString completionPrefix = textCursor.selectedText();
auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position());
// RegEx [3] [4]
// (Module.subModule).(property/subModule)
const int MODULE_INDEX = 3;
const int PROPERTY_INDEX = 4;
// TODO: disallow invalid characters on left of property
QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$");
regExp.indexIn(leftOfCursor);
auto rexExpCapturedTexts = regExp.capturedTexts();
auto memberOf = rexExpCapturedTexts[MODULE_INDEX];
completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX];
bool switchedModule = false;
if (memberOf != _completerModule) {
_completerModule = memberOf;
auto autoCompleteModel = getAutoCompleteModel(memberOf);
if (autoCompleteModel == nullptr) {
_completer->popup()->hide();
return false;
}
_completer->setModel(autoCompleteModel);
_completer->popup()->installEventFilter(this);
switchedModule = true;
}
if (switchedModule || completionPrefix != _completer->completionPrefix()) {
_completer->setCompletionPrefix(completionPrefix);
_completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0));
}
auto cursorRect = _ui->promptTextEdit->cursorRect();
cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) +
_completer->popup()->verticalScrollBar()->sizeHint().width());
_completer->complete(cursorRect);
highlightedCompletion(_completer->popup()->currentIndex());
return false;
}
}
return false;
}
@ -321,7 +609,7 @@ void JSConsole::appendMessage(const QString& gutter, const QString& message) {
void JSConsole::clear() {
QLayoutItem* item;
while ((item = _ui->logArea->layout()->takeAt(0)) != NULL) {
while ((item = _ui->logArea->layout()->takeAt(0)) != nullptr) {
delete item->widget();
delete item;
}

View file

@ -12,12 +12,11 @@
#ifndef hifi_JSConsole_h
#define hifi_JSConsole_h
#include <QDialog>
#include <QEvent>
#include <QFutureWatcher>
#include <QObject>
#include <QWidget>
#include <QSharedPointer>
#include <QCompleter>
#include <QtCore/QJsonArray>
#include "ui_console.h"
#include "ScriptEngine.h"
@ -54,12 +53,20 @@ protected slots:
void handleError(const QString& message, const QString& scriptName);
void commandFinished();
private slots:
void insertCompletion(const QModelIndex& completion);
void highlightedCompletion(const QModelIndex& completion);
private:
void appendMessage(const QString& gutter, const QString& message);
void setToNextCommandInHistory();
void setToPreviousCommandInHistory();
void resetCurrentCommandHistory();
void readAPI();
QStandardItemModel* getAutoCompleteModel(const QString& memberOf = nullptr);
QFutureWatcher<QScriptValue> _executeWatcher;
Ui::Console* _ui;
int _currentCommandInHistory;
@ -68,6 +75,9 @@ private:
QString _rootCommand;
ScriptEnginePointer _scriptEngine;
static const QString _consoleFileName;
QJsonArray _apiDocs;
QCompleter* _completer;
QString _completerModule {""};
};

View file

@ -139,7 +139,7 @@ public slots:
Q_INVOKABLE bool canRezTmpCertified();
/**jsdoc
* @function Entities.canWriteAsseets
* @function Entities.canWriteAssets
* @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server
*/
Q_INVOKABLE bool canWriteAssets();

View file

@ -1679,14 +1679,17 @@ void EntityTree::entityChanged(EntityItemPointer entity) {
}
}
void EntityTree::fixupNeedsParentFixups() {
PROFILE_RANGE(simulation_physics, "FixupParents");
MovingEntitiesOperator moveOperator;
QVector<EntityItemWeakPointer> entitiesToFixup;
{
QWriteLocker locker(&_needsParentFixupLock);
entitiesToFixup = _needsParentFixup;
_needsParentFixup.clear();
}
QWriteLocker locker(&_needsParentFixupLock);
QMutableVectorIterator<EntityItemWeakPointer> iter(_needsParentFixup);
QMutableVectorIterator<EntityItemWeakPointer> iter(entitiesToFixup);
while (iter.hasNext()) {
EntityItemWeakPointer entityWP = iter.next();
EntityItemPointer entity = entityWP.lock();
@ -1749,6 +1752,12 @@ void EntityTree::fixupNeedsParentFixups() {
PerformanceTimer perfTimer("recurseTreeWithOperator");
recurseTreeWithOperator(&moveOperator);
}
{
QWriteLocker locker(&_needsParentFixupLock);
// add back the entities that did not get fixup
_needsParentFixup.append(entitiesToFixup);
}
}
void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) {

View file

@ -340,14 +340,6 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
return resource;
}
if (QThread::currentThread() != thread()) {
qCDebug(networking) << "Fetching asynchronously:" << url;
QMetaObject::invokeMethod(this, "getResource",
Q_ARG(QUrl, url), Q_ARG(QUrl, fallback));
// Cannot use extra parameter as it might be freed before the invocation
return QSharedPointer<Resource>();
}
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
return getResource(fallback, QUrl());
}
@ -358,6 +350,7 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
extra);
resource->setSelf(resource);
resource->setCache(this);
resource->moveToThread(qApp->thread());
connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);
{
QWriteLocker locker(&_resourcesLock);

View file

@ -115,6 +115,7 @@ void CauterizedModel::updateClusterMatrices() {
Transform clusterTransform;
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
state.clusterTransforms[j].setCauterizationParameters(0.0f, jointPose.trans());
#else
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
@ -151,6 +152,7 @@ void CauterizedModel::updateClusterMatrices() {
Transform clusterTransform;
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
state.clusterTransforms[j].setCauterizationParameters(1.0f, cauterizePose.trans());
#else
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
#endif

View file

@ -99,7 +99,6 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie
// Diffuse from ambient
diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), lowNormalCurvature.xyz).xyz;
diffuse /= 3.1415926;
specular = vec3(0.0);
}
<@endif@>

View file

@ -263,26 +263,34 @@ public:
_scale.x = p.scale().x;
_scale.y = p.scale().y;
_scale.z = p.scale().z;
_scale.w = 0.0f;
_dq = DualQuaternion(p.rot(), p.trans());
}
TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) {
_scale.x = scale.x;
_scale.y = scale.y;
_scale.z = scale.z;
_scale.w = 0.0f;
_dq = DualQuaternion(rot, trans);
}
TransformDualQuaternion(const Transform& transform) {
_scale = glm::vec4(transform.getScale(), 0.0f);
_scale.w = 0.0f;
_dq = DualQuaternion(transform.getRotation(), transform.getTranslation());
}
glm::vec3 getScale() const { return glm::vec3(_scale); }
glm::quat getRotation() const { return _dq.getRotation(); }
glm::vec3 getTranslation() const { return _dq.getTranslation(); }
glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); };
void setCauterizationParameters(float cauterizationAmount, const glm::vec3& cauterizedPosition) {
_scale.w = cauterizationAmount;
_cauterizedPosition = glm::vec4(cauterizedPosition, 1.0f);
}
protected:
glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f };
DualQuaternion _dq;
glm::vec4 _padding;
glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f };
};
#endif

View file

@ -58,17 +58,19 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) {
void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) {
// linearly blend scale and dual quaternion components
vec3 sAccum = vec3(0.0, 0.0, 0.0);
vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
float clusterWeight = skinClusterWeight[i];
vec3 scale = vec3(clusterMatrix[0]);
vec4 scale = clusterMatrix[0];
vec4 real = clusterMatrix[1];
vec4 dual = clusterMatrix[2];
vec4 cauterizedPos = clusterMatrix[3];
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
float dqClusterWeight = clusterWeight;
@ -79,6 +81,7 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio
sAccum += scale * clusterWeight;
rAccum += real * dqClusterWeight;
dAccum += dual * dqClusterWeight;
cAccum += cauterizedPos * clusterWeight;
}
// normalize dual quaternion
@ -88,25 +91,37 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio
// conversion from dual quaternion to 4x4 matrix.
mat4 m = dualQuatToMat4(rAccum, dAccum);
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
// sAccum.w indicates the amount of cauterization for this vertex.
// 0 indicates no cauterization and 1 indicates full cauterization.
// TODO: make this cauterization smoother or implement full dual-quaternion scale support.
const float CAUTERIZATION_THRESHOLD = 0.1;
if (sAccum.w > CAUTERIZATION_THRESHOLD) {
skinnedPosition = cAccum;
} else {
sAccum.w = 1.0;
skinnedPosition = m * (sAccum * inPosition);
}
}
void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal,
out vec4 skinnedPosition, out vec3 skinnedNormal) {
// linearly blend scale and dual quaternion components
vec3 sAccum = vec3(0.0, 0.0, 0.0);
vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
float clusterWeight = skinClusterWeight[i];
vec3 scale = vec3(clusterMatrix[0]);
vec4 scale = clusterMatrix[0];
vec4 real = clusterMatrix[1];
vec4 dual = clusterMatrix[2];
vec4 cauterizedPos = clusterMatrix[3];
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
float dqClusterWeight = clusterWeight;
@ -117,6 +132,7 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP
sAccum += scale * clusterWeight;
rAccum += real * dqClusterWeight;
dAccum += dual * dqClusterWeight;
cAccum += cauterizedPos * clusterWeight;
}
// normalize dual quaternion
@ -126,7 +142,18 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP
// conversion from dual quaternion to 4x4 matrix.
mat4 m = dualQuatToMat4(rAccum, dAccum);
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
// sAccum.w indicates the amount of cauterization for this vertex.
// 0 indicates no cauterization and 1 indicates full cauterization.
// TODO: make this cauterization smoother or implement full dual-quaternion scale support.
const float CAUTERIZATION_THRESHOLD = 0.1;
if (sAccum.w > CAUTERIZATION_THRESHOLD) {
skinnedPosition = cAccum;
} else {
sAccum.w = 1.0;
skinnedPosition = m * (sAccum * inPosition);
}
skinnedNormal = vec3(m * vec4(inNormal, 0));
}
@ -134,18 +161,20 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
out vec4 skinnedPosition, out vec3 skinnedNormal, out vec3 skinnedTangent) {
// linearly blend scale and dual quaternion components
vec3 sAccum = vec3(0.0, 0.0, 0.0);
vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
float clusterWeight = skinClusterWeight[i];
vec3 scale = vec3(clusterMatrix[0]);
vec4 scale = clusterMatrix[0];
vec4 real = clusterMatrix[1];
vec4 dual = clusterMatrix[2];
vec4 cauterizedPos = clusterMatrix[3];
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
float dqClusterWeight = clusterWeight;
@ -156,6 +185,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
sAccum += scale * clusterWeight;
rAccum += real * dqClusterWeight;
dAccum += dual * dqClusterWeight;
cAccum += cauterizedPos * clusterWeight;
}
// normalize dual quaternion
@ -165,7 +195,18 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
// conversion from dual quaternion to 4x4 matrix.
mat4 m = dualQuatToMat4(rAccum, dAccum);
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
// sAccum.w indicates the amount of cauterization for this vertex.
// 0 indicates no cauterization and 1 indicates full cauterization.
// TODO: make this cauterization smoother or implement full dual-quaternion scale support.
const float CAUTERIZATION_THRESHOLD = 0.1;
if (sAccum.w > CAUTERIZATION_THRESHOLD) {
skinnedPosition = cAccum;
} else {
sAccum.w = 1.0;
skinnedPosition = m * (sAccum * inPosition);
}
skinnedNormal = vec3(m * vec4(inNormal, 0));
skinnedTangent = vec3(m * vec4(inTangent, 0));
}

View file

@ -70,36 +70,40 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program
void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state,
BatchSetter batchSetter, ItemSetter itemSetter) {
ShapeKey key{ filter._flags };
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL));
slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING));
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL));
slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY));
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO));
slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS));
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL));
slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC));
slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP));
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION));
slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING));
slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), Slot::BUFFER::KEY_LIGHT));
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT));
slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER));
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT));
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK));
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS));
slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL));
// don't call makeProgram on shaders that are already made.
if (program->getUniformBuffers().empty()) {
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL));
slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING));
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL));
slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY));
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO));
slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS));
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL));
slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC));
slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP));
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION));
slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING));
slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), Slot::BUFFER::KEY_LIGHT));
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT));
slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER));
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT));
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK));
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS));
slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL));
if (key.isTranslucent()) {
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
if (key.isTranslucent()) {
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
}
gpu::Shader::makeProgram(*program, slotBindings);
}
gpu::Shader::makeProgram(*program, slotBindings);
auto locations = std::make_shared<Locations>();
locations->albedoTextureUnit = program->getTextures().findLocation("albedoMap");

View file

@ -150,7 +150,7 @@ signals:
/**jsdoc
* Notifies scripts that a user has disconnected from the domain
* @function Users.avatar.avatarDisconnected
* @function Users.avatarDisconnected
* @param {nodeID} NodeID The session ID of the avatar that has disconnected
*/
void avatarDisconnected(const QUuid& nodeID);

View file

@ -12,4 +12,4 @@ if (ANDROID)
endif()
target_zlib()
target_nsight()
target_nsight()

View file

@ -20,6 +20,7 @@
#include "SettingHelpers.h"
#include "SettingManager.h"
#include "SharedLogging.h"
#include "SharedUtil.h"
namespace Setting {
static QSharedPointer<Manager> globalManager;
@ -32,14 +33,34 @@ namespace Setting {
// tell the private instance to clean itself up on its thread
DependencyManager::destroy<Manager>();
//
globalManager.reset();
// quit the settings manager thread and wait on it to make sure it's gone
settingsManagerThread->quit();
settingsManagerThread->wait();
}
void setupPrivateInstance() {
// Ensure Setting::init has already ran and qApp exists
if (qApp && globalManager) {
// Let's set up the settings Private instance on its own thread
QThread* thread = new QThread();
Q_CHECK_PTR(thread);
thread->setObjectName("Settings Thread");
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
globalManager->moveToThread(thread);
thread->start();
qCDebug(shared) << "Settings thread started.";
// Register cleanupPrivateInstance to run inside QCoreApplication's destructor.
qAddPostRoutine(cleanupPrivateInstance);
}
}
FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance)
// Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand,
void init() {
// Set settings format
@ -59,23 +80,9 @@ namespace Setting {
qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename;
}
// Let's set up the settings Private instance on its own thread
QThread* thread = new QThread();
Q_CHECK_PTR(thread);
thread->setObjectName("Settings Thread");
globalManager = DependencyManager::set<Manager>();
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
globalManager->moveToThread(thread);
thread->start();
qCDebug(shared) << "Settings thread started.";
// Register cleanupPrivateInstance to run inside QCoreApplication's destructor.
qAddPostRoutine(cleanupPrivateInstance);
setupPrivateInstance();
}
void Interface::init() {

View file

@ -20,6 +20,7 @@
#include <mutex>
#include <thread>
#include <set>
#include <unordered_map>
#include <glm/glm.hpp>
@ -62,6 +63,43 @@ extern "C" FILE * __cdecl __iob_func(void) {
#include "OctalCode.h"
#include "SharedLogging.h"
static std::unordered_map<std::string, QVariant> stagedGlobalInstances;
std::mutex& globalInstancesMutex() {
static std::mutex mutex;
return mutex;
}
static void commitGlobalInstances() {
std::unique_lock<std::mutex> lock(globalInstancesMutex());
for (const auto& it : stagedGlobalInstances) {
qApp->setProperty(it.first.c_str(), it.second);
}
stagedGlobalInstances.clear();
}
FIXED_Q_COREAPP_STARTUP_FUNCTION(commitGlobalInstances)
QVariant getGlobalInstance(const char* propertyName) {
if (qApp) {
return qApp->property(propertyName);
} else {
auto it = stagedGlobalInstances.find(propertyName);
if (it != stagedGlobalInstances.end()) {
return it->second;
}
}
return QVariant();
}
void setGlobalInstance(const char* propertyName, const QVariant& variant) {
if (qApp) {
qApp->setProperty(propertyName, variant);
} else {
stagedGlobalInstances[propertyName] = variant;
}
}
static qint64 usecTimestampNowAdjust = 0; // in usec
void usecTimestampNowForceClockSkew(qint64 clockSkew) {
::usecTimestampNowAdjust = clockSkew;

View file

@ -25,6 +25,22 @@
#include <QtCore/QCoreApplication>
#include <QUuid>
// Workaround for https://bugreports.qt.io/browse/QTBUG-54479
// Wrap target function inside another function that holds
// a unique string identifier and uses it to ensure it only runs once
// by storing a state within the qApp
// We cannot used std::call_once with a static once_flag because
// this is used in shared libraries that are linked by several DLLs
// (ie. plugins), meaning the static will be useless in that case
#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \
static void AFUNC ## _fixed() { \
const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \
if (!qApp->property(propertyName.c_str()).toBool()) { \
AFUNC(); \
qApp->setProperty(propertyName.c_str(), QVariant(true)); \
} \
} \
Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed)
// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows
// the value to be reset when the sessionID changes.
@ -52,6 +68,10 @@ bool destroyGlobalInstance() {
return false;
}
std::mutex& globalInstancesMutex();
QVariant getGlobalInstance(const char* propertyName);
void setGlobalInstance(const char* propertyName, const QVariant& variant);
// Provides efficient access to a named global type. By storing the value
// in the QApplication by name we can implement the singleton pattern and
// have the single instance function across DLL boundaries.
@ -60,9 +80,9 @@ T* globalInstance(const char* propertyName, Args&&... args) {
static T* resultInstance { nullptr };
static std::mutex mutex;
if (!resultInstance) {
std::unique_lock<std::mutex> lock(mutex);
std::unique_lock<std::mutex> lock(globalInstancesMutex());
if (!resultInstance) {
auto variant = qApp->property(propertyName);
auto variant = getGlobalInstance(propertyName);
if (variant.isNull()) {
std::unique_ptr<T>& instancePtr = globalInstancePointer<T>();
if (!instancePtr.get()) {
@ -72,7 +92,7 @@ T* globalInstance(const char* propertyName, Args&&... args) {
}
void* voidInstance = &(*instancePtr);
variant = QVariant::fromValue(voidInstance);
qApp->setProperty(propertyName, variant);
setGlobalInstance(propertyName, variant);
}
void* returnedVoidInstance = variant.value<void*>();
resultInstance = static_cast<T*>(returnedVoidInstance);

View file

@ -78,7 +78,7 @@
});
}
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise)
url = METAVERSE_BASE + '/api/v1/users?'
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {

View file

@ -469,7 +469,7 @@ var toolBar = (function () {
// tablet version of new-model dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack("NewModelDialog.qml");
tablet.pushOntoStack("hifi/tablet/NewModelDialog.qml");
});
addButton("newCubeButton", "cube-01.svg", function () {

View file

@ -361,7 +361,7 @@ function getProfilePicture(username, callback) { // callback(url) if successfull
});
}
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
url = METAVERSE_BASE + '/api/v1/users?'
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {

View file

@ -100,7 +100,7 @@
button.editProperties({isActive: shouldActivateButton});
wireEventBridge(true);
messagesWaiting(false);
tablet.sendToQml({ method: 'refreshFeeds' })
tablet.sendToQml({ method: 'refreshFeeds', protocolSignature: Window.protocolSignature() })
} else {
shouldActivateButton = false;

View file

@ -33,8 +33,6 @@ private:
Qt59TestApp::Qt59TestApp(int argc, char* argv[]) :
QCoreApplication(argc, argv)
{
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<AccountManager>([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); });
DependencyManager::set<AddressManager>();

View file

@ -2,6 +2,12 @@
add_subdirectory(scribe)
set_target_properties(scribe PROPERTIES FOLDER "Tools")
find_npm()
if (NPM_EXECUTABLE)
add_subdirectory(jsdoc)
set_target_properties(jsdoc PROPERTIES FOLDER "Tools")
endif()
if (BUILD_TOOLS)
add_subdirectory(udt-test)
set_target_properties(udt-test PROPERTIES FOLDER "Tools")
@ -27,4 +33,3 @@ if (BUILD_TOOLS)
add_subdirectory(auto-tester)
set_target_properties(auto-tester PROPERTIES FOLDER "Tools")
endif()

View file

@ -97,7 +97,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
_password = pieces[1];
}
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<AccountManager>([&]{ return QString("Mozilla/5.0 (HighFidelityACClient)"); });

View file

@ -25,6 +25,8 @@ int main(int argc, char * argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
Setting::init();
ACClientApp app(argc, argv);
return app.exec();

View file

@ -135,7 +135,6 @@ ATPClientApp::ATPClientApp(int argc, char* argv[]) :
_domainServerAddress = domainURL.toString();
}
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<StatTracker>();

View file

@ -25,6 +25,8 @@ int main(int argc, char * argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
Setting::init();
ATPClientApp app(argc, argv);
return app.exec();

View file

@ -0,0 +1,17 @@
set(TARGET_NAME jsdoc)
add_custom_target(${TARGET_NAME})
find_npm()
set(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc)
file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH)
file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH)
file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR)
file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR)
add_custom_command(TARGET ${TARGET_NAME}
COMMAND ${NPM_EXECUTABLE} --no-progress install && ${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}
WORKING_DIRECTORY ${JSDOC_WORKING_DIR}
COMMENT "generate the JSDoc JSON for the JSConsole auto-completer"
)

View file

@ -4,5 +4,8 @@
"outputSourceFiles": false
}
},
"plugins": ["plugins/hifi"]
"plugins": [
"plugins/hifi",
"plugins/hifiJSONExport"
]
}

7
tools/jsdoc/package.json Normal file
View file

@ -0,0 +1,7 @@
{
"name": "hifiJSDoc",
"dependencies": {
"jsdoc": "^3.5.5"
},
"private": true
}

View file

@ -10,6 +10,8 @@ function endsWith(path, exts) {
exports.handlers = {
beforeParse: function(e) {
const pathTools = require('path');
var rootFolder = pathTools.dirname(e.filename);
console.log("Scanning hifi source for jsdoc comments...");
// directories to scan for jsdoc comments
@ -34,9 +36,10 @@ exports.handlers = {
const fs = require('fs');
dirList.forEach(function (dir) {
var files = fs.readdirSync(dir)
var joinedDir = pathTools.join(rootFolder, dir);
var files = fs.readdirSync(joinedDir)
files.forEach(function (file) {
var path = dir + "/" + file;
var path = pathTools.join(joinedDir, file);
if (fs.lstatSync(path).isFile() && endsWith(path, exts)) {
var data = fs.readFileSync(path, "utf8");
var reg = /(\/\*\*jsdoc(.|[\r\n])*?\*\/)/gm;

View file

@ -0,0 +1,19 @@
exports.handlers = {
processingComplete: function(e) {
const pathTools = require('path');
var outputFolder = pathTools.join(__dirname, '../out');
var doclets = e.doclets.map(doclet => Object.assign({}, doclet));
const fs = require('fs');
if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder);
}
doclets.map(doclet => {delete doclet.meta; delete doclet.comment});
fs.writeFile(pathTools.join(outputFolder, "hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) {
if (err) {
return console.log(err);
}
console.log("The Hifi JSDoc JSON was saved!");
});
}
};

View file

@ -14,7 +14,6 @@
#include <QtCore/QCommandLineParser>
#include <image/Image.h>
#include <SettingInterface.h>
#include "ui/OvenMainWindow.h"
#include "Oven.h"
@ -29,12 +28,6 @@ static const QString CLI_TYPE_PARAMETER = "t";
Oven::Oven(int argc, char* argv[]) :
QApplication(argc, argv)
{
QCoreApplication::setOrganizationName("High Fidelity");
QCoreApplication::setApplicationName("Oven");
// init the settings interface so we can save and load settings
Setting::init();
// parse the command line parameters
QCommandLineParser parser;

View file

@ -10,7 +10,15 @@
#include "Oven.h"
#include <SettingInterface.h>
int main (int argc, char** argv) {
QCoreApplication::setOrganizationName("High Fidelity");
QCoreApplication::setApplicationName("Oven");
// init the settings interface so we can save and load settings
Setting::init();
Oven app(argc, argv);
return app.exec();
}