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

This commit is contained in:
David Kelly 2017-02-09 20:01:43 -07:00
commit 5ca80803c0
82 changed files with 2450 additions and 363 deletions

View file

@ -138,7 +138,6 @@ void Agent::handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, Sh
void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
_receivedAudioStream.parseData(*message);
_lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness();
_receivedAudioStream.clearBuffer();
}
@ -323,12 +322,14 @@ void Agent::scriptRequestFinished() {
request->deleteLater();
}
void Agent::executeScript() {
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload));
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
// setup an Avatar for the script to use
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
connect(_scriptEngine.get(), SIGNAL(update(float)), scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection);
scriptedAvatar->setForceFaceTrackerConnected(true);
@ -338,11 +339,33 @@ void Agent::executeScript() {
// give this AvatarData object to the script engine
_scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data());
auto player = DependencyManager::get<recording::Deck>();
connect(player.data(), &recording::Deck::playbackStateChanged, [=] {
if (player->isPlaying()) {
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
if (recordingInterface->getPlayFromCurrentLocation()) {
scriptedAvatar->setRecordingBasis();
}
} else {
scriptedAvatar->clearRecordingBasis();
}
});
using namespace recording;
static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME);
// FIXME how to deal with driving multiple avatars locally?
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) {
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel();
// FIXME - the ability to switch the avatar URL is not actually supported when playing back from a recording
if (!useFrameSkeleton) {
static std::once_flag warning;
std::call_once(warning, [] {
qWarning() << "Recording.setPlayerUseSkeletonModel(false) is not currently supported.";
});
}
AvatarData::fromFrame(frame->data, *scriptedAvatar);
});
@ -352,8 +375,11 @@ void Agent::executeScript() {
const QByteArray& audio = frame->data;
static quint16 audioSequenceNumber{ 0 };
Transform audioTransform;
auto headOrientation = scriptedAvatar->getHeadOrientation();
audioTransform.setTranslation(scriptedAvatar->getPosition());
audioTransform.setRotation(scriptedAvatar->getOrientation());
audioTransform.setRotation(headOrientation);
QByteArray encodedBuffer;
if (_encoder) {
_encoder->encode(audio, encodedBuffer);
@ -537,7 +563,10 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
}
void Agent::processAgentAvatarAudio() {
if (_isAvatar && (_isListeningToAudioStream || _avatarSound)) {
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
bool isPlayingRecording = recordingInterface->isPlaying();
if (_isAvatar && ((_isListeningToAudioStream && !isPlayingRecording) || _avatarSound)) {
// if we have an avatar audio stream then send it out to our audio-mixer
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
bool silentFrame = true;

View file

@ -27,6 +27,7 @@ public:
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
quint16 assignmentMonitorPort);
~AssignmentClient();
private slots:
void sendAssignmentRequest();
void assignmentCompleted();

View file

@ -11,9 +11,13 @@
#include "EntityScriptServer.h"
#include <mutex>
#include <AudioConstants.h>
#include <AudioInjectorManager.h>
#include <ClientServerUtils.h>
#include <EntityScriptingInterface.h>
#include <LogHandler.h>
#include <MessagesClient.h>
#include <plugins/CodecPlugin.h>
#include <plugins/PluginManager.h>
@ -24,12 +28,29 @@
#include <UUID.h>
#include <WebSocketServerClass.h>
#include "ClientServerUtils.h"
#include "EntityScriptServerLogging.h"
#include "../entities/AssignmentParentFinder.h"
using Mutex = std::mutex;
using Lock = std::lock_guard<Mutex>;
static std::mutex logBufferMutex;
static std::string logBuffer;
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
auto logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message);
if (!logMessage.isEmpty()) {
Lock lock(logBufferMutex);
logBuffer.append(logMessage.toStdString() + '\n');
}
}
int EntityScriptServer::_entitiesScriptEngineCount = 0;
EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) {
qInstallMessageHandler(messageHandler);
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
ResourceManager::init();
@ -57,6 +78,17 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
static const int LOG_INTERVAL = MSECS_PER_SECOND / 10;
auto timer = new QTimer(this);
timer->setInterval(LOG_INTERVAL);
connect(timer, &QTimer::timeout, this, &EntityScriptServer::pushLogs);
timer->start();
}
EntityScriptServer::~EntityScriptServer() {
qInstallMessageHandler(LogHandler::verboseMessageHandler);
}
static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
@ -68,7 +100,7 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<Rec
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
if (_entityViewer.getTree() && !_shuttingDown) {
qDebug() << "Reloading: " << entityID;
qCDebug(entity_script_server) << "Reloading: " << entityID;
_entitiesScriptEngine->unloadEntityScript(entityID);
checkAndCallPreload(entityID, true);
}
@ -100,6 +132,99 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<Receiv
}
}
void EntityScriptServer::handleSettings() {
auto nodeList = DependencyManager::get<NodeList>();
auto& domainHandler = nodeList->getDomainHandler();
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server";
if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) {
qWarning() << "Received settings from the domain-server with no entity_script_server section.";
return;
}
auto entityScriptServerSettings = settingsObject[ENTITY_SCRIPT_SERVER_SETTINGS_KEY].toObject();
static const QString MAX_ENTITY_PPS_OPTION = "max_total_entity_pps";
static const QString ENTITY_PPS_PER_SCRIPT = "entity_pps_per_script";
if (!entityScriptServerSettings.contains(MAX_ENTITY_PPS_OPTION) || !entityScriptServerSettings.contains(ENTITY_PPS_PER_SCRIPT)) {
qWarning() << "Received settings from the domain-server with no max_total_entity_pps or entity_pps_per_script properties.";
return;
}
_maxEntityPPS = std::max(0, entityScriptServerSettings[MAX_ENTITY_PPS_OPTION].toInt());
_entityPPSPerScript = std::max(0, entityScriptServerSettings[ENTITY_PPS_PER_SCRIPT].toInt());
qDebug() << QString("Received entity script server settings, Max Entity PPS: %1, Entity PPS Per Entity Script: %2")
.arg(_maxEntityPPS).arg(_entityPPSPerScript);
}
void EntityScriptServer::updateEntityPPS() {
int numRunningScripts = _entitiesScriptEngine->getNumRunningEntityScripts();
int pps;
if (std::numeric_limits<int>::max() / _entityPPSPerScript < numRunningScripts) {
qWarning() << QString("Integer multiplaction would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript);
pps = std::numeric_limits<int>::max();
pps = std::min(_maxEntityPPS, pps);
} else {
pps = _entityPPSPerScript * numRunningScripts;
pps = std::min(_maxEntityPPS, pps);
}
_entityEditSender.setPacketsPerSecond(pps);
qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps);
}
void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
bool enable = false;
message->readPrimitive(&enable);
auto senderUUID = senderNode->getUUID();
auto it = _logListeners.find(senderUUID);
if (enable && senderNode->getCanRez()) {
if (it == std::end(_logListeners)) {
_logListeners.insert(senderUUID);
qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream";
}
} else {
if (it != std::end(_logListeners)) {
_logListeners.erase(it);
qCInfo(entity_script_server) << "Node" << senderUUID << "unsubscribed from log stream";
}
}
}
void EntityScriptServer::pushLogs() {
std::string buffer;
{
Lock lock(logBufferMutex);
std::swap(logBuffer, buffer);
}
if (buffer.empty()) {
return;
}
if (_logListeners.empty()) {
return;
}
auto nodeList = DependencyManager::get<NodeList>();
for (auto uuid : _logListeners) {
auto node = nodeList->nodeWithUUID(uuid);
if (node && node->getActiveSocket()) {
auto packet = NLPacketList::create(PacketType::EntityServerScriptLog, QByteArray(), true, true);
packet->write(buffer.data(), buffer.size());
nodeList->sendPacketList(std::move(packet), *node);
}
}
}
void EntityScriptServer::run() {
// make sure we request our script once the agent connects to the domain
auto nodeList = DependencyManager::get<NodeList>();
@ -114,6 +239,9 @@ void EntityScriptServer::run() {
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings);
// make sure we hear about connected nodes so we can grab an ATP script if a request is pending
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &EntityScriptServer::nodeActivated);
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptServer::nodeKilled);
@ -154,9 +282,67 @@ void EntityScriptServer::run() {
connect(tree, &EntityTree::entityServerScriptChanging, this, &EntityScriptServer::entityServerScriptChanging, Qt::QueuedConnection);
}
void EntityScriptServer::cleanupOldKilledListeners() {
auto threshold = usecTimestampNow() - 5 * USECS_PER_SECOND;
using ValueType = std::pair<QUuid, quint64>;
auto it = std::remove_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) {
return value.second < threshold;
});
_killedListeners.erase(it, std::end(_killedListeners));
}
void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) {
if (activatedNode->getType() == NodeType::AudioMixer) {
negotiateAudioFormat();
switch (activatedNode->getType()) {
case NodeType::AudioMixer:
negotiateAudioFormat();
break;
case NodeType::Agent: {
auto activatedNodeUUID = activatedNode->getUUID();
using ValueType = std::pair<QUuid, quint64>;
auto it = std::find_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) {
return value.first == activatedNodeUUID;
});
if (it != std::end(_killedListeners)) {
_killedListeners.erase(it);
_logListeners.insert(activatedNodeUUID);
}
break;
}
default:
// Do nothing
break;
}
}
void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
switch (killedNode->getType()) {
case NodeType::EntityServer: {
if (!_shuttingDown) {
if (_entitiesScriptEngine) {
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
resetEntitiesScriptEngine();
_entityViewer.clear();
}
break;
}
case NodeType::Agent: {
cleanupOldKilledListeners();
auto killedNodeUUID = killedNode->getUUID();
auto it = _logListeners.find(killedNodeUUID);
if (it != std::end(_logListeners)) {
_logListeners.erase(killedNodeUUID);
_killedListeners.emplace_back(killedNodeUUID, usecTimestampNow());
}
break;
}
default:
// Do nothing
break;
}
}
@ -188,7 +374,7 @@ void EntityScriptServer::handleSelectedAudioFormat(QSharedPointer<ReceivedMessag
void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) {
_selectedCodecName = selectedCodecName;
qDebug() << "Selected Codec:" << _selectedCodecName;
qCDebug(entity_script_server) << "Selected Codec:" << _selectedCodecName;
// release any old codec encoder/decoder first...
if (_codec && _encoder) {
@ -202,7 +388,7 @@ void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) {
if (_selectedCodecName == plugin->getName()) {
_codec = plugin;
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
qDebug() << "Selected Codec Plugin:" << _codec.get();
qCDebug(entity_script_server) << "Selected Codec Plugin:" << _codec.get();
break;
}
}
@ -232,7 +418,9 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
newEngine->runInThread();
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngine.data());
disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS);
_entitiesScriptEngine.swap(newEngine);
connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS);
}
@ -288,26 +476,13 @@ void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, const
QString scriptUrl = entity->getServerScripts();
if (!scriptUrl.isEmpty()) {
scriptUrl = ResourceManager::normalizeURL(scriptUrl);
qDebug() << "Loading entity server script" << scriptUrl << "for" << entityID;
qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID;
ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload);
}
}
}
}
void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
if (!_shuttingDown && killedNode->getType() == NodeType::EntityServer) {
if (_entitiesScriptEngine) {
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
resetEntitiesScriptEngine();
_entityViewer.clear();
}
}
void EntityScriptServer::sendStatsPacket() {
}

View file

@ -12,7 +12,11 @@
#ifndef hifi_EntityScriptServer_h
#define hifi_EntityScriptServer_h
#include <set>
#include <vector>
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <EntityEditPacketSender.h>
#include <EntityTreeHeadlessViewer.h>
@ -20,11 +24,15 @@
#include <ScriptEngine.h>
#include <ThreadedAssignment.h>
static const int DEFAULT_MAX_ENTITY_PPS = 9000;
static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900;
class EntityScriptServer : public ThreadedAssignment {
Q_OBJECT
public:
EntityScriptServer(ReceivedMessage& message);
~EntityScriptServer();
virtual void aboutToFinish() override;
@ -42,6 +50,13 @@ private slots:
void handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleSettings();
void updateEntityPPS();
void handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void pushLogs();
private:
void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName);
@ -55,6 +70,8 @@ private:
void entityServerScriptChanging(const EntityItemID& entityID, const bool reload);
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
void cleanupOldKilledListeners();
bool _shuttingDown { false };
static int _entitiesScriptEngineCount;
@ -62,6 +79,12 @@ private:
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
int _maxEntityPPS { DEFAULT_MAX_ENTITY_PPS };
int _entityPPSPerScript { DEFAULT_ENTITY_PPS_PER_SCRIPT };
std::set<QUuid> _logListeners;
std::vector<std::pair<QUuid, quint64>> _killedListeners;
QString _selectedCodecName;
CodecPluginPointer _codec;
Encoder* _encoder { nullptr };

View file

@ -0,0 +1,14 @@
//
// EntityScriptServerLogging.cpp
// assignment-client/src/scripts
//
// Created by Clement on 2/2/17.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntityScriptServerLogging.h"
Q_LOGGING_CATEGORY(entity_script_server, "hifi.entity-script-server")

View file

@ -0,0 +1,19 @@
//
// EntityScriptServerLogging.h
// assignment-client/src/scripts
//
// Created by Clement on 2/2/17.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityScriptServerLogging_h
#define hifi_EntityScriptServerLogging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(entity_script_server)
#endif // hifi_EntityScriptServerLogging_h

View file

@ -178,7 +178,7 @@
{
"name": "maximum_user_capacity_redirect_location",
"label": "Redirect to Location on Maximum Capacity",
"help": "Is there another domain, you'd like to redirect clients to when the maximum number of avatars are connected.",
"help": "The location to redirect users to when the maximum number of avatars are connected.",
"placeholder": "",
"default": "",
"advanced": false
@ -822,6 +822,29 @@
}
]
},
{
"name": "entity_script_server",
"label": "Entity Script Server (ESS)",
"assignment-types": [5],
"settings": [
{
"name": "entity_pps_per_script",
"label": "Entity PPS per script",
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
"default": 900,
"type": "int",
"advanced": true
},
{
"name": "max_total_entity_pps",
"label": "Maximum Total Entity PPS",
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
"default": 9000,
"type": "int",
"advanced": true
}
]
},
{
"name": "avatars",
"label": "Avatars",

View file

@ -22,7 +22,7 @@
"to": "Actions.Up",
"filters":
[
{ "type": "deadZone", "min": 0.95 },
{ "type": "deadZone", "min": 0.6 },
"invert"
]
},

View file

@ -105,6 +105,14 @@ Item {
visible: root.expanded
text: "Asset Mbps In/Out: " + root.assetMbpsIn.toFixed(2) + "/" + root.assetMbpsOut.toFixed(2)
}
StatText {
visible: root.expanded
text: "Fully Simulated Avatars: " + root.fullySimulatedAvatarCount
}
StatText {
visible: root.expanded
text: "Partially Simulated Avatars: " + root.partiallySimulatedAvatarCount
}
}
}
@ -217,7 +225,10 @@ Item {
text: " Batch: " + root.batchFrameTime.toFixed(1) + " ms"
}
StatText {
text: " GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
text: " GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
}
StatText {
text: " Avatar: " + root.avatarSimulationTime.toFixed(1) + " ms"
}
StatText {
text: "Triangles: " + root.triangles +

View file

@ -129,7 +129,7 @@ Rectangle {
property int dropSamples: 9;
property int dropSpread: 0;
DropShadow {
visible: showPlace && desktop.gradientsSupported;
visible: showPlace && (desktop ? desktop.gradientsSupported : false)
source: place;
anchors.fill: place;
horizontalOffset: dropHorizontalOffset;

View file

@ -16,6 +16,8 @@ import QtQuick.Controls 1.4
import "../styles-uit"
import "../controls-uit" as HifiControls
// references HMD, Users, UserActivityLogger from root context
Rectangle {
id: pal
// Size
@ -26,7 +28,7 @@ Rectangle {
// Properties
property int myCardHeight: 90
property int rowHeight: 70
property int actionButtonWidth: 75
property int actionButtonWidth: 55
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
@ -35,7 +37,9 @@ Rectangle {
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
property var gainSliderValueDB: ({});
HifiConstants { id: hifi }
// The letterbox used for popup messages
LetterboxMessage {
id: letterboxMessage
@ -54,8 +58,8 @@ Rectangle {
property bool punctuationMode: false
id: palContainer
// Size
width: pal.width - 50
height: pal.height - 50
width: pal.width - 10
height: pal.height - 10
// Style
color: pal.color
// Anchors
@ -397,7 +401,7 @@ Rectangle {
width: 20
height: 28
anchors.right: adminTab.right
anchors.rightMargin: 31 + hifi.dimensions.scrollbarBackgroundWidth
anchors.rightMargin: 10 + hifi.dimensions.scrollbarBackgroundWidth
anchors.top: adminTab.top
anchors.topMargin: 2
RalewayRegular {
@ -422,6 +426,8 @@ Rectangle {
onExited: adminHelpText.color = hifi.colors.redHighlight
}
}
}
HifiControls.Keyboard {
id: keyboard
raised: myCard.currentlyEditingDisplayName && HMD.active
@ -432,7 +438,7 @@ Rectangle {
right: parent.right
}
}
}
// Timer used when selecting table rows that aren't yet present in the model
// (i.e. when selecting avatars using edit.js or sphere overlays)
Timer {

View file

@ -0,0 +1,555 @@
//
// TabletAddressDialog.qml
//
// Created by Dante Ruiz on 2016/07/16
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import QtGraphicalEffects 1.0
import "../../controls"
import "../../styles"
import "../../windows"
import "../"
import "../toolbars"
import "../../styles-uit" as HifiStyles
import "../../controls-uit" as HifiControls
Item {
id: root
HifiConstants { id: hifi }
HifiStyles.HifiConstants { id: hifiStyleConstants }
width: parent.width
height: parent.height
property var allStories: [];
property int cardWidth: 370;
property int cardHeight: 320;
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
Component.onCompleted: {
fillDestinations();
updateLocationText();
root.parentChanged.connect(center);
center();
}
Component.onDestruction: {
root.parentChanged.disconnect(center);
}
function center() {
// Explicitly center in order to avoid warnings at shutdown
anchors.centerIn = parent;
}
function resetAfterTeleport() {
//storyCardFrame.shown = root.shown = false;
}
function goCard(targetString) {
if (0 !== targetString.indexOf('hifi://')) {
return;
}
addressLine.text = targetString;
toggleOrGo(true);
clearAddressLineTimer.start();
}
property bool isCursorVisible: false // Override default cursor visibility.
AddressBarDialog {
id: addressBarDialog
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
width: parent.width
height: parent.height
anchors {
right: parent.right
left: parent.left
top: parent.top
bottom: parent.bottom
}
onMetaverseServerUrlChanged: updateLocationTextTimer.start();
Rectangle {
id: topBar
height: 90
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#1e1e1e"
}
}
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.topMargin: 0
anchors.top: parent.top
Row {
id: thing
spacing: 2 * hifi.layout.spacing
anchors {
top: parent.top;
left: parent.left
}
TextButton {
id: allTab;
text: "ALL";
property string includeActions: 'snapshot, concurrency';
selected: allTab === selectedTab;
action: tabSelect;
}
TextButton {
id: placeTab;
text: "PLACES";
property string includeActions: 'concurrency';
selected: placeTab === selectedTab;
action: tabSelect;
}
TextButton {
id: snapTab;
text: "SNAP";
property string includeActions: 'snapshot';
selected: snapTab === selectedTab;
action: tabSelect;
}
}
}
Rectangle {
id: bgMain
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#0f212e"
}
}
anchors.bottom: backgroundImage.top
anchors.bottomMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: topBar.bottom
anchors.topMargin: 0
ListModel { id: suggestions }
ListView {
id: scroll
property int stackedCardShadowHeight: 10;
clip: true
spacing: 14
anchors {
bottom: parent.bottom
top: parent.top
left: parent.left
right: parent.right
leftMargin: 50
}
model: suggestions
orientation: ListView.Vertical
delegate: Card {
width: cardWidth;
height: cardHeight;
goFunction: goCard;
userName: model.username;
placeName: model.place_name;
hifiUrl: model.place_name + model.path;
thumbnail: model.thumbnail_url;
imageUrl: model.image_url;
action: model.action;
timestamp: model.created_at;
onlineUsers: model.online_users;
storyId: model.metaverseId;
drillDownToPlace: model.drillDownToPlace;
shadowHeight: scroll.stackedCardShadowHeight;
hoverThunk: function () { scroll.currentIndex = index; }
unhoverThunk: function () { scroll.currentIndex = -1; }
}
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; }
}
}
Rectangle {
id: backgroundImage
width: 480
height: 70
gradient: Gradient {
GradientStop {
position: 0
color: "#c2ced8"
}
GradientStop {
position: 1
color: "#c2ced8"
}
}
anchors {
bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom
right: parent.right
left: parent.left
}
ToolbarButton {
id: homeButton
imageURL: "../../../images/home.svg"
onClicked: {
addressBarDialog.loadHome();
root.shown = false;
}
anchors {
left: parent.left
leftMargin: homeButton.width / 2
verticalCenter: parent.verticalCenter
}
}
property int inputAreaHeight: 70
property int inputAreaStep: (height - inputAreaHeight) / 2
HifiStyles.RalewayLight {
id: notice;
font.pixelSize: hifi.fonts.pixelSize * 0.50;
anchors {
top: parent.top
topMargin: parent.inputAreaStep + 12
left: addressLine.left
right: addressLine.right
}
}
HifiStyles.FiraSansRegular {
id: location;
font.pixelSize: addressLine.font.pixelSize;
color: "gray";
clip: true;
anchors.fill: addressLine;
visible: addressLine.text.length === 0
}
TextInput {
id: addressLine
focus: true
anchors {
bottom: parent.bottom
left: homeButton.right
right: parent.right
leftMargin: homeButton.width
rightMargin: homeButton.width / 2
topMargin: parent.inputAreaStep + (2 * hifi.layout.spacing)
bottomMargin: parent.inputAreaStep
}
font.pixelSize: hifi.fonts.pixelSize * 0.75
cursorVisible: false
onTextChanged: {
filterChoicesByText();
updateLocationText(text.length > 0);
if (!isCursorVisible && text.length > 0) {
isCursorVisible = true;
cursorVisible = true;
}
}
onAccepted: {
addressBarDialog.keyboardEnabled = false;
}
onActiveFocusChanged: {
cursorVisible = isCursorVisible && focus;
}
MouseArea {
// If user clicks in address bar show cursor to indicate ability to enter address.
anchors.fill: parent
onClicked: {
isCursorVisible = true;
//parent.cursorVisible = true;
parent.forceActiveFocus();
addressBarDialog.keyboardEnabled = HMD.active
tabletRoot.playButtonClickSound();
}
}
}
}
Timer {
// Delay updating location text a bit to avoid flicker of content and so that connection status is valid.
id: updateLocationTextTimer
running: false
interval: 500 // ms
repeat: false
onTriggered: updateLocationText(false);
}
Timer {
// Delay clearing address line so as to avoid flicker of "not connected" being displayed after entering an address.
id: clearAddressLineTimer
running: false
interval: 100 // ms
repeat: false
onTriggered: {
addressLine.text = "";
isCursorVisible = false;
}
}
HifiControls.Keyboard {
id: keyboard
raised: parent.keyboardEnabled
numeric: parent.punctuationMode
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
}
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
// TODO: make available to other .qml.
var request = new XMLHttpRequest();
// QT bug: apparently doesn't handle onload. Workaround using readyState.
request.onreadystatechange = function () {
var READY_STATE_DONE = 4;
var HTTP_OK = 200;
if (request.readyState >= READY_STATE_DONE) {
var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText,
response = !error && request.responseText,
contentType = !error && request.getResponseHeader('content-type');
if (!error && contentType.indexOf('application/json') === 0) {
try {
response = JSON.parse(response);
} catch (e) {
error = e;
}
}
cb(error, response);
}
};
request.open("GET", url, true);
request.send();
}
function identity(x) {
return x;
}
function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey
if (!error && (data.status === 'success')) {
return;
}
if (!error) { // Create a message from the data
error = data.status + ': ' + data.error;
}
if (typeof(error) === 'string') { // Make a proper Error object
error = new Error(error);
}
error.message += ' in ' + url; // Include the url.
cb(error);
return true;
}
function resolveUrl(url) {
return (url.indexOf('/') === 0) ? (addressBarDialog.metaverseServerUrl + url) : url;
}
function makeModelData(data) { // create a new obj from data
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
// So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story.
var name = data.place_name,
tags = data.tags || [data.action, data.username],
description = data.description || "",
thumbnail_url = data.thumbnail_url || "";
return {
place_name: name,
username: data.username || "",
path: data.path || "",
created_at: data.created_at || "",
action: data.action || "",
thumbnail_url: resolveUrl(thumbnail_url),
image_url: resolveUrl(data.details.image_url),
metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity.
tags: tags,
description: description,
online_users: data.details.concurrency || 0,
drillDownToPlace: false,
searchText: [name].concat(tags, description || []).join(' ').toUpperCase()
}
}
function suggestable(place) {
if (place.action === 'snapshot') {
return true;
}
return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain.
}
property var selectedTab: allTab;
function tabSelect(textButton) {
selectedTab = textButton;
fillDestinations();
}
property var placeMap: ({});
function addToSuggestions(place) {
var collapse = allTab.selected && (place.action !== 'concurrency');
if (collapse) {
var existing = placeMap[place.place_name];
if (existing) {
existing.drillDownToPlace = true;
return;
}
}
suggestions.append(place);
if (collapse) {
placeMap[place.place_name] = suggestions.get(suggestions.count - 1);
} else if (place.action === 'concurrency') {
suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories).
}
}
property int requestId: 0;
function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model
var options = [
'now=' + new Date().toISOString(),
'include_actions=' + selectedTab.includeActions,
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
'require_online=true',
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
'page=' + pageNumber
];
var url = metaverseBase + 'user_stories?' + options.join('&');
var thisRequestId = ++requestId;
getRequest(url, function (error, data) {
if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) {
return;
}
var stories = data.user_stories.map(function (story) { // explicit single-argument function
return makeModelData(story, url);
});
allStories = allStories.concat(stories);
stories.forEach(makeFilteredPlaceProcessor());
if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now
return getUserStoryPage(pageNumber + 1, cb);
}
cb();
});
}
function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity),
data = allStories;
function matches(place) {
if (!words.length) {
return suggestable(place);
}
return words.every(function (word) {
return place.searchText.indexOf(word) >= 0;
});
}
return function (place) {
if (matches(place)) {
addToSuggestions(place);
}
};
}
function filterChoicesByText() {
suggestions.clear();
placeMap = {};
allStories.forEach(makeFilteredPlaceProcessor());
}
function fillDestinations() {
allStories = [];
suggestions.clear();
placeMap = {};
getUserStoryPage(1, function (error) {
console.log('user stories query', error || 'ok', allStories.length);
});
}
function updateLocationText(enteringAddress) {
if (enteringAddress) {
notice.text = "Go to a place, @user, path or network address";
notice.color = hifiStyleConstants.colors.baseGrayHighlight;
} else {
notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected";
notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.baseGrayHighlight : hifiStyleConstants.colors.redHighlight;
// Display hostname, which includes ip address, localhost, and other non-placenames.
location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '');
}
}
onVisibleChanged: {
updateLocationText(false);
if (visible) {
addressLine.forceActiveFocus();
fillDestinations();
}
}
function toggleOrGo(fromSuggestions) {
if (addressLine.text !== "") {
addressBarDialog.loadAddress(addressLine.text, fromSuggestions)
}
root.shown = false;
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
root.shown = false
clearAddressLineTimer.start();
event.accepted = true
break
case Qt.Key_Enter:
case Qt.Key_Return:
toggleOrGo()
clearAddressLineTimer.start();
event.accepted = true
break
}
}
}

View file

@ -18,6 +18,16 @@ Item {
loader.item.scriptURL = injectedJavaScriptUrl;
}
// used to send a message from qml to interface script.
signal sendToScript(var message);
// used to receive messages from interface script
function fromScript(message) {
if (loader.item.hasOwnProperty("fromScript")) {
loader.item.fromScript(message);
}
}
SoundEffect {
id: buttonClickSound
volume: 0.1
@ -55,6 +65,9 @@ Item {
}
});
}
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
loader.item.forceActiveFocus();
}
}

View file

@ -41,6 +41,7 @@
#include <QtMultimedia/QMediaPlayer>
#include <QFontDatabase>
#include <QProcessEnvironment>
#include <QTemporaryDir>
@ -62,6 +63,7 @@
#include <DebugDraw.h>
#include <DeferredLightingEffect.h>
#include <EntityScriptClient.h>
#include <EntityScriptServerLogClient.h>
#include <EntityScriptingInterface.h>
#include <ErrorDialog.h>
#include <FileScriptingInterface.h>
@ -174,7 +176,6 @@
#include "FrameTimingsScriptingInterface.h"
#include <GPUIdent.h>
#include <gl/GLHelpers.h>
#include <EntityScriptClient.h>
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
@ -436,6 +437,7 @@ bool setupEssentials(int& argc, char** argv) {
}
DependencyManager::set<tracing::Tracer>();
PROFILE_SET_THREAD_NAME("Main Thread");
#if defined(Q_OS_WIN)
// Select appropriate audio DLL
@ -518,6 +520,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<CompositorHelper>();
DependencyManager::set<OffscreenQmlSurfaceCache>();
DependencyManager::set<EntityScriptClient>();
DependencyManager::set<EntityScriptServerLogClient>();
return previousSessionCrashed;
}
@ -5522,6 +5525,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
connect(scriptEngine, &ScriptEngine::printedMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onPrintedMessage);
connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onErrorMessage);
@ -6282,6 +6288,17 @@ void Application::toggleLogDialog() {
}
}
void Application::toggleEntityScriptServerLogDialog() {
if (! _entityScriptServerLogDialog) {
_entityScriptServerLogDialog = new EntityScriptServerLogDialog(nullptr);
}
if (_entityScriptServerLogDialog->isVisible()) {
_entityScriptServerLogDialog->hide();
} else {
_entityScriptServerLogDialog->show();
}
}
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
postLambdaEvent([notify, includeAnimated, aspectRatio, this] {

View file

@ -62,6 +62,7 @@
#include "scripting/DialogsManagerScriptingInterface.h"
#include "ui/ApplicationOverlay.h"
#include "ui/BandwidthDialog.h"
#include "ui/EntityScriptServerLogDialog.h"
#include "ui/LodToolsDialog.h"
#include "ui/LogDialog.h"
#include "ui/OctreeStatsDialog.h"
@ -314,6 +315,7 @@ public slots:
Q_INVOKABLE void loadDialog();
Q_INVOKABLE void loadScriptURLDialog() const;
void toggleLogDialog();
void toggleEntityScriptServerLogDialog();
void toggleRunningScriptsWidget() const;
Q_INVOKABLE void showAssetServerWidget(QString filePath = "");
@ -566,6 +568,7 @@ private:
NodeToOctreeSceneStats _octreeServerSceneStats;
ControllerScriptingInterface* _controllerScriptingInterface{ nullptr };
QPointer<LogDialog> _logDialog;
QPointer<EntityScriptServerLogDialog> _entityScriptServerLogDialog;
FileLogger* _logger;

View file

@ -702,7 +702,14 @@ Menu::Menu() {
// Developer > Log...
addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L,
qApp, SLOT(toggleLogDialog()));
qApp, SLOT(toggleLogDialog()));
auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0,
qApp, SLOT(toggleEntityScriptServerLogDialog()));
QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] {
auto nodeList = DependencyManager::get<NodeList>();
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
});
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
action = addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...");
connect(action, &QAction::triggered, [] {

View file

@ -98,6 +98,7 @@ namespace MenuOption {
const QString EchoServerAudio = "Echo Server Audio";
const QString EnableCharacterController = "Enable avatar collisions";
const QString EnableInverseKinematics = "Enable Inverse Kinematics";
const QString EntityScriptServerLog = "Entity Script Server Log";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";

View file

@ -307,12 +307,22 @@ bool Avatar::shouldDie() const {
void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
_simulationRate.increment();
if (inView) {
_simulationInViewRate.increment();
}
PerformanceTimer perfTimer("simulate");
{
PROFILE_RANGE(simulation, "updateJoints");
if (inView && _hasNewJointData) {
_skeletonModel->getRig()->copyJointsFromJointData(_jointData);
_jointDataSimulationRate.increment();
_skeletonModel->simulate(deltaTime, true);
_skeletonModelSimulationRate.increment();
locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointData = false;
@ -328,6 +338,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
_skeletonModelSimulationRate.increment();
}
}
@ -357,6 +368,21 @@ void Avatar::simulate(float deltaTime, bool inView) {
}
}
float Avatar::getSimulationRate(const QString& rateName) const {
if (rateName == "") {
return _simulationRate.rate();
} else if (rateName == "avatar") {
return _simulationRate.rate();
} else if (rateName == "avatarInView") {
return _simulationInViewRate.rate();
} else if (rateName == "skeletonModel") {
return _skeletonModelSimulationRate.rate();
} else if (rateName == "jointData") {
return _jointDataSimulationRate.rate();
}
return 0.0f;
}
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
const float HEAD_SPHERE_RADIUS = 0.1f;
glm::vec3 theirLookAt = dynamic_pointer_cast<Avatar>(avatar)->getHead()->getLookAtPosition();
@ -1010,7 +1036,7 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
void Avatar::setModelURLFinished(bool success) {
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
qDebug() << "Using default after failing to load Avatar model: " << _skeletonModelURL;
qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL;
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",

View file

@ -182,6 +182,8 @@ public:
void animateScaleChanges(float deltaTime);
void setTargetScale(float targetScale) override;
Q_INVOKABLE float getSimulationRate(const QString& rateName = QString("")) const;
public slots:
// FIXME - these should be migrated to use Pose data instead
@ -259,6 +261,13 @@ protected:
void addToScene(AvatarSharedPointer self);
void ensureInScene(AvatarSharedPointer self);
// Some rate tracking support
RateCounter<> _simulationRate;
RateCounter<> _simulationInViewRate;
RateCounter<> _skeletonModelSimulationRate;
RateCounter<> _jointDataSimulationRate;
private:
uint64_t _lastRenderUpdateTime { 0 };
int _leftPointerGeometryID { 0 };

View file

@ -132,11 +132,23 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
Q_LOGGING_CATEGORY(trace_simulation_avatar, "trace.simulation.avatar");
float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) {
float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) const {
auto avatar = getAvatarBySessionID(sessionID);
return avatar->getDataRate(rateName);
return avatar ? avatar->getDataRate(rateName) : 0.0f;
}
float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const {
auto avatar = getAvatarBySessionID(sessionID);
return avatar ? avatar->getUpdateRate(rateName) : 0.0f;
}
float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const {
auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(sessionID));
return avatar ? avatar->getSimulationRate(rateName) : 0.0f;
}
class AvatarPriority {
public:
AvatarPriority(AvatarSharedPointer a, float p) : avatar(a), priority(p) {}
@ -218,6 +230,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
const uint64_t MAX_UPDATE_BUDGET = 2000; // usec
uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET;
uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET;
int fullySimulatedAvatars = 0;
int partiallySimulatedAvatars = 0;
while (!sortedAvatars.empty()) {
const AvatarPriority& sortData = sortedAvatars.top();
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
@ -246,11 +261,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
avatar->simulate(deltaTime, inView);
avatar->updateRenderItem(pendingChanges);
avatar->setLastRenderUpdateTime(startTime);
fullySimulatedAvatars++;
} else if (now < maxExpiry) {
// we've spent most of our time budget, but we still simulate() the avatar as it if were out of view
// --> some avatars may freeze until their priority trickles up
const bool inView = false;
avatar->simulate(deltaTime, inView);
partiallySimulatedAvatars++;
} else {
// we've spent ALL of our time budget --> bail on the rest of the avatar updates
// --> some scale or fade animations may glitch
@ -259,6 +276,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
}
sortedAvatars.pop();
}
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
_fullySimulatedAvatars = fullySimulatedAvatars;
_partiallySimulatedAvatars = partiallySimulatedAvatars;
qApp->getMain3DScene()->enqueuePendingChanges(pendingChanges);
simulateAvatarFades(deltaTime);
@ -492,7 +513,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
}
AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) {
AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) const {
if (sessionID == AVATAR_SELF_ID || sessionID == _myAvatar->getSessionUUID()) {
return _myAvatar;
}

View file

@ -41,7 +41,11 @@ public:
void init();
std::shared_ptr<MyAvatar> getMyAvatar() { return _myAvatar; }
AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) override;
AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const override;
int getFullySimulatedAvatars() const { return _fullySimulatedAvatars; }
int getPartiallySimulatedAvatars() const { return _partiallySimulatedAvatars; }
float getAvatarSimulationTime() const { return _avatarSimulationTime; }
void updateMyAvatar(float deltaTime);
void updateOtherAvatars(float deltaTime);
@ -69,7 +73,10 @@ public:
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
void handleCollisionEvents(const CollisionEvents& collisionEvents);
Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString(""));
Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
Q_INVOKABLE float getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
Q_INVOKABLE float getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude = QScriptValue(),
const QScriptValue& avatarIdsToDiscard = QScriptValue());
@ -109,6 +116,9 @@ private:
VectorOfMotionStates _motionStatesToRemoveFromPhysics;
RateCounter<> _myAvatarSendRate;
int _fullySimulatedAvatars { 0 };
int _partiallySimulatedAvatars { 0 };
float _avatarSimulationTime { 0.0f };
};

View file

@ -116,7 +116,7 @@ void CauterizedModel::updateClusterMatrices() {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
#if GLM_ARCH & GLM_ARCH_SSE2
#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS)
glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
state.clusterMatrices[j] = out;
@ -155,7 +155,7 @@ void CauterizedModel::updateClusterMatrices() {
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
#if GLM_ARCH & GLM_ARCH_SSE2
#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS)
glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
state.clusterMatrices[j] = out;

View file

@ -795,6 +795,11 @@ void MyAvatar::saveData() {
}
settings.endArray();
if (_avatarEntityData.size() == 0) {
// HACK: manually remove empty 'avatarEntityData' else deleted avatarEntityData may show up in settings file
settings.remove("avatarEntityData");
}
settings.beginWriteArray("avatarEntityData");
int avatarEntityIndex = 0;
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
@ -931,6 +936,10 @@ void MyAvatar::loadData() {
updateAvatarEntity(entityID, properties);
}
settings.endArray();
if (avatarEntityCount == 0) {
// HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file
settings.remove("avatarEntityData");
}
setAvatarEntityDataChanged(true);
setDisplayName(settings.value("displayName").toString());
@ -1165,7 +1174,6 @@ void MyAvatar::clearJointsData() {
}
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL);
render::ScenePointer scene = qApp->getMain3DScene();
_skeletonModel->setVisibleInScene(true, scene);

View file

@ -134,3 +134,9 @@ void TestScriptingInterface::startTraceEvent(QString name) {
void TestScriptingInterface::endTraceEvent(QString name) {
tracing::traceEvent(trace_test(), name, tracing::DurationEnd);
}
void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) {
PROFILE_RANGE(script, name);
fn.call();
}

View file

@ -13,6 +13,8 @@
#include <functional>
#include <QtCore/QObject>
class QScriptValue;
class TestScriptingInterface : public QObject {
Q_OBJECT
@ -69,6 +71,7 @@ public slots:
void endTraceEvent(QString name);
Q_INVOKABLE void profileRange(const QString& name, QScriptValue function);
private:
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);

View file

@ -16,6 +16,8 @@
#include <SettingHandle.h>
#include <display-plugins/CompositorHelper.h>
#include "Application.h"
#include "DomainHandler.h"
#include "MainWindow.h"
@ -147,6 +149,15 @@ void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location
Setting::Handle<QVariant>(LAST_BROWSE_LOCATION_SETTING).set(location);
}
/// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and
/// might be in same thread as a script that sets the reticle to invisible
void WindowScriptingInterface::ensureReticleVisible() const {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
if (!compositorHelper->getReticleVisible()) {
compositorHelper->setReticleVisible(true);
}
}
/// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
@ -154,6 +165,7 @@ void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) {
ensureReticleVisible();
QString path = directory;
if (path.isEmpty()) {
path = getPreviousBrowseLocation();
@ -175,6 +187,7 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) {
ensureReticleVisible();
QString path = directory;
if (path.isEmpty()) {
path = getPreviousBrowseLocation();

View file

@ -83,6 +83,8 @@ private:
QString getPreviousBrowseLocation() const;
void setPreviousBrowseLocation(const QString& location);
void ensureReticleVisible() const;
int createMessageBox(QString title, QString text, int buttons, int defaultButton);
QHash<int, QQuickItem*> _messageBoxes;
int _lastMessageBoxID{ -1 };

View file

@ -0,0 +1,135 @@
//
// BaseLogDialog.cpp
// interface/src/ui
//
// Created by Clement Brisset on 1/31/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "BaseLogDialog.h"
#include <QDir>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSyntaxHighlighter>
#include <PathUtils.h>
const int TOP_BAR_HEIGHT = 46;
const int INITIAL_WIDTH = 720;
const int INITIAL_HEIGHT = 480;
const int MINIMAL_WIDTH = 570;
const int SEARCH_BUTTON_LEFT = 25;
const int SEARCH_BUTTON_WIDTH = 20;
const int SEARCH_TEXT_WIDTH = 240;
const QColor HIGHLIGHT_COLOR = QColor("#3366CC");
class KeywordHighlighter : public QSyntaxHighlighter {
public:
KeywordHighlighter(QTextDocument* parent = nullptr);
QString keyword;
protected:
void highlightBlock(const QString& text) override;
private:
QTextCharFormat keywordFormat;
};
BaseLogDialog::BaseLogDialog(QWidget* parent) : QDialog(parent, Qt::Window) {
setWindowTitle("Base Log");
setAttribute(Qt::WA_DeleteOnClose);
QFile styleSheet(PathUtils::resourcesPath() + "styles/log_dialog.qss");
if (styleSheet.open(QIODevice::ReadOnly)) {
QDir::setCurrent(PathUtils::resourcesPath());
setStyleSheet(styleSheet.readAll());
}
initControls();
resize(INITIAL_WIDTH, INITIAL_HEIGHT);
setMinimumWidth(MINIMAL_WIDTH);
}
BaseLogDialog::~BaseLogDialog() {
deleteLater();
}
void BaseLogDialog::initControls() {
_searchButton = new QPushButton(this);
// set object name for css styling
_searchButton->setObjectName("searchButton");
_leftPad = SEARCH_BUTTON_LEFT;
_searchButton->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_BUTTON_WIDTH, ELEMENT_HEIGHT);
_leftPad += SEARCH_BUTTON_WIDTH;
_searchButton->show();
connect(_searchButton, SIGNAL(clicked()), SLOT(handleSearchButton()));
_searchTextBox = new QLineEdit(this);
// disable blue outline in Mac
_searchTextBox->setAttribute(Qt::WA_MacShowFocusRect, false);
_searchTextBox->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_TEXT_WIDTH, ELEMENT_HEIGHT);
_leftPad += SEARCH_TEXT_WIDTH + CHECKBOX_MARGIN;
_searchTextBox->show();
connect(_searchTextBox, SIGNAL(textChanged(QString)), SLOT(handleSearchTextChanged(QString)));
_logTextBox = new QPlainTextEdit(this);
_logTextBox->setReadOnly(true);
_logTextBox->show();
_highlighter = new KeywordHighlighter(_logTextBox->document());
}
void BaseLogDialog::showEvent(QShowEvent* event) {
showLogData();
}
void BaseLogDialog::resizeEvent(QResizeEvent* event) {
_logTextBox->setGeometry(0, TOP_BAR_HEIGHT, width(), height() - TOP_BAR_HEIGHT);
}
void BaseLogDialog::appendLogLine(QString logLine) {
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
_logTextBox->appendPlainText(logLine.trimmed());
}
}
void BaseLogDialog::handleSearchButton() {
_searchTextBox->setFocus();
}
void BaseLogDialog::handleSearchTextChanged(QString searchText) {
_searchTerm = searchText;
_highlighter->keyword = searchText;
_highlighter->rehighlight();
}
void BaseLogDialog::showLogData() {
_logTextBox->clear();
_logTextBox->appendPlainText(getCurrentLog());
_logTextBox->ensureCursorVisible();
}
KeywordHighlighter::KeywordHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) {
keywordFormat.setForeground(HIGHLIGHT_COLOR);
}
void KeywordHighlighter::highlightBlock(const QString& text) {
if (keyword.isNull() || keyword.isEmpty()) {
return;
}
int index = text.indexOf(keyword, 0, Qt::CaseInsensitive);
int length = keyword.length();
while (index >= 0) {
setFormat(index, length, keywordFormat);
index = text.indexOf(keyword, index + length, Qt::CaseInsensitive);
}
}

View file

@ -0,0 +1,60 @@
//
// BaseLogDialog.h
// interface/src/ui
//
// Created by Clement Brisset on 1/31/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_BaseLogDialog_h
#define hifi_BaseLogDialog_h
#include <QDialog>
const int ELEMENT_MARGIN = 7;
const int ELEMENT_HEIGHT = 32;
const int CHECKBOX_MARGIN = 12;
const int CHECKBOX_WIDTH = 140;
class QPushButton;
class QLineEdit;
class QPlainTextEdit;
class KeywordHighlighter;
class BaseLogDialog : public QDialog {
Q_OBJECT
public:
BaseLogDialog(QWidget* parent);
~BaseLogDialog();
public slots:
void appendLogLine(QString logLine);
private slots:
void handleSearchButton();
void handleSearchTextChanged(QString text);
protected:
int _leftPad { 0 };
void resizeEvent(QResizeEvent* event) override;
void showEvent(QShowEvent* event) override;
virtual QString getCurrentLog() = 0;
private:
QPushButton* _searchButton { nullptr };
QLineEdit* _searchTextBox { nullptr };
QPlainTextEdit* _logTextBox { nullptr };
QString _searchTerm;
KeywordHighlighter* _highlighter { nullptr };
void initControls();
void showLogData();
};
#endif // hifi_BaseLogDialog_h

View file

@ -0,0 +1,23 @@
//
// EntityScriptServerLogDialog.cpp
// interface/src/ui
//
// Created by Clement Brisset on 1/31/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntityScriptServerLogDialog.h"
#include <EntityScriptServerLogClient.h>
EntityScriptServerLogDialog::EntityScriptServerLogDialog(QWidget* parent) : BaseLogDialog(parent) {
setWindowTitle("Entity Script Server Log");
auto client = DependencyManager::get<EntityScriptServerLogClient>();
QObject::connect(client.data(), &EntityScriptServerLogClient::receivedNewLogLines,
this, &EntityScriptServerLogDialog::appendLogLine);
}

View file

@ -0,0 +1,27 @@
//
// EntityScriptServerLogDialog.h
// interface/src/ui
//
// Created by Clement Brisset on 1/31/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityScriptServerLogDialog_h
#define hifi_EntityScriptServerLogDialog_h
#include "BaseLogDialog.h"
class EntityScriptServerLogDialog : public BaseLogDialog {
Q_OBJECT
public:
EntityScriptServerLogDialog(QWidget* parent = nullptr);
protected:
QString getCurrentLog() override { return QString(); };
};
#endif // hifi_EntityScriptServerLogDialog_h

View file

@ -9,90 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
#include "LogDialog.h"
#include <QDesktopWidget>
#include <QTextBlock>
#include <QCheckBox>
#include <QPushButton>
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
#include <shared/AbstractLoggerInterface.h>
#include <PathUtils.h>
#include <SharedUtil.h>
#include "Application.h"
#include "ui/LogDialog.h"
const int TOP_BAR_HEIGHT = 46;
const int INITIAL_WIDTH = 720;
const int MINIMAL_WIDTH = 570;
const int ELEMENT_MARGIN = 7;
const int ELEMENT_HEIGHT = 32;
const int SEARCH_BUTTON_LEFT = 25;
const int SEARCH_BUTTON_WIDTH = 20;
const int SEARCH_TEXT_WIDTH = 240;
const int CHECKBOX_MARGIN = 12;
const int CHECKBOX_WIDTH = 140;
const int REVEAL_BUTTON_WIDTH = 122;
const float INITIAL_HEIGHT_RATIO = 0.6f;
const QString HIGHLIGHT_COLOR = "#3366CC";
int qTextCursorMeta = qRegisterMetaType<QTextCursor>("QTextCursor");
int qTextBlockMeta = qRegisterMetaType<QTextBlock>("QTextBlock");
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Window) {
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLogDialog(parent) {
_logger = logger;
setWindowTitle("Log");
setAttribute(Qt::WA_DeleteOnClose);
QFile styleSheet(PathUtils::resourcesPath() + "styles/log_dialog.qss");
if (styleSheet.open(QIODevice::ReadOnly)) {
QDir::setCurrent(PathUtils::resourcesPath());
setStyleSheet(styleSheet.readAll());
}
initControls();
QDesktopWidget desktop;
QRect screen = desktop.screenGeometry();
resize(INITIAL_WIDTH, static_cast<int>(screen.height() * INITIAL_HEIGHT_RATIO));
move(screen.center() - rect().center());
setMinimumWidth(MINIMAL_WIDTH);
connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection);
}
LogDialog::~LogDialog() {
deleteLater();
}
void LogDialog::initControls() {
int left;
_searchButton = new QPushButton(this);
// set object name for css styling
_searchButton->setObjectName("searchButton");
left = SEARCH_BUTTON_LEFT;
_searchButton->setGeometry(left, ELEMENT_MARGIN, SEARCH_BUTTON_WIDTH, ELEMENT_HEIGHT);
left += SEARCH_BUTTON_WIDTH;
_searchButton->show();
connect(_searchButton, SIGNAL(clicked()), SLOT(handleSearchButton()));
_searchTextBox = new QLineEdit(this);
// disable blue outline in Mac
_searchTextBox->setAttribute(Qt::WA_MacShowFocusRect, false);
_searchTextBox->setGeometry(left, ELEMENT_MARGIN, SEARCH_TEXT_WIDTH, ELEMENT_HEIGHT);
left += SEARCH_TEXT_WIDTH + CHECKBOX_MARGIN;
_searchTextBox->show();
connect(_searchTextBox, SIGNAL(textChanged(QString)), SLOT(handleSearchTextChanged(QString)));
_extraDebuggingBox = new QCheckBox("Extra debugging", this);
_extraDebuggingBox->setGeometry(left, ELEMENT_MARGIN, CHECKBOX_WIDTH, ELEMENT_HEIGHT);
_extraDebuggingBox->setGeometry(_leftPad, ELEMENT_MARGIN, CHECKBOX_WIDTH, ELEMENT_HEIGHT);
if (_logger->extraDebugging()) {
_extraDebuggingBox->setCheckState(Qt::Checked);
}
@ -105,72 +36,25 @@ void LogDialog::initControls() {
_revealLogButton->show();
connect(_revealLogButton, SIGNAL(clicked()), SLOT(handleRevealButton()));
_logTextBox = new QPlainTextEdit(this);
_logTextBox->setReadOnly(true);
_logTextBox->show();
_highlighter = new KeywordHighlighter(_logTextBox->document());
connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection);
}
void LogDialog::showEvent(QShowEvent*) {
showLogData();
}
void LogDialog::resizeEvent(QResizeEvent*) {
_logTextBox->setGeometry(0, TOP_BAR_HEIGHT, width(), height() - TOP_BAR_HEIGHT);
void LogDialog::resizeEvent(QResizeEvent* event) {
BaseLogDialog::resizeEvent(event);
_revealLogButton->setGeometry(width() - ELEMENT_MARGIN - REVEAL_BUTTON_WIDTH,
ELEMENT_MARGIN,
REVEAL_BUTTON_WIDTH,
ELEMENT_HEIGHT);
}
void LogDialog::appendLogLine(QString logLine) {
if (isVisible()) {
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
_logTextBox->appendPlainText(logLine.trimmed());
}
}
}
void LogDialog::handleSearchButton() {
_searchTextBox->setFocus();
}
void LogDialog::handleRevealButton() {
_logger->locateLog();
}
void LogDialog::handleExtraDebuggingCheckbox(const int state) {
void LogDialog::handleExtraDebuggingCheckbox(int state) {
_logger->setExtraDebugging(state != 0);
}
void LogDialog::handleSearchTextChanged(const QString searchText) {
_searchTerm = searchText;
_highlighter->keyword = searchText;
showLogData();
}
void LogDialog::showLogData() {
_logTextBox->clear();
_logTextBox->insertPlainText(_logger->getLogData());
_logTextBox->ensureCursorVisible();
}
KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() {
keywordFormat.setForeground(QColor(HIGHLIGHT_COLOR));
}
void KeywordHighlighter::highlightBlock(const QString &text) {
if (keyword.isNull() || keyword.isEmpty()) {
return;
}
int index = text.indexOf(keyword, 0, Qt::CaseInsensitive);
int length = keyword.length();
while (index >= 0) {
setFormat(index, length, keywordFormat);
index = text.indexOf(keyword, index + length, Qt::CaseInsensitive);
}
QString LogDialog::getCurrentLog() {
return _logger->getLogData();
}

View file

@ -12,64 +12,32 @@
#ifndef hifi_LogDialog_h
#define hifi_LogDialog_h
#include <QDialog>
#include <QMutex>
#include <QPlainTextEdit>
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
#include <QSyntaxHighlighter>
#include "BaseLogDialog.h"
#include <shared/AbstractLoggerInterface.h>
class QCheckBox;
class QPushButton;
class QResizeEvent;
class AbstractLoggerInterface;
class KeywordHighlighter : public QSyntaxHighlighter {
class LogDialog : public BaseLogDialog {
Q_OBJECT
public:
KeywordHighlighter(QTextDocument *parent = 0);
QString keyword;
protected:
void highlightBlock(const QString &text) override;
private:
QTextCharFormat keywordFormat;
};
class LogDialog : public QDialog {
Q_OBJECT
public:
LogDialog(QWidget*, AbstractLoggerInterface*);
~LogDialog();
public slots:
void appendLogLine(QString logLine);
LogDialog(QWidget* parent, AbstractLoggerInterface* logger);
private slots:
void handleSearchButton();
void handleRevealButton();
void handleExtraDebuggingCheckbox(const int);
void handleSearchTextChanged(const QString);
void handleExtraDebuggingCheckbox(int);
protected:
void resizeEvent(QResizeEvent*) override;
void showEvent(QShowEvent*) override;
void resizeEvent(QResizeEvent* event) override;
QString getCurrentLog() override;
private:
QPushButton* _searchButton;
QLineEdit* _searchTextBox;
QCheckBox* _extraDebuggingBox;
QPushButton* _revealLogButton;
QPlainTextEdit* _logTextBox;
QString _searchTerm;
KeywordHighlighter* _highlighter;
AbstractLoggerInterface* _logger;
void initControls();
void showLogData();
};
#endif // hifi_LogDialog_h

View file

@ -121,6 +121,8 @@ void Stats::updateStats(bool force) {
auto avatarManager = DependencyManager::get<AvatarManager>();
// we need to take one avatar out so we don't include ourselves
STAT_UPDATE(avatarCount, avatarManager->size() - 1);
STAT_UPDATE(fullySimulatedAvatarCount, avatarManager->getFullySimulatedAvatars());
STAT_UPDATE(partiallySimulatedAvatarCount, avatarManager->getPartiallySimulatedAvatars());
STAT_UPDATE(serverCount, (int)nodeList->size());
STAT_UPDATE(framerate, qApp->getFps());
if (qApp->getActiveDisplayPlugin()) {
@ -306,6 +308,8 @@ void Stats::updateStats(bool force) {
// Update Frame timing (in ms)
STAT_UPDATE(gpuFrameTime, (float)gpuContext->getFrameTimerGPUAverage());
STAT_UPDATE(batchFrameTime, (float)gpuContext->getFrameTimerBatchAverage());
STAT_UPDATE(avatarSimulationTime, (float)avatarManager->getAvatarSimulationTime());
STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount());
STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemoryUsage()));

View file

@ -49,6 +49,8 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, simrate, 0)
STATS_PROPERTY(int, avatarSimrate, 0)
STATS_PROPERTY(int, avatarCount, 0)
STATS_PROPERTY(int, fullySimulatedAvatarCount, 0)
STATS_PROPERTY(int, partiallySimulatedAvatarCount, 0)
STATS_PROPERTY(int, packetInCount, 0)
STATS_PROPERTY(int, packetOutCount, 0)
STATS_PROPERTY(float, mbpsIn, 0)
@ -111,6 +113,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, gpuFreeMemory, 0)
STATS_PROPERTY(float, gpuFrameTime, 0)
STATS_PROPERTY(float, batchFrameTime, 0)
STATS_PROPERTY(float, avatarSimulationTime, 0)
public:
static Stats* getInstance();
@ -156,6 +159,8 @@ signals:
void simrateChanged();
void avatarSimrateChanged();
void avatarCountChanged();
void fullySimulatedAvatarCountChanged();
void partiallySimulatedAvatarCountChanged();
void packetInCountChanged();
void packetOutCountChanged();
void mbpsInChanged();
@ -216,6 +221,7 @@ signals:
void gpuFreeMemoryChanged();
void gpuFrameTimeChanged();
void batchFrameTimeChanged();
void avatarSimulationTimeChanged();
void rectifiedTextureCountChanged();
void decimatedTextureCountChanged();

View file

@ -23,14 +23,20 @@
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <GeometryUtil.h>
#include <scripting/HMDScriptingInterface.h>
#include <gl/OffscreenQmlSurface.h>
#include <PathUtils.h>
#include <RegisteredMetaTypes.h>
#include <TabletScriptingInterface.h>
#include <TextureCache.h>
#include <UsersScriptingInterface.h>
#include <UserActivityLoggerScriptingInterface.h>
#include <AbstractViewStateInterface.h>
#include <gl/OffscreenQmlSurface.h>
#include <gl/OffscreenQmlSurfaceCache.h>
#include <AddressManager.h>
#include "scripting/AccountScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
@ -149,10 +155,17 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->load(_url, [&](QQmlContext* context, QObject* obj) {});
_webSurface->resume();
_webSurface->getRootContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();
_webSurface->getRootContext()->setContextProperty("offscreenFlags", flags);
_webSurface->getRootContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
_webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance());
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
// Override min fps for tablet UI, for silky smooth scrolling

View file

@ -283,14 +283,20 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(packetStateFlags);
if (hasAvatarGlobalPosition) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::AvatarGlobalPosition*>(destinationBuffer);
data->globalPosition[0] = _globalPosition.x;
data->globalPosition[1] = _globalPosition.y;
data->globalPosition[2] = _globalPosition.z;
destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
int numBytes = destinationBuffer - startSection;
_globalPositionRateOutbound.increment(numBytes);
}
if (hasAvatarBoundingBox) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::AvatarBoundingBox*>(destinationBuffer);
data->avatarDimensions[0] = _globalBoundingBoxDimensions.x;
@ -302,36 +308,56 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
data->boundOriginOffset[2] = _globalBoundingBoxOffset.z;
destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
int numBytes = destinationBuffer - startSection;
_avatarBoundingBoxRateOutbound.increment(numBytes);
}
if (hasAvatarOrientation) {
auto startSection = destinationBuffer;
auto localOrientation = getLocalOrientation();
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
int numBytes = destinationBuffer - startSection;
_avatarOrientationRateOutbound.increment(numBytes);
}
if (hasAvatarScale) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::AvatarScale*>(destinationBuffer);
auto scale = getDomainLimitedScale();
packFloatRatioToTwoByte((uint8_t*)(&data->scale), scale);
destinationBuffer += sizeof(AvatarDataPacket::AvatarScale);
int numBytes = destinationBuffer - startSection;
_avatarScaleRateOutbound.increment(numBytes);
}
if (hasLookAtPosition) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::LookAtPosition*>(destinationBuffer);
auto lookAt = _headData->getLookAtPosition();
data->lookAtPosition[0] = lookAt.x;
data->lookAtPosition[1] = lookAt.y;
data->lookAtPosition[2] = lookAt.z;
destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition);
int numBytes = destinationBuffer - startSection;
_lookAtPositionRateOutbound.increment(numBytes);
}
if (hasAudioLoudness) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::AudioLoudness*>(destinationBuffer);
data->audioLoudness = packFloatGainToByte(_headData->getAudioLoudness() / AUDIO_LOUDNESS_SCALE);
destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness);
int numBytes = destinationBuffer - startSection;
_audioLoudnessRateOutbound.increment(numBytes);
}
if (hasSensorToWorldMatrix) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::SensorToWorldMatrix*>(destinationBuffer);
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
packOrientationQuatToSixBytes(data->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
@ -341,9 +367,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
data->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
data->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix);
int numBytes = destinationBuffer - startSection;
_sensorToWorldRateOutbound.increment(numBytes);
}
if (hasAdditionalFlags) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::AdditionalFlags*>(destinationBuffer);
uint8_t flags { 0 };
@ -370,27 +400,39 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
}
data->flags = flags;
destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
int numBytes = destinationBuffer - startSection;
_additionalFlagsRateOutbound.increment(numBytes);
}
if (hasAvatarLocalPosition) {
auto startSection = destinationBuffer;
auto data = reinterpret_cast<AvatarDataPacket::AvatarLocalPosition*>(destinationBuffer);
auto localPosition = getLocalPosition();
data->localPosition[0] = localPosition.x;
data->localPosition[1] = localPosition.y;
data->localPosition[2] = localPosition.z;
destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
int numBytes = destinationBuffer - startSection;
_localPositionRateOutbound.increment(numBytes);
}
if (hasParentInfo) {
auto startSection = destinationBuffer;
auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer);
QByteArray referentialAsBytes = parentID.toRfc4122();
memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size());
parentInfo->parentJointIndex = _parentJointIndex;
destinationBuffer += sizeof(AvatarDataPacket::ParentInfo);
int numBytes = destinationBuffer - startSection;
_parentInfoRateOutbound.increment(numBytes);
}
// If it is connected, pack up the data
if (hasFaceTrackerInfo) {
auto startSection = destinationBuffer;
auto faceTrackerInfo = reinterpret_cast<AvatarDataPacket::FaceTrackerInfo*>(destinationBuffer);
faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink;
@ -403,10 +445,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
// followed by a variable number of float coefficients
memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float));
destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
int numBytes = destinationBuffer - startSection;
_faceTrackerRateOutbound.increment(numBytes);
}
// If it is connected, pack up the data
if (hasJointData) {
auto startSection = destinationBuffer;
QReadLocker readLock(&_jointDataLock);
// joint rotation data
@ -554,6 +600,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
<< (destinationBuffer - startPosition);
}
#endif
int numBytes = destinationBuffer - startSection;
_jointDataRateOutbound.increment(numBytes);
}
int avatarDataSize = destinationBuffer - startPosition;
@ -670,6 +719,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
int numBytesRead = sourceBuffer - startSection;
_globalPositionRate.increment(numBytesRead);
_globalPositionUpdateRate.increment();
// if we don't have a parent, make sure to also set our local position
if (!hasParent()) {
@ -698,6 +748,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
int numBytesRead = sourceBuffer - startSection;
_avatarBoundingBoxRate.increment(numBytesRead);
_avatarBoundingBoxUpdateRate.increment();
}
if (hasAvatarOrientation) {
@ -713,6 +764,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
int numBytesRead = sourceBuffer - startSection;
_avatarOrientationRate.increment(numBytesRead);
_avatarOrientationUpdateRate.increment();
}
if (hasAvatarScale) {
@ -732,6 +784,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::AvatarScale);
int numBytesRead = sourceBuffer - startSection;
_avatarScaleRate.increment(numBytesRead);
_avatarScaleUpdateRate.increment();
}
if (hasLookAtPosition) {
@ -750,6 +803,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::LookAtPosition);
int numBytesRead = sourceBuffer - startSection;
_lookAtPositionRate.increment(numBytesRead);
_lookAtPositionUpdateRate.increment();
}
if (hasAudioLoudness) {
@ -770,6 +824,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
_headData->setAudioLoudness(audioLoudness);
int numBytesRead = sourceBuffer - startSection;
_audioLoudnessRate.increment(numBytesRead);
_audioLoudnessUpdateRate.increment();
}
if (hasSensorToWorldMatrix) {
@ -790,6 +845,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix);
int numBytesRead = sourceBuffer - startSection;
_sensorToWorldRate.increment(numBytesRead);
_sensorToWorldUpdateRate.increment();
}
if (hasAdditionalFlags) {
@ -833,6 +889,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
int numBytesRead = sourceBuffer - startSection;
_additionalFlagsRate.increment(numBytesRead);
_additionalFlagsUpdateRate.increment();
}
// FIXME -- make sure to handle the existance of a parent vs a change in the parent...
@ -855,7 +912,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
int numBytesRead = sourceBuffer - startSection;
_parentInfoRate.increment(numBytesRead);
} else {
_parentInfoUpdateRate.increment();
}
else {
// FIXME - this aint totally right, for switching to parent/no-parent
_parentID = QUuid();
}
@ -877,6 +936,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
int numBytesRead = sourceBuffer - startSection;
_localPositionRate.increment(numBytesRead);
_localPositionUpdateRate.increment();
}
if (hasFaceTrackerInfo) {
@ -899,6 +959,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += coefficientsSize;
int numBytesRead = sourceBuffer - startSection;
_faceTrackerRate.increment(numBytesRead);
_faceTrackerUpdateRate.increment();
}
if (hasJointData) {
@ -991,17 +1052,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
int numBytesRead = sourceBuffer - startSection;
_jointDataRate.increment(numBytesRead);
_jointDataUpdateRate.increment();
}
int numBytesRead = sourceBuffer - startPosition;
_averageBytesReceived.updateAverage(numBytesRead);
_parseBufferRate.increment(numBytesRead);
_parseBufferUpdateRate.increment();
return numBytesRead;
}
float AvatarData::getDataRate(const QString& rateName) {
float AvatarData::getDataRate(const QString& rateName) const {
if (rateName == "") {
return _parseBufferRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "globalPosition") {
@ -1028,10 +1091,64 @@ float AvatarData::getDataRate(const QString& rateName) {
return _faceTrackerRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointData") {
return _jointDataRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "globalPositionOutbound") {
return _globalPositionRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "localPositionOutbound") {
return _localPositionRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "avatarBoundingBoxOutbound") {
return _avatarBoundingBoxRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "avatarOrientationOutbound") {
return _avatarOrientationRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "avatarScaleOutbound") {
return _avatarScaleRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "lookAtPositionOutbound") {
return _lookAtPositionRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "audioLoudnessOutbound") {
return _audioLoudnessRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "sensorToWorkMatrixOutbound") {
return _sensorToWorldRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "additionalFlagsOutbound") {
return _additionalFlagsRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "parentInfoOutbound") {
return _parentInfoRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "faceTrackerOutbound") {
return _faceTrackerRateOutbound.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointDataOutbound") {
return _jointDataRateOutbound.rate() / BYTES_PER_KILOBIT;
}
return 0.0f;
}
float AvatarData::getUpdateRate(const QString& rateName) const {
if (rateName == "") {
return _parseBufferUpdateRate.rate();
} else if (rateName == "globalPosition") {
return _globalPositionUpdateRate.rate();
} else if (rateName == "localPosition") {
return _localPositionUpdateRate.rate();
} else if (rateName == "avatarBoundingBox") {
return _avatarBoundingBoxUpdateRate.rate();
} else if (rateName == "avatarOrientation") {
return _avatarOrientationUpdateRate.rate();
} else if (rateName == "avatarScale") {
return _avatarScaleUpdateRate.rate();
} else if (rateName == "lookAtPosition") {
return _lookAtPositionUpdateRate.rate();
} else if (rateName == "audioLoudness") {
return _audioLoudnessUpdateRate.rate();
} else if (rateName == "sensorToWorkMatrix") {
return _sensorToWorldUpdateRate.rate();
} else if (rateName == "additionalFlags") {
return _additionalFlagsUpdateRate.rate();
} else if (rateName == "parentInfo") {
return _parentInfoUpdateRate.rate();
} else if (rateName == "faceTracker") {
return _faceTrackerUpdateRate.rate();
} else if (rateName == "jointData") {
return _jointDataUpdateRate.rate();
}
return 0.0f;
}
int AvatarData::getAverageBytesReceivedPerSecond() const {
return lrint(_averageBytesReceived.getAverageSampleValuePerSecond());
@ -1842,8 +1959,7 @@ QJsonObject AvatarData::toJson() const {
return root;
}
void AvatarData::fromJson(const QJsonObject& json) {
void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
int version;
if (json.contains(JSON_AVATAR_VERSION)) {
version = json[JSON_AVATAR_VERSION].toInt();
@ -1865,7 +1981,7 @@ void AvatarData::fromJson(const QJsonObject& json) {
if (json.contains(JSON_AVATAR_BODY_MODEL)) {
auto bodyModelURL = json[JSON_AVATAR_BODY_MODEL].toString();
if (bodyModelURL != getSkeletonModelURL().toString()) {
if (useFrameSkeleton && bodyModelURL != getSkeletonModelURL().toString()) {
setSkeletonModelURL(bodyModelURL);
}
}
@ -1958,8 +2074,9 @@ QByteArray AvatarData::toFrame(const AvatarData& avatar) {
}
void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) {
void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result, bool useFrameSkeleton) {
QJsonDocument doc = QJsonDocument::fromBinaryData(frameData);
#ifdef WANT_JSON_DEBUG
{
QJsonObject obj = doc.object();
@ -1967,7 +2084,7 @@ void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) {
qCDebug(avatars).noquote() << QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Indented);
}
#endif
result.fromJson(doc.object());
result.fromJson(doc.object(), useFrameSkeleton);
}
float AvatarData::getBodyYaw() const {

View file

@ -329,7 +329,7 @@ public:
static const QString FRAME_NAME;
static void fromFrame(const QByteArray& frameData, AvatarData& avatar);
static void fromFrame(const QByteArray& frameData, AvatarData& avatar, bool useFrameSkeleton = true);
static QByteArray toFrame(const AvatarData& avatar);
AvatarData();
@ -380,8 +380,27 @@ public:
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
virtual void updateAttitude() {} // Tell skeleton mesh about changes
glm::quat getHeadOrientation() const { return _headData->getOrientation(); }
void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); }
glm::quat getHeadOrientation() {
lazyInitHeadData();
return _headData->getOrientation();
}
void setHeadOrientation(const glm::quat& orientation) {
if (_headData) {
_headData->setOrientation(orientation);
}
}
void setLookAtPosition(const glm::vec3& lookAtPosition) {
if (_headData) {
_headData->setLookAtPosition(lookAtPosition);
}
}
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) {
if (_headData) {
_headData->setBlendshapeCoefficients(blendshapeCoefficients);
}
}
// access to Head().set/getMousePitch (degrees)
float getHeadPitch() const { return _headData->getBasePitch(); }
@ -513,7 +532,7 @@ public:
TransformPointer getRecordingBasis() const;
void setRecordingBasis(TransformPointer recordingBasis = TransformPointer());
QJsonObject toJson() const;
void fromJson(const QJsonObject& json);
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
glm::vec3 getGlobalBoundingBoxCorner() { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; }
@ -528,7 +547,8 @@ public:
Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const;
Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const;
float getDataRate(const QString& rateName = QString(""));
Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const;
Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const;
int getJointCount() { return _jointData.size(); }
@ -596,7 +616,7 @@ protected:
bool _forceFaceTrackerConnected;
bool _hasNewJointData; // set in AvatarData, cleared in Avatar
HeadData* _headData;
HeadData* _headData { nullptr };
QUrl _skeletonModelURL;
bool _firstSkeletonCheck { true };
@ -644,7 +664,7 @@ protected:
quint64 _lastToByteArray { 0 }; // tracks the last time we did a toByteArray
// Some rate data for incoming data
// Some rate data for incoming data in bytes
RateCounter<> _parseBufferRate;
RateCounter<> _globalPositionRate;
RateCounter<> _localPositionRate;
@ -659,6 +679,35 @@ protected:
RateCounter<> _faceTrackerRate;
RateCounter<> _jointDataRate;
// Some rate data for incoming data updates
RateCounter<> _parseBufferUpdateRate;
RateCounter<> _globalPositionUpdateRate;
RateCounter<> _localPositionUpdateRate;
RateCounter<> _avatarBoundingBoxUpdateRate;
RateCounter<> _avatarOrientationUpdateRate;
RateCounter<> _avatarScaleUpdateRate;
RateCounter<> _lookAtPositionUpdateRate;
RateCounter<> _audioLoudnessUpdateRate;
RateCounter<> _sensorToWorldUpdateRate;
RateCounter<> _additionalFlagsUpdateRate;
RateCounter<> _parentInfoUpdateRate;
RateCounter<> _faceTrackerUpdateRate;
RateCounter<> _jointDataUpdateRate;
// Some rate data for outgoing data
RateCounter<> _globalPositionRateOutbound;
RateCounter<> _localPositionRateOutbound;
RateCounter<> _avatarBoundingBoxRateOutbound;
RateCounter<> _avatarOrientationRateOutbound;
RateCounter<> _avatarScaleRateOutbound;
RateCounter<> _lookAtPositionRateOutbound;
RateCounter<> _audioLoudnessRateOutbound;
RateCounter<> _sensorToWorldRateOutbound;
RateCounter<> _additionalFlagsRateOutbound;
RateCounter<> _parentInfoRateOutbound;
RateCounter<> _faceTrackerRateOutbound;
RateCounter<> _jointDataRateOutbound;
glm::vec3 _globalBoundingBoxDimensions;
glm::vec3 _globalBoundingBoxOffset;

View file

@ -90,7 +90,7 @@ AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID,
return avatar;
}
AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) {
AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const {
QReadLocker locker(&_hashLock);
if (_avatarHash.contains(sessionUUID)) {
return _avatarHash.value(sessionUUID);

View file

@ -40,7 +40,7 @@ public:
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID);
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); }
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const { return findAvatar(sessionID); }
int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters);
signals:
@ -65,7 +65,7 @@ protected:
virtual AvatarSharedPointer newSharedAvatar();
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
@ -73,7 +73,7 @@ protected:
AvatarHash _avatarHash;
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.
// If you read from a different thread, you must read-lock the _hashLock. (Scripted write access is not supported).
QReadWriteLock _hashLock;
mutable QReadWriteLock _hashLock;
private:
QUuid _lastOwnerSessionUUID;

View file

@ -118,6 +118,8 @@ public:
virtual void run() override {
PROFILE_SET_THREAD_NAME("Present Thread");
// FIXME determine the best priority balance between this and the main thread...
// It may be dependent on the display plugin being used, since VR plugins should
// have higher priority on rendering (although we could say that the Oculus plugin

View file

@ -0,0 +1,85 @@
//
// EntityScriptServerLogClient.cpp
// interface/src
//
// Created by Clement Brisset on 2/1/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntityScriptServerLogClient.h"
EntityScriptServerLogClient::EntityScriptServerLogClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
QObject::connect(nodeList.data(), &NodeList::nodeActivated, this, &EntityScriptServerLogClient::nodeActivated);
QObject::connect(nodeList.data(), &NodeList::nodeKilled, this, &EntityScriptServerLogClient::nodeKilled);
QObject::connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptServerLogClient::canRezChanged);
}
void EntityScriptServerLogClient::connectNotify(const QMetaMethod& signal) {
// This needs to be delayed or "receivers()" will return completely inconsistent values
QMetaObject::invokeMethod(this, "connectionsChanged", Qt::QueuedConnection);
}
void EntityScriptServerLogClient::disconnectNotify(const QMetaMethod& signal) {
// This needs to be delayed or "receivers()" will return completely inconsistent values
QMetaObject::invokeMethod(this, "connectionsChanged", Qt::QueuedConnection);
}
void EntityScriptServerLogClient::connectionsChanged() {
auto numReceivers = receivers(SIGNAL(receivedNewLogLines(QString)));
if (!_subscribed && numReceivers > 0) {
enableToEntityServerScriptLog(DependencyManager::get<NodeList>()->getThisNodeCanRez());
} else if (_subscribed && numReceivers == 0) {
enableToEntityServerScriptLog(false);
}
}
void EntityScriptServerLogClient::enableToEntityServerScriptLog(bool enable) {
auto nodeList = DependencyManager::get<NodeList>();
if (auto node = nodeList->soloNodeOfType(NodeType::EntityScriptServer)) {
auto packet = NLPacket::create(PacketType::EntityServerScriptLog, sizeof(bool), true);
packet->writePrimitive(enable);
nodeList->sendPacket(std::move(packet), *node);
if (_subscribed != enable) {
if (enable) {
emit receivedNewLogLines("====================== Subscribded to the Entity Script Server's log ======================");
} else {
emit receivedNewLogLines("==================== Unsubscribded from the Entity Script Server's log ====================");
}
}
_subscribed = enable;
}
}
void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
emit receivedNewLogLines(QString::fromUtf8(message->readAll()));
}
void EntityScriptServerLogClient::nodeActivated(SharedNodePointer activatedNode) {
if (_subscribed && activatedNode->getType() == NodeType::EntityScriptServer) {
_subscribed = false;
enableToEntityServerScriptLog(DependencyManager::get<NodeList>()->getThisNodeCanRez());
}
}
void EntityScriptServerLogClient::nodeKilled(SharedNodePointer killedNode) {
if (killedNode->getType() == NodeType::EntityScriptServer) {
emit receivedNewLogLines("====================== Connection to the Entity Script Server lost ======================");
}
}
void EntityScriptServerLogClient::canRezChanged(bool canRez) {
auto numReceivers = receivers(SIGNAL(receivedNewLogLines(QString)));
if (numReceivers > 0) {
enableToEntityServerScriptLog(canRez);
}
}

View file

@ -0,0 +1,46 @@
//
// EntityScriptServerLogClient.h
// interface/src
//
// Created by Clement Brisset on 2/1/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityScriptServerLogClient_h
#define hifi_EntityScriptServerLogClient_h
#include <QObject>
#include <NodeList.h>
class EntityScriptServerLogClient : public QObject, public Dependency {
Q_OBJECT
public:
EntityScriptServerLogClient();
signals:
void receivedNewLogLines(QString logLines);
protected:
void connectNotify(const QMetaMethod& signal) override;
void disconnectNotify(const QMetaMethod& signal) override;
private slots:
void enableToEntityServerScriptLog(bool enable);
void handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void nodeActivated(SharedNodePointer activatedNode);
void nodeKilled(SharedNodePointer killedNode);
void canRezChanged(bool canRez);
void connectionsChanged();
private:
bool _subscribed { false };
};
#endif // hifi_EntityScriptServerLogClient_h

View file

@ -27,6 +27,7 @@
#include "ZoneEntityItem.h"
#include "WebEntityItem.h"
#include <EntityScriptClient.h>
#include <Profile.h>
EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) :
@ -178,6 +179,8 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) {
PROFILE_RANGE(script_entities, __FUNCTION__);
_activityTracking.addedEntityCount++;
EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);
@ -259,6 +262,8 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) {
PROFILE_RANGE(script_entities, __FUNCTION__);
EntityItemProperties results;
if (_entityTree) {
_entityTree->withReadLock([&] {
@ -305,6 +310,8 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
}
QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) {
PROFILE_RANGE(script_entities, __FUNCTION__);
_activityTracking.editedEntityCount++;
EntityItemProperties properties = scriptSideProperties;
@ -456,6 +463,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
}
void EntityScriptingInterface::deleteEntity(QUuid id) {
PROFILE_RANGE(script_entities, __FUNCTION__);
_activityTracking.deletedEntityCount++;
EntityItemID entityID(id);
@ -511,6 +520,8 @@ void EntityScriptingInterface::setEntitiesScriptEngine(EntitiesScriptEngineProvi
}
void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, const QStringList& params) {
PROFILE_RANGE(script_entities, __FUNCTION__);
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
if (_entitiesScriptEngine) {
EntityItemID entityID{ id };
@ -519,6 +530,8 @@ void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method,
}
QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {
PROFILE_RANGE(script_entities, __FUNCTION__);
EntityItemID result;
if (_entityTree) {
EntityItemPointer closestEntity;
@ -542,6 +555,8 @@ void EntityScriptingInterface::dumpTree() const {
}
QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const {
PROFILE_RANGE(script_entities, __FUNCTION__);
QVector<QUuid> result;
if (_entityTree) {
QVector<EntityItemPointer> entities;
@ -557,6 +572,8 @@ QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, f
}
QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const {
PROFILE_RANGE(script_entities, __FUNCTION__);
QVector<QUuid> result;
if (_entityTree) {
QVector<EntityItemPointer> entities;
@ -573,6 +590,8 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn
}
QVector<QUuid> EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frustum) const {
PROFILE_RANGE(script_entities, __FUNCTION__);
QVector<QUuid> result;
const QString POSITION_PROPERTY = "position";
@ -616,6 +635,7 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking,
const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
PROFILE_RANGE(script_entities, __FUNCTION__);
QVector<EntityItemID> entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude);
QVector<EntityItemID> entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard);
@ -723,6 +743,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
}
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
obj.setProperty("accurate", value.accurate);
@ -770,6 +792,8 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
}
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
value.intersects = object.property("intersects").toVariant().toBool();
value.accurate = object.property("accurate").toVariant().toBool();
QScriptValue entityIDValue = object.property("entityID");
@ -807,6 +831,8 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
bool EntityScriptingInterface::setVoxels(QUuid entityID,
std::function<bool(PolyVoxEntityItem&)> actor) {
PROFILE_RANGE(script_entities, __FUNCTION__);
if (!_entityTree) {
return false;
}
@ -831,6 +857,8 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
}
bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor) {
PROFILE_RANGE(script_entities, __FUNCTION__);
if (!_entityTree) {
return false;
}
@ -870,18 +898,24 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setSphere(center, radius, value);
});
}
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setVoxelInVolume(position, value);
});
}
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setAll(value);
});
@ -889,12 +923,16 @@ bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
const glm::vec3& cuboidSize, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
});
}
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
PROFILE_RANGE(script_entities, __FUNCTION__);
EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID));
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID;
@ -913,6 +951,8 @@ bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::v
}
bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& point) {
PROFILE_RANGE(script_entities, __FUNCTION__);
EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID));
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID;
@ -988,6 +1028,8 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
const QUuid& entityID,
const QVariantMap& arguments) {
PROFILE_RANGE(script_entities, __FUNCTION__);
QUuid actionID = QUuid::createUuid();
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
bool success = false;
@ -1019,6 +1061,8 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
bool success = entity->updateAction(simulation, actionID, arguments);
if (success) {
@ -1029,6 +1073,8 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
}
bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) {
PROFILE_RANGE(script_entities, __FUNCTION__);
bool success = false;
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
success = entity->removeAction(simulation, actionID);
@ -1042,6 +1088,8 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
}
QVector<QUuid> EntityScriptingInterface::getActionIDs(const QUuid& entityID) {
PROFILE_RANGE(script_entities, __FUNCTION__);
QVector<QUuid> result;
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
QList<QUuid> actionIDs = entity->getActionIDs();
@ -1052,6 +1100,8 @@ QVector<QUuid> EntityScriptingInterface::getActionIDs(const QUuid& entityID) {
}
QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, const QUuid& actionID) {
PROFILE_RANGE(script_entities, __FUNCTION__);
QVariantMap result;
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
result = entity->getActionArguments(actionID);

View file

@ -537,6 +537,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
FBXGeometry* geometryPtr = new FBXGeometry;
FBXGeometry& geometry = *geometryPtr;
geometry.originalURL = url;
float unitScaleFactor = 1.0f;
glm::vec3 ambientColor;
QString hifiGlobalNodeID;

View file

@ -288,6 +288,7 @@ class FBXGeometry {
public:
using Pointer = std::shared_ptr<FBXGeometry>;
QString originalURL;
QString author;
QString applicationName; ///< the name of the application that generated the model

View file

@ -62,7 +62,10 @@ template<class T> QVariant readBinaryArray(QDataStream& in, int& position) {
position += sizeof(T) * arrayLength;
in.readRawData(arrayData.data(), arrayData.size());
}
memcpy(&values[0], arrayData.constData(), arrayData.size());
if (arrayData.size() > 0) {
memcpy(&values[0], arrayData.constData(), arrayData.size());
}
} else {
values.reserve(arrayLength);
const unsigned int DEFLATE_ENCODING = 1;

View file

@ -604,6 +604,9 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
qFatal("Could not load object as root item");
return nullptr;
}
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
// The root item is ready. Associate it with the window.
_rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem());
@ -952,4 +955,13 @@ void OffscreenQmlSurface::emitWebEvent(const QVariant& message) {
}
}
void OffscreenQmlSurface::sendToQml(const QVariant& message) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitQmlEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
} else if (_rootItem) {
// call fromScript method on qml root
QMetaObject::invokeMethod(_rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
}
#include "OffscreenQmlSurface.moc"

View file

@ -107,6 +107,11 @@ signals:
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
// qml event bridge
public slots:
void sendToQml(const QVariant& message);
signals:
void fromQml(QVariant message);
protected:
bool filterEnabled(QObject* originalDestination, QEvent* event) const;

View file

@ -328,12 +328,12 @@ void GLBackend::render(const Batch& batch) {
void GLBackend::syncCache() {
PROFILE_RANGE(render_gpu_gl_detail, __FUNCTION__);
syncTransformStateCache();
syncPipelineStateCache();
syncInputStateCache();
syncOutputStateCache();
glEnable(GL_LINE_SMOOTH);
}
#ifdef GPU_STEREO_DRAWCALL_DOUBLED

View file

@ -1,4 +1,4 @@
//
//
// GLBackendPipeline.cpp
// libraries/gpu/src/gpu
//

View file

@ -28,11 +28,8 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) {
}
}
}
}
void GLBackend::syncPipelineStateCache() {
State::Data state;
// force a few states regardless
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Point size is always on
@ -43,6 +40,25 @@ void GLBackend::syncPipelineStateCache() {
// Default line width accross the board
glLineWidth(1.0f);
glEnable(GL_LINE_SMOOTH);
}
void GLBackend::syncPipelineStateCache() {
State::Data state;
// force a few states regardless
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Point size is always on
// FIXME CORE
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
// Default line width accross the board
glLineWidth(1.0f);
glEnable(GL_LINE_SMOOTH);
getCurrentGLState(state);
State::Signature signature = State::evalSignature(state);

View file

@ -151,6 +151,7 @@ bool GLTextureTransferHelper::process() {
#endif
return true;
}
PROFILE_COUNTER_IF_CHANGED(render_gpu_gl, "transferringTextures", int, (int) _transferringTextures.size())
static auto lastReport = usecTimestampNow();
auto now = usecTimestampNow();

View file

@ -297,6 +297,7 @@ void GL45Texture::startTransfer() {
}
bool GL45Texture::continueTransfer() {
PROFILE_RANGE(render_gpu_gl, "continueTransfer")
size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1;
for (uint8_t face = 0; face < maxFace; ++face) {
for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) {
@ -306,6 +307,8 @@ bool GL45Texture::continueTransfer() {
_sparseInfo.allocatedPages += _sparseInfo.getPageCount(size);
}
if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) {
PROFILE_RANGE_EX(render_gpu_gl, "texSubImage", 0x0000ffff, (size.x * size.y * maxFace / 1024));
auto mip = _gpuObject.accessStoredMipFace(mipLevel, face);
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat());
if (GL_TEXTURE_2D == _target) {
@ -379,6 +382,8 @@ void GL45Texture::stripToMip(uint16_t newMinMip) {
return;
}
PROFILE_RANGE(render_gpu_gl, "GL45Texture::stripToMip");
auto mipLevels = usedMipLevels();
{
Lock lock(texturesByMipCountsMutex);
@ -420,29 +425,35 @@ void GL45Texture::stripToMip(uint16_t newMinMip) {
auto newLevels = usedMipLevels();
// Create and setup the new texture (allocate)
glCreateTextures(_target, 1, &const_cast<GLuint&>(_id));
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0);
glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip);
Vec3u newDimensions = _gpuObject.evalMipDimensions(_mipOffset);
glTextureStorage2D(_id, newLevels, _internalFormat, newDimensions.x, newDimensions.y);
{
Vec3u newDimensions = _gpuObject.evalMipDimensions(_mipOffset);
PROFILE_RANGE_EX(render_gpu_gl, "Re-Allocate", 0xff0000ff, (newDimensions.x * newDimensions.y));
glCreateTextures(_target, 1, &const_cast<GLuint&>(_id));
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0);
glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip);
glTextureStorage2D(_id, newLevels, _internalFormat, newDimensions.x, newDimensions.y);
}
// Copy the contents of the old texture to the new
GLuint fbo { 0 };
glCreateFramebuffers(1, &fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
for (uint16 targetMip = _minMip; targetMip <= _maxMip; ++targetMip) {
uint16 sourceMip = targetMip + mipDelta;
Vec3u mipDimensions = _gpuObject.evalMipDimensions(targetMip + _mipOffset);
for (GLenum target : getFaceTargets(_target)) {
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip);
(void)CHECK_GL_ERROR();
glCopyTextureSubImage2D(_id, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y);
(void)CHECK_GL_ERROR();
{
PROFILE_RANGE(render_gpu_gl, "Blit");
// Preferred path only available in 4.3
for (uint16 targetMip = _minMip; targetMip <= _maxMip; ++targetMip) {
uint16 sourceMip = targetMip + mipDelta;
Vec3u mipDimensions = _gpuObject.evalMipDimensions(targetMip + _mipOffset);
for (GLenum target : getFaceTargets(_target)) {
glCopyImageSubData(
oldId, target, sourceMip, 0, 0, 0,
_id, target, targetMip, 0, 0, 0,
mipDimensions.x, mipDimensions.y, 1
);
(void)CHECK_GL_ERROR();
}
}
glDeleteTextures(1, &oldId);
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
glDeleteTextures(1, &oldId);
}
// Re-sync the sampler to force access to the new mip level

View file

@ -17,7 +17,6 @@
#include <Profile.h>
#include "ModelLogging.h"
using namespace model;
using namespace gpu;
@ -142,8 +141,9 @@ const QImage TextureUsage::process2DImageColor(const QImage& srcImage, bool& val
validAlpha = (numOpaques != NUM_PIXELS);
}
if (!validAlpha && image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
// Force all the color images to be rgba32bits
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
return image;
@ -200,7 +200,7 @@ const QImage& image, bool isLinear, bool doCompress) {
}
}
#define CPU_MIPMAPS 0
#define CPU_MIPMAPS 1
void generateMips(gpu::Texture* texture, QImage& image, gpu::Element formatMip) {
#if CPU_MIPMAPS
@ -286,15 +286,16 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src
PROFILE_RANGE(resource_parse, "createNormalTextureFromNormalImage");
QImage image = processSourceImage(srcImage, false);
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
// Make sure the normal map source image is RGBA32
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
@ -330,7 +331,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
const double pStrength = 2.0;
int width = image.width();
int height = image.height();
QImage result(width, height, QImage::Format_RGB888);
// THe end result image for normal map is RGBA32 even though the A is not used
QImage result(width, height, QImage::Format_RGBA8888);
for (int i = 0; i < width; i++) {
const int iNextClamped = clampPixelCoordinate(i + 1, width - 1);
@ -378,8 +380,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
@ -398,8 +400,8 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma
image = image.convertToFormat(QImage::Format_RGB888);
}
} else {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
}
@ -433,8 +435,8 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s
image = image.convertToFormat(QImage::Format_RGB888);
}
} else {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
}
@ -472,8 +474,8 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag
image = image.convertToFormat(QImage::Format_RGB888);
}
} else {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
}
@ -762,8 +764,8 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
gpu::Texture* theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
gpu::Element formatGPU;

View file

@ -162,4 +162,4 @@ void EntityScriptClient::forceFailureOfPendingRequests(SharedNodePointer node) {
messageMapIt->second.clear();
}
}
}
}

View file

@ -111,7 +111,8 @@ public:
EntityScriptGetStatusReply,
ReloadEntityServerScript,
EntityPhysics,
LAST_PACKET_TYPE = EntityPhysics
EntityServerScriptLog,
LAST_PACKET_TYPE = EntityServerScriptLog
};
};

View file

@ -1178,7 +1178,7 @@ void Model::updateClusterMatrices() {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
#if GLM_ARCH & GLM_ARCH_SSE2
#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS)
glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
state.clusterMatrices[j] = out;

View file

@ -64,6 +64,8 @@
#include "ScriptEngines.h"
#include "TabletScriptingInterface.h"
#include <Profile.h>
#include "MIDIEvent.h"
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
@ -863,6 +865,10 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
}
void ScriptEngine::run() {
auto filenameParts = _fileNameString.split("/");
auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown";
PROFILE_SET_THREAD_NAME("Script: " + name);
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
return; // bail early - avoid setting state in init(), as evaluate() will bail too
}
@ -923,7 +929,10 @@ void ScriptEngine::run() {
bool processedEvents = false;
while (!_isFinished && clock::now() < sleepUntil) {
QCoreApplication::processEvents(); // before we sleep again, give events a chance to process
{
PROFILE_RANGE(script, "processEvents-sleep");
QCoreApplication::processEvents(); // before we sleep again, give events a chance to process
}
processedEvents = true;
// If after processing events, we're past due, exit asap
@ -938,6 +947,8 @@ void ScriptEngine::run() {
std::this_thread::sleep_until(smallSleepUntil);
}
PROFILE_RANGE(script, "ScriptMainLoop");
#ifdef SCRIPT_DELAY_DEBUG
{
auto actuallySleptUntil = clock::now();
@ -961,6 +972,7 @@ void ScriptEngine::run() {
// Only call this if we didn't processEvents as part of waiting for next frame
if (!processedEvents) {
PROFILE_RANGE(script, "processEvents");
QCoreApplication::processEvents();
}
@ -985,7 +997,10 @@ void ScriptEngine::run() {
float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND;
if (!_isFinished) {
auto preUpdate = clock::now();
emit update(deltaTime);
{
PROFILE_RANGE(script, "ScriptUpdate");
emit update(deltaTime);
}
auto postUpdate = clock::now();
auto elapsed = (postUpdate - preUpdate);
totalUpdates += std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
@ -1393,6 +1408,16 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
}
}
int ScriptEngine::getNumRunningEntityScripts() const {
int sum = 0;
for (auto& st : _entityScripts) {
if (st.status == RUNNING) {
++sum;
}
}
return sum;
}
bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const {
auto it = _entityScripts.constFind(entityID);
if (it == _entityScripts.constEnd()) {
@ -1456,6 +1481,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.status = ERROR_LOADING_SCRIPT;
newDetails.errorInfo = "Failed to load script";
_entityScripts[entityID] = newDetails;
emit entityScriptDetailsUpdated();
return;
}
@ -1467,6 +1493,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.status = ERROR_RUNNING_SCRIPT;
newDetails.errorInfo = "Bad syntax";
_entityScripts[entityID] = newDetails;
emit entityScriptDetailsUpdated();
return; // done processing script
}
@ -1497,6 +1524,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.status = ERROR_RUNNING_SCRIPT;
newDetails.errorInfo = exceptionMessage;
_entityScripts[entityID] = newDetails;
emit entityScriptDetailsUpdated();
return;
}
@ -1523,6 +1551,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.status = ERROR_RUNNING_SCRIPT;
newDetails.errorInfo = "Could not find constructor";
_entityScripts[entityID] = newDetails;
emit entityScriptDetailsUpdated();
return; // done processing script
}
@ -1544,6 +1573,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.lastModified = lastModified;
newDetails.definingSandboxURL = sandboxURL;
_entityScripts[entityID] = newDetails;
emit entityScriptDetailsUpdated();
if (isURL) {
setParentURL("");
@ -1575,6 +1605,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
}
_entityScripts.remove(entityID);
stopAllTimersForEntityScript(entityID);
emit entityScriptDetailsUpdated();
}
}
@ -1596,6 +1627,7 @@ void ScriptEngine::unloadAllEntityScripts() {
}
}
_entityScripts.clear();
emit entityScriptDetailsUpdated();
#ifdef DEBUG_ENGINE_STATE
qCDebug(scriptengine) << "---- CURRENT STATE OF ENGINE: --------------------------";

View file

@ -199,6 +199,7 @@ public:
void scriptWarningMessage(const QString& message);
void scriptInfoMessage(const QString& message);
int getNumRunningEntityScripts() const;
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
public slots:
@ -222,6 +223,10 @@ signals:
void reloadScript(const QString& scriptName, bool isUserLoaded);
void doneRunning();
// Emitted when an entity script is added or removed, or when the status of an entity
// script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example)
void entityScriptDetailsUpdated();
protected:
void init();

View file

@ -183,6 +183,18 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
_qmlTabletRoot = qmlTabletRoot;
if (_qmlTabletRoot && _qmlOffscreenSurface) {
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant)));
// forward qml surface events to interface js
connect(dynamic_cast<OffscreenQmlSurface*>(_qmlOffscreenSurface), &OffscreenQmlSurface::fromQml, [this](QVariant message) {
if (message.canConvert<QJSValue>()) {
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
} else if (message.canConvert<QString>()) {
emit fromQml(message.toString());
} else {
qWarning() << "fromQml: Unsupported message type " << message;
}
});
gotoHomeScreen();
QMetaObject::invokeMethod(_qmlTabletRoot, "setUsername", Q_ARG(const QVariant&, QVariant(getUsername())));
@ -197,6 +209,7 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
} else {
removeButtonsFromHomeScreen();
_state = State::Uninitialized;
emit screenChanged(QVariant("Closed"), QVariant(""));
}
}
@ -208,10 +221,21 @@ void TabletProxy::gotoMenuScreen() {
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection);
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
_state = State::Menu;
emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL));
}
}
}
void TabletProxy::loadQMLSource(const QVariant& path) {
if (_qmlTabletRoot) {
if (_state != State::QML) {
removeButtonsFromHomeScreen();
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, path));
_state = State::QML;
emit screenChanged(QVariant("QML"), path);
}
}
}
void TabletProxy::gotoHomeScreen() {
if (_qmlTabletRoot) {
if (_state != State::Home) {
@ -220,6 +244,7 @@ void TabletProxy::gotoHomeScreen() {
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
_state = State::Home;
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
}
}
}
@ -236,6 +261,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
if (_state != State::Web) {
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL)));
_state = State::Web;
emit screenChanged(QVariant("Web"), QVariant(url));
}
QMetaObject::invokeMethod(_qmlTabletRoot, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)),
Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
@ -298,6 +324,12 @@ void TabletProxy::emitScriptEvent(QVariant msg) {
}
}
void TabletProxy::sendToQml(QVariant msg) {
if (_qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
}
}
void TabletProxy::addButtonsToHomeScreen() {
auto tablet = getQmlTablet();
if (!tablet) {

View file

@ -89,6 +89,8 @@ public:
Q_INVOKABLE void gotoWebScreen(const QString& url);
Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl);
Q_INVOKABLE void loadQMLSource(const QVariant& path);
/**jsdoc
* Creates a new button, adds it to this and returns it.
* @function TabletProxy#addButton
@ -120,6 +122,13 @@ public:
*/
Q_INVOKABLE void emitScriptEvent(QVariant msg);
/**jsdoc
* Used to send an event to the qml embedded in the tablet
* @function TabletProxy#sendToQml
* @param msg {object|string}
*/
Q_INVOKABLE void sendToQml(QVariant msg);
Q_INVOKABLE bool onHomeScreen();
QObject* getTabletSurface();
@ -137,6 +146,22 @@ signals:
*/
void webEventReceived(QVariant msg);
/**jsdoc
* Signaled when this tablet receives an event from the qml embedded in the tablet
* @function TabletProxy#fromQml
* @param msg {object|string}
* @returns {Signal}
*/
void fromQml(QVariant msg);
/**jsdoc
* Signales when this tablet screen changes.
* @function TabletProxy#screenChanged
* @param type {string} - "Home", "Web", "Menu", "QML", "Closed"
* @param url {string} - only valid for Web and QML.
*/
void screenChanged(QVariant type, QVariant url);
private slots:
void addButtonsToHomeScreen();
void addButtonsToMenuScreen();
@ -149,7 +174,7 @@ protected:
QQuickItem* _qmlTabletRoot { nullptr };
QObject* _qmlOffscreenSurface { nullptr };
enum class State { Uninitialized, Home, Web, Menu };
enum class State { Uninitialized, Home, Web, Menu, QML };
State _state { State::Uninitialized };
};

View file

@ -177,7 +177,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
prefixString.append(QString(" [%1]").arg(_targetName));
}
QString logMessage = QString("%1 %2").arg(prefixString, message.split("\n").join("\n" + prefixString + " "));
QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " "));
fprintf(stdout, "%s\n", qPrintable(logMessage));
return logMessage;
}

View file

@ -10,6 +10,7 @@
Q_LOGGING_CATEGORY(trace_app, "trace.app")
Q_LOGGING_CATEGORY(trace_app_detail, "trace.app.detail")
Q_LOGGING_CATEGORY(trace_metadata, "trace.metadata")
Q_LOGGING_CATEGORY(trace_network, "trace.network")
Q_LOGGING_CATEGORY(trace_parse, "trace.parse")
Q_LOGGING_CATEGORY(trace_render, "trace.render")
@ -18,6 +19,8 @@ Q_LOGGING_CATEGORY(trace_render_gpu, "trace.render.gpu")
Q_LOGGING_CATEGORY(trace_resource, "trace.resource")
Q_LOGGING_CATEGORY(trace_resource_network, "trace.resource.network")
Q_LOGGING_CATEGORY(trace_resource_parse, "trace.resource.parse")
Q_LOGGING_CATEGORY(trace_script, "trace.script")
Q_LOGGING_CATEGORY(trace_script_entities, "trace.script.entities")
Q_LOGGING_CATEGORY(trace_simulation, "trace.simulation")
Q_LOGGING_CATEGORY(trace_simulation_detail, "trace.simulation.detail")
Q_LOGGING_CATEGORY(trace_simulation_animation, "trace.simulation.animation")

View file

@ -16,6 +16,7 @@
// When profiling something that may happen many times per frame, use a xxx_detail category so that they may easily be filtered out of trace results
Q_DECLARE_LOGGING_CATEGORY(trace_app)
Q_DECLARE_LOGGING_CATEGORY(trace_app_detail)
Q_DECLARE_LOGGING_CATEGORY(trace_metadata)
Q_DECLARE_LOGGING_CATEGORY(trace_network)
Q_DECLARE_LOGGING_CATEGORY(trace_render)
Q_DECLARE_LOGGING_CATEGORY(trace_render_detail)
@ -23,6 +24,8 @@ Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu)
Q_DECLARE_LOGGING_CATEGORY(trace_resource)
Q_DECLARE_LOGGING_CATEGORY(trace_resource_parse)
Q_DECLARE_LOGGING_CATEGORY(trace_resource_network)
Q_DECLARE_LOGGING_CATEGORY(trace_script)
Q_DECLARE_LOGGING_CATEGORY(trace_script_entities)
Q_DECLARE_LOGGING_CATEGORY(trace_simulation)
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_detail)
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation)
@ -69,6 +72,10 @@ inline void counter(const QLoggingCategory& category, const QString& name, const
}
}
inline void metadata(const QString& metadataType, const QVariantMap& args) {
tracing::traceEvent(trace_metadata(), metadataType, tracing::Metadata, "", args);
}
#define PROFILE_RANGE(category, name) Duration profileRangeThis(trace_##category(), name);
#define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__);
#define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor)
@ -78,6 +85,7 @@ inline void counter(const QLoggingCategory& category, const QString& name, const
#define PROFILE_COUNTER_IF_CHANGED(category, name, type, value) { static type lastValue = 0; type newValue = value; if (newValue != lastValue) { counter(trace_##category(), name, { { name, newValue }}); lastValue = newValue; } }
#define PROFILE_COUNTER(category, name, ...) counter(trace_##category(), name, ##__VA_ARGS__);
#define PROFILE_INSTANT(category, name, ...) instant(trace_##category(), name, ##__VA_ARGS__);
#define PROFILE_SET_THREAD_NAME(threadName) metadata("thread_name", { { "name", threadName } });
#define SAMPLE_PROFILE_RANGE(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_RANGE(category, name); }
#define SAMPLE_PROFILE_RANGE_EX(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_RANGE_EX(category, name, argbColor, payload, ##__VA_ARGS__); }

View file

@ -28,9 +28,10 @@ QString Settings::fileName() const {
}
void Settings::remove(const QString& key) {
if (key == "" || _manager->contains(key)) {
_manager->remove(key);
}
// NOTE: you can't check _manager->contains(key) here because it will return 'false'
// when the key is a valid child group with child keys.
// However, calling remove() without checking will do the right thing.
_manager->remove(key);
}
QStringList Settings::childGroups() const {
@ -46,6 +47,7 @@ QStringList Settings::allKeys() const {
}
bool Settings::contains(const QString& key) const {
// NOTE: this will return 'false' if key is a valid child group with child keys.
return _manager->contains(key);
}

View file

@ -132,6 +132,9 @@ void Tracer::serialize(const QString& originalPath) {
{
std::lock_guard<std::mutex> guard(_eventsMutex);
currentEvents.swap(_events);
for (auto& event : _metadataEvents) {
currentEvents.push_back(event);
}
}
// If the file exists and we can't remove it, fail early
@ -194,33 +197,53 @@ void Tracer::serialize(const QString& originalPath) {
#endif
}
void Tracer::traceEvent(const QLoggingCategory& category,
const QString& name, EventType type,
qint64 timestamp, qint64 processID, qint64 threadID,
const QString& id,
const QVariantMap& args, const QVariantMap& extra) {
void Tracer::traceEvent(const QLoggingCategory& category,
const QString& name, EventType type,
qint64 timestamp, qint64 processID, qint64 threadID,
const QString& id,
const QVariantMap& args, const QVariantMap& extra) {
std::lock_guard<std::mutex> guard(_eventsMutex);
if (!_enabled) {
// We always want to store metadata events even if tracing is not enabled so that when
// tracing is enabled we will be able to associate that metadata with that trace.
// Metadata events should be used sparingly - as of 12/30/16 the Chrome Tracing
// spec only supports thread+process metadata, so we should only expect to see metadata
// events created when a new thread or process is created.
if (!_enabled && type != Metadata) {
return;
}
_events.push_back({
id,
name,
type,
timestamp,
processID,
threadID,
category,
args,
extra
});
if (type == Metadata) {
_metadataEvents.push_back({
id,
name,
type,
timestamp,
processID,
threadID,
category,
args,
extra
});
} else {
_events.push_back({
id,
name,
type,
timestamp,
processID,
threadID,
category,
args,
extra
});
}
}
void Tracer::traceEvent(const QLoggingCategory& category,
const QString& name, EventType type, const QString& id,
const QVariantMap& args, const QVariantMap& extra) {
if (!_enabled) {
if (!_enabled && type != Metadata) {
return;
}

View file

@ -97,6 +97,7 @@ private:
bool _enabled { false };
std::list<TraceEvent> _events;
std::list<TraceEvent> _metadataEvents;
std::mutex _eventsMutex;
};

View file

@ -0,0 +1,183 @@
//
// BetterClientSimulationBotFromRecording.js
// examples
//
// Created by Brad Hefta-Gaub on 2/6/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var WANT_DEBUGGING = false;
randFloat = function(low, high) {
return low + Math.random() * (high - low);
}
var AVATARS_ARRAY = [
"http://mpassets.highfidelity.com/0c2c264b-2fd2-46a4-bf80-de681881f66b-v1/F_MotRac.fst",
"http://mpassets.highfidelity.com/bd80a6d7-7173-489e-87c6-f7ee56e65530-v1/M_RetFut.fst",
"http://mpassets.highfidelity.com/47c8d706-d486-4c2d-afcc-70d4e1e25117-v1/M_RetSpaSuit.fst",
"http://mpassets.highfidelity.com/548d0792-0bac-4933-bbfc-57d71912d77e-v1/M_OutMer.fst",
"http://mpassets.highfidelity.com/13277c09-892f-4a5e-b9a5-8994a37d68bf-v1/F_WasWar.fst",
"http://mpassets.highfidelity.com/2d384111-0f0e-42e2-b800-66bfcab4aefb-v1/F_VooQue.fst",
"http://mpassets.highfidelity.com/57e4d1cd-9f52-4c95-9051-326f9bb114ea-v1/F_SteAvi.fst",
"http://mpassets.highfidelity.com/da2ad4cd-47d4-41da-b764-41f39ff77e30-v1/F_JerGir.fst",
"http://mpassets.highfidelity.com/96c747ab-f71b-44ee-8eb9-d19fc9593dda-v1/F_CatBur.fst",
"http://mpassets.highfidelity.com/ede82c38-c66e-4f67-9e0b-0bb0782db18f-v1/M_WesOut.fst",
"http://mpassets.highfidelity.com/8872ae86-a763-4db3-8373-d27514c1481e-v1/M_VinAvi.fst",
"http://mpassets.highfidelity.com/faf505f1-4fd1-4ed2-8909-816af246c48f-v1/M_VicGen.fst",
"http://mpassets.highfidelity.com/d807a7d2-5122-4436-a6f9-3173c94d1c49-v1/M_SuaGen.fst",
"http://mpassets.highfidelity.com/1dd41735-06f4-45a3-9ec0-d05215ace77b-v1/M_MarSen.fst",
"http://mpassets.highfidelity.com/2cad3894-8ab3-4ba5-a723-0234f93fbd6a-v1/M_BowBea.fst",
"http://mpassets.highfidelity.com/cf0eb1be-9ec7-4756-8eaf-ac8f3ec09eba-v1/F_ClaDef.fst",
"http://mpassets.highfidelity.com/0cedeca3-c1a4-4be9-9fd5-dad716afcc7e-v1/F_Cyria.fst",
"http://mpassets.highfidelity.com/dc55803b-9215-47dd-9408-eb835dac4082-v1/F_ParGir.fst",
"http://mpassets.highfidelity.com/775a8fb3-cfe7-494d-b603-a0a2d6910e55-v1/F_VinCov.fst",
"http://mpassets.highfidelity.com/eba0d8f8-aa72-4a6b-ab64-4d3fd4695b20-v1/F_VogHei.fst",
"http://mpassets.highfidelity.com/4f400c78-38f9-42af-b03b-11b5451d41b9-v1/M_MidRog.fst",
"http://mpassets.highfidelity.com/ad774d79-13f1-46e2-87c9-de49a261b264-v1/F_GunSli.fst",
"http://mpassets.highfidelity.com/5acbaefa-5455-49a2-8d40-89d12aa393ca-v1/M_KniWol.fst",
"http://mpassets.highfidelity.com/aaa1b0a8-3e1b-492a-9aee-600e5dc907db-v1/F_RetSciSuit.fst",
"http://mpassets.highfidelity.com/d8da10b6-25c1-40e2-9a66-369316c722d7-v1/F_AniSuit.fst",
"http://mpassets.highfidelity.com/f3fbb9f4-e159-49ed-ac32-03af9056b17e-v1/matthew.fst",
"http://mpassets.highfidelity.com/0c954ba0-4d87-4353-b65e-c45509f85658-v1/priscilla.fst",
"http://mpassets.highfidelity.com/e76946cc-c272-4adf-9bb6-02cde0a4b57d-v1/9e8c5c42a0cbd436962d6bd36f032ab3.fst",
"http://mpassets.highfidelity.com/72e083ee-194d-4113-9c61-0591d8257493-v1/skeleton_Rigged.fst",
"http://mpassets.highfidelity.com/f14bf7c9-49a1-4249-988a-0a577ed78957-v1/beingOfLight.fst",
"http://mpassets.highfidelity.com/1b7e1e7c-6c0b-4f20-9cd0-1d5ccedae620-v1/bb64e937acf86447f6829767e958073c.fst",
"http://mpassets.highfidelity.com/67d7c7aa-c300-4d03-85f4-86480130eaa5-v1/F_StarCrew.fst",
"http://mpassets.highfidelity.com/d293ef06-c659-467a-9288-c3cbaff0372a-v1/arya_avatar.fst",
"http://mpassets.highfidelity.com/faf249d5-12a8-48e2-a08e-fb0c33087011-v1/F_Ranger.fst",
"http://mpassets.highfidelity.com/b4502145-15eb-4023-b7d6-a81c5cbf6abf-v1/F_FitTra.fst",
"http://mpassets.highfidelity.com/548d0792-0bac-4933-bbfc-57d71912d77e-v1/M_OutMer.fst",
"http://mpassets.highfidelity.com/caa61e5d-5629-4165-81d8-6a7eb55e942d-v1/F_DeaSur.fst",
"http://mpassets.highfidelity.com/2cad3894-8ab3-4ba5-a723-0234f93fbd6a-v1/M_BowBea.fst",
"http://mpassets.highfidelity.com/fd4fa45a-9d2a-463e-a484-f9d1b3bba724-v1/M_BeaWar.fst",
"http://mpassets.highfidelity.com/367a5b60-8a92-4d56-a152-a00f3086f02b-v1/M_Espio.fst",
"http://mpassets.highfidelity.com/ab466729-31da-4b4c-a33c-366f7c1d38e5-v1/M_MMAFig.fst",
"http://mpassets.highfidelity.com/b0795a0c-493d-4abd-b4cc-5f32e6d6df46-v1/M_SalMer.fst",
"http://mpassets.highfidelity.com/0a1d44bf-a988-4199-b29e-a532ab85a2e8-v1/M_StaShi.fst",
"http://mpassets.highfidelity.com/d807a7d2-5122-4436-a6f9-3173c94d1c49-v1/M_SuaGen.fst",
"http://mpassets.highfidelity.com/cb20212c-36f2-4d41-bdad-132361ca6ff4-v1/M_TreTee.fst",
"http://mpassets.highfidelity.com/830988dc-619a-4e88-96e1-a19fa0aaa30f-v1/M_UrbEnf.fst",
"http://mpassets.highfidelity.com/faf505f1-4fd1-4ed2-8909-816af246c48f-v1/M_VicGen.fst",
"http://mpassets.highfidelity.com/883ac86f-dd29-4676-8bda-7dd52fb6465f-v1/M_WasWan.fst",
"http://mpassets.highfidelity.com/ede82c38-c66e-4f67-9e0b-0bb0782db18f-v1/M_WesOut.fst",
"http://mpassets.highfidelity.com/04c9a1e9-0390-4a7f-b6c6-5f135c19e3fb-v1/F_ArmTro.fst",
"http://mpassets.highfidelity.com/e863348f-a777-4f36-86e6-af6e65ffa161-v1/F_BloSam.fst",
"http://mpassets.highfidelity.com/cf0eb1be-9ec7-4756-8eaf-ac8f3ec09eba-v1/F_ClaDef.fst",
"http://mpassets.highfidelity.com/0cedeca3-c1a4-4be9-9fd5-dad716afcc7e-v1/F_Cyria.fst",
"http://mpassets.highfidelity.com/da2ad4cd-47d4-41da-b764-41f39ff77e30-v1/F_JerGir.fst",
"http://mpassets.highfidelity.com/534d42f8-ec13-4145-929f-5c8facac2fb7-v1/F_LegFig.fst",
"http://mpassets.highfidelity.com/dc55803b-9215-47dd-9408-eb835dac4082-v1/F_ParGir.fst",
"http://mpassets.highfidelity.com/f823e831-d8c4-4191-a3bd-427e406e69f9-v1/F_Shinjuku.fst",
"http://mpassets.highfidelity.com/eba0d8f8-aa72-4a6b-ab64-4d3fd4695b20-v1/F_VogHei.fst",
"http://mpassets.highfidelity.com/13277c09-892f-4a5e-b9a5-8994a37d68bf-v1/F_WasWar.fst",
"http://mpassets.highfidelity.com/9b589fbb-59e4-47a9-8b3f-bf8d3a0bd1d8-v1/M_LawSur.fst",
"http://mpassets.highfidelity.com/4f400c78-38f9-42af-b03b-11b5451d41b9-v1/M_MidRog.fst",
"http://mpassets.highfidelity.com/c90d755d-0456-48fd-b98c-09c4d85cd481-v1/M_MouOff.fst",
"http://mpassets.highfidelity.com/c2ed3b9a-b3a9-4424-9fd2-8a798209f32b-v1/M_PerTra.fst",
"http://mpassets.highfidelity.com/c48928ac-7657-41f4-bbdc-9b47385736ab-v1/M_SpaMar.fst",
"http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst",
"http://mpassets.highfidelity.com/c85c497d-c87b-42b1-9bbf-5405e05a0ad3-v1/M_ArmSol.fst",
"http://mpassets.highfidelity.com/1dd41735-06f4-45a3-9ec0-d05215ace77b-v1/M_MarSen.fst",
"http://mpassets.highfidelity.com/bd80a6d7-7173-489e-87c6-f7ee56e65530-v1/M_RetFut.fst",
"http://mpassets.highfidelity.com/8872ae86-a763-4db3-8373-d27514c1481e-v1/M_VinAvi.fst",
"http://mpassets.highfidelity.com/f798d926-9a9e-481a-b298-af0e45451252-v1/F_Assassin.fst",
"http://mpassets.highfidelity.com/ad774d79-13f1-46e2-87c9-de49a261b264-v1/F_GunSli.fst",
"http://mpassets.highfidelity.com/aaa1b0a8-3e1b-492a-9aee-600e5dc907db-v1/F_RetSciSuit.fst"
];
var AVATAR_URL = AVATARS_ARRAY[Math.floor(Math.random() * AVATARS_ARRAY.length)];
print("RANDOM AVATAR SELECTED:" + AVATAR_URL);
var RECORDINGS_ARRAY = [
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/waiting6.hfr",
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/waiting7.hfr",
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/waiting10.hfr",
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot1.hfr",
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot2.hfr",
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot3.hfr",
"http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot4.hfr"
];
var RECORDING_URL = RECORDINGS_ARRAY[Math.floor(Math.random() * RECORDINGS_ARRAY.length)];
print("RANDOM RECORDING SELECTED:" + RECORDING_URL);
// not quite what I want...
var LOCATIONS_ARRAY = [
{ min_x: 97.0, max_x: 103.0, y:-0.6, min_z: 30.8, max_z: 40 },
{ min_x: 92.7, max_x: 106.6, y:-0.3, min_z: 43 , max_z: 43 },
{ min_x: 92.7, max_x: 106.6, y: 0.3, min_z: 45 , max_z: 45 },
{ min_x: 92.7, max_x: 106.6, y: 1 , min_z: 47 , max_z: 47 },
{ min_x: 92.7, max_x: 106.6, y: 1.7, min_z: 51.9, max_z: 51.9 },
];
var LOCATION_PARAMS = LOCATIONS_ARRAY[Math.floor(Math.random() * LOCATIONS_ARRAY.length)];
var LOCATION = { x: randFloat(LOCATION_PARAMS.min_x, LOCATION_PARAMS.max_x), y: LOCATION_PARAMS.y, z: randFloat(LOCATION_PARAMS.min_z, LOCATION_PARAMS.max_z) };
Vec3.print("RANDOM LOCATION SELECTED:", LOCATION);
var playFromCurrentLocation = true;
var loop = true;
// Set position here if playFromCurrentLocation is true
Avatar.position = LOCATION;
Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0);
Avatar.scale = 1.0;
Agent.isAvatar = true;
// make the agent "listen" to the audio stream to cause additional audio-mixer load, technically this isn't needed when you're playing a recording
// but if you switch to a non-recording bot, you will need this, so we can leave this.
Agent.isListeningToAudioStream = true;
Avatar.skeletonModelURL = AVATAR_URL; // FIXME - currently setting an avatar while playing a recording doesn't work it will be ignored
Recording.loadRecording(RECORDING_URL);
count = 300; // This is necessary to wait for the audio mixer to connect
function update(event) {
if (count > 0) {
count--;
return;
}
if (count == 0) {
Recording.setPlayFromCurrentLocation(playFromCurrentLocation);
Recording.setPlayerLoop(loop);
Recording.setPlayerUseDisplayName(true);
Recording.setPlayerUseAttachments(true);
Recording.setPlayerUseHeadModel(false);
Recording.setPlayerUseSkeletonModel(false); // FIXME - this would allow you to override the recording avatar, but that's not currently working
Recording.startPlaying();
Vec3.print("Playing from ", Avatar.position);
count--;
} else if (WANT_DEBUGGING) {
count = 100;
Vec3.print("Avatar at: ", Avatar.position);
Quat.print("Avatar head orientation: ", Avatar.headOrientation);
print("outbound:"
+" GP: " + Avatar.getDataRate("globalPositionOutbound").toFixed(2) + "\n"
+" LP: " + Avatar.getDataRate("localPositionOutbound").toFixed(2) + "\n"
+" BB: " + Avatar.getDataRate("avatarBoundingBoxOutbound").toFixed(2) + "\n"
+" AO: " + Avatar.getDataRate("avatarOrientationOutbound").toFixed(2) + "\n"
+" AS: " + Avatar.getDataRate("avatarScaleOutbound").toFixed(2) + "\n"
+" LA: " + Avatar.getDataRate("lookAtPositionOutbound").toFixed(2) + "\n"
+" AL: " + Avatar.getDataRate("audioLoudnessOutbound").toFixed(2) + "\n"
+" SW: " + Avatar.getDataRate("sensorToWorkMatrixOutbound").toFixed(2) + "\n"
+" AF: " + Avatar.getDataRate("additionalFlagsOutbound").toFixed(2) + "\n"
+" PI: " + Avatar.getDataRate("parentInfoOutbound").toFixed(2) + "\n"
+" FT: " + Avatar.getDataRate("faceTrackerOutbound").toFixed(2) + "\n"
+" JD: " + Avatar.getDataRate("jointDataOutbound").toFixed(2));
}
if (!Recording.isPlaying()) {
Script.update.disconnect(update);
}
}
Script.update.connect(update);

View file

@ -60,19 +60,26 @@ function updateOverlays() {
var overlayPosition = avatar.getJointPosition("Head");
overlayPosition.y += 1.05;
var text = " All: " + AvatarManager.getAvatarDataRate(avatarID).toFixed(2) + "\n"
+" GP: " + AvatarManager.getAvatarDataRate(avatarID,"globalPosition").toFixed(2) + "\n"
+" LP: " + AvatarManager.getAvatarDataRate(avatarID,"localPosition").toFixed(2) + "\n"
+" BB: " + AvatarManager.getAvatarDataRate(avatarID,"avatarBoundingBox").toFixed(2) + "\n"
+" AO: " + AvatarManager.getAvatarDataRate(avatarID,"avatarOrientation").toFixed(2) + "\n"
+" AS: " + AvatarManager.getAvatarDataRate(avatarID,"avatarScale").toFixed(2) + "\n"
+" LA: " + AvatarManager.getAvatarDataRate(avatarID,"lookAtPosition").toFixed(2) + "\n"
+" AL: " + AvatarManager.getAvatarDataRate(avatarID,"audioLoudness").toFixed(2) + "\n"
+" SW: " + AvatarManager.getAvatarDataRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "\n"
+" AF: " + AvatarManager.getAvatarDataRate(avatarID,"additionalFlags").toFixed(2) + "\n"
+" PI: " + AvatarManager.getAvatarDataRate(avatarID,"parentInfo").toFixed(2) + "\n"
+" FT: " + AvatarManager.getAvatarDataRate(avatarID,"faceTracker").toFixed(2) + "\n"
+" JD: " + AvatarManager.getAvatarDataRate(avatarID,"jointData").toFixed(2);
var text = avatarID + "\n"
+"--- Data from Mixer ---\n"
+"All: " + AvatarManager.getAvatarDataRate(avatarID).toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID).toFixed(2) + "hz)" + "\n"
+" GP: " + AvatarManager.getAvatarDataRate(avatarID,"globalPosition").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"globalPosition").toFixed(2) + "hz)" + "\n"
+" LP: " + AvatarManager.getAvatarDataRate(avatarID,"localPosition").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"localPosition").toFixed(2) + "hz)" + "\n"
+" BB: " + AvatarManager.getAvatarDataRate(avatarID,"avatarBoundingBox").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"avatarBoundingBox").toFixed(2) + "hz)" + "\n"
+" AO: " + AvatarManager.getAvatarDataRate(avatarID,"avatarOrientation").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"avatarOrientation").toFixed(2) + "hz)" + "\n"
+" AS: " + AvatarManager.getAvatarDataRate(avatarID,"avatarScale").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"avatarScale").toFixed(2) + "hz)" + "\n"
+" LA: " + AvatarManager.getAvatarDataRate(avatarID,"lookAtPosition").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"lookAtPosition").toFixed(2) + "hz)" + "\n"
+" AL: " + AvatarManager.getAvatarDataRate(avatarID,"audioLoudness").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"audioLoudness").toFixed(2) + "hz)" + "\n"
+" SW: " + AvatarManager.getAvatarDataRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "hz)" + "\n"
+" AF: " + AvatarManager.getAvatarDataRate(avatarID,"additionalFlags").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"additionalFlags").toFixed(2) + "hz)" + "\n"
+" PI: " + AvatarManager.getAvatarDataRate(avatarID,"parentInfo").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"parentInfo").toFixed(2) + "hz)" + "\n"
+" FT: " + AvatarManager.getAvatarDataRate(avatarID,"faceTracker").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"faceTracker").toFixed(2) + "hz)" + "\n"
+" JD: " + AvatarManager.getAvatarDataRate(avatarID,"jointData").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"jointData").toFixed(2) + "hz)" + "\n"
+"--- Simulation ---\n"
+"All: " + AvatarManager.getAvatarSimulationRate(avatarID,"avatar").toFixed(2) + "hz \n"
+" inView: " + AvatarManager.getAvatarSimulationRate(avatarID,"avatarInView").toFixed(2) + "hz \n"
+" SM: " + AvatarManager.getAvatarSimulationRate(avatarID,"skeletonModel").toFixed(2) + "hz \n"
+" JD: " + AvatarManager.getAvatarSimulationRate(avatarID,"jointData").toFixed(2) + "hz \n"
if (avatarID in debugOverlays) {
// keep the overlay above the current position of this avatar
@ -85,8 +92,8 @@ function updateOverlays() {
var newOverlay = Overlays.addOverlay("text3d", {
position: overlayPosition,
dimensions: {
x: 1,
y: 13 * 0.13
x: 1.25,
y: 19 * 0.13
},
lineHeight: 0.1,
font:{size:0.1},

View file

@ -0,0 +1,28 @@
//
// essDebugWindow.js
//
// Created by Clement Brisset on 2/2/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
// Set up the qml ui
var qml = Script.resolvePath('debugWindow.qml');
var window = new OverlayWindow({
title: 'Entity Script Server Log Window',
source: qml,
width: 400, height: 900,
});
window.setPosition(25, 50);
window.closed.connect(function() { Script.stop(); });
EntityScriptServerLog.receivedNewLogLines.connect(function(message) {
window.sendToQml(message.trim());
});
}()); // END LOCAL_SCOPE

View file

@ -26,6 +26,7 @@ Script.include("/~/system/libraries/controllers.js");
//
// add lines where the hand ray picking is happening
//
var WANT_DEBUG = false;
var WANT_DEBUG_STATE = false;
var WANT_DEBUG_SEARCH_NAME = null;
@ -752,6 +753,7 @@ function MyController(hand) {
this.previouslyUnhooked = {};
this.shouldScale = false;
this.isScalingAvatar = false;
// handPosition is where the avatar's hand appears to be, in-world.
this.getHandPosition = function () {
@ -824,11 +826,7 @@ function MyController(hand) {
this.update = function(deltaTime, timestamp) {
this.updateSmoothedTrigger();
// If both trigger and grip buttons squeezed and nothing is held, rescale my avatar!
if (this.hand === RIGHT_HAND && this.state === STATE_SEARCHING &&
this.getOtherHandController().state === STATE_SEARCHING) {
this.maybeScaleMyAvatar();
}
this.maybeScaleMyAvatar();
if (this.ignoreInput()) {
@ -2595,22 +2593,29 @@ function MyController(hand) {
};
this.maybeScaleMyAvatar = function() {
if (!myAvatarScalingEnabled) {
if (!myAvatarScalingEnabled || this.shouldScale || this.hand === LEFT_HAND) {
// If scaling disabled, or if we are currently scaling an entity, don't scale avatar
// and only rescale avatar for one hand (so we're not doing it twice)
return;
}
if (!this.shouldScale) {
// Only scale avatar if both triggers and grips are squeezed
var tryingToScale = this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed() &&
this.triggerSmoothedSqueezed() && this.getOtherHandController().triggerSmoothedSqueezed();
if (!this.isScalingAvatar) {
// If both secondary triggers squeezed, start scaling
if (this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed()) {
if (tryingToScale) {
this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(),
this.getOtherHandController().getHandPosition()));
this.scalingStartAvatarScale = MyAvatar.scale;
this.shouldScale = true;
this.isScalingAvatar = true;
}
} else if (!this.secondarySqueezed() || !this.getOtherHandController().secondarySqueezed()) {
this.shouldScale = false;
} else if (!tryingToScale) {
this.isScalingAvatar = false;
}
if (this.shouldScale) {
if (this.isScalingAvatar) {
var scalingCurrentDistance = Vec3.length(Vec3.subtract(this.getHandPosition(),
this.getOtherHandController().getHandPosition()));
var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale;

112
scripts/system/nameTag.js Normal file
View file

@ -0,0 +1,112 @@
"use strict";
/*jslint vars: true, plusplus: true*/
/*global Entities, Script, Quat, Vec3, MyAvatar, print*/
// nameTag.js
//
// Created by Triplelexx on 17/01/31
// Copyright 2017 High Fidelity, Inc.
//
// Running the script creates a text entity that will hover over the user's head showing their display name.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
const CLIENTONLY = false;
const NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
const ENTITY_CHECK_INTERVAL = 5000; // ms = 5 seconds
const STARTUP_DELAY = 2000; // ms = 2 second
const OLD_AGE = 3500; // we recreate the entity if older than this time in seconds
const TTL = 2; // time to live in seconds if script is not running
const HEIGHT_ABOVE_HEAD = 0.2;
const HEAD_OFFSET = -0.025;
const SIZE_Y = 0.075;
const LETTER_OFFSET = 0.03; // arbitrary value to dynamically change width, could be more accurate by detecting characters
const LINE_HEIGHT = 0.05;
var nameTagEntityID = NULL_UUID;
var lastCheckForEntity = 0;
// create the name tag entity after a brief delay
Script.setTimeout(function() {
addNameTag();
}, STARTUP_DELAY);
function addNameTag() {
var nameTagPosition = Vec3.sum(MyAvatar.getHeadPosition(), Vec3.multiply(HEAD_OFFSET, Quat.getFront(MyAvatar.orientation)));
nameTagPosition.y += HEIGHT_ABOVE_HEAD;
var nameTagProperties = {
name: MyAvatar.displayName + ' Name Tag',
type: 'Text',
text: MyAvatar.displayName,
lineHeight: LINE_HEIGHT,
parentID: MyAvatar.sessionUUID,
dimensions: dimensionsFromName(),
position: nameTagPosition
}
nameTagEntityID = Entities.addEntity(nameTagProperties, CLIENTONLY);
}
function updateNameTag() {
var nameTagProps = Entities.getEntityProperties(nameTagEntityID);
var nameTagPosition = Vec3.sum(MyAvatar.getHeadPosition(), Vec3.multiply(HEAD_OFFSET, Quat.getFront(MyAvatar.orientation)));
nameTagPosition.y += HEIGHT_ABOVE_HEAD;
Entities.editEntity(nameTagEntityID, {
position: nameTagPosition,
dimensions: dimensionsFromName(),
// lifetime is in seconds we add TTL on top of the next poll time
lifetime: Math.round(nameTagProps.age) + (ENTITY_CHECK_INTERVAL / 1000) + TTL,
text: MyAvatar.displayName
});
};
function deleteNameTag() {
if(nameTagEntityID !== NULL_UUID) {
Entities.deleteEntity(nameTagEntityID);
nameTagEntityID = NULL_UUID;
}
}
function dimensionsFromName() {
return {
x: LETTER_OFFSET * MyAvatar.displayName.length,
y: SIZE_Y,
z: 0.0
}
};
// cleanup on ending
Script.scriptEnding.connect(cleanup);
function cleanup() {
deleteNameTag();
}
Script.update.connect(update);
function update() {
// if no entity we return
if(nameTagEntityID == NULL_UUID) {
return;
}
if(Date.now() - lastCheckForEntity > ENTITY_CHECK_INTERVAL) {
checkForEntity();
lastCheckForEntity = Date.now();
}
}
function checkForEntity() {
var nameTagProps = Entities.getEntityProperties(nameTagEntityID);
// it is possible for the age to not be a valid number, we check for this and return accordingly
if(nameTagProps.age == -1) {
return;
}
// it's too old or we receive undefined make a new one, otherwise update
if(nameTagProps.age > OLD_AGE || nameTagProps.age == undefined) {
deleteNameTag();
addNameTag();
} else {
updateNameTag();
}
}

View file

@ -203,8 +203,7 @@ var pal = new OverlayWindow({
height: 640,
visible: false
});
pal.fromQml.connect(function (message) { // messages are {method, params}, like json-rpc. See also sendToQml.
print('From PAL QML:', JSON.stringify(message));
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
switch (message.method) {
case 'selected':
selectedIds = message.params;
@ -259,7 +258,15 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
default:
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
}
});
}
function sendToQml(message) {
if (Settings.getValue("HUDUIEnabled")) {
pal.sendToQml(message);
} else {
tablet.sendToQml(message);
}
}
//
// Main operations.
@ -298,10 +305,10 @@ function populateUserList(selectData) {
data.push(avatarPalDatum);
print('PAL data:', JSON.stringify(avatarPalDatum));
});
pal.sendToQml({ method: 'users', params: data });
sendToQml({ method: 'users', params: data });
if (selectData) {
selectData[2] = true;
pal.sendToQml({ method: 'select', params: selectData });
sendToQml({ method: 'select', params: selectData });
}
}
@ -322,7 +329,7 @@ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
}
print('Username Data:', JSON.stringify(data));
// Ship the data off to QML
pal.sendToQml({ method: 'updateUsername', params: data });
sendToQml({ method: 'updateUsername', params: data });
}
var pingPong = true;
@ -396,7 +403,7 @@ function handleClick(pickRay) {
ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
// Don't select directly. Tell qml, who will give us back a list of ids.
var message = {method: 'select', params: [[overlay.key], !overlay.selected, false]};
pal.sendToQml(message);
sendToQml(message);
return true;
});
}
@ -492,6 +499,7 @@ if (Settings.getValue("HUDUIEnabled")) {
visible: true,
alpha: 0.9
});
pal.fromQml.connect(fromQml);
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
@ -499,7 +507,9 @@ if (Settings.getValue("HUDUIEnabled")) {
icon: "icons/tablet-icons/people-i.svg",
sortOrder: 7
});
tablet.fromQml.connect(fromQml);
}
var isWired = false;
var audioTimer;
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
@ -518,10 +528,26 @@ function off() {
Users.requestsDomainListData = false;
}
function onClicked() {
if (!pal.visible) {
if (Settings.getValue("HUDUIEnabled")) {
if (!pal.visible) {
Users.requestsDomainListData = true;
populateUserList();
pal.raise();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
} else {
off();
}
pal.setVisible(!pal.visible);
} else {
tablet.loadQMLSource("../Pal.qml");
Users.requestsDomainListData = true;
populateUserList();
pal.raise();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
@ -529,10 +555,7 @@ function onClicked() {
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
} else {
off();
}
pal.setVisible(!pal.visible);
}
//
@ -550,7 +573,7 @@ function receiveMessage(channel, messageString, senderID) {
if (!pal.visible) {
onClicked();
}
pal.sendToQml(message); // Accepts objects, not just strings.
sendToQml(message); // Accepts objects, not just strings.
break;
default:
print('Unrecognized PAL message', messageString);
@ -607,13 +630,13 @@ function createAudioInterval(interval) {
var userId = id || 0;
param[userId] = level;
});
pal.sendToQml({method: 'updateAudioLevel', params: param});
sendToQml({method: 'updateAudioLevel', params: param});
}, interval);
}
function avatarDisconnected(nodeID) {
// remove from the pal list
pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]});
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
}
//
// Button state.
@ -624,11 +647,20 @@ function onVisibleChanged() {
button.clicked.connect(onClicked);
pal.visibleChanged.connect(onVisibleChanged);
pal.closed.connect(off);
if (!Settings.getValue("HUDUIEnabled")) {
tablet.screenChanged.connect(function (type, url) {
if (type !== "QML" || url !== "../Pal.qml") {
off();
}
});
}
Users.usernameFromIDReply.connect(usernameFromIDReply);
Users.avatarDisconnected.connect(avatarDisconnected);
function clearLocalQMLDataAndClosePAL() {
pal.sendToQml({ method: 'clearLocalQMLData' });
sendToQml({ method: 'clearLocalQMLData' });
if (pal.visible) {
onClicked(); // Close the PAL
}

View file

@ -0,0 +1,64 @@
"use strict";
//
// goto.js
// scripts/system/
//
// Created by Dante Ruiz on 8 February 2017
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var gotoQmlSource = "TabletAddressDialog.qml";
var button;
var buttonName = "GOTO";
var toolBar = null;
var tablet = null;
function onAddressBarShown(visible) {
if (toolBar) {
button.editProperties({isActive: visible});
}
}
function onClicked(){
if (toolBar) {
DialogsManager.toggleAddressBar();
} else {
tablet.loadQMLSource(gotoQmlSource);
}
}
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/directory.svg"),
visible: true,
alpha: 0.9
});
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/goto-i.svg",
text: buttonName,
sortOrder: 8
});
}
button.clicked.connect(onClicked);
DialogsManager.addressBarShown.connect(onAddressBarShown);
Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton(buttonName);
}
DialogsManager.addressBarShown.disconnect(onAddressBarShown);
});
}()); // END LOCAL_SCOPE

View file

@ -8,34 +8,39 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var positionToSpawn = Vec3.sum(MyAvatar.position, Quat.getFront(MyAvatar.rotation));
var forward = Quat.getFront(MyAvatar.orientation);
Vec3.print("Forward: ", forward);
var positionToSpawn = Vec3.sum(MyAvatar.position, Vec3.multiply(3, forward));
var scale = 0.5;
positionToSpawn.y += 0.5;
var stopwatchID = Entities.addEntity({
type: "Model",
name: "stopwatch/base",
position: positionToSpawn,
modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch.fbx",
dimensions: {"x":4.129462242126465,"y":1.058512806892395,"z":5.773681640625}
modelURL: Script.resolvePath("models/Stopwatch.fbx"),
dimensions: Vec3.multiply(scale, {"x":4.129462242126465,"y":1.058512806892395,"z":5.773681640625}),
rotation: Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(90, 0, 0))
});
var secondHandID = Entities.addEntity({
type: "Model",
name: "stopwatch/seconds",
parentID: stopwatchID,
localPosition: {"x":-0.004985813982784748,"y":0.39391064643859863,"z":0.8312804698944092},
dimensions: {"x":0.14095762372016907,"y":0.02546107769012451,"z":1.6077008247375488},
localPosition: Vec3.multiply(scale, {"x":-0.004985813982784748,"y":0.39391064643859863,"z":0.8312804698944092}),
dimensions: Vec3.multiply(scale, {"x":0.14095762372016907,"y":0.02546107769012451,"z":1.6077008247375488}),
registrationPoint: {"x":0.5,"y":0.5,"z":1},
modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch-sec-hand.fbx",
modelURL: Script.resolvePath("models/Stopwatch-sec-hand.fbx"),
});
var minuteHandID = Entities.addEntity({
type: "Model",
name: "stopwatch/minutes",
parentID: stopwatchID,
localPosition: {"x":-0.0023056098725646734,"y":0.3308190703392029,"z":0.21810021996498108},
dimensions: {"x":0.045471154153347015,"y":0.015412690117955208,"z":0.22930574417114258},
localPosition: Vec3.multiply(scale, {"x":-0.0023056098725646734,"y":0.3308190703392029,"z":0.21810021996498108}),
dimensions: Vec3.multiply(scale, {"x":0.045471154153347015,"y":0.015412690117955208,"z":0.22930574417114258}),
registrationPoint: {"x":0.5,"y":0.5,"z":1},
modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch-min-hand.fbx",
modelURL: Script.resolvePath("models/Stopwatch-min-hand.fbx"),
});
Entities.editEntity(stopwatchID, {
@ -46,3 +51,5 @@ Entities.editEntity(stopwatchID, {
script: Script.resolvePath("stopwatchClient.js"),
serverScripts: Script.resolvePath("stopwatchServer.js")
});
Script.stop()

View file

@ -17,11 +17,11 @@
self.secondHandID = null;
self.minuteHandID = null;
self.tickSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/huffman/tick.wav");
self.tickSound = SoundCache.getSound(Script.resolvePath("sounds/tick.wav"));
self.tickInjector = null;
self.tickIntervalID = null;
self.chimeSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/huffman/chime.wav");
self.chimeSound = SoundCache.getSound(Script.resolvePath("sounds/chime.wav"));
self.preload = function(entityID) {
print("Preloading stopwatch: ", entityID);