mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 21:30:33 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi
This commit is contained in:
commit
a3fa82d72e
74 changed files with 1455 additions and 400 deletions
|
@ -84,7 +84,7 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<PluginManager>();
|
||||
DependencyManager::set<PluginManager>()->instantiate();
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
|
||||
|
@ -511,6 +511,7 @@ void Agent::executeScript() {
|
|||
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
|
||||
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_scriptEngine);
|
||||
_scriptEngine->run();
|
||||
|
||||
Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <HifiConfigVariantMap.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <ShutdownEventListener.h>
|
||||
#include <shared/ScriptInitializerMixin.h>
|
||||
|
||||
#include "Assignment.h"
|
||||
#include "AssignmentClient.h"
|
||||
|
@ -240,6 +241,7 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
QThread::currentThread()->setObjectName("main thread");
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::set<ScriptInitializers>();
|
||||
|
||||
if (numForks || minForks || maxForks) {
|
||||
AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks,
|
||||
|
|
|
@ -499,6 +499,8 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) {
|
|||
} else {
|
||||
_sessionDisplayNames.erase(displayNameIter);
|
||||
}
|
||||
|
||||
nodeData->getAvatar().stopChallengeTimer();
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> killPacket;
|
||||
|
|
|
@ -332,7 +332,7 @@ void MixerAvatar::sendOwnerChallenge() {
|
|||
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||
QByteArray avatarID;
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
QMetaObject::invokeMethod(&_challengeTimer, &QTimer::stop);
|
||||
stopChallengeTimer();
|
||||
if (_verifyState == challengeClient) {
|
||||
QByteArray responseData = response.readAll();
|
||||
if (responseData.length() < 8) {
|
||||
|
@ -365,3 +365,11 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
|||
qCDebug(avatars) << "WARNING: Unexpected avatar challenge-response in state" << stateToName(_verifyState);
|
||||
}
|
||||
}
|
||||
|
||||
void MixerAvatar::stopChallengeTimer() {
|
||||
if (QThread::currentThread() == thread()) {
|
||||
_challengeTimer.stop();
|
||||
} else {
|
||||
QMetaObject::invokeMethod(&_challengeTimer, &QTimer::stop);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
void processCertifyEvents();
|
||||
void processChallengeResponse(ReceivedMessage& response);
|
||||
|
||||
void stopChallengeTimer();
|
||||
|
||||
// Avatar certification/verification:
|
||||
enum VerifyState {
|
||||
nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <EntityTree.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <EntityEditFilters.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
|
@ -41,6 +42,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
|||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<PluginManager>()->instantiate();
|
||||
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
DependencyManager::set<AssignmentDynamicFactory>();
|
||||
|
|
|
@ -64,7 +64,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
|
|||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
|
||||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<PluginManager>();
|
||||
DependencyManager::set<PluginManager>()->instantiate();
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
|
||||
|
@ -462,7 +462,7 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
|
|||
_entityViewer.getTree()->update();
|
||||
});
|
||||
|
||||
|
||||
scriptEngines->runScriptInitializers(newEngine);
|
||||
newEngine->runInThread();
|
||||
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngineSP);
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
#
|
||||
macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN)
|
||||
set(${TARGET_NAME}_SHARED 1)
|
||||
setup_hifi_library(${ARGV})
|
||||
set(PLUGIN_SUBFOLDER ${ARGN})
|
||||
setup_hifi_library()
|
||||
|
||||
if (BUILD_CLIENT)
|
||||
add_dependencies(interface ${TARGET_NAME})
|
||||
|
@ -27,6 +28,11 @@ macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN)
|
|||
set(SERVER_PLUGIN_PATH "plugins")
|
||||
endif()
|
||||
|
||||
if (PLUGIN_SUBFOLDER)
|
||||
set(CLIENT_PLUGIN_PATH "${CLIENT_PLUGIN_PATH}/${PLUGIN_SUBFOLDER}")
|
||||
set(SERVER_PLUGIN_PATH "${SERVER_PLUGIN_PATH}/${PLUGIN_SUBFOLDER}")
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles")
|
||||
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/")
|
||||
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/")
|
||||
|
|
|
@ -1156,7 +1156,17 @@ FunctionEnd
|
|||
|
||||
Section "-Core installation"
|
||||
|
||||
;The following delete blocks are temporary and can be removed once users who had the initial installer have updated
|
||||
; 2016-02-25 - The following delete blocks are temporary and can be removed once users who had the initial installer have updated
|
||||
; 2019-09-10 - (3 and a half years later) Sure they are buddy. Sure they are.
|
||||
|
||||
; MessageBox MB_OK|MB_ICONEXCLAMATION "installer type is @INSTALLER_TYPE@"
|
||||
|
||||
;Delete any server executables that might have been installed by bad versions of the client-only installer, but ONLY if we are a client-only installer
|
||||
${If} "@INSTALLER_TYPE@" == "client_only"
|
||||
; MessageBox MB_OK|MB_ICONEXCLAMATION "trying to delete server binaries"
|
||||
Delete "$INSTDIR\assignment-client.exe"
|
||||
Delete "$INSTDIR\domain-server.exe"
|
||||
${EndIf}
|
||||
|
||||
;Delete any server-console files installed before it was placed in sub-folder
|
||||
Delete "$INSTDIR\server-console.exe"
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -822,6 +822,19 @@
|
|||
},
|
||||
"id": "seatedReactionPositiveCheer",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 64,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_agree_acknowledge.fbx"
|
||||
},
|
||||
"id": "seatedReactionPositiveAcknowledge",
|
||||
"type": "clip"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
|
@ -867,6 +880,15 @@
|
|||
"resume": false,
|
||||
"transitions": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "seatedReactionPositiveAcknowledge",
|
||||
"interpDuration": 1,
|
||||
"interpTarget": 1,
|
||||
"priority": 1,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeScale": 1,
|
||||
|
@ -916,6 +938,19 @@
|
|||
},
|
||||
"id": "seatedReactionNegativeDisagreeDisbelief",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 70,
|
||||
"loopFlag": false,
|
||||
"startFrame": 0,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_disagree_dismiss.fbx"
|
||||
},
|
||||
"id": "seatedReactionNegativeDisagreeDismiss",
|
||||
"type": "clip"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
|
@ -958,6 +993,17 @@
|
|||
"resume": false,
|
||||
"transitions": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionNegativeDisagreeDismiss",
|
||||
"interpDuration": 1,
|
||||
"interpTarget": 1,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 1,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeScale": 1,
|
||||
|
@ -1046,6 +1092,47 @@
|
|||
},
|
||||
"id": "seatedReactionRaiseHand02Outro",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 15,
|
||||
"loopFlag": false,
|
||||
"startFrame": 0,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx"
|
||||
},
|
||||
"id": "seatedReactionRaiseHand03Intro",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 233,
|
||||
"loopFlag": true,
|
||||
"mirrorFlag": false,
|
||||
"startFrame": 15,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx"
|
||||
},
|
||||
"id": "seatedReactionRaiseHand03Loop",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 296,
|
||||
"loopFlag": false,
|
||||
"mirrorFlag": false,
|
||||
"startFrame": 233,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx"
|
||||
},
|
||||
"id": "seatedReactionRaiseHand03Outro",
|
||||
"type": "clip"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
|
@ -1057,7 +1144,7 @@
|
|||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionRaiseHandIntro",
|
||||
"interpDuration": 8,
|
||||
"interpTarget": 8,
|
||||
"interpTarget": 9,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 1,
|
||||
"resume": false,
|
||||
|
@ -1138,6 +1225,49 @@
|
|||
"var": "reactionRaiseHandEnabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionRaiseHand03Intro",
|
||||
"interpDuration": 8,
|
||||
"interpTarget": 8,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 1,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
{
|
||||
"randomSwitchState": "seatedReactionRaiseHand03Loop",
|
||||
"var": "seatedReactionRaiseHand03IntroOnDone"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "seatedReactionRaiseHand03Loop",
|
||||
"interpDuration": 1,
|
||||
"interpTarget": 1,
|
||||
"priority": 0,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
{
|
||||
"randomSwitchState": "seatedReactionRaiseHand03Outro",
|
||||
"var": "reactionRaiseHandDisabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionRaiseHand03Outro",
|
||||
"interpDuration": 12,
|
||||
"interpTarget": 12,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 0,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
{
|
||||
"randomSwitchState": "seatedReactionRaiseHand03Loop",
|
||||
"var": "reactionRaiseHandEnabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"triggerRandomSwitch": ""
|
||||
|
@ -1224,6 +1354,45 @@
|
|||
},
|
||||
"id": "seatedReactionApplaud02Outro",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 17,
|
||||
"loopFlag": false,
|
||||
"startFrame": 0,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx"
|
||||
},
|
||||
"id": "seatedReactionApplaud03Intro",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 111,
|
||||
"loopFlag": true,
|
||||
"startFrame": 17,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx"
|
||||
},
|
||||
"id": "seatedReactionApplaud03Loop",
|
||||
"type": "clip"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 136,
|
||||
"loopFlag": false,
|
||||
"startFrame": 111,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx"
|
||||
},
|
||||
"id": "seatedReactionApplaud03Outro",
|
||||
"type": "clip"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
|
@ -1320,6 +1489,51 @@
|
|||
"var": "reactionApplaudEnabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionApplaud03Intro",
|
||||
"interpDuration": 8,
|
||||
"interpTarget": 8,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 1,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
{
|
||||
"randomSwitchState": "seatedReactionApplaud03Loop",
|
||||
"var": "seatedReactionApplaud03IntroOnDone"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionApplaud03Loop",
|
||||
"interpDuration": 1,
|
||||
"interpTarget": 1,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 0,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
{
|
||||
"randomSwitchState": "seatedReactionApplaud03Outro",
|
||||
"var": "reactionApplaudDisabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "seatedReactionApplaud03Outro",
|
||||
"interpDuration": 12,
|
||||
"interpTarget": 12,
|
||||
"interpType": "evaluateBoth",
|
||||
"priority": 0,
|
||||
"resume": false,
|
||||
"transitions": [
|
||||
{
|
||||
"randomSwitchState": "seatedReactionApplaud03Loop",
|
||||
"var": "reactionApplaudEnabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"triggerRandomSwitch": ""
|
||||
|
@ -1333,7 +1547,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 22,
|
||||
"endFrame": 21,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -1346,9 +1560,9 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 43,
|
||||
"endFrame": 100,
|
||||
"loopFlag": true,
|
||||
"startFrame": 22,
|
||||
"startFrame": 21,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_point_all.fbx"
|
||||
},
|
||||
|
@ -1359,9 +1573,10 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 71,
|
||||
"endFrame": 134,
|
||||
"loopFlag": false,
|
||||
"startFrame": 43,
|
||||
"mirrorFlag": false,
|
||||
"startFrame": 100,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/sitting_emote_point_all.fbx"
|
||||
},
|
||||
|
@ -1481,6 +1696,10 @@
|
|||
"state": "seatedTalkOverlay",
|
||||
"var": "seatedReactionPositiveCheerOnDone"
|
||||
},
|
||||
{
|
||||
"state": "seatedTalkOverlay",
|
||||
"var": "seatedReactionPositiveAcknowledgeOnDone"
|
||||
},
|
||||
{
|
||||
"state": "seatedReactionNegative",
|
||||
"var": "reactionNegativeTrigger"
|
||||
|
@ -1522,6 +1741,10 @@
|
|||
"state": "seatedTalkOverlay",
|
||||
"var": "seatedReactionNegativeDisagreeDisbeliefOnDone"
|
||||
},
|
||||
{
|
||||
"state": "seatedTalkOverlay",
|
||||
"var": "seatedReactionNegativeDisagreeDismissOnDone"
|
||||
},
|
||||
{
|
||||
"state": "seatedReactionRaiseHand",
|
||||
"var": "reactionRaiseHandEnabled"
|
||||
|
@ -2430,7 +2653,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 58,
|
||||
"endFrame": 64,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -2456,7 +2679,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 78,
|
||||
"endFrame": 94,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -2469,7 +2692,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 66,
|
||||
"endFrame": 68,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -2482,7 +2705,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 85,
|
||||
"endFrame": 84,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -2979,7 +3202,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 18,
|
||||
"endFrame": 17,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -2992,9 +3215,9 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 97,
|
||||
"endFrame": 111,
|
||||
"loopFlag": true,
|
||||
"startFrame": 18,
|
||||
"startFrame": 17,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/emote_clap01_all.fbx"
|
||||
},
|
||||
|
@ -3005,9 +3228,9 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 146,
|
||||
"endFrame": 160,
|
||||
"loopFlag": false,
|
||||
"startFrame": 97,
|
||||
"startFrame": 111,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/emote_clap01_all.fbx"
|
||||
},
|
||||
|
@ -3308,7 +3531,7 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 22,
|
||||
"endFrame": 21,
|
||||
"loopFlag": false,
|
||||
"startFrame": 1,
|
||||
"timeScale": 1,
|
||||
|
@ -3321,9 +3544,9 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 40,
|
||||
"endFrame": 100,
|
||||
"loopFlag": true,
|
||||
"startFrame": 22,
|
||||
"startFrame": 21,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/emote_point01_all.fbx"
|
||||
},
|
||||
|
@ -3334,9 +3557,9 @@
|
|||
"children": [
|
||||
],
|
||||
"data": {
|
||||
"endFrame": 78,
|
||||
"endFrame": 134,
|
||||
"loopFlag": false,
|
||||
"startFrame": 40,
|
||||
"startFrame": 100,
|
||||
"timeScale": 1,
|
||||
"url": "qrc:///avatar/animations/emote_point01_all.fbx"
|
||||
},
|
||||
|
@ -3405,8 +3628,8 @@
|
|||
{
|
||||
"easingType": "easeInOutQuad",
|
||||
"id": "idleTalkOverlay",
|
||||
"interpDuration": 24,
|
||||
"interpTarget": 24,
|
||||
"interpDuration": 20,
|
||||
"interpTarget": 20,
|
||||
"interpType": "evaluateBoth",
|
||||
"transitions": [
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Copyright 2016 High Fidelity, Inc. -->
|
||||
<!-- Copyright 2016 High Fidelity, Inc. -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
|
@ -77,9 +77,9 @@
|
|||
var handControllerImageURL = null;
|
||||
var index = 0;
|
||||
var count = 3;
|
||||
var handControllerRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/vr-controls.html#vr-controls";
|
||||
var keyboardRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/desktop.html#movement-controls";
|
||||
var gamepadRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/vr-controls.html#gamepad";
|
||||
var handControllerRefURL = "https://docs.highfidelity.com/explore/get-started/vr-controls.html#vr-controls";
|
||||
var keyboardRefURL = "https://docs.highfidelity.com/explore/get-started/desktop.html#movement-controls";
|
||||
var gamepadRefURL = "https://docs.highfidelity.com/explore/get-started/vr-controls.html#gamepad";
|
||||
|
||||
function showKbm() {
|
||||
document.getElementById("main_image").setAttribute("src", "img/tablet-help-keyboard.jpg");
|
||||
|
|
|
@ -266,6 +266,7 @@ Rectangle {
|
|||
labelTextSize: 16;
|
||||
backgroundOnColor: "#E3E3E3";
|
||||
checked: AudioScriptingInterface.warnWhenMuted;
|
||||
visible: bar.currentIndex !== 0;
|
||||
onClicked: {
|
||||
AudioScriptingInterface.warnWhenMuted = checked;
|
||||
checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding
|
||||
|
@ -277,8 +278,8 @@ Rectangle {
|
|||
id: audioLevelSwitch
|
||||
height: root.switchHeight;
|
||||
switchWidth: root.switchWidth;
|
||||
anchors.top: warnMutedSwitch.bottom
|
||||
anchors.topMargin: 24
|
||||
anchors.top: warnMutedSwitch.visible ? warnMutedSwitch.bottom : parent.top
|
||||
anchors.topMargin: bar.currentIndex === 0 ? 0 : 24
|
||||
anchors.left: parent.left
|
||||
labelTextOn: qsTr("Audio Level Meter");
|
||||
labelTextSize: 16;
|
||||
|
|
|
@ -240,9 +240,96 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// -- Plugin Permissions --
|
||||
Item {
|
||||
id: kpiContainer;
|
||||
anchors.top: accountContainer.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: childrenRect.height;
|
||||
|
||||
Rectangle {
|
||||
id: kpiHeaderContainer;
|
||||
anchors.top: parent.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: 55;
|
||||
color: hifi.colors.baseGrayHighlight;
|
||||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
text: "Plugin Permissions";
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 20;
|
||||
color: hifi.colors.white;
|
||||
size: 18;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: kpiScriptContainer;
|
||||
anchors.top: kpiHeaderContainer.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: 80;
|
||||
|
||||
HifiControlsUit.CheckBox {
|
||||
id: kpiScriptCheckbox;
|
||||
readonly property string kpiSettingsKey: "private/enableScriptingPlugins"
|
||||
checked: Settings.getValue(kpiSettingsKey, false);
|
||||
text: "Enable custom script plugins (requires restart)"
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 20;
|
||||
boxSize: 24;
|
||||
labelFontSize: 18;
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
color: hifi.colors.white;
|
||||
width: 300;
|
||||
onCheckedChanged: Settings.setValue(kpiSettingsKey, checked);
|
||||
}
|
||||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
id: kpiScriptHelp;
|
||||
text: '[?]';
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: kpiScriptCheckbox.right;
|
||||
width: 30;
|
||||
height: 30;
|
||||
// Text size
|
||||
size: 18;
|
||||
// Style
|
||||
color: hifi.colors.blueHighlight;
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
onEntered: {
|
||||
parent.color = hifi.colors.blueAccent;
|
||||
}
|
||||
onExited: {
|
||||
parent.color = hifi.colors.blueHighlight;
|
||||
}
|
||||
onClicked: {
|
||||
lightboxPopup.titleText = "Script Plugin Infrastructure by Kasen";
|
||||
lightboxPopup.bodyText = "Toggles the activation of scripting plugins in the 'plugins/scripting' folder. \n\n"
|
||||
+ "Created by https://kasen.io/";
|
||||
lightboxPopup.button1text = "OK";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: walletContainer;
|
||||
anchors.top: accountContainer.bottom;
|
||||
anchors.top: kpiContainer.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: childrenRect.height;
|
||||
|
|
|
@ -167,7 +167,7 @@ Flickable {
|
|||
Component.onCompleted: {
|
||||
var cpu = JSON.parse(PlatformInfo.getCPU(0));
|
||||
var cpuModel = cpu.model;
|
||||
if (cpuModel.length === 0) {
|
||||
if (!cpuModel || cpuModel.length === 0) {
|
||||
cpuModel = "Unknown";
|
||||
}
|
||||
|
||||
|
@ -213,7 +213,7 @@ Flickable {
|
|||
Component.onCompleted: {
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(PlatformInfo.getMasterGPU()));
|
||||
var gpuModel = gpu.model;
|
||||
if (gpuModel.length === 0) {
|
||||
if (!gpuModel || gpuModel.length === 0) {
|
||||
gpuModel = "Unknown";
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@ Flickable {
|
|||
|
||||
var cpu = JSON.parse(PlatformInfo.getCPU(0));
|
||||
var cpuModel = cpu.model;
|
||||
if (cpuModel.length === 0) {
|
||||
if (!cpuModel || cpuModel.length === 0) {
|
||||
cpuModel = "Unknown";
|
||||
}
|
||||
|
||||
|
@ -338,7 +338,7 @@ Flickable {
|
|||
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(PlatformInfo.getMasterGPU()));
|
||||
var gpuModel = gpu.model;
|
||||
if (gpuModel.length === 0) {
|
||||
if (!gpuModel || gpuModel.length === 0) {
|
||||
gpuModel = "Unknown";
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,8 @@ TextField {
|
|||
leftPadding: 0
|
||||
rightPadding: root.rightGlyph === "" ? 0 : rightGlyphItem.implicitWidth + simplifiedUI.sizes.controls.textField.rightGlyphPadding
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
}
|
||||
onPressed: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
}
|
||||
|
||||
onHoveredChanged: {
|
||||
|
|
|
@ -810,6 +810,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
}
|
||||
|
||||
// Tell the plugin manager about our statically linked plugins
|
||||
DependencyManager::set<ScriptInitializers>();
|
||||
DependencyManager::set<PluginManager>();
|
||||
auto pluginManager = PluginManager::getInstance();
|
||||
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
|
||||
|
@ -859,7 +860,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
#endif
|
||||
DependencyManager::set<StatTracker>();
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption);
|
||||
DependencyManager::set<ScriptInitializerMixin, NativeScriptInitializers>();
|
||||
DependencyManager::set<Preferences>();
|
||||
DependencyManager::set<recording::Deck>();
|
||||
DependencyManager::set<recording::Recorder>();
|
||||
|
@ -3425,7 +3425,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext));
|
||||
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
@ -3541,7 +3541,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
surfaceContext->setContextProperty("offscreenFlags", flags);
|
||||
surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
|
||||
|
||||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext));
|
||||
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Performance", new PerformanceScriptingInterface());
|
||||
|
||||
|
|
|
@ -725,45 +725,51 @@ Menu::Menu() {
|
|||
DependencyManager::get<PickManager>().data(), SLOT(setForceCoarsePicking(bool)));
|
||||
|
||||
// Developer > Crash >>>
|
||||
MenuWrapper* crashMenu = developerMenu->addMenu("Crash");
|
||||
bool result = false;
|
||||
const QString HIFI_SHOW_DEVELOPER_CRASH_MENU("HIFI_SHOW_DEVELOPER_CRASH_MENU");
|
||||
result = QProcessEnvironment::systemEnvironment().contains(HIFI_SHOW_DEVELOPER_CRASH_MENU);
|
||||
if (result) {
|
||||
MenuWrapper* crashMenu = developerMenu->addMenu("Crash");
|
||||
|
||||
// Developer > Crash > Display Crash Options
|
||||
addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true);
|
||||
// Developer > Crash > Display Crash Options
|
||||
addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true);
|
||||
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication()));
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication()));
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::pureVirtualCall).join(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::pureVirtualCall).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doubleFree).join(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doubleFree).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doAbort).join(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doAbort).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::nullDeref).join(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::nullDeref).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::outOfBoundsVectorCrash).join(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::outOfBoundsVectorCrash).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::newFault(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::newFault(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); });
|
||||
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOnShutdown, 0, qApp, SLOT(crashOnShutdown()));
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOnShutdown, 0, qApp, SLOT(crashOnShutdown()));
|
||||
}
|
||||
|
||||
|
||||
// Developer > Show Statistics
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats, 0, true);
|
||||
|
|
|
@ -313,8 +313,8 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
if (spine2Joint >= 0) {
|
||||
params.spine2ShapeInfo = hfmModel.joints[spine2Joint].shapeInfo;
|
||||
}
|
||||
|
||||
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
const float TALKING_TIME_THRESHOLD = 0.75f;
|
||||
params.isTalking = head->getTimeWithoutTalking() <= TALKING_TIME_THRESHOLD;
|
||||
|
||||
myAvatar->updateRigControllerParameters(params);
|
||||
|
||||
|
|
|
@ -71,32 +71,44 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
|
|||
Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled };
|
||||
|
||||
AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) {
|
||||
auto& setting1 = getSetting(true, QAudio::AudioInput);
|
||||
if (setting1.isSet()) {
|
||||
qDebug() << "Device name in settings for HMD, Input" << setting1.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for HMD, Input not set";
|
||||
if (mode == QAudio::AudioInput) {
|
||||
auto& setting1 = getSetting(true, QAudio::AudioInput);
|
||||
if (setting1.isSet()) {
|
||||
qDebug() << "Device name in settings for HMD, Input" << setting1.get();
|
||||
_backupSelectedHMDDeviceName = setting1.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for HMD, Input not set";
|
||||
}
|
||||
}
|
||||
|
||||
auto& setting2 = getSetting(true, QAudio::AudioOutput);
|
||||
if (setting2.isSet()) {
|
||||
qDebug() << "Device name in settings for HMD, Output" << setting2.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for HMD, Output not set";
|
||||
if (mode == QAudio::AudioOutput) {
|
||||
auto& setting2 = getSetting(true, QAudio::AudioOutput);
|
||||
if (setting2.isSet()) {
|
||||
qDebug() << "Device name in settings for HMD, Output" << setting2.get();
|
||||
_backupSelectedHMDDeviceName = setting2.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for HMD, Output not set";
|
||||
}
|
||||
}
|
||||
|
||||
auto& setting3 = getSetting(false, QAudio::AudioInput);
|
||||
if (setting3.isSet()) {
|
||||
qDebug() << "Device name in settings for Desktop, Input" << setting3.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for Desktop, Input not set";
|
||||
if (mode == QAudio::AudioInput) {
|
||||
auto& setting3 = getSetting(false, QAudio::AudioInput);
|
||||
if (setting3.isSet()) {
|
||||
qDebug() << "Device name in settings for Desktop, Input" << setting3.get();
|
||||
_backupSelectedDesktopDeviceName = setting3.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for Desktop, Input not set";
|
||||
}
|
||||
}
|
||||
|
||||
auto& setting4 = getSetting(false, QAudio::AudioOutput);
|
||||
if (setting4.isSet()) {
|
||||
qDebug() << "Device name in settings for Desktop, Output" << setting4.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for Desktop, Output not set";
|
||||
if (mode == QAudio::AudioOutput) {
|
||||
auto& setting4 = getSetting(false, QAudio::AudioOutput);
|
||||
if (setting4.isSet()) {
|
||||
qDebug() << "Device name in settings for Desktop, Output" << setting4.get();
|
||||
_backupSelectedDesktopDeviceName = setting4.get();
|
||||
} else {
|
||||
qDebug() << "Device name in settings for Desktop, Output not set";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,14 @@ void SettingsScriptingInterface::setValue(const QString& setting, const QVariant
|
|||
if (getValue(setting) == value) {
|
||||
return;
|
||||
}
|
||||
if (setting.startsWith("private/")) {
|
||||
if (_restrictPrivateValues) {
|
||||
qWarning() << "SettingsScriptingInterface::setValue -- restricted write: " << setting << value;
|
||||
return;
|
||||
} else {
|
||||
qInfo() << "SettingsScriptingInterface::setValue -- allowing restricted write: " << setting << value;
|
||||
}
|
||||
}
|
||||
// Make a deep-copy of the string.
|
||||
// Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine.
|
||||
QString deepCopy = QString::fromUtf16(setting.utf16());
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
class SettingsScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
SettingsScriptingInterface() { };
|
||||
public:
|
||||
static SettingsScriptingInterface* getInstance();
|
||||
|
||||
|
@ -67,6 +66,16 @@ public slots:
|
|||
|
||||
signals:
|
||||
void valueChanged(const QString& setting, const QVariant& value);
|
||||
|
||||
protected:
|
||||
SettingsScriptingInterface(QObject* parent = nullptr) : QObject(parent) { };
|
||||
bool _restrictPrivateValues { true };
|
||||
};
|
||||
|
||||
class QMLSettingsScriptingInterface : public SettingsScriptingInterface {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QMLSettingsScriptingInterface(QObject* parent) : SettingsScriptingInterface(parent) { _restrictPrivateValues = false; }
|
||||
};
|
||||
|
||||
#endif // hifi_SettingsScriptingInterface_h
|
||||
|
|
|
@ -2117,7 +2117,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
_previousIsTalking = params.isTalking;
|
||||
|
||||
const float TOTAL_EASE_IN_TIME = 0.75f;
|
||||
const float TOTAL_EASE_OUT_TIME = 1.5f;
|
||||
const float TOTAL_EASE_OUT_TIME = 0.75f;
|
||||
if (params.isTalking) {
|
||||
if (_talkIdleInterpTime < 1.0f) {
|
||||
_talkIdleInterpTime += dt / TOTAL_EASE_IN_TIME;
|
||||
|
|
|
@ -49,7 +49,7 @@ void Head::simulate(float deltaTime) {
|
|||
|
||||
// Update audio trailing average for rendering facial animations
|
||||
const float AUDIO_AVERAGING_SECS = 0.05f;
|
||||
const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f;
|
||||
const float AUDIO_LONG_TERM_AVERAGING_SECS = 15.0f;
|
||||
_averageLoudness = glm::mix(_averageLoudness, audioLoudness, glm::min(deltaTime / AUDIO_AVERAGING_SECS, 1.0f));
|
||||
|
||||
if (_longTermAverageLoudness == -1.0f) {
|
||||
|
@ -84,7 +84,7 @@ void Head::simulate(float deltaTime) {
|
|||
if (getHasProceduralBlinkFaceMovement()) {
|
||||
// Detect transition from talking to not; force blink after that and a delay
|
||||
bool forceBlink = false;
|
||||
const float TALKING_LOUDNESS = 100.0f;
|
||||
const float TALKING_LOUDNESS = 150.0f;
|
||||
const float BLINK_AFTER_TALKING = 0.25f;
|
||||
_timeWithoutTalking += deltaTime;
|
||||
if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
|
||||
|
@ -176,7 +176,7 @@ void Head::simulate(float deltaTime) {
|
|||
}
|
||||
|
||||
void Head::calculateMouthShapes(float deltaTime) {
|
||||
const float JAW_OPEN_SCALE = 0.015f;
|
||||
const float JAW_OPEN_SCALE = 0.35f;
|
||||
const float JAW_OPEN_RATE = 0.9f;
|
||||
const float JAW_CLOSE_RATE = 0.90f;
|
||||
const float TIMESTEP_CONSTANT = 0.0032f;
|
||||
|
@ -188,11 +188,13 @@ void Head::calculateMouthShapes(float deltaTime) {
|
|||
const float FUNNEL_SPEED = 2.335f;
|
||||
const float STOP_GAIN = 5.0f;
|
||||
const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
|
||||
const float MAX_DELTA_LOUDNESS = 100.0f;
|
||||
|
||||
float deltaTimeRatio = deltaTime / (1.0f / NORMAL_HZ);
|
||||
|
||||
// From the change in loudness, decide how much to open or close the jaw
|
||||
float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE;
|
||||
float deltaLoudness = glm::max(glm::min(_averageLoudness - _longTermAverageLoudness, MAX_DELTA_LOUDNESS), 0.0f) / MAX_DELTA_LOUDNESS;
|
||||
float audioDelta = powf(deltaLoudness, 2.0f) * JAW_OPEN_SCALE;
|
||||
if (audioDelta > _audioJawOpen) {
|
||||
_audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE * deltaTimeRatio;
|
||||
} else {
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include <QJsonArray>
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
_originalInputModelURL(inputModelURL),
|
||||
_modelURL(inputModelURL),
|
||||
_bakedOutputDir(bakedOutputDirectory),
|
||||
_originalOutputDir(originalOutputDirectory),
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
QUrl getModelURL() const { return _modelURL; }
|
||||
QUrl getOriginalInputModelURL() const { return _originalInputModelURL; }
|
||||
virtual QUrl getFullOutputMappingURL() const;
|
||||
QUrl getBakedModelURL() const { return _bakedModelURL; }
|
||||
|
||||
|
@ -67,6 +68,7 @@ protected:
|
|||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
QUrl _originalInputModelURL;
|
||||
QUrl _modelURL;
|
||||
QUrl _outputURLSuffix;
|
||||
QUrl _mappingURL;
|
||||
|
|
|
@ -250,6 +250,7 @@ void TextureBaker::processTexture() {
|
|||
QFile file { _metaTextureFileName };
|
||||
if (!file.open(QIODevice::WriteOnly) || file.write(data) == -1) {
|
||||
handleError("Could not write meta texture for " + _textureURL.toString());
|
||||
return;
|
||||
} else {
|
||||
_outputFiles.push_back(_metaTextureFileName);
|
||||
}
|
||||
|
|
|
@ -585,26 +585,13 @@ void OpenGLDisplayPlugin::updateFrameData() {
|
|||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&)> OpenGLDisplayPlugin::getHUDOperator() {
|
||||
auto hudPipeline = _hudPipeline;
|
||||
auto hudStereo = isStereo();
|
||||
auto hudCompositeFramebufferSize = _compositeFramebuffer->getSize();
|
||||
std::array<glm::ivec4, 2> hudEyeViewports;
|
||||
for_each_eye([&](Eye eye) {
|
||||
hudEyeViewports[eye] = eyeViewport(eye);
|
||||
});
|
||||
auto hudCompositeFramebufferSize = getRecommendedRenderSize();
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) {
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.enableStereo(false);
|
||||
batch.setPipeline(hudPipeline);
|
||||
batch.setResourceTexture(0, hudTexture);
|
||||
if (hudStereo) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
batch.setViewportTransform(hudEyeViewports[eye]);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -614,22 +601,14 @@ void OpenGLDisplayPlugin::compositePointer() {
|
|||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
auto cursorTransform = DependencyManager::get<CompositorHelper>()->getReticleTransform(glm::mat4());
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, cursorData.texture);
|
||||
batch.resetViewTransform();
|
||||
batch.setModelTransform(cursorTransform);
|
||||
if (isStereo()) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
batch.setViewportTransform(eyeViewport(eye));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize()));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize()));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -871,16 +850,6 @@ bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|||
return Parent::beginFrameRender(frameIndex);
|
||||
}
|
||||
|
||||
ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const {
|
||||
uvec2 vpSize = _compositeFramebuffer->getSize();
|
||||
vpSize.x /= 2;
|
||||
uvec2 vpPos;
|
||||
if (eye == Eye::Right) {
|
||||
vpPos.x = vpSize.x;
|
||||
}
|
||||
return ivec4(vpPos, vpSize);
|
||||
}
|
||||
|
||||
gpu::gl::GLBackend* OpenGLDisplayPlugin::getGLBackend() {
|
||||
if (!_gpuContext || !_gpuContext->getBackend()) {
|
||||
return nullptr;
|
||||
|
@ -906,7 +875,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
|
|||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
||||
auto renderSize = glm::uvec2(getRecommendedRenderSize());
|
||||
auto renderSize = getRecommendedRenderSize();
|
||||
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
|
||||
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y));
|
||||
}
|
||||
|
|
|
@ -137,7 +137,6 @@ protected:
|
|||
|
||||
void present(const std::shared_ptr<RefreshRateController>& refreshRateController);
|
||||
virtual void swapBuffers();
|
||||
ivec4 eyeViewport(Eye eye) const;
|
||||
|
||||
void render(std::function<void(gpu::Batch& batch)> f);
|
||||
|
||||
|
|
|
@ -68,6 +68,16 @@ glm::mat4 HmdDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection
|
|||
return _cullingProjection;
|
||||
}
|
||||
|
||||
glm::ivec4 HmdDisplayPlugin::eyeViewport(Eye eye) const {
|
||||
uvec2 vpSize = getRecommendedRenderSize();
|
||||
vpSize.x /= 2;
|
||||
uvec2 vpPos;
|
||||
if (eye == Eye::Right) {
|
||||
vpPos.x = vpSize.x;
|
||||
}
|
||||
return ivec4(vpPos, vpSize);
|
||||
}
|
||||
|
||||
#define DISABLE_PREVIEW_MENU_ITEM_DELAY_MS 500
|
||||
|
||||
bool HmdDisplayPlugin::internalActivate() {
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; }
|
||||
bool isDisplayVisible() const override { return isHmdMounted(); }
|
||||
|
||||
ivec4 eyeViewport(Eye eye) const;
|
||||
|
||||
QRect getRecommendedHUDRect() const override final;
|
||||
|
||||
virtual glm::mat4 getHeadPose() const override;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include <PrioritySortUtil.h>
|
||||
#include <Rig.h>
|
||||
#include <SceneScriptingInterface.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <EntitySimulation.h>
|
||||
#include <ZoneRenderer.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
|
@ -146,7 +146,7 @@ int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
|
|||
void EntityTreeRenderer::resetEntitiesScriptEngine() {
|
||||
_entitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
|
||||
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
|
||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
||||
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_entitiesScriptEngine);
|
||||
_entitiesScriptEngine->runInThread();
|
||||
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_entitiesScriptEngine);
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QUrl>
|
||||
|
||||
#include <ResourceManager.h>
|
||||
#include <shared/ScriptInitializerMixin.h>
|
||||
|
||||
QList<EntityItemID> EntityEditFilters::getZonesByPosition(glm::vec3& position) {
|
||||
QList<EntityItemID> zones;
|
||||
|
@ -258,7 +259,13 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
|
|||
if (hasCorrectSyntax(program)) {
|
||||
// create a QScriptEngine for this script
|
||||
QScriptEngine* engine = new QScriptEngine();
|
||||
engine->evaluate(scriptContents);
|
||||
engine->setObjectName("filter:" + entityID.toString());
|
||||
engine->setProperty("type", "edit_filter");
|
||||
engine->setProperty("fileName", urlString);
|
||||
engine->setProperty("entityID", entityID);
|
||||
engine->globalObject().setProperty("Script", engine->newQObject(engine));
|
||||
DependencyManager::get<ScriptInitializers>()->runScriptInitializers(engine);
|
||||
engine->evaluate(scriptContents, urlString);
|
||||
if (!hadUncaughtExceptions(*engine, urlString)) {
|
||||
// put the engine in the engine map (so we don't leak them, etc...)
|
||||
FilterData filterData;
|
||||
|
|
|
@ -1075,16 +1075,13 @@ void EntityItem::setMass(float mass) {
|
|||
|
||||
void EntityItem::setHref(QString value) {
|
||||
auto href = value.toLower();
|
||||
|
||||
// If the string has something and doesn't start with with "hifi://" it shouldn't be set
|
||||
// We allow the string to be empty, because that's the initial state of this property
|
||||
if (!value.isEmpty() &&
|
||||
!(value.toLower().startsWith("hifi://")) &&
|
||||
!(value.toLower().startsWith("file://"))
|
||||
// TODO: serverless-domains will eventually support http and https also
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Let's let the user set the value of this property to anything, then let consumers of the property
|
||||
// decide what to do with it. Currently, the only in-engine consumers are `EntityTreeRenderer::mousePressEvent()`
|
||||
// and `OtherAvatar::handleChangedAvatarEntityData()` (to remove the href property from others' avatar entities).
|
||||
//
|
||||
// We want this property to be as flexible as possible. The value of this property _should_ only be values that can
|
||||
// be handled by `AddressManager::handleLookupString()`. That function will return `false` and not do
|
||||
// anything if the value of this property isn't something that function can handle.
|
||||
withWriteLock([&] {
|
||||
_href = value;
|
||||
});
|
||||
|
|
|
@ -107,7 +107,6 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu
|
|||
glm::quat rotationOffset = itr.value();
|
||||
jointRotationOffsets.insert(jointIndex, rotationOffset);
|
||||
qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,13 @@ namespace {
|
|||
const QString& CACHE_ERROR_MESSAGE{ "AssetClient::Error: %1 %2" };
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Cache status value returned by {@link Assets.getCacheStatus}.
|
||||
* @typedef {object} Assets.GetCacheStatusResult
|
||||
* @property {string} cacheDirectory - The path of the cache directory.
|
||||
* @property {number} cacheSize - The current cache size, in bytes.
|
||||
* @property {number} maximumCacheSize - The maximum cache size, in bytes.
|
||||
*/
|
||||
MiniPromise::Promise AssetClient::cacheInfoRequestAsync(MiniPromise::Promise deferred) {
|
||||
if (!deferred) {
|
||||
deferred = makePromise(__FUNCTION__); // create on caller's thread
|
||||
|
@ -106,6 +113,20 @@ MiniPromise::Promise AssetClient::cacheInfoRequestAsync(MiniPromise::Promise def
|
|||
return deferred;
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Information on an asset in the cache. Value returned by {@link Assets.queryCacheMeta} and included in the data returned by
|
||||
* {@link Assets.loadFromCache}.
|
||||
* @typedef {object} Assets.CacheItemMetaData
|
||||
* @property {object} [attributes] - The attributes that are stored with this cache item. <em>Not used.</em>
|
||||
* @property {Date} [expirationDate] - The date and time when the meta data expires. An invalid date means "never expires".
|
||||
* @property {boolean} isValid - <code>true</code> if the item specified in the URL is in the cache, <code>false</code> if
|
||||
* it isn't.
|
||||
* @property {Date} [lastModified] - The date and time when the meta data was last modified.
|
||||
* @property {object} [rawHeaders] - The raw headers that are set in the meta data. <em>Not used.</em>
|
||||
* @property {boolean} [saveToDisk] - <code>true</code> if the cache item is allowed to be store on disk,
|
||||
* <code>false</code> if it isn't.
|
||||
* @property {string} [url|metaDataURL] - The ATP URL of the cached item.
|
||||
*/
|
||||
MiniPromise::Promise AssetClient::queryCacheMetaAsync(const QUrl& url, MiniPromise::Promise deferred) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "queryCacheMetaAsync", Q_ARG(const QUrl&, url), Q_ARG(MiniPromise::Promise, deferred));
|
||||
|
@ -202,6 +223,24 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Last-modified and expiry times for a cache item.
|
||||
* @typedef {object} Assets.SaveToCacheHeaders
|
||||
* @property {string} [expires] - The date and time the cache value expires, in the format:
|
||||
* <code>"ddd, dd MMM yyyy HH:mm:ss"</code>. The default value is an invalid date, representing "never expires".
|
||||
* @property {string} [last-modified] - The date and time the cache value was last modified, in the format:
|
||||
* <code>"ddd, dd MMM yyyy HH:mm:ss"</code>. The default value is the current date and time.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Information on saving asset data to the cache with {@link Assets.saveToCache}.
|
||||
* @typedef {object} Assets.SaveToCacheResult
|
||||
* @property {number} [byteLength] - The size of the cached data, in bytes.
|
||||
* @property {Date} [expirationDate] - The date and time that the cache item expires. An invalid date means "never expires".
|
||||
* @property {Date} [lastModified] - The date and time that the cache item was last modified.
|
||||
* @property {string} [metaDataURL] - The URL associated with the cache item.
|
||||
* @property {boolean} [success] - <code>true</code> if the save to cache request was successful.
|
||||
* @property {string} [url] - The URL associated with the cache item.
|
||||
*/
|
||||
MiniPromise::Promise AssetClient::saveToCacheAsync(const QUrl& url, const QByteArray& data, const QVariantMap& headers, MiniPromise::Promise deferred) {
|
||||
if (!deferred) {
|
||||
deferred = makePromise(__FUNCTION__); // create on caller's thread
|
||||
|
|
|
@ -154,7 +154,7 @@ const char* Assignment::typeToString(Assignment::Type type) {
|
|||
|
||||
QDebug operator<<(QDebug debug, const Assignment &assignment) {
|
||||
debug.nospace() << "UUID: " << qPrintable(assignment.getUUID().toString()) <<
|
||||
", Type: " << assignment.getType();
|
||||
", Type: " << assignment.getTypeName() << " (" << assignment.getType() << ")";
|
||||
|
||||
if (!assignment.getPool().isEmpty()) {
|
||||
debug << ", Pool: " << assignment.getPool();
|
||||
|
|
|
@ -68,6 +68,17 @@ Promise BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) {
|
|||
return assetClient()->queryCacheMetaAsync(url, makePromise(__FUNCTION__));
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Data and information returned by {@link Assets.loadFromCache}.
|
||||
* @typedef {object} Assets.LoadFromCacheResult
|
||||
* @property {number} [byteLength] - The number of bytes in the retrieved data.
|
||||
* @property {string} [contentType] - The automatically detected MIME type of the content.
|
||||
* @property {ArrayBuffer} data - The data bytes.
|
||||
* @property {Assets.CacheItemMetaData} metadata - Information on the cache item.
|
||||
* @property {string|object|ArrayBuffer} [response] - The content of the response.
|
||||
* @property {Assets.ResponseType} responseType - The type of the content in <code>response</code>.
|
||||
* @property {string} url - The URL of the cache item.
|
||||
*/
|
||||
Promise BaseAssetScriptingInterface::loadFromCache(const QUrl& url, bool decompress, const QString& responseType) {
|
||||
QVariantMap metaData = {
|
||||
{ "_type", "cache" },
|
||||
|
|
|
@ -24,6 +24,22 @@
|
|||
class BaseAssetScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
/**jsdoc
|
||||
* <p>Types of response that {@link Assets.decompressData}, {@link Assets.getAsset}, or {@link Assets.loadFromCache} may
|
||||
* provide.</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Value</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>"arraybuffer"</code></td><td>A binary <code>ArrayBuffer</code> object.</td></tr>
|
||||
* <tr><td><code>"json"</code></td><td>A parsed <code>JSON</code> object.</td></tr>
|
||||
* <tr><td><code>"text"</code></td><td>UTF-8 decoded <code>string</code> value.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {string} Assets.ResponseType
|
||||
*/
|
||||
const QStringList RESPONSE_TYPES{ "text", "arraybuffer", "json" };
|
||||
using Promise = MiniPromise::Promise;
|
||||
QSharedPointer<AssetClient> assetClient();
|
||||
|
@ -33,51 +49,62 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Checks whether a string is a valid path. Note: A valid path must start with a <code>"/"</code>.
|
||||
* @function Assets.isValidPath
|
||||
* @param {string} input
|
||||
* @returns {boolean}
|
||||
* @param {string} path - The path to check.
|
||||
* @returns {boolean} <code>true</code> if the path is a valid path, <code>false</code> if it isn't.
|
||||
*/
|
||||
bool isValidPath(QString input) { return AssetUtils::isValidPath(input); }
|
||||
|
||||
/**jsdoc
|
||||
* Checks whether a string is a valid path and filename. Note: A valid path and filename must start with a <code>"/"</code>
|
||||
* but must not end with a <code>"/"</code>.
|
||||
* @function Assets.isValidFilePath
|
||||
* @param {string} input
|
||||
* @returns {boolean}
|
||||
* @param {string} path - The path to check.
|
||||
* @returns {boolean} <code>true</code> if the path is a valid file path, <code>false</code> if it isn't.
|
||||
*/
|
||||
bool isValidFilePath(QString input) { return AssetUtils::isValidFilePath(input); }
|
||||
|
||||
/**jsdoc
|
||||
* Gets the normalized ATP URL for a path or hash: ensures that it has <code>"atp:"</code> at the start.
|
||||
* @function Assets.getATPUrl
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
* @param {string} url - The URL to normalize.
|
||||
* @returns {string} The normalized ATP URL.
|
||||
*/
|
||||
QUrl getATPUrl(QString input) { return AssetUtils::getATPUrl(input); }
|
||||
|
||||
/**jsdoc
|
||||
* Gets the SHA256 hexadecimal hash portion of an asset server URL.
|
||||
* @function Assets.extractAssetHash
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
* @param {string} url - The URL to get the SHA256 hexadecimal hash from.
|
||||
* @returns {string} The SHA256 hexadecimal hash portion of the URL if present and valid, <code>""</code> otherwise.
|
||||
*/
|
||||
QString extractAssetHash(QString input) { return AssetUtils::extractAssetHash(input); }
|
||||
|
||||
/**jsdoc
|
||||
* Checks whether a string is a valid SHA256 hexadecimal hash, i.e., 64 hexadecimal characters.
|
||||
* @function Assets.isValidHash
|
||||
* @param {string} input
|
||||
* @returns {boolean}
|
||||
* @param {string} hash - The hash to check.
|
||||
* @returns {boolean} <code>true</code> if the hash is a valid SHA256 hexadecimal string, <code>false</code> if it isn't.
|
||||
*/
|
||||
bool isValidHash(QString input) { return AssetUtils::isValidHash(input); }
|
||||
|
||||
/**jsdoc
|
||||
* Calculates the SHA256 hash of given data.
|
||||
* @function Assets.hashData
|
||||
* @param {} data
|
||||
* @returns {object}
|
||||
* @param {string|ArrayBuffer} data - The data to calculate the hash of.
|
||||
* @returns {ArrayBuffer} The SHA256 hash of the <code>data</code>.
|
||||
*/
|
||||
QByteArray hashData(const QByteArray& data) { return AssetUtils::hashData(data); }
|
||||
|
||||
/**jsdoc
|
||||
* Calculates the SHA256 hash of given data, in hexadecimal format.
|
||||
* @function Assets.hashDataHex
|
||||
* @param {} data
|
||||
* @returns {string}
|
||||
* @param {string|ArrayBuffer} data - The data to calculate the hash of.
|
||||
* @returns {string} The SHA256 hash of the <code>data</code>, in hexadecimal format.
|
||||
* @example <caption>Calculate the hash of some text.</caption>
|
||||
* var text = "Hello world!";
|
||||
* print("Hash: " + Assets.hashDataHex(text));
|
||||
*/
|
||||
QString hashDataHex(const QByteArray& data) { return hashData(data).toHex(); }
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) :
|
|||
_domainServerTimer(this),
|
||||
_statsTimer(this)
|
||||
{
|
||||
// use <mixer-type> as a temporary targetName name until commonInit can be called later
|
||||
LogHandler::getInstance().setTargetName(QString("<%1>").arg(getTypeName()));
|
||||
|
||||
static const int STATS_TIMEOUT_MS = 1000;
|
||||
_statsTimer.setInterval(STATS_TIMEOUT_MS); // 1s, Qt::CoarseTimer acceptable
|
||||
connect(&_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket);
|
||||
|
|
|
@ -84,6 +84,11 @@ bool isDisabled(QJsonObject metaData) {
|
|||
return false;
|
||||
}
|
||||
|
||||
int PluginManager::instantiate() {
|
||||
auto loaders = getLoadedPlugins();
|
||||
return std::count_if(loaders.begin(), loaders.end(), [](const auto& loader) { return (bool)loader->instance(); });
|
||||
}
|
||||
|
||||
auto PluginManager::getLoadedPlugins() const -> const LoaderList& {
|
||||
static std::once_flag once;
|
||||
static LoaderList loadedPlugins;
|
||||
|
@ -105,6 +110,16 @@ bool isDisabled(QJsonObject metaData) {
|
|||
pluginDir.setNameFilters(QStringList() << "libplugins_lib*.so");
|
||||
#endif
|
||||
auto candidates = pluginDir.entryList();
|
||||
|
||||
if (_enableScriptingPlugins.get()) {
|
||||
QDir scriptingPluginDir{ pluginDir };
|
||||
scriptingPluginDir.cd("scripting");
|
||||
qCDebug(plugins) << "Loading scripting plugins from " << scriptingPluginDir.path();
|
||||
for (auto plugin : scriptingPluginDir.entryList()) {
|
||||
candidates << "scripting/" + plugin;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto plugin : candidates) {
|
||||
qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin);
|
||||
QSharedPointer<QPluginLoader> loader(new QPluginLoader(pluginPath + plugin));
|
||||
|
@ -139,6 +154,8 @@ bool isDisabled(QJsonObject metaData) {
|
|||
qCDebug(plugins) << " " << qPrintable(loader->errorString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "pluginPath does not exit..." << pluginDir;
|
||||
}
|
||||
});
|
||||
return loadedPlugins;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <QObject>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include "Forward.h"
|
||||
|
||||
|
@ -38,6 +39,7 @@ public:
|
|||
void saveSettings();
|
||||
void setContainer(PluginContainer* container) { _container = container; }
|
||||
|
||||
int instantiate();
|
||||
void shutdown();
|
||||
|
||||
// Application that have statically linked plugins can expose them to the plugin manager with these function
|
||||
|
@ -69,6 +71,9 @@ private:
|
|||
using LoaderList = QList<Loader>;
|
||||
|
||||
const LoaderList& getLoadedPlugins() const;
|
||||
Setting::Handle<bool> _enableScriptingPlugins {
|
||||
"private/enableScriptingPlugins", (bool)qgetenv("enableScriptingPlugins").toInt()
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: we should define this value in CMake, and then use CMake
|
||||
|
|
|
@ -81,6 +81,11 @@ void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValu
|
|||
setMappingRequest->start();
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* The success or failure of an {@link Assets.downloadData} call.
|
||||
* @typedef {object} Assets.DownloadDataError
|
||||
* @property {string} errorMessage - <code>""</code> if the download was successful, otherwise a description of the error.
|
||||
*/
|
||||
void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) {
|
||||
// FIXME: historically this API method failed silently when given a non-atp prefixed
|
||||
// urlString (or if the AssetRequest failed).
|
||||
|
@ -219,20 +224,31 @@ void AssetScriptingInterface::deleteAsset(QScriptValue options, QScriptValue sco
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* @typedef {string} Assets.GetOptions.ResponseType
|
||||
* <p>Available <code>responseType</code> values for use with @{link Assets.getAsset} and @{link Assets.loadFromCache} configuration option. </p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>responseType</th><th>typeof response value</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>"text"</code></td><td>contents returned as utf-8 decoded <code>String</code> value</td></tr>
|
||||
* <tr><td><code>"arraybuffer"</code></td><td>contents as a binary <code>ArrayBuffer</code> object</td></tr>
|
||||
* <tr><td><code>"json"</code></td><td>contents as a parsed <code>JSON</code> object</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* Source and download options for {@link Assets.getAsset}.
|
||||
* @typedef {object} Assets.GetOptions
|
||||
* @property {boolean} [decompress=false] - <code>true</code> to gunzip decompress the downloaded data. Synonym:
|
||||
* <code>compressed</code>.
|
||||
* @property {Assets.ResponseType} [responseType="text"] - The desired result type.
|
||||
* @property {string} url - The mapped path or hash to download. May have a leading <code>"atp:"</code>.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.getAsset}.
|
||||
* @typedef {object} Assets.GetResult
|
||||
* @property {number} [byteLength] - The number of bytes in the downloaded content in <code>response</code>.
|
||||
* @property {boolean} cached - <code>true</code> if the item was retrieved from the cache, <code>false</code> if it was
|
||||
* downloaded.
|
||||
* @property {string} [contentType] - The automatically detected MIME type of the content.
|
||||
* @property {boolean} [decompressed] - <code>true</code> if the content was decompressed, <code>false</code> if it wasn't.
|
||||
* @property {string} [hash] - The hash for the downloaded asset.
|
||||
* @property {string} [hashURL] - The ATP URL of the hash file.
|
||||
* @property {string} [path] - The path for the asset, if a path was requested. Otherwise, <code>undefined</code>.
|
||||
* @property {string|object|ArrayBuffer} [response] - The downloaded content.
|
||||
* @property {Assets.ResponseType} [responseType] - The type of the downloaded content in <code>response</code>.
|
||||
* @property {string} [url] - The URL of the asset requested: the path with leading <code>"atp:"</code> if a path was
|
||||
* requested, otherwise the requested URL.
|
||||
* @property {boolean} [wasRedirected] - <code>true</code> if the downloaded data is the baked version of the asset,
|
||||
* <code>false</code> if it isn't baked.
|
||||
*/
|
||||
|
||||
void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
JS_VERIFY(options.isObject() || options.isString(), "expected request options Object or URL as first parameter");
|
||||
|
||||
|
@ -283,6 +299,22 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope,
|
|||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Source options for {@link Assets.resolveAsset}.
|
||||
* @typedef {object} Assets.ResolveOptions
|
||||
* @property {string} url - The hash or path to resolve. May have a leading <code>"atp:"</code>.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.resolveAsset}.
|
||||
* <p>Note: If resolving a hash, a file of that hash need not be present on the asset server for the hash to resolve.</p>
|
||||
* @typedef {object} Assets.ResolveResult
|
||||
* @property {string} [hash] - The hash of the asset.
|
||||
* @property {string} [hashURL] - The url of the asset's hash file, with leading <code>atp:</code>.
|
||||
* @property {string} [path] - The path to the asset.
|
||||
* @property {string} [url] - The URL of the asset.
|
||||
* @property {boolean} [wasRedirected] - <code>true</code> if the resolved data is for the baked version of the asset,
|
||||
* <code>false</code> if it isn't.
|
||||
*/
|
||||
void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
const QString& URL{ "url" };
|
||||
|
||||
|
@ -295,6 +327,21 @@ void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue sc
|
|||
jsPromiseReady(getAssetInfo(asset), scope, callback);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Content and decompression options for {@link Assets.decompressData}.
|
||||
* @typedef {object} Assets.DecompressOptions
|
||||
* @property {ArrayBuffer} data - The data to decompress.
|
||||
* @property {Assets.ResponseType} [responseType=text] - The type of decompressed data to return.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.decompressData}.
|
||||
* @typedef {object} Assets.DecompressResult
|
||||
* @property {number} [byteLength] - The number of bytes in the decompressed data.
|
||||
* @property {string} [contentType] - The MIME type of the decompressed data.
|
||||
* @property {boolean} [decompressed] - <code>true</code> if the data is decompressed.
|
||||
* @property {string|object|ArrayBuffer} [response] - The decompressed data.
|
||||
* @property {Assets.ResponseType} [responseType] - The type of the decompressed data in <code>response</code>.
|
||||
*/
|
||||
void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
auto data = options.property("data");
|
||||
QByteArray dataByteArray = qscriptvalue_cast<QByteArray>(data);
|
||||
|
@ -319,6 +366,23 @@ namespace {
|
|||
const int32_t DEFAULT_GZIP_COMPRESSION_LEVEL = -1;
|
||||
const int32_t MAX_GZIP_COMPRESSION_LEVEL = 9;
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Content and compression options for {@link Assets.compressData}.
|
||||
* @typedef {object} Assets.CompressOptions
|
||||
* @property {string|ArrayBuffer} data - The data to compress.
|
||||
* @property {number} level - The compression level, range <code>-1</code> – <code>9</code>. <code>-1</code> means
|
||||
* use the default gzip compression level, <code>0</code> means no compression, and <code>9</code> means maximum
|
||||
* compression.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.compressData}.
|
||||
* @typedef {object} Assets.CompressResult
|
||||
* @property {number} [byteLength] - The number of bytes in the compressed data.
|
||||
* @property {boolean} [compressed] - <code>true</code> if the data is compressed.
|
||||
* @property {string} [contentType] - The MIME type of the compressed data, i.e., <code>"application/gzip"</code>.
|
||||
* @property {ArrayBuffer} [data] - The compressed data.
|
||||
*/
|
||||
void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
auto data = options.property("data").isValid() ? options.property("data") : options;
|
||||
QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast<QByteArray>(data);
|
||||
|
@ -327,6 +391,27 @@ void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue sc
|
|||
jsPromiseReady(compressBytes(dataByteArray, level), scope, callback);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Content and upload options for {@link Assets.putAsset}.
|
||||
* @typedef {object} Assets.PutOptions
|
||||
* @property {boolean} [compress=false] - <code>true</code> to gzip compress the content for upload and storage,
|
||||
* <code>false</code> to upload and store the data without gzip compression. Synonym: <code>compressed</code>.
|
||||
* @property {string|ArrayBuffer} data - The content to upload.
|
||||
* @property {string} [path] - A user-friendly path for the file in the asset server. May have a leading
|
||||
* <code>"atp:"</code>. IF not specified, no path-to-hash mapping is set.
|
||||
* <p>Note: The asset server destroys any unmapped SHA256-named file at server restart. Either set the mapping path
|
||||
* with this property or use {@link Assets.setMapping} to set a path-to-hash mapping for the uploaded file.</p>
|
||||
*/
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.putAsset}.
|
||||
* @typedef {object} Assets.PutResult
|
||||
* @property {number} [byteLength] - The number of bytes in the hash file stored on the asset server.
|
||||
* @property {boolean} [compressed] - <code>true</code> if the content stored is gzip compressed.
|
||||
* @property {string} [contentType] - <code>"application/gzip"</code> if the content stored is gzip compressed.
|
||||
* @property {string} [hash] - The SHA256 hash of the content.
|
||||
* @property {string} [url] - The <code>atp:</code> URL of the content: using the path if specified, otherwise the hash.
|
||||
* @property {string} [path] - The uploaded content's mapped path, if specified.
|
||||
*/
|
||||
void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
auto compress = options.property("compress").toBool() || options.property("compressed").toBool();
|
||||
auto data = options.isObject() ? options.property("data") : options;
|
||||
|
@ -377,12 +462,27 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope,
|
|||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Source for {@link Assets.queryCacheMeta}.
|
||||
* @typedef {object} Assets.QueryCacheMetaOptions
|
||||
* @property {string} url - The URL of the cached asset to get information on. Must start with <code>"atp:"</code> or
|
||||
* <code>"cache:"</code>.
|
||||
*/
|
||||
void AssetScriptingInterface::queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
QString url = options.isString() ? options.toString() : options.property("url").toString();
|
||||
JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url));
|
||||
jsPromiseReady(Parent::queryCacheMeta(url), scope, callback);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Source and retrieval options for {@link Assets.loadFromCache}.
|
||||
* @typedef {object} Assets.LoadFromCacheOptions
|
||||
* @property {boolean} [decompress=false] - <code>true</code> to gunzip decompress the cached data. Synonym:
|
||||
* <code>compressed</code>.
|
||||
* @property {Assets.ResponseType} [responseType=text] - The desired result type.
|
||||
* @property {string} url - The URL of the asset to load from cache. Must start with <code>"atp:"</code> or
|
||||
* <code>"cache:"</code>.
|
||||
*/
|
||||
void AssetScriptingInterface::loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
QString url, responseType;
|
||||
bool decompress = false;
|
||||
|
@ -417,6 +517,14 @@ bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* The data to save to the cache and cache options for {@link Assets.saveToCache}.
|
||||
* @typedef {object} Assets.SaveToCacheOptions
|
||||
* @property {string|ArrayBuffer} data - The data to save to the cache.
|
||||
* @property {Assets.SaveToCacheHeaders} [headers] - The last-modified and expiry times for the cache item.
|
||||
* @property {string} [url] - The URL to associate with the cache item. Must start with <code>"atp:"</code> or
|
||||
* <code>"cache:"</code>. If not specified, the URL is <code>"atp:"</code> followed by the SHA256 hash of the content.
|
||||
*/
|
||||
void AssetScriptingInterface::saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback) {
|
||||
JS_VERIFY(options.isObject(), QString("expected options object as first parameter not: %1").arg(options.toVariant().typeName()));
|
||||
|
||||
|
|
|
@ -25,7 +25,14 @@
|
|||
#include <QtNetwork/QNetworkDiskCache>
|
||||
|
||||
/**jsdoc
|
||||
* The Assets API allows you to communicate with the Asset Browser.
|
||||
* The <code>Assets</code> API provides facilities for interacting with the domain's asset server and the client cache.
|
||||
* <p>Assets are stored in the asset server in files with SHA256 names. These files are mapped to user-friendly URLs of the
|
||||
* format: <code>atp:/path/filename</code>. The assets may optionally be baked, in which case a request for the original
|
||||
* unbaked version of the asset is automatically redirected to the baked version. The asset data may optionally be stored as
|
||||
* compressed.</p>
|
||||
* <p>The client cache can be access directly, using <code>"atp:"</code> or <code>"cache:"</code> URLs. Interface, avatar, and
|
||||
* assignment client scripts can write to the cache. All script types can read from the cache.</p>
|
||||
*
|
||||
* @namespace Assets
|
||||
*
|
||||
* @hifi-interface
|
||||
|
@ -41,251 +48,490 @@ public:
|
|||
AssetScriptingInterface(QObject* parent = nullptr);
|
||||
|
||||
/**jsdoc
|
||||
* Upload content to the connected domain's asset server.
|
||||
* @function Assets.uploadData
|
||||
* @static
|
||||
* @param data {string} content to upload
|
||||
* @param callback {Assets~uploadDataCallback} called when upload is complete
|
||||
* Called when an {@link Assets.uploadData} call is complete.
|
||||
* @callback Assets~uploadDataCallback
|
||||
* @param {string} url - The raw URL of the file that the content is stored in, with <code>atp:</code> as the scheme and
|
||||
* the SHA256 hash as the filename (with no extension).
|
||||
* @param {string} hash - The SHA256 hash of the content.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Called when uploadData is complete
|
||||
* @callback Assets~uploadDataCallback
|
||||
* @param {string} url
|
||||
* @param {string} hash
|
||||
* Uploads content to the asset server, storing it in a SHA256-named file.
|
||||
* <p>Note: The asset server destroys any unmapped SHA256-named file at server restart. Use {@link Assets.setMapping} to
|
||||
* set a path-to-hash mapping for the new file.</p>
|
||||
* @function Assets.uploadData
|
||||
* @param {string} data - The content to upload.
|
||||
* @param {Assets~uploadDataCallback} callback - The function to call upon completion.
|
||||
* @example <caption>Store a string in the asset server.</caption>
|
||||
* Assets.uploadData("Hello world!", function (url, hash) {
|
||||
* print("URL: " + url); // atp:0a1b...9g
|
||||
* Assets.setMapping("/assetsExamples/helloWorld.txt", hash, function (error) {
|
||||
* if (error) {
|
||||
* print("ERROR: Could not set mapping!");
|
||||
* return;
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
Q_INVOKABLE void uploadData(QString data, QScriptValue callback);
|
||||
|
||||
/**jsdoc
|
||||
* Download data from the connected domain's asset server.
|
||||
* @function Assets.downloadData
|
||||
* @param url {string} URL of asset to download, must be ATP scheme URL.
|
||||
* @param callback {Assets~downloadDataCallback}
|
||||
* Called when an {@link Assets.downloadData} call is complete.
|
||||
* @callback Assets~downloadDataCallback
|
||||
* @param {string} data - The content that was downloaded.
|
||||
* @param {Assets.DownloadDataError} error - The success or failure of the download.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Called when downloadData is complete
|
||||
* @callback Assets~downloadDataCallback
|
||||
* @param data {string} content that was downloaded
|
||||
* Downloads content from the asset server, from a SHA256-named file.
|
||||
* @function Assets.downloadData
|
||||
* @param {string} url - The raw URL of asset to download: <code>atp:</code> followed by the assets's SHA256 hash.
|
||||
* @param {Assets~downloadDataCallback} callback - The function to call upon completion.
|
||||
* @example <caption>Store and retrieve a string from the asset server.</caption>
|
||||
* var assetURL;
|
||||
*
|
||||
* // Store the string.
|
||||
* Assets.uploadData("Hello world!", function (url, hash) {
|
||||
* assetURL = url;
|
||||
* print("url: " + assetURL); // atp:a0g89...
|
||||
* Assets.setMapping("/assetsExamples/helloWorld.txt", hash, function (error) {
|
||||
* if (error) {
|
||||
* print("ERROR: Could not set mapping!");
|
||||
* return;
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* // Retrieve the string.
|
||||
* Script.setTimeout(function () {
|
||||
* Assets.downloadData(assetURL, function (data, error) {
|
||||
* print("Downloaded data: " + data);
|
||||
* print("Error: " + JSON.stringify(error));
|
||||
* });
|
||||
* }, 1000);
|
||||
*/
|
||||
Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete);
|
||||
Q_INVOKABLE void downloadData(QString url, QScriptValue callback);
|
||||
|
||||
/**jsdoc
|
||||
* Sets up a path to hash mapping within the connected domain's asset server
|
||||
* @function Assets.setMapping
|
||||
* @param path {string}
|
||||
* @param hash {string}
|
||||
* @param callback {Assets~setMappingCallback}
|
||||
* Called when an {@link Assets.setMapping} call is complete.
|
||||
* @callback Assets~setMappingCallback
|
||||
* @param {string} error - <code>null</code> if the path-to-hash mapping was set, otherwise a description of the error.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Called when setMapping is complete
|
||||
* @callback Assets~setMappingCallback
|
||||
* @param {string} error
|
||||
* Sets a path-to-hash mapping within the asset server.
|
||||
* @function Assets.setMapping
|
||||
* @param {string} path - A user-friendly path for the file in the asset server, without leading <code>"atp:"</code>.
|
||||
* @param {string} hash - The hash in the asset server.
|
||||
* @param {Assets~setMappingCallback} callback - The function to call upon completion.
|
||||
*/
|
||||
Q_INVOKABLE void setMapping(QString path, QString hash, QScriptValue callback);
|
||||
|
||||
/**jsdoc
|
||||
* Look up a path to hash mapping within the connected domain's asset server
|
||||
* @function Assets.getMapping
|
||||
* @param path {string}
|
||||
* @param callback {Assets~getMappingCallback}
|
||||
* Called when an {@link Assets.getMapping} call is complete.
|
||||
* @callback Assets~getMappingCallback
|
||||
* @param {string} error - <code>null</code> if the path was found, otherwise a description of the error.
|
||||
* @param {string} hash - The hash value if the path was found, <code>""</code> if it wasn't.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Called when getMapping is complete.
|
||||
* @callback Assets~getMappingCallback
|
||||
* @param assetID {string} hash value if found, else an empty string
|
||||
* @param error {string} error description if the path could not be resolved; otherwise a null value.
|
||||
* Gets the hash for a path within the asset server. The hash is for the unbaked or baked version of the
|
||||
* asset, according to the asset server setting for the particular path.
|
||||
* @function Assets.getMapping
|
||||
* @param {string} path - The path to a file in the asset server to get the hash of.
|
||||
* @param {Assets~getMappingCallback} callback - The function to call upon completion.
|
||||
* @example <caption>Report the hash of an asset server item.</caption>
|
||||
* var assetPath = Window.browseAssets();
|
||||
* if (assetPath) {
|
||||
* var mapping = Assets.getMapping(assetPath, function (error, hash) {
|
||||
* print("Asset: " + assetPath);
|
||||
* print("- hash: " + hash);
|
||||
* print("- error: " + error);
|
||||
* });
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE void getMapping(QString path, QScriptValue callback);
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.setBakingEnabled
|
||||
* @param path {string}
|
||||
* @param enabled {boolean}
|
||||
* @param callback {}
|
||||
* Called when an {@link Assets.setBakingEnabled} call is complete.
|
||||
* @callback Assets~setBakingEnabledCallback
|
||||
* @param {string} error - <code>null</code> if baking was successfully enabled or disabled, otherwise a description of the
|
||||
* error.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Called when setBakingEnabled is complete.
|
||||
* @callback Assets~setBakingEnabledCallback
|
||||
* Sets whether or not to bake an asset in the asset server.
|
||||
* @function Assets.setBakingEnabled
|
||||
* @param {string} path - The path to a file in the asset server.
|
||||
* @param {boolean} enabled - <code>true</code> to enable baking of the asset, <code>false</code> to disable.
|
||||
* @param {Assets~setBakingEnabledCallback} callback - The function to call upon completion.
|
||||
*/
|
||||
// Note: Second callback parameter not documented because it's always {}.
|
||||
Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback);
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
/**
|
||||
* This function is purely for development purposes, and not meant for use in a
|
||||
* production context. It is not a public-facing API, so it should not contain jsdoc.
|
||||
* production context. It is not a public-facing API, so it should not have JSDoc.
|
||||
*/
|
||||
Q_INVOKABLE void sendFakedHandshake();
|
||||
#endif
|
||||
|
||||
/**jsdoc
|
||||
* Request Asset data from the ATP Server
|
||||
* @function Assets.getAsset
|
||||
* @param {URL|Assets.GetOptions} options An atp: style URL, hash, or relative mapped path; or an {@link Assets.GetOptions} object with request parameters
|
||||
* @param {Assets~getAssetCallback} scope A scope callback function to receive (error, results) values
|
||||
* @param {function} [callback=undefined]
|
||||
* Details of a callback function.
|
||||
* @typedef {object} Assets.CallbackDetails
|
||||
* @property {object} scope - The scope that the <code>callback</code> function is defined in. This object is bound to
|
||||
* <code>this</code> when the function is called.
|
||||
* @property {Assets~compressDataCallback|Assets~decompressDataCallback|Assets~getAssetCallback
|
||||
* |Assets~getCacheStatusCallback|Assets~loadFromCacheCallback|Assets~putAssetCallback|Assets~queryCacheMetaCallback
|
||||
* |Assets~resolveAssetCallback|Assets~saveToCacheCallback}
|
||||
* callback - The function to call upon completion. May be an inline function or a function identifier. If a function
|
||||
* identifier, it must be a member of <code>scope</code>.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Assets.getAsset}.
|
||||
* @typedef {object} Assets.GetOptions
|
||||
* @property {string} [url] an "atp:" style URL, hash, or relative mapped path to fetch
|
||||
* @property {string} [responseType=text] the desired reponse type (text | arraybuffer | json)
|
||||
* @property {boolean} [decompress=false] whether to attempt gunzip decompression on the fetched data
|
||||
* See: {@link Assets.putAsset} and its .compress=true option
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Called when Assets.getAsset is complete.
|
||||
* Called when an {@link Assets.getAsset} call is complete.
|
||||
* @callback Assets~getAssetCallback
|
||||
* @param {string} error - contains error message or null value if no error occured fetching the asset
|
||||
* @param {Asset~getAssetResult} result - result object containing, on success containing asset metadata and contents
|
||||
* @param {string} error - <code>null</code> if the content was downloaded, otherwise a description of the error.
|
||||
* @param {Assets.GetResult} result - Information on and the content downloaded.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.getAsset}.
|
||||
* @typedef {object} Assets~getAssetResult
|
||||
* @property {string} [url] the resolved "atp:" style URL for the fetched asset
|
||||
* @property {string} [hash] the resolved hash for the fetched asset
|
||||
* @property {string|ArrayBuffer|Object} [response] response data (possibly converted per .responseType value)
|
||||
* @property {string} [responseType] response type (text | arraybuffer | json)
|
||||
* @property {string} [contentType] detected asset mime-type (autodetected)
|
||||
* @property {number} [byteLength] response data size in bytes
|
||||
* @property {number} [decompressed] flag indicating whether data was decompressed
|
||||
* Downloads content from the asset server.
|
||||
* @function Assets.getAsset
|
||||
* @param {string|Assets.GetOptions} source - What to download and download options. If a string, the mapped path or hash
|
||||
* to download, optionally including a leading <code>"atp:"</code>.
|
||||
* @param {object|Assets.CallbackDetails|Assets~getAssetCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~getAssetCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Retrieve a string from the asset server.</caption>
|
||||
* Assets.getAsset(
|
||||
* {
|
||||
* url: "/assetsExamples/helloWorld.txt",
|
||||
* responseType: "text"
|
||||
* },
|
||||
* function (error, result) {
|
||||
* if (error) {
|
||||
* print("ERROR: Data not downloaded");
|
||||
* } else {
|
||||
* print("Data: " + result.response);
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* Upload Asset data to the ATP Server
|
||||
* Called when an {@link Assets.putAsset} call is complete.
|
||||
* @callback Assets~putAssetCallback
|
||||
* @param {string} error - <code>null</code> if the content was uploaded and any path-to-hash mapping set, otherwise a
|
||||
* description of the error.
|
||||
* @param {Assets.PutResult} result - Information on the content uploaded.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Uploads content to the asset server and sets a path-to-hash mapping.
|
||||
* @function Assets.putAsset
|
||||
* @param {Assets.PutOptions} options A PutOptions object with upload parameters
|
||||
* @param {Assets~putAssetCallback} scope[callback] A scoped callback function invoked with (error, results)
|
||||
* @param {function} [callback=undefined]
|
||||
* @param {string|Assets.PutOptions} options - The content to upload and upload options. If a string, the value of the
|
||||
* string is uploaded but a path-to-hash mapping is not set.
|
||||
* @param {object|Assets.CallbackDetails|Assets~putAssetCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~putAssetCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Store a string in the asset server.</caption>
|
||||
* Assets.putAsset(
|
||||
* {
|
||||
* data: "Hello world!",
|
||||
* path: "/assetsExamples/helloWorld.txt"
|
||||
* },
|
||||
* function (error, result) {
|
||||
* if (error) {
|
||||
* print("ERROR: Data not uploaded or mapping not set");
|
||||
* } else {
|
||||
* print("URL: " + result.url); // atp:/assetsExamples/helloWorld.txt
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Assets.putAsset}.
|
||||
* @typedef {object} Assets.PutOptions
|
||||
* @property {ArrayBuffer|string} [data] byte buffer or string value representing the new asset's content
|
||||
* @property {string} [path=null] ATP path mapping to automatically create (upon successful upload to hash)
|
||||
* @property {boolean} [compress=false] whether to gzip compress data before uploading
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Called when Assets.putAsset is complete.
|
||||
* @callback Assets~puttAssetCallback
|
||||
* @param {string} error - contains error message (or null value if no error occured while uploading/mapping the new asset)
|
||||
* @param {Asset~putAssetResult} result - result object containing error or result status of asset upload
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Result value returned by {@link Assets.putAsset}.
|
||||
* @typedef {object} Assets~putAssetResult
|
||||
* @property {string} [url] the resolved "atp:" style URL for the uploaded asset (based on .path if specified, otherwise on the resulting ATP hash)
|
||||
* @property {string} [path] the uploaded asset's resulting ATP path (or undefined if no path mapping was assigned)
|
||||
* @property {string} [hash] the uploaded asset's resulting ATP hash
|
||||
* @property {boolean} [compressed] flag indicating whether the data was compressed before upload
|
||||
* @property {number} [byteLength] flag indicating final byte size of the data uploaded to the ATP server
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.deleteAsset
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback = ""]
|
||||
* Called when an {@link Assets.deleteAsset} call is complete.
|
||||
* <p class="important">Not implemented: This type is not implemented yet.</p>
|
||||
* @callback Assets~deleteAssetCallback
|
||||
* @param {string} error - <code>null</code> if the content was deleted, otherwise a description of the error.
|
||||
* @param {Assets.DeleteResult} result - Information on the content deleted.
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.resolveAsset
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback = ""]
|
||||
* Deletes content from the asset server.
|
||||
* <p class="important">Not implemented: This method is not implemented yet.</p>
|
||||
* @function Assets.deleteAsset
|
||||
* @param {Assets.DeleteOptions} options - The content to delete and delete options.
|
||||
* @param {object} scope - The scope that the <code>callback</code> function is defined in.
|
||||
* @param {Assets~deleteAssetCallback} callback - The function to call upon completion.
|
||||
*/
|
||||
Q_INVOKABLE void deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* Called when an {@link Assets.resolveAsset} call is complete.
|
||||
* @callback Assets~resolveAssetCallback
|
||||
* @param {string} error - <code>null</code> if the asset hash or path was resolved, otherwise a description of the error.
|
||||
* @param {Assets.ResolveResult} result - Information on the hash or path resolved.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Resolves and returns information on a hash or a path in the asset server.
|
||||
* @function Assets.resolveAsset
|
||||
* @param {string|Assets.ResolveOptions} source - The hash or path to resolve if a string, otherwise an object specifying
|
||||
* what to resolve. If a string, it may have a leading <code>"atp:"</code>.
|
||||
* @param {object|Assets.CallbackDetails|Assets~resolveAssetCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~resolveAssetCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Get the hash and URL for a path.</caption>
|
||||
* Assets.resolveAsset(
|
||||
* "/assetsExamples/helloWorld.txt",
|
||||
* function (error, result) {
|
||||
* if (error) {
|
||||
* print("ERROR: " + error);
|
||||
* } else {
|
||||
* print("Hash: " + result.hash);
|
||||
* print("URL: " + result.url);
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
Q_INVOKABLE void resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.decompressData
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback = ""]
|
||||
* Called when an {@link Assets.decompressData} call is complete.
|
||||
* @callback Assets~decompressDataCallback
|
||||
* @param {string} error - <code>null</code> if the data was successfully compressed, otherwise a description of the error.
|
||||
* @param {Assets.DecompressResult} result - Information on and the decompressed data.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Decompresses data in memory using gunzip.
|
||||
* @function Assets.decompressData
|
||||
* @param {Assets.DecompressOptions} source - What to decompress and decompression options.
|
||||
* @param {object|Assets.CallbackDetails|Assets~decompressDataCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~decompressDataCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.compressData
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback = ""]
|
||||
* Called when an {@link Assets.compressData} call is complete.
|
||||
* @callback Assets~compressDataCallback
|
||||
* @param {string} error - <code>null</code> if the data was successfully compressed, otherwise a description of the error.
|
||||
* @param {Assets.CompressResult} result - Information on and the compressed data.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Compresses data in memory using gzip.
|
||||
* @function Assets.compressData
|
||||
* @param {string|ArrayBuffer|Assets.CompressOptions} source - What to compress and compression options. If a string or
|
||||
* ArrayBuffer, the data to compress.
|
||||
* @param {object|Assets.CallbackDetails|Assets~compressDataCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~compressDataCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void compressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* Initializes the cache if it isn't already initialized.
|
||||
* @function Assets.initializeCache
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} <code>true</code> if the cache is initialized, <code>false</code> if it isn't.
|
||||
*/
|
||||
|
||||
Q_INVOKABLE bool initializeCache() { return Parent::initializeCache(); }
|
||||
|
||||
/**jsdoc
|
||||
* Checks whether the script can write to the cache.
|
||||
* @function Assets.canWriteCacheValue
|
||||
* @param {string} url
|
||||
* @returns {boolean}
|
||||
* @param {string} url - <em>Not used.</em>
|
||||
* @returns {boolean} <code>true</code> if the script is an Interface, avatar, or assignment client script,
|
||||
* <code>false</code> if the script is a client entity or server entity script.
|
||||
* @example <caption>Report whether the script can write to the cache.</caption>
|
||||
* print("Can write to cache: " + Assets.canWriteCacheValue(null));
|
||||
*/
|
||||
|
||||
Q_INVOKABLE bool canWriteCacheValue(const QUrl& url);
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.getCacheStatus
|
||||
* @param {} scope
|
||||
* @param {} [callback=undefined]
|
||||
* Called when a {@link Assets.getCacheStatus} call is complete.
|
||||
* @callback Assets~getCacheStatusCallback
|
||||
* @param {string} error - <code>null</code> if the cache status was retrieved without error, otherwise a description of
|
||||
* the error.
|
||||
* @param {Assets.GetCacheStatusResult} result - Details of the current cache status.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Gets the current cache status.
|
||||
* @function Assets.getCacheStatus
|
||||
* @param {object|Assets.CallbackDetails|Assets~getCacheStatusCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~getCacheStatusCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Report the cache status.</caption>
|
||||
* Assets.getCacheStatus(function (error, status) {
|
||||
* print("Cache status");
|
||||
* print("- Error: " + error);
|
||||
* print("- Status: " + JSON.stringify(status));
|
||||
* });
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void getCacheStatus(QScriptValue scope, QScriptValue callback = QScriptValue()) {
|
||||
jsPromiseReady(Parent::getCacheStatus(), scope, callback);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.queryCacheMeta
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback=undefined]
|
||||
* Called when {@link Assets.queryCacheMeta} is complete.
|
||||
* @callback Assets~queryCacheMetaCallback
|
||||
* @param {string} error - <code>null</code> if the URL has a valid cache entry, otherwise a description of the error.
|
||||
* @param {Assets.CacheItemMetaData} result - Information on an asset in the cache.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Gets information about the status of an asset in the cache.
|
||||
* @function Assets.queryCacheMeta
|
||||
* @param {string|Assets.QueryCacheMetaOptions} path - The URL of the cached asset to get information on if a string,
|
||||
* otherwise an object specifying the cached asset to get information on. The URL must start with <code>"atp:"</code>
|
||||
* or <code>"cache:"</code>.
|
||||
* @param {object|Assets.CallbackDetails|Assets~queryCacheMetaCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~queryCacheMetaCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Report details of a string store in the cache.</caption>
|
||||
* Assets.queryCacheMeta(
|
||||
* "cache:/cacheExample/helloCache.txt",
|
||||
* function (error, result) {
|
||||
* if (error) {
|
||||
* print("Error: " + error);
|
||||
* } else {
|
||||
* print("Success:");
|
||||
* print("- URL: " + result.url);
|
||||
* print("- isValid: " + result.isValid);
|
||||
* print("- saveToDisk: " + result.saveToDisk);
|
||||
* print("- expirationDate: " + result.expirationDate);
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.loadFromCache
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback=undefined]
|
||||
* Called when an {@link Assets.loadFromCache} call is complete.
|
||||
* @callback Assets~loadFromCacheCallback
|
||||
* @param {string} error - <code>null</code> if the cache item was successfully retrieved, otherwise a description of the
|
||||
* error.
|
||||
* @param {Assets.LoadFromCacheResult} result - Information on and the retrieved data.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Retrieves data from the cache directly, without downloading it.
|
||||
* @function Assets.loadFromCache
|
||||
* @param {string|Assets.LoadFromCacheOptions} options - The URL of the asset to load from the cache if a string, otherwise
|
||||
* an object specifying the asset to load from the cache and load options. The URL must start with <code>"atp:"</code>
|
||||
* or <code>"cache:"</code>.
|
||||
* @param {object|Assets.CallbackDetails|Assets~loadFromCacheCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~loadFromCacheCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Retrieve a string from the cache.</caption>
|
||||
* Assets.loadFromCache(
|
||||
* "cache:/cacheExample/helloCache.txt",
|
||||
* function (error, result) {
|
||||
* if (error) {
|
||||
* print("Error: " + error);
|
||||
* } else {
|
||||
* print("Success:");
|
||||
* print("- Response: " + result.response);
|
||||
* print("- Content type: " + result.contentType);
|
||||
* print("- Number of bytes: " + result.byteLength);
|
||||
* print("- Bytes: " + [].slice.call(new Uint8Array(result.data), 0, result.byteLength));
|
||||
* print("- URL: " + result.url);
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* @function Assets.saveToCache
|
||||
* @param {} options
|
||||
* @param {} scope
|
||||
* @param {} [callback=undefined]
|
||||
* Called when an {@link Assets.saveToCache} call is complete.
|
||||
* @callback Assets~saveToCacheCallback
|
||||
* @param {string} error - <code>null</code> if the asset data was successfully saved to the cache, otherwise a description
|
||||
* of the error.
|
||||
* @param {Assets.SaveToCacheResult} result - Information on the cached data.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Saves asset data to the cache directly, without downloading it from a URL.
|
||||
* <p>Note: Can only be used in Interface, avatar, and assignment client scripts.</p>
|
||||
* @function Assets.saveToCache
|
||||
* @param {Assets.SaveToCacheOptions} options - The data to save to the cache and cache options.
|
||||
* @param {object|Assets.CallbackDetails|Assets~saveToCacheCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~saveToCacheCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
* @example <caption>Save a string in the cache.</caption>
|
||||
* Assets.saveToCache(
|
||||
* {
|
||||
* url: "cache:/cacheExample/helloCache.txt",
|
||||
* data: "Hello cache"
|
||||
* },
|
||||
* function (error, result) {
|
||||
* if (error) {
|
||||
* print("Error: " + error);
|
||||
* } else {
|
||||
* print("Success:");
|
||||
* print("- Bytes: " + result.byteLength);
|
||||
* print("- URL: " + result.url);
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
|
||||
/**jsdoc
|
||||
* Saves asset data to the cache directly, without downloading it from a URL.
|
||||
* <p>Note: Can only be used in Interface, avatar, and assignment client scripts.</p>
|
||||
* @function Assets.saveToCache
|
||||
* @param {} url
|
||||
* @param {} data
|
||||
* @param {} metadata
|
||||
* @param {} scope
|
||||
* @param {} [callback=undefined]
|
||||
* @param {string} url - The URL to associate with the cache item. Must start with <code>"atp:"</code> or
|
||||
* <code>"cache:"</code>.
|
||||
* @param {string|ArrayBuffer} data - The data to save to the cache.
|
||||
* @param {Assets.SaveToCacheHeaders} headers - The last-modified and expiry times for the cache item.
|
||||
* @param {object|Assets.CallbackDetails|Assets~saveToCacheCallback} scopeOrCallback - If an object, then the scope that
|
||||
* the <code>callback</code> function is defined in. This object is bound to <code>this</code> when the function is
|
||||
* called.
|
||||
* <p>Otherwise, the function to call upon completion. This may be an inline function or a function identifier.</p>
|
||||
* @param {Assets~saveToCacheCallback} [callback] - Used if <code>scopeOrCallback</code> specifies the scope.
|
||||
* <p>The function to call upon completion. May be an inline function, a function identifier, or the name of a function
|
||||
* in a string. If the name of a function or a function identifier, it must be a member of the scope specified by
|
||||
* <code>scopeOrCallback</code>.</p>
|
||||
*/
|
||||
|
||||
Q_INVOKABLE void saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata,
|
||||
QScriptValue scope, QScriptValue callback = QScriptValue());
|
||||
protected:
|
||||
|
|
|
@ -237,6 +237,11 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
|
|||
}
|
||||
}
|
||||
|
||||
QString ScriptEngine::getTypeAsString() const {
|
||||
auto value = QVariant::fromValue(_type).toString();
|
||||
return value.isEmpty() ? "unknown" : value.toLower();
|
||||
}
|
||||
|
||||
QString ScriptEngine::getContext() const {
|
||||
switch (_context) {
|
||||
case CLIENT_SCRIPT:
|
||||
|
|
|
@ -122,6 +122,8 @@ public:
|
|||
class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString context READ getContext)
|
||||
Q_PROPERTY(QString type READ getTypeAsString)
|
||||
Q_PROPERTY(QString fileName MEMBER _fileNameString CONSTANT)
|
||||
public:
|
||||
|
||||
enum Context {
|
||||
|
@ -138,6 +140,7 @@ public:
|
|||
AGENT,
|
||||
AVATAR
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
static int processLevelMaxRetries;
|
||||
ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine"));
|
||||
|
@ -636,6 +639,7 @@ public:
|
|||
|
||||
void setType(Type type) { _type = type; };
|
||||
Type getType() { return _type; };
|
||||
QString getTypeAsString() const;
|
||||
|
||||
bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget
|
||||
bool isRunning() const { return _isRunning; } // used by ScriptWidget
|
||||
|
|
|
@ -136,24 +136,6 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) {
|
|||
|
||||
QObject* scriptsModel();
|
||||
|
||||
bool NativeScriptInitializers::registerNativeScriptInitializer(NativeScriptInitializer initializer) {
|
||||
return registerScriptInitializer([initializer](ScriptEnginePointer engine) {
|
||||
initializer(qobject_cast<QScriptEngine*>(engine.data()));
|
||||
});
|
||||
}
|
||||
|
||||
bool NativeScriptInitializers::registerScriptInitializer(ScriptInitializer initializer) {
|
||||
if (auto scriptEngines = DependencyManager::get<ScriptEngines>().data()) {
|
||||
scriptEngines->registerScriptInitializer(initializer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
|
||||
_scriptInitializers.push_back(initializer);
|
||||
}
|
||||
|
||||
void ScriptEngines::addScriptEngine(ScriptEnginePointer engine) {
|
||||
if (!_isStopped) {
|
||||
QMutexLocker locker(&_allScriptsMutex);
|
||||
|
@ -590,12 +572,8 @@ void ScriptEngines::quitWhenFinished() {
|
|||
}
|
||||
|
||||
int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) {
|
||||
int ii=0;
|
||||
for (auto initializer : _scriptInitializers) {
|
||||
ii++;
|
||||
initializer(scriptEngine);
|
||||
}
|
||||
return ii;
|
||||
auto nativeCount = DependencyManager::get<ScriptInitializers>()->runScriptInitializers(scriptEngine.data());
|
||||
return nativeCount + ScriptInitializerMixin<ScriptEnginePointer>::runScriptInitializers(scriptEngine);
|
||||
}
|
||||
|
||||
void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) {
|
||||
|
|
|
@ -48,13 +48,8 @@ class ScriptEngine;
|
|||
* scripts directory of the Interface installation.
|
||||
* <em>Read-only.</em>
|
||||
*/
|
||||
class NativeScriptInitializers : public ScriptInitializerMixin {
|
||||
public:
|
||||
bool registerNativeScriptInitializer(NativeScriptInitializer initializer) override;
|
||||
bool registerScriptInitializer(ScriptInitializer initializer) override;
|
||||
};
|
||||
|
||||
class ScriptEngines : public QObject, public Dependency {
|
||||
class ScriptEngines : public QObject, public Dependency, public ScriptInitializerMixin<ScriptEnginePointer> {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(ScriptsModel* scriptsModel READ scriptsModel CONSTANT)
|
||||
|
@ -62,11 +57,9 @@ class ScriptEngines : public QObject, public Dependency {
|
|||
Q_PROPERTY(QString debugScriptUrl READ getDebugScriptUrl WRITE setDebugScriptUrl)
|
||||
|
||||
public:
|
||||
using ScriptInitializer = ScriptInitializerMixin::ScriptInitializer;
|
||||
|
||||
ScriptEngines(ScriptEngine::Context context, const QUrl& defaultScriptsOverride = QUrl());
|
||||
void registerScriptInitializer(ScriptInitializer initializer);
|
||||
int runScriptInitializers(ScriptEnginePointer engine);
|
||||
int runScriptInitializers(ScriptEnginePointer engine) override;
|
||||
|
||||
void loadScripts();
|
||||
void saveScripts();
|
||||
|
||||
|
@ -347,7 +340,6 @@ protected:
|
|||
QHash<QUrl, ScriptEnginePointer> _scriptEnginesHash;
|
||||
QSet<ScriptEnginePointer> _allKnownScriptEngines;
|
||||
QMutex _allScriptsMutex;
|
||||
std::list<ScriptInitializer> _scriptInitializers;
|
||||
ScriptsModel _scriptsModel;
|
||||
ScriptsModelFilter _scriptsModelFilter;
|
||||
std::atomic<bool> _isStopped { false };
|
||||
|
|
|
@ -9,30 +9,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <QSharedPointer>
|
||||
#include "../DependencyManager.h"
|
||||
|
||||
class QScriptEngine;
|
||||
class ScriptEngine;
|
||||
|
||||
class ScriptInitializerMixin : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
template <typename T> class ScriptInitializerMixin {
|
||||
public:
|
||||
using ScriptInitializer = std::function<void(T)>;
|
||||
virtual void registerScriptInitializer(ScriptInitializer initializer) {
|
||||
InitializerLock lock(_scriptInitializerMutex);
|
||||
_scriptInitializers.push_back(initializer);
|
||||
}
|
||||
virtual int runScriptInitializers(T engine) {
|
||||
InitializerLock lock(_scriptInitializerMutex);
|
||||
return std::count_if(_scriptInitializers.begin(), _scriptInitializers.end(),
|
||||
[engine](auto initializer){ initializer(engine); return true; }
|
||||
);
|
||||
}
|
||||
virtual ~ScriptInitializerMixin() {}
|
||||
protected:
|
||||
std::mutex _scriptInitializerMutex;
|
||||
using InitializerLock = std::lock_guard<std::mutex>;
|
||||
std::list<ScriptInitializer> _scriptInitializers;
|
||||
};
|
||||
|
||||
class ScriptInitializers : public ScriptInitializerMixin<QScriptEngine*>, public Dependency {
|
||||
public:
|
||||
// Lightweight `QScriptEngine*` initializer (only depends on built-in Qt components)
|
||||
// example registration:
|
||||
// eg: [&](QScriptEngine* engine) -> bool {
|
||||
// eg: [&](QScriptEngine* engine) {
|
||||
// engine->globalObject().setProperties("API", engine->newQObject(...instance...))
|
||||
// return true;
|
||||
// }
|
||||
using NativeScriptInitializer = std::function<void(QScriptEngine*)>;
|
||||
virtual bool registerNativeScriptInitializer(NativeScriptInitializer initializer) = 0;
|
||||
|
||||
// Heavyweight `ScriptEngine*` initializer (tightly coupled to Interface and script-engine library internals)
|
||||
// eg: [&](ScriptEnginePointer scriptEngine) -> bool {
|
||||
// engine->registerGlobalObject("API", ...instance..);
|
||||
// return true;
|
||||
// }
|
||||
using ScriptEnginePointer = QSharedPointer<ScriptEngine>;
|
||||
using ScriptInitializer = std::function<void(ScriptEnginePointer)>;
|
||||
virtual bool registerScriptInitializer(ScriptInitializer initializer) { return false; };
|
||||
// };
|
||||
};
|
||||
|
|
|
@ -90,6 +90,17 @@ public:
|
|||
using Config = JobConfig;
|
||||
};
|
||||
|
||||
/**jsdoc
|
||||
* @namespace Workload
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @property {number} cpuRunTime - <em>Read-only.</em>
|
||||
* @property {boolean} enabled
|
||||
* @property {number} branch
|
||||
*/
|
||||
// A default Config is always on; to create an enableable Config, use the ctor JobConfig(bool enabled)
|
||||
class JobConfig : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -139,7 +150,7 @@ public:
|
|||
double getCPURunTime() const { return _msCPURunTime; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Render.getConfig
|
||||
* @function Workload.getConfig
|
||||
* @param {string} name
|
||||
* @returns {object}
|
||||
*/
|
||||
|
@ -162,19 +173,19 @@ public:
|
|||
|
||||
// Describe the node graph data connections of the associated Job/Task
|
||||
/**jsdoc
|
||||
* @function JobConfig.isTask
|
||||
* @function Workload.isTask
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool isTask() const { return _isTask; }
|
||||
|
||||
/**jsdoc
|
||||
* @function JobConfig.isSwitch
|
||||
* @function Workload.isSwitch
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool isSwitch() const { return _isSwitch; }
|
||||
|
||||
/**jsdoc
|
||||
* @function JobConfig.getSubConfigs
|
||||
* @function Workload.getSubConfigs
|
||||
* @returns {object[]}
|
||||
*/
|
||||
Q_INVOKABLE QObjectList getSubConfigs() const {
|
||||
|
@ -187,13 +198,13 @@ public:
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* @function JobConfig.getNumSubs
|
||||
* @function Workload.getNumSubs
|
||||
* @returns {number}
|
||||
*/
|
||||
Q_INVOKABLE int getNumSubs() const { return getSubConfigs().size(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function JobConfig.getSubConfig
|
||||
* @function Workload.getSubConfig
|
||||
* @param {number} index
|
||||
* @returns {object}
|
||||
*/
|
||||
|
@ -214,7 +225,7 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function Workload.load
|
||||
* @param {object} map
|
||||
* @param {object} json
|
||||
*/
|
||||
void load(const QJsonObject& val) { qObjectFromJsonValue(val, *this); emit loaded(); }
|
||||
|
||||
|
|
|
@ -43,3 +43,7 @@ set(DIR "pcmCodec")
|
|||
add_subdirectory(${DIR})
|
||||
set(DIR "hifiCodec")
|
||||
add_subdirectory(${DIR})
|
||||
|
||||
# example plugins
|
||||
set(DIR "KasenAPIExample")
|
||||
add_subdirectory(${DIR})
|
||||
|
|
3
plugins/KasenAPIExample/CMakeLists.txt
Normal file
3
plugins/KasenAPIExample/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
set(TARGET_NAME KasenAPIExample)
|
||||
setup_hifi_client_server_plugin(scripting)
|
||||
link_hifi_libraries(shared plugins avatars networking graphics gpu)
|
56
plugins/KasenAPIExample/src/ExampleScriptPlugin.h
Normal file
56
plugins/KasenAPIExample/src/ExampleScriptPlugin.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// ExampleScriptPlugin.h
|
||||
// plugins/KasenAPIExample/src
|
||||
//
|
||||
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
|
||||
// Copyright 2019 Kasen IO
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// Supporting file containing all QtScript specific integration.
|
||||
|
||||
#ifndef EXAMPLE_SCRIPT_PLUGIN_H
|
||||
#define EXAMPLE_SCRIPT_PLUGIN_H
|
||||
|
||||
#if DEV_BUILD
|
||||
#pragma message("QtScript is deprecated see: doc.qt.io/qt-5/topics-scripting.html")
|
||||
#endif
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QCoreApplication>
|
||||
#include <shared/ScriptInitializerMixin.h>
|
||||
|
||||
namespace example {
|
||||
|
||||
extern const QLoggingCategory& logger;
|
||||
|
||||
inline void setGlobalInstance(QScriptEngine* engine, const QString& name, QObject* object) {
|
||||
auto value = engine->newQObject(object, QScriptEngine::QtOwnership);
|
||||
engine->globalObject().setProperty(name, value);
|
||||
qCDebug(logger) << "setGlobalInstance" << name << engine->property("fileName");
|
||||
}
|
||||
|
||||
class ScriptPlugin : public QObject {
|
||||
Q_OBJECT
|
||||
QString _version;
|
||||
Q_PROPERTY(QString version MEMBER _version CONSTANT)
|
||||
protected:
|
||||
inline ScriptPlugin(const QString& name, const QString& version) : _version(version) {
|
||||
setObjectName(name);
|
||||
if (!DependencyManager::get<ScriptInitializers>()) {
|
||||
qCWarning(logger) << "COULD NOT INITIALIZE (ScriptInitializers unavailable)" << qApp << this;
|
||||
return;
|
||||
}
|
||||
qCWarning(logger) << "registering w/ScriptInitializerMixin..." << DependencyManager::get<ScriptInitializers>().data();
|
||||
DependencyManager::get<ScriptInitializers>()->registerScriptInitializer(
|
||||
[this](QScriptEngine* engine) { setGlobalInstance(engine, objectName(), this); });
|
||||
}
|
||||
public slots:
|
||||
inline QString toString() const { return QString("[%1 version=%2]").arg(objectName()).arg(_version); }
|
||||
};
|
||||
|
||||
} // namespace example
|
||||
|
||||
#endif
|
139
plugins/KasenAPIExample/src/KasenAPIExample.cpp
Normal file
139
plugins/KasenAPIExample/src/KasenAPIExample.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
//
|
||||
// KasenAPIExample.cpp
|
||||
// plugins/KasenAPIExample/src
|
||||
//
|
||||
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
|
||||
// Copyright 2019 Kasen IO
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// Example of prototyping new JS APIs by leveraging the existing plugin system.
|
||||
|
||||
#include "ExampleScriptPlugin.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <AvatarHashMap.h>
|
||||
|
||||
namespace custom_api_example {
|
||||
|
||||
QLoggingCategory logger{ "custom_api_example" };
|
||||
|
||||
class KasenAPIExample : public example::ScriptPlugin {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "KasenAPIExample" FILE "plugin.json")
|
||||
public:
|
||||
KasenAPIExample() : example::ScriptPlugin("KasenAPIExample", "0.0.1") {
|
||||
qCInfo(logger) << "plugin loaded" << qApp << toString() << QThread::currentThread();
|
||||
}
|
||||
|
||||
public slots:
|
||||
/**jsdoc
|
||||
* Returns current microseconds (usecs) since Epoch. note: 1000usecs == 1ms
|
||||
* @example <caption>Measure current setTimeout accuracy.</caption>
|
||||
* var expected = 1000;
|
||||
* var start = KasenAPIExample.now();
|
||||
* Script.setTimeout(function () {
|
||||
* var elapsed = (KasenAPIExample.now() - start)/1000;
|
||||
* print("expected (ms):", expected, "actual (ms):", elapsed);
|
||||
* }, expected);
|
||||
*/
|
||||
QVariant now() const {
|
||||
return usecTimestampNow();
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Returns the available blendshape names for an avatar.
|
||||
* @example <caption>Get blendshape names</caption>
|
||||
* print(JSON.stringify(KasenAPIExample.getBlendshapeNames(MyAvatar.sessionUUID)));
|
||||
*/
|
||||
QStringList getBlendshapeNames(const QUuid& avatarID) const {
|
||||
QVector<QString> out;
|
||||
if (auto head = getAvatarHead(avatarID)) {
|
||||
for (const auto& kv : head->getBlendshapeMap().toStdMap()) {
|
||||
if (kv.second >= out.size()) out.resize(kv.second+1);
|
||||
out[kv.second] = kv.first;
|
||||
}
|
||||
}
|
||||
return out.toList();
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Returns a key-value object with active (non-zero) blendshapes.
|
||||
* eg: { JawOpen: 1.0, ... }
|
||||
* @example <caption>Get active blendshape map</caption>
|
||||
* print(JSON.stringify(KasenAPIExample.getActiveBlendshapes(MyAvatar.sessionUUID)));
|
||||
*/
|
||||
QVariant getActiveBlendshapes(const QUuid& avatarID) const {
|
||||
if (auto head = getAvatarHead(avatarID)) {
|
||||
return head->toJson()["blendShapes"].toVariant();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant getBlendshapeMapping(const QUuid& avatarID) const {
|
||||
QVariantMap out;
|
||||
if (auto head = getAvatarHead(avatarID)) {
|
||||
for (const auto& kv : head->getBlendshapeMap().toStdMap()) {
|
||||
out[kv.first] = kv.second;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariant getBlendshapes(const QUuid& avatarID) const {
|
||||
QVariantMap result;
|
||||
if (auto head = getAvatarHead(avatarID)) {
|
||||
QStringList names = getBlendshapeNames(avatarID);
|
||||
auto states = head->getBlendshapeStates();
|
||||
result = {
|
||||
{ "base", zipNonZeroValues(names, states.base) },
|
||||
{ "summed", zipNonZeroValues(names, states.summed) },
|
||||
{ "transient", zipNonZeroValues(names, states.transient) },
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static QVariantMap zipNonZeroValues(const QStringList& keys, const QVector<float>& values) {
|
||||
QVariantMap out;
|
||||
for (int i=1; i < values.size(); i++) {
|
||||
if (fabs(values[i]) > 1.0e-6) {
|
||||
out[keys.value(i)] = values[i];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
struct _HeadHelper : public HeadData {
|
||||
QMap<QString,int> getBlendshapeMap() const {
|
||||
return _blendshapeLookupMap;
|
||||
}
|
||||
struct States { QVector<float> base, summed, transient; };
|
||||
States getBlendshapeStates() const {
|
||||
return {
|
||||
_blendshapeCoefficients,
|
||||
_summedBlendshapeCoefficients,
|
||||
_transientBlendshapeCoefficients
|
||||
};
|
||||
}
|
||||
};
|
||||
static const _HeadHelper* getAvatarHead(const QUuid& avatarID) {
|
||||
auto avatars = DependencyManager::get<AvatarHashMap>();
|
||||
auto avatar = avatars ? avatars->getAvatarBySessionID(avatarID) : nullptr;
|
||||
auto head = avatar ? avatar->getHeadData() : nullptr;
|
||||
return reinterpret_cast<const _HeadHelper*>(head);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const QLoggingCategory& example::logger{ custom_api_example::logger };
|
||||
|
||||
#include "KasenAPIExample.moc"
|
21
plugins/KasenAPIExample/src/plugin.json
Normal file
21
plugins/KasenAPIExample/src/plugin.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name":"Kasen JS API Example",
|
||||
"version": 1,
|
||||
"package": {
|
||||
"author": "Revofire",
|
||||
"homepage": "www.realities.dev",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"hifi-interface": ">= 0.83.0",
|
||||
"hifi-assignment-client": ">= 0.83.0"
|
||||
},
|
||||
"config": {
|
||||
"client": true,
|
||||
"entity_client": true,
|
||||
"entity_server": true,
|
||||
"edit_filter": true,
|
||||
"agent": true,
|
||||
"avatar": true
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -48,7 +48,6 @@ var UTF_CODE = 0;
|
|||
// Only plays a sound if it is downloaded.
|
||||
// Only plays one sound at a time.
|
||||
var emojiCreateSound = SoundCache.getSound(Script.resolvePath('resources/sounds/emojiPopSound1.wav'));
|
||||
var emojiDestroySound = SoundCache.getSound(Script.resolvePath('resources/sounds/emojiPopSound2.wav'));
|
||||
var injector;
|
||||
var DEFAULT_VOLUME = 0.01;
|
||||
var local = false;
|
||||
|
@ -326,9 +325,7 @@ function playPopAnimation() {
|
|||
if (popType === "in") {
|
||||
currentPopScale = MIN_POP_SCALE;
|
||||
} else {
|
||||
// Start with the pop sound on the out
|
||||
currentPopScale = finalInPopScale ? finalInPopScale : MAX_POP_SCALE;
|
||||
playSound(emojiDestroySound, DEFAULT_VOLUME, MyAvatar.position, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
// *************************************
|
||||
// #region dependencies
|
||||
|
||||
|
||||
// The information needed to properly use the sprite sheets and get the general information
|
||||
// about the emojis
|
||||
var emojiList = Script.require("./emojiApp/resources/modules/emojiList.js");
|
||||
var customEmojiList = Script.require("./emojiApp/resources/modules/customEmojiList.js");
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END dependencies
|
||||
|
@ -181,6 +183,7 @@ function maybeClearClapSoundInterval() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// URLs for this fn are relative to SimplifiedEmoteIndicator.qml
|
||||
function toggleReaction(reaction) {
|
||||
var reactionEnding = reactionsBegun.indexOf(reaction) > -1;
|
||||
|
@ -192,6 +195,7 @@ function toggleReaction(reaction) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function maybeDeleteRemoteIndicatorTimeout() {
|
||||
if (restoreEmoteIndicatorTimeout) {
|
||||
Script.clearTimeout(restoreEmoteIndicatorTimeout);
|
||||
|
@ -199,6 +203,7 @@ function maybeDeleteRemoteIndicatorTimeout() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
var reactionsBegun = [];
|
||||
var pointReticle = null;
|
||||
var mouseMoveEventsConnected = false;
|
||||
|
@ -229,6 +234,7 @@ function beginReactionWrapper(reaction) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks to see if there are any reticle entities already to delete
|
||||
function deleteOldReticles() {
|
||||
MyAvatar.getAvatarEntitiesVariant()
|
||||
|
@ -313,6 +319,7 @@ function triggerReactionWrapper(reaction) {
|
|||
}, WAIT_TO_RESTORE_EMOTE_INDICATOR_ICON_MS);
|
||||
}
|
||||
|
||||
|
||||
function maybeClearReticleUpdateLimiterTimeout() {
|
||||
if (reticleUpdateRateLimiterTimer) {
|
||||
Script.clearTimeout(reticleUpdateRateLimiterTimer);
|
||||
|
@ -393,6 +400,7 @@ function onMessageFromEmoteAppBar(message) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function getEmojiURLFromCode(code) {
|
||||
var emojiObject = emojiList[emojiCodeMap[code]];
|
||||
var emojiFilename;
|
||||
|
@ -405,6 +413,7 @@ function getEmojiURLFromCode(code) {
|
|||
return "../../emojiApp/resources/images/emojis/52px/" + emojiFilename;
|
||||
}
|
||||
|
||||
|
||||
function updateEmoteIndicatorIcon(iconURL) {
|
||||
emoteAppBarWindow.sendToQml({
|
||||
"source": "simplifiedEmote.js",
|
||||
|
@ -451,7 +460,10 @@ function keyPressHandler(event) {
|
|||
} else if (event.text === RAISE_HAND_KEY) {
|
||||
toggleReaction("raiseHand");
|
||||
} else if (event.text === APPLAUD_KEY) {
|
||||
toggleReaction("applaud");
|
||||
// Make sure this doesn't get triggered if you are flying, falling, or jumping
|
||||
if (!MyAvatar.isInAir()) {
|
||||
toggleReaction("applaud");
|
||||
}
|
||||
} else if (event.text === POINT_KEY) {
|
||||
toggleReaction("point");
|
||||
} else if (event.text === EMOTE_WINDOW && !(Settings.getValue("io.highfidelity.isEditing", false))) {
|
||||
|
@ -639,6 +651,7 @@ function unload() {
|
|||
// *************************************
|
||||
// #region EMOJI_UTILITY
|
||||
|
||||
|
||||
var EMOJI_52_BASE_URL = "../../resources/images/emojis/52px/";
|
||||
function selectedEmoji(code) {
|
||||
emojiAPI.addEmoji(code);
|
||||
|
@ -744,6 +757,7 @@ function toggleEmojiApp() {
|
|||
emojiAPI.registerAvimojiQMLWindow(emojiAppWindow);
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END EMOJI_MAIN
|
||||
|
|
|
@ -45,7 +45,8 @@ Rectangle {
|
|||
}
|
||||
|
||||
Behavior on requestedWidth {
|
||||
enabled: true
|
||||
enabled: false // Set this to `true` once we have a different windowing system that better supports on-screen widgets
|
||||
// like the Emote Indicator.
|
||||
SmoothedAnimation { duration: 220 }
|
||||
}
|
||||
|
||||
|
|
|
@ -445,7 +445,7 @@ void DomainBaker::handleFinishedModelBaker() {
|
|||
|
||||
// enumerate the QJsonRef values for the URL of this model from our multi hash of
|
||||
// entity objects needing a URL re-write
|
||||
for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getModelURL())) {
|
||||
for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getOriginalInputModelURL())) {
|
||||
QString property = propertyEntityPair.first;
|
||||
// convert the entity QJsonValueRef to a QJsonObject so we can modify its URL
|
||||
auto entity = propertyEntityPair.second.toObject();
|
||||
|
@ -485,10 +485,10 @@ void DomainBaker::handleFinishedModelBaker() {
|
|||
}
|
||||
|
||||
// remove the baked URL from the multi hash of entities needing a re-write
|
||||
_entitiesNeedingRewrite.remove(baker->getModelURL());
|
||||
_entitiesNeedingRewrite.remove(baker->getOriginalInputModelURL());
|
||||
|
||||
// drop our shared pointer to this baker so that it gets cleaned up
|
||||
_modelBakers.remove(baker->getModelURL());
|
||||
_modelBakers.remove(baker->getOriginalInputModelURL());
|
||||
|
||||
// emit progress to tell listeners how many models we have baked
|
||||
emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes);
|
||||
|
|
Loading…
Reference in a new issue