mirror of
https://github.com/overte-org/overte.git
synced 2025-04-15 10:08:46 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into audio-hdr-volume
This commit is contained in:
commit
9a81a076e8
39 changed files with 1030 additions and 328 deletions
|
@ -50,6 +50,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);
|
||||
|
@ -108,6 +109,13 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
// only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame
|
||||
// Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times
|
||||
// per second.
|
||||
// This value should be a power of two for performance purposes, as the mixer performs a modulo operation every frame
|
||||
// to determine whether the extra data should be sent.
|
||||
const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16;
|
||||
|
||||
// NOTE: The following code calculates the _performanceThrottlingRatio based on how much the avatar-mixer was
|
||||
// able to sleep. This will eventually be used to ask for an additional avatar-mixer to help out. Currently the value
|
||||
// is unused as it is assumed this should not be hit before the avatar-mixer hits the desired bandwidth limit per client.
|
||||
|
@ -205,6 +213,15 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
// use the data rate specifically for avatar data for FRD adjustment checks
|
||||
float avatarDataRateLastSecond = nodeData->getOutboundAvatarDataKbps();
|
||||
|
||||
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that are not in the view frustrum
|
||||
bool getsOutOfView = nodeData->getRequestsDomainListData();
|
||||
|
||||
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that they've ignored
|
||||
bool getsIgnoredByMe = getsOutOfView;
|
||||
|
||||
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
|
||||
bool getsAnyIgnored = getsIgnoredByMe && node->getCanKick();
|
||||
|
||||
// Check if it is time to adjust what we send this client based on the observed
|
||||
// bandwidth to this node. We do this once a second, which is also the window for
|
||||
// the bandwidth reported by node->getOutboundBandwidth();
|
||||
|
@ -275,14 +292,14 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
// or that has ignored the viewing node
|
||||
if (!otherNode->getLinkedData()
|
||||
|| otherNode->getUUID() == node->getUUID()
|
||||
|| node->isIgnoringNodeWithID(otherNode->getUUID())
|
||||
|| otherNode->isIgnoringNodeWithID(node->getUUID())) {
|
||||
|| (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe)
|
||||
|| (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
|
||||
return false;
|
||||
} else {
|
||||
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
// Check to see if the space bubble is enabled
|
||||
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
|
||||
if ((node->isIgnoreRadiusEnabled() && !getsIgnoredByMe) || (otherNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
// Define the minimum bubble size
|
||||
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
|
||||
// Define the scale of the box for the current node
|
||||
|
@ -333,7 +350,6 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
&& (forceSend
|
||||
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|
||||
|| distribution(generator) < IDENTITY_SEND_PROBABILITY)) {
|
||||
|
||||
sendIdentityPacket(otherNodeData, node);
|
||||
}
|
||||
|
||||
|
@ -349,6 +365,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar);
|
||||
|
||||
if (distanceToAvatar != 0.0f
|
||||
&& !getsOutOfView
|
||||
&& distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) {
|
||||
return;
|
||||
}
|
||||
|
@ -380,15 +397,21 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
|
||||
// start a new segment in the PacketList for this avatar
|
||||
avatarPacketList->startSegment();
|
||||
|
||||
// determine if avatar is in view, to determine how much data to include...
|
||||
glm::vec3 otherNodeBoxScale = (otherNodeData->getPosition() - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f;
|
||||
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
|
||||
|
||||
// this throttles the extra data to only be sent every Nth message
|
||||
if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start a new segment in the PacketList for this avatar
|
||||
avatarPacketList->startSegment();
|
||||
|
||||
AvatarData::AvatarDataDetail detail;
|
||||
if (!nodeData->otherAvatarInView(otherNodeBox)) {
|
||||
if (!isInView && !getsOutOfView) {
|
||||
detail = AvatarData::MinimumData;
|
||||
nodeData->incrementAvatarOutOfView();
|
||||
} else {
|
||||
|
@ -527,6 +550,20 @@ void AvatarMixer::handleViewFrustumPacket(QSharedPointer<ReceivedMessage> messag
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->getOrCreateLinkedData(senderNode);
|
||||
|
||||
if (senderNode->getLinkedData()) {
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
if (nodeData != nullptr) {
|
||||
bool isRequesting;
|
||||
message->readPrimitive(&isRequesting);
|
||||
nodeData->setRequestsDomainListData(isRequesting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->updateNodeWithDataFromPacket(message, senderNode);
|
||||
|
|
|
@ -42,6 +42,7 @@ private slots:
|
|||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void domainSettingsRequestComplete();
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
|
||||
|
|
|
@ -101,6 +101,8 @@ public:
|
|||
void incrementAvatarOutOfView() { _recentOtherAvatarsOutOfView++; }
|
||||
const QString& getBaseDisplayName() { return _baseDisplayName; }
|
||||
void setBaseDisplayName(const QString& baseDisplayName) { _baseDisplayName = baseDisplayName; }
|
||||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
||||
|
||||
private:
|
||||
AvatarSharedPointer _avatar { new AvatarData() };
|
||||
|
@ -129,6 +131,7 @@ private:
|
|||
int _recentOtherAvatarsInView { 0 };
|
||||
int _recentOtherAvatarsOutOfView { 0 };
|
||||
QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary.
|
||||
bool _requestsDomainListData { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixerClientData_h
|
||||
|
|
Binary file not shown.
|
@ -72,6 +72,18 @@ Original.CheckBox {
|
|||
border.color: hifi.colors.checkboxCheckedBorder
|
||||
visible: checked && !pressed || !checked && pressed
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: disabledOverlay
|
||||
visible: !enabled
|
||||
width: boxSize
|
||||
height: boxSize
|
||||
radius: boxRadius
|
||||
border.width: 1
|
||||
border.color: hifi.colors.baseGrayHighlight
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
|
||||
label: Label {
|
||||
|
|
|
@ -35,6 +35,7 @@ Row {
|
|||
property int usernameTextHeight: 12
|
||||
property real audioLevel: 0.0
|
||||
|
||||
/* User image commented out for now - will probably be re-introduced later.
|
||||
Column {
|
||||
id: avatarImage
|
||||
// Size
|
||||
|
@ -48,10 +49,11 @@ Row {
|
|||
height: parent.height
|
||||
}
|
||||
}
|
||||
*/
|
||||
Column {
|
||||
id: textContainer
|
||||
// Size
|
||||
width: parent.width - avatarImage.width - parent.anchors.leftMargin - parent.anchors.rightMargin - parent.spacing
|
||||
width: parent.width - /*avatarImage.width - */parent.anchors.leftMargin - parent.anchors.rightMargin - parent.spacing
|
||||
height: contentHeight
|
||||
|
||||
// DisplayName Text
|
||||
|
|
|
@ -40,7 +40,11 @@ Item {
|
|||
property int myCardHeight: 70
|
||||
property int rowHeight: 70
|
||||
property int actionButtonWidth: 75
|
||||
property int nameCardWidth: width - actionButtonWidth*(iAmAdmin ? 4 : 2)
|
||||
property int nameCardWidth: width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4
|
||||
property var myData: ({displayName: "", userName: "", audioLevel: 0.0}) // 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.
|
||||
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
|
||||
property bool iAmAdmin: false
|
||||
|
||||
// This contains the current user's NameCard and will contain other information in the future
|
||||
Rectangle {
|
||||
|
@ -85,7 +89,7 @@ Item {
|
|||
Rectangle {
|
||||
id: adminTab
|
||||
// Size
|
||||
width: actionButtonWidth * 2 - 2
|
||||
width: actionButtonWidth * 2 + 2
|
||||
height: 40
|
||||
// Anchors
|
||||
anchors.bottom: myInfo.bottom
|
||||
|
@ -169,7 +173,9 @@ Item {
|
|||
movable: false
|
||||
resizable: false
|
||||
}
|
||||
model: userModel
|
||||
model: ListModel {
|
||||
id: userModel
|
||||
}
|
||||
|
||||
// This Rectangle refers to each Row in the table.
|
||||
rowDelegate: Rectangle { // The only way I know to specify a row height.
|
||||
|
@ -183,16 +189,17 @@ Item {
|
|||
// This Item refers to the contents of each Cell
|
||||
itemDelegate: Item {
|
||||
id: itemCell
|
||||
property bool isCheckBox: typeof(styleData.value) === 'boolean'
|
||||
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore"
|
||||
property bool isButton: styleData.role === "mute" || styleData.role === "kick"
|
||||
// This NameCard refers to the cell that contains an avatar's
|
||||
// DisplayName and UserName
|
||||
NameCard {
|
||||
id: nameCard
|
||||
// Properties
|
||||
displayName: styleData.value
|
||||
userName: model.userName
|
||||
audioLevel: model.audioLevel
|
||||
visible: !isCheckBox
|
||||
userName: model && model.userName
|
||||
audioLevel: model && model.audioLevel
|
||||
visible: !isCheckBox && !isButton
|
||||
// Size
|
||||
width: nameCardWidth
|
||||
height: parent.height
|
||||
|
@ -200,19 +207,69 @@ Item {
|
|||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
// This CheckBox belongs in the columns that contain the action buttons ("Mute", "Ban", etc)
|
||||
// This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now)
|
||||
// KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox
|
||||
// will appear in the "hovered" state. Hovering over the checkbox will fix it.
|
||||
// Clicking on the sides of the sorting header doesn't cause this problem.
|
||||
// I'm guessing this is a QT bug and not anything I can fix. I spent too long trying to work around it...
|
||||
// I'm just going to leave the minor visual bug in.
|
||||
HifiControls.CheckBox {
|
||||
id: actionCheckBox
|
||||
visible: isCheckBox
|
||||
anchors.centerIn: parent
|
||||
checked: model[styleData.role]
|
||||
// If this is a "Personal Mute" checkbox, disable the checkbox if the "Ignore" checkbox is checked.
|
||||
enabled: !(styleData.role === "personalMute" && model["ignore"])
|
||||
boxSize: 24
|
||||
onClicked: {
|
||||
var newValue = !model[styleData.role]
|
||||
var datum = userData[model.userIndex]
|
||||
datum[styleData.role] = model[styleData.role] = newValue
|
||||
userModel.setProperty(model.userIndex, styleData.role, newValue)
|
||||
userModelData[model.userIndex][styleData.role] = newValue // Defensive programming
|
||||
Users[styleData.role](model.sessionId, newValue)
|
||||
if (styleData.role === "ignore") {
|
||||
userModel.setProperty(model.userIndex, "personalMute", newValue)
|
||||
userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming
|
||||
if (newValue) {
|
||||
ignored[model.sessionId] = userModelData[model.userIndex]
|
||||
} else {
|
||||
delete ignored[model.sessionId]
|
||||
}
|
||||
}
|
||||
// http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html#creating-property-bindings-from-javascript
|
||||
// I'm using an explicit binding here because clicking a checkbox breaks the implicit binding as set by
|
||||
// "checked:" statement above.
|
||||
checked = Qt.binding(function() { return (model[styleData.role])})
|
||||
}
|
||||
}
|
||||
|
||||
// This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now)
|
||||
HifiControls.Button {
|
||||
id: actionButton
|
||||
color: 2 // Red
|
||||
visible: isButton
|
||||
anchors.centerIn: parent
|
||||
width: 32
|
||||
height: 24
|
||||
onClicked: {
|
||||
Users[styleData.role](model.sessionId)
|
||||
// Just for now, while we cannot undo things:
|
||||
userData.splice(model.userIndex, 1)
|
||||
sortModel()
|
||||
if (styleData.role === "kick") {
|
||||
// Just for now, while we cannot undo "Ban":
|
||||
userModel.remove(model.userIndex)
|
||||
delete userModelData[model.userIndex] // Defensive programming
|
||||
sortModel()
|
||||
}
|
||||
}
|
||||
// muted/error glyphs
|
||||
HiFiGlyphs {
|
||||
text: (styleData.role === "kick") ? hifi.glyphs.error : hifi.glyphs.muted
|
||||
// Size
|
||||
size: parent.height*1.3
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
// Style
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: enabled ? hifi.buttons.textColor[actionButton.color]
|
||||
: hifi.buttons.disabledTextColor[actionButton.colorScheme]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +313,6 @@ Item {
|
|||
onExited: reloadButton.color = (pressed ? hifi.colors.lightGrayText: hifi.colors.darkGray)
|
||||
}
|
||||
}
|
||||
|
||||
// Separator between user and admin functions
|
||||
Rectangle {
|
||||
// Size
|
||||
|
@ -309,18 +365,22 @@ Item {
|
|||
radius: hifi.dimensions.borderRadius
|
||||
}
|
||||
Rectangle {
|
||||
width: Math.min(parent.width * 0.75, 400)
|
||||
height: popupText.contentHeight*2
|
||||
width: Math.max(parent.width * 0.75, 400)
|
||||
height: popupText.contentHeight*1.5
|
||||
anchors.centerIn: parent
|
||||
radius: hifi.dimensions.borderRadius
|
||||
color: "white"
|
||||
FiraSansSemiBold {
|
||||
id: popupText
|
||||
text: "This is temporary text. It will eventually be used to explain what 'Names' means."
|
||||
text: "Bold names in the list are Avatar Display Names.\n" +
|
||||
"If a Display Name isn't set, a unique Session Display Name is assigned." +
|
||||
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present."
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.darkGray
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 15
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
@ -333,11 +393,8 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
property var userData: []
|
||||
property var myData: ({displayName: "", userName: "", audioLevel: 0.0}) // valid dummy until set
|
||||
property bool iAmAdmin: false
|
||||
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
||||
var i, data = optionalData || userData, length = data.length;
|
||||
var data = optionalData || userModelData, length = data.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (data[i].sessionId === sessionId) {
|
||||
return i;
|
||||
|
@ -349,11 +406,24 @@ Item {
|
|||
switch (message.method) {
|
||||
case 'users':
|
||||
var data = message.params;
|
||||
var myIndex = findSessionIndex('', data);
|
||||
iAmAdmin = Users.canKick;
|
||||
myData = data[myIndex];
|
||||
data.splice(myIndex, 1);
|
||||
userData = data;
|
||||
var index = -1;
|
||||
index = findSessionIndex('', data);
|
||||
if (index !== -1) {
|
||||
iAmAdmin = Users.canKick;
|
||||
myData = data[index];
|
||||
data.splice(index, 1);
|
||||
} else {
|
||||
console.log("This user's data was not found in the user list. PAL will not function properly.");
|
||||
}
|
||||
userModelData = data;
|
||||
for (var ignoredID in ignored) {
|
||||
index = findSessionIndex(ignoredID);
|
||||
if (index === -1) { // Add back any missing ignored to the PAL, because they sometimes take a moment to show up.
|
||||
userModelData.push(ignored[ignoredID]);
|
||||
} else { // Already appears in PAL; update properties of existing element in model data
|
||||
userModelData[index] = ignored[ignoredID];
|
||||
}
|
||||
}
|
||||
sortModel();
|
||||
break;
|
||||
case 'select':
|
||||
|
@ -378,11 +448,15 @@ Item {
|
|||
myData.userName = userName;
|
||||
myCard.userName = userName; // Defensive programming
|
||||
} else {
|
||||
// Get the index in userModel and userData associated with the passed UUID
|
||||
// Get the index in userModel and userModelData associated with the passed UUID
|
||||
var userIndex = findSessionIndex(userId);
|
||||
// Set the userName appropriately
|
||||
userModel.get(userIndex).userName = userName;
|
||||
userData[userIndex].userName = userName; // Defensive programming
|
||||
if (userIndex != -1) {
|
||||
// Set the userName appropriately
|
||||
userModel.setProperty(userIndex, "userName", userName);
|
||||
userModelData[userIndex].userName = userName; // Defensive programming
|
||||
} else {
|
||||
console.log("updateUsername() called with unknown UUID: ", userId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'updateAudioLevel':
|
||||
|
@ -393,25 +467,28 @@ Item {
|
|||
myData.audioLevel = audioLevel;
|
||||
myCard.audioLevel = audioLevel; // Defensive programming
|
||||
} else {
|
||||
console.log("userid:" + userId);
|
||||
var userIndex = findSessionIndex(userId);
|
||||
userModel.get(userIndex).audioLevel = audioLevel;
|
||||
userData[userIndex].audioLevel = audioLevel; // Defensive programming
|
||||
if (userIndex != -1) {
|
||||
userModel.setProperty(userIndex, "audioLevel", audioLevel);
|
||||
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
|
||||
} else {
|
||||
console.log("updateUsername() called with unknown UUID: ", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'clearIgnored':
|
||||
ignored = {};
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message:', JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
ListModel {
|
||||
id: userModel
|
||||
}
|
||||
function sortModel() {
|
||||
var sortProperty = table.getColumn(table.sortIndicatorColumn).role;
|
||||
var before = (table.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1;
|
||||
var after = -1 * before;
|
||||
userData.sort(function (a, b) {
|
||||
userModelData.sort(function (a, b) {
|
||||
var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase();
|
||||
switch (true) {
|
||||
case (aValue < bValue): return before;
|
||||
|
@ -420,9 +497,10 @@ Item {
|
|||
}
|
||||
});
|
||||
table.selection.clear();
|
||||
|
||||
userModel.clear();
|
||||
var userIndex = 0;
|
||||
userData.forEach(function (datum) {
|
||||
userModelData.forEach(function (datum) {
|
||||
function init(property) {
|
||||
if (datum[property] === undefined) {
|
||||
datum[property] = false;
|
||||
|
@ -437,7 +515,7 @@ Item {
|
|||
function noticeSelection() {
|
||||
var userIds = [];
|
||||
table.selection.forEach(function (userIndex) {
|
||||
userIds.push(userData[userIndex].sessionId);
|
||||
userIds.push(userModelData[userIndex].sessionId);
|
||||
});
|
||||
pal.sendToScript({method: 'selected', params: userIds});
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
#include <RecordingScriptingInterface.h>
|
||||
#include <RenderableWebEntityItem.h>
|
||||
#include <RenderShadowTask.h>
|
||||
#include <render/RenderFetchCullSortTask.h>
|
||||
#include <RenderDeferredTask.h>
|
||||
#include <RenderForwardTask.h>
|
||||
#include <ResourceCache.h>
|
||||
|
@ -1819,11 +1820,13 @@ void Application::initializeGL() {
|
|||
// Set up the render engine
|
||||
render::CullFunctor cullFunctor = LODManager::shouldRender;
|
||||
_renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
|
||||
const auto items = _renderEngine->addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
|
||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
||||
if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) {
|
||||
_renderEngine->addJob<RenderForwardTask>("RenderForwardTask", cullFunctor);
|
||||
_renderEngine->addJob<RenderForwardTask>("RenderForwardTask", items.get<RenderFetchCullSortTask::Output>());
|
||||
} else {
|
||||
_renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", cullFunctor);
|
||||
_renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", items.get<RenderFetchCullSortTask::Output>());
|
||||
}
|
||||
_renderEngine->load();
|
||||
_renderEngine->registerScene(_main3DScene);
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "Util.h"
|
||||
#include "world.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "SceneScriptingInterface.h"
|
||||
#include "SoftAttachmentModel.h"
|
||||
#include <Rig.h>
|
||||
|
||||
|
@ -292,7 +293,10 @@ void Avatar::updateAvatarEntities() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void Avatar::setShouldDie() {
|
||||
// This will cause the avatar to be shrunk away and removed (the actual Avatar gets removed), but then it comes back.
|
||||
_owningAvatarMixer.clear();
|
||||
}
|
||||
|
||||
void Avatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
|
@ -459,6 +463,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene>
|
|||
attachmentModel->addToScene(scene, pendingChanges);
|
||||
}
|
||||
|
||||
_inScene = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -469,6 +474,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
|
|||
for (auto& attachmentModel : _attachmentModels) {
|
||||
attachmentModel->removeFromScene(scene, pendingChanges);
|
||||
}
|
||||
_inScene = false;
|
||||
}
|
||||
|
||||
void Avatar::updateRenderItem(render::PendingChanges& pendingChanges) {
|
||||
|
@ -1328,3 +1334,21 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::addToScene(AvatarSharedPointer myHandle) {
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
if (scene) {
|
||||
render::PendingChanges pendingChanges;
|
||||
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars() && !DependencyManager::get<NodeList>()->isIgnoringNode(getSessionUUID())) {
|
||||
addToScene(myHandle, scene, pendingChanges);
|
||||
}
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
} else {
|
||||
qCWarning(interfaceapp) << "AvatarManager::addAvatar() : Unexpected null scene, possibly during application shutdown";
|
||||
}
|
||||
}
|
||||
void Avatar::ensureInScene(AvatarSharedPointer self) {
|
||||
if (!_inScene) {
|
||||
addToScene(self);
|
||||
}
|
||||
}
|
|
@ -178,6 +178,8 @@ public:
|
|||
glm::vec3 getUncachedRightPalmPosition() const;
|
||||
glm::quat getUncachedRightPalmRotation() const;
|
||||
|
||||
Q_INVOKABLE void setShouldDie();
|
||||
|
||||
public slots:
|
||||
|
||||
// FIXME - these should be migrated to use Pose data instead
|
||||
|
@ -254,6 +256,9 @@ protected:
|
|||
ThreadSafeValueCache<glm::vec3> _rightPalmPositionCache { glm::vec3() };
|
||||
ThreadSafeValueCache<glm::quat> _rightPalmRotationCache { glm::quat() };
|
||||
|
||||
void addToScene(AvatarSharedPointer self);
|
||||
void ensureInScene(AvatarSharedPointer self);
|
||||
|
||||
private:
|
||||
int _leftPointerGeometryID { 0 };
|
||||
int _rightPointerGeometryID { 0 };
|
||||
|
@ -262,6 +267,7 @@ private:
|
|||
bool _shouldAnimate { true };
|
||||
bool _shouldSkipRender { false };
|
||||
bool _isLookAtTarget { false };
|
||||
bool _inScene { false };
|
||||
|
||||
float getBoundingRadius() const;
|
||||
|
||||
|
|
|
@ -82,7 +82,11 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
|
||||
// when we hear that the user has ignored an avatar by session UUID
|
||||
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
||||
connect(nodeList.data(), "ignoredNode", this, "removeAvatar");
|
||||
connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) {
|
||||
if (enabled) {
|
||||
removeAvatar(nodeID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AvatarManager::~AvatarManager() {
|
||||
|
@ -159,6 +163,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
removeAvatar(avatarIterator.key());
|
||||
++avatarIterator;
|
||||
} else {
|
||||
avatar->ensureInScene(avatar);
|
||||
avatar->simulate(deltaTime);
|
||||
++avatarIterator;
|
||||
|
||||
|
@ -219,16 +224,7 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
|
|||
auto newAvatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
|
||||
auto rawRenderableAvatar = std::static_pointer_cast<Avatar>(newAvatar);
|
||||
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
if (scene) {
|
||||
render::PendingChanges pendingChanges;
|
||||
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
|
||||
rawRenderableAvatar->addToScene(rawRenderableAvatar, scene, pendingChanges);
|
||||
}
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
} else {
|
||||
qCWarning(interfaceapp) << "AvatarManager::addAvatar() : Unexpected null scene, possibly during application shutdown";
|
||||
}
|
||||
rawRenderableAvatar->addToScene(rawRenderableAvatar);
|
||||
|
||||
return newAvatar;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,6 @@ private:
|
|||
// virtual overrides
|
||||
virtual AvatarSharedPointer newSharedAvatar() override;
|
||||
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
|
||||
|
||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
|
||||
|
||||
QVector<AvatarSharedPointer> _avatarFades;
|
||||
|
|
|
@ -112,7 +112,7 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<ReceivedMessage> mess
|
|||
// make sure this isn't our own avatar data or for a previously ignored node
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (sessionUUID != _lastOwnerSessionUUID && !nodeList->isIgnoringNode(sessionUUID)) {
|
||||
if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) {
|
||||
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
||||
|
||||
// have the matching (or new) avatar parse the data from the packet
|
||||
|
@ -145,7 +145,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
|||
identity.uuid = EMPTY;
|
||||
}
|
||||
}
|
||||
if (!nodeList->isIgnoringNode(identity.uuid)) {
|
||||
if (!nodeList->isIgnoringNode(identity.uuid) || nodeList->getRequestsDomainListData()) {
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);
|
||||
avatar->processAvatarIdentity(identity);
|
||||
|
|
|
@ -81,17 +81,24 @@ void Node::updateClockSkewUsec(qint64 clockSkewSample) {
|
|||
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
|
||||
}
|
||||
|
||||
void Node::parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message) {
|
||||
void Node::parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message) {
|
||||
bool addToIgnore;
|
||||
message->readPrimitive(&addToIgnore);
|
||||
while (message->getBytesLeftToRead()) {
|
||||
// parse out the UUID being ignored from the packet
|
||||
QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
addIgnoredNode(ignoredUUID);
|
||||
if (addToIgnore) {
|
||||
addIgnoredNode(ignoredUUID);
|
||||
} else {
|
||||
removeIgnoredNode(ignoredUUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::addIgnoredNode(const QUuid& otherNodeID) {
|
||||
if (!otherNodeID.isNull() && otherNodeID != _uuid) {
|
||||
QReadLocker lock { &_ignoredNodeIDSetLock };
|
||||
qCDebug(networking) << "Adding" << uuidStringWithoutCurlyBraces(otherNodeID) << "to ignore set for"
|
||||
<< uuidStringWithoutCurlyBraces(_uuid);
|
||||
|
||||
|
@ -102,6 +109,20 @@ void Node::addIgnoredNode(const QUuid& otherNodeID) {
|
|||
}
|
||||
}
|
||||
|
||||
void Node::removeIgnoredNode(const QUuid& otherNodeID) {
|
||||
if (!otherNodeID.isNull() && otherNodeID != _uuid) {
|
||||
// insert/find are read locked concurrently. unsafe_erase is not concurrent, and needs a write lock.
|
||||
QWriteLocker lock { &_ignoredNodeIDSetLock };
|
||||
qCDebug(networking) << "Removing" << uuidStringWithoutCurlyBraces(otherNodeID) << "from ignore set for"
|
||||
<< uuidStringWithoutCurlyBraces(_uuid);
|
||||
|
||||
// remove the session UUID from the set of ignored ones for this listening node
|
||||
_ignoredNodeIDSet.unsafe_erase(otherNodeID);
|
||||
} else {
|
||||
qCWarning(networking) << "Node::removeIgnoredNode called with null ID or ID of ignoring node.";
|
||||
}
|
||||
}
|
||||
|
||||
void Node::parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message) {
|
||||
bool enabled;
|
||||
message->readPrimitive(&enabled);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <QReadLocker>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <tbb/concurrent_unordered_set.h>
|
||||
|
@ -73,7 +74,8 @@ public:
|
|||
|
||||
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
void addIgnoredNode(const QUuid& otherNodeID);
|
||||
bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); }
|
||||
void removeIgnoredNode(const QUuid& otherNodeID);
|
||||
bool isIgnoringNodeWithID(const QUuid& nodeID) const { QReadLocker lock { &_ignoredNodeIDSetLock }; return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); }
|
||||
void parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
friend QDataStream& operator<<(QDataStream& out, const Node& node);
|
||||
|
@ -97,6 +99,7 @@ private:
|
|||
MovingPercentile _clockSkewMovingPercentile;
|
||||
NodePermissions _permissions;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDSet;
|
||||
mutable QReadWriteLock _ignoredNodeIDSetLock;
|
||||
|
||||
std::atomic_bool _ignoreRadiusEnabled;
|
||||
};
|
||||
|
|
|
@ -242,6 +242,10 @@ void NodeList::reset() {
|
|||
_ignoredSetLock.lockForWrite();
|
||||
_ignoredNodeIDs.clear();
|
||||
_ignoredSetLock.unlock();
|
||||
// lock and clear our set of personally muted IDs
|
||||
_personalMutedSetLock.lockForWrite();
|
||||
_personalMutedNodeIDs.clear();
|
||||
_personalMutedSetLock.unlock();
|
||||
|
||||
// refresh the owner UUID to the NULL UUID
|
||||
setSessionUUID(QUuid());
|
||||
|
@ -777,7 +781,7 @@ void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationN
|
|||
sendPacket(std::move(ignorePacket), *destinationNode);
|
||||
}
|
||||
|
||||
void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
|
||||
void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
|
||||
// enumerate the nodes to send a reliable ignore packet to each that can leverage it
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||
eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool {
|
||||
|
@ -786,25 +790,36 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
}, [&nodeID, this](const SharedNodePointer& destinationNode) {
|
||||
}, [&nodeID, ignoreEnabled, this](const SharedNodePointer& destinationNode) {
|
||||
// create a reliable NLPacket with space for the ignore UUID
|
||||
auto ignorePacket = NLPacket::create(PacketType::NodeIgnoreRequest, NUM_BYTES_RFC4122_UUID, true);
|
||||
auto ignorePacket = NLPacket::create(PacketType::NodeIgnoreRequest, NUM_BYTES_RFC4122_UUID + sizeof(bool), true);
|
||||
|
||||
ignorePacket->writePrimitive(ignoreEnabled);
|
||||
// write the node ID to the packet
|
||||
ignorePacket->write(nodeID.toRfc4122());
|
||||
|
||||
qCDebug(networking) << "Sending packet to ignore node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
qCDebug(networking) << "Sending packet to" << (destinationNode->getType() == NodeType::AudioMixer ? "AudioMixer" : "AvatarMixer") << "to"
|
||||
<< (ignoreEnabled ? "ignore" : "unignore") << "node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
|
||||
// send off this ignore packet reliably to the matching node
|
||||
sendPacket(std::move(ignorePacket), *destinationNode);
|
||||
});
|
||||
|
||||
QReadLocker setLocker { &_ignoredSetLock };
|
||||
|
||||
// add this nodeID to our set of ignored IDs
|
||||
_ignoredNodeIDs.insert(nodeID);
|
||||
|
||||
emit ignoredNode(nodeID);
|
||||
if (ignoreEnabled) {
|
||||
QReadLocker ignoredSetLocker{ &_ignoredSetLock }; // read lock for insert
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||
// add this nodeID to our set of ignored IDs
|
||||
_ignoredNodeIDs.insert(nodeID);
|
||||
// add this nodeID to our set of personal muted IDs
|
||||
_personalMutedNodeIDs.insert(nodeID);
|
||||
emit ignoredNode(nodeID, true);
|
||||
} else {
|
||||
QWriteLocker ignoredSetLocker{ &_ignoredSetLock }; // write lock for unsafe_erase
|
||||
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock }; // write lock for unsafe_erase
|
||||
_ignoredNodeIDs.unsafe_erase(nodeID);
|
||||
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
||||
emit ignoredNode(nodeID, false);
|
||||
}
|
||||
|
||||
} else {
|
||||
qWarning() << "NodeList::ignoreNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
|
||||
|
@ -812,21 +827,94 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
|
|||
}
|
||||
|
||||
bool NodeList::isIgnoringNode(const QUuid& nodeID) const {
|
||||
QReadLocker setLocker { &_ignoredSetLock };
|
||||
QReadLocker ignoredSetLocker{ &_ignoredSetLock };
|
||||
return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend();
|
||||
}
|
||||
|
||||
void NodeList::personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled) {
|
||||
// cannot personal mute yourself, or nobody
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||
auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
|
||||
if (audioMixer) {
|
||||
if (isIgnoringNode(nodeID)) {
|
||||
qCDebug(networking) << "You can't personally mute or unmute a node you're already ignoring.";
|
||||
}
|
||||
else {
|
||||
// setup the packet
|
||||
auto personalMutePacket = NLPacket::create(PacketType::NodeIgnoreRequest, NUM_BYTES_RFC4122_UUID + sizeof(bool), true);
|
||||
|
||||
personalMutePacket->writePrimitive(muteEnabled);
|
||||
// write the node ID to the packet
|
||||
personalMutePacket->write(nodeID.toRfc4122());
|
||||
|
||||
qCDebug(networking) << "Sending Personal Mute Packet to" << (muteEnabled ? "mute" : "unmute") << "node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
|
||||
sendPacket(std::move(personalMutePacket), *audioMixer);
|
||||
|
||||
|
||||
if (muteEnabled) {
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||
// add this nodeID to our set of personal muted IDs
|
||||
_personalMutedNodeIDs.insert(nodeID);
|
||||
} else {
|
||||
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock }; // write lock for unsafe_erase
|
||||
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Couldn't find audio mixer to send node personal mute request";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "NodeList::personalMuteNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeList::isPersonalMutingNode(const QUuid& nodeID) const {
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock };
|
||||
return _personalMutedNodeIDs.find(nodeID) != _personalMutedNodeIDs.cend();
|
||||
}
|
||||
|
||||
void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
|
||||
if (newNode->getType() == NodeType::AudioMixer || newNode->getType() == NodeType::AvatarMixer) {
|
||||
if (newNode->getType() == NodeType::AudioMixer) {
|
||||
// this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session,
|
||||
// so send that list along now (assuming it isn't empty)
|
||||
|
||||
QReadLocker setLocker { &_ignoredSetLock };
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock };
|
||||
|
||||
if (_personalMutedNodeIDs.size() > 0) {
|
||||
// setup a packet list so we can send the stream of ignore IDs
|
||||
auto personalMutePacketList = NLPacketList::create(PacketType::NodeIgnoreRequest, QByteArray(), true);
|
||||
|
||||
// Force the "enabled" flag in this packet to true
|
||||
personalMutePacketList->writePrimitive(true);
|
||||
|
||||
// enumerate the ignored IDs and write them to the packet list
|
||||
auto it = _personalMutedNodeIDs.cbegin();
|
||||
while (it != _personalMutedNodeIDs.end()) {
|
||||
personalMutePacketList->write(it->toRfc4122());
|
||||
++it;
|
||||
}
|
||||
|
||||
// send this NLPacketList to the new node
|
||||
sendPacketList(std::move(personalMutePacketList), *newNode);
|
||||
}
|
||||
|
||||
// also send them the current ignore radius state.
|
||||
sendIgnoreRadiusStateToNode(newNode);
|
||||
}
|
||||
if (newNode->getType() == NodeType::AvatarMixer) {
|
||||
// this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session,
|
||||
// so send that list along now (assuming it isn't empty)
|
||||
|
||||
QReadLocker ignoredSetLocker{ &_ignoredSetLock };
|
||||
|
||||
if (_ignoredNodeIDs.size() > 0) {
|
||||
// setup a packet list so we can send the stream of ignore IDs
|
||||
auto ignorePacketList = NLPacketList::create(PacketType::NodeIgnoreRequest, QByteArray(), true);
|
||||
|
||||
// Force the "enabled" flag in this packet to true
|
||||
ignorePacketList->writePrimitive(true);
|
||||
|
||||
// enumerate the ignored IDs and write them to the packet list
|
||||
auto it = _ignoredNodeIDs.cbegin();
|
||||
while (it != _ignoredNodeIDs.end()) {
|
||||
|
@ -930,3 +1018,18 @@ void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> messag
|
|||
|
||||
emit usernameFromIDReply(nodeUUIDString, username, machineFingerprintString);
|
||||
}
|
||||
|
||||
void NodeList::setRequestsDomainListData(bool isRequesting) {
|
||||
// Tell the avatar mixer whether I want to receive any additional data to which I might be entitled
|
||||
if (_requestsDomainListData == isRequesting) {
|
||||
return;
|
||||
}
|
||||
eachMatchingNode([](const SharedNodePointer& node)->bool {
|
||||
return node->getType() == NodeType::AvatarMixer;
|
||||
}, [this, isRequesting](const SharedNodePointer& destinationNode) {
|
||||
auto packet = NLPacket::create(PacketType::RequestsDomainListData, sizeof(bool), true); // reliable
|
||||
packet->writePrimitive(isRequesting);
|
||||
sendPacket(std::move(packet), *destinationNode);
|
||||
});
|
||||
_requestsDomainListData = isRequesting;
|
||||
}
|
||||
|
|
|
@ -76,12 +76,16 @@ public:
|
|||
void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); }
|
||||
void enableIgnoreRadius() { ignoreNodesInRadius(true); }
|
||||
void disableIgnoreRadius() { ignoreNodesInRadius(false); }
|
||||
void ignoreNodeBySessionID(const QUuid& nodeID);
|
||||
void ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled);
|
||||
bool isIgnoringNode(const QUuid& nodeID) const;
|
||||
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
|
||||
bool isPersonalMutingNode(const QUuid& nodeID) const;
|
||||
|
||||
void kickNodeBySessionID(const QUuid& nodeID);
|
||||
void muteNodeBySessionID(const QUuid& nodeID);
|
||||
void requestUsernameFromSessionID(const QUuid& nodeID);
|
||||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool isRequesting);
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
|
@ -109,7 +113,7 @@ public slots:
|
|||
signals:
|
||||
void limitOfSilentDomainCheckInsReached();
|
||||
void receivedDomainServerList();
|
||||
void ignoredNode(const QUuid& nodeID);
|
||||
void ignoredNode(const QUuid& nodeID, bool enabled);
|
||||
void ignoreRadiusEnabledChanged(bool isIgnored);
|
||||
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
|
||||
|
||||
|
@ -153,9 +157,12 @@ private:
|
|||
HifiSockAddr _assignmentServerSocket;
|
||||
bool _isShuttingDown { false };
|
||||
QTimer _keepAlivePingTimer;
|
||||
bool _requestsDomainListData;
|
||||
|
||||
mutable QReadWriteLock _ignoredSetLock;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
||||
mutable QReadWriteLock _personalMutedSetLock;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _personalMutedNodeIDs;
|
||||
|
||||
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
|
||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
|
||||
|
|
|
@ -53,7 +53,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SessionDisplayName);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::Unignore);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
|
|
|
@ -104,7 +104,8 @@ public:
|
|||
UsernameFromIDRequest,
|
||||
UsernameFromIDReply,
|
||||
ViewFrustum,
|
||||
LAST_PACKET_TYPE = ViewFrustum
|
||||
RequestsDomainListData,
|
||||
LAST_PACKET_TYPE = RequestsDomainListData
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -207,7 +208,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
SensorToWorldMat,
|
||||
HandControllerJoints,
|
||||
HasKillAvatarReason,
|
||||
SessionDisplayName
|
||||
SessionDisplayName,
|
||||
Unignore
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
@ -242,6 +244,7 @@ enum class AudioVersion : PacketVersion {
|
|||
Exactly10msAudioPackets,
|
||||
TerminatingStreamStats,
|
||||
SpaceBubbleChanges,
|
||||
HasPersonalMute,
|
||||
};
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -48,49 +48,19 @@ using namespace render;
|
|||
extern void initOverlay3DPipelines(render::ShapePlumber& plumber);
|
||||
extern void initDeferredPipelines(render::ShapePlumber& plumber);
|
||||
|
||||
RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
||||
cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&){ return true; };
|
||||
|
||||
RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
|
||||
// Prepare the ShapePipelines
|
||||
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
|
||||
initDeferredPipelines(*shapePlumber);
|
||||
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
auto spatialFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
|
||||
const auto spatialSelection = addJob<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
|
||||
const auto culledSpatialSelection = addJob<CullSpatialSelection>("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter);
|
||||
|
||||
// Overlays are not culled
|
||||
const auto nonspatialSelection = addJob<FetchNonspatialItems>("FetchOverlaySelection");
|
||||
|
||||
// Multi filter visible items into different buckets
|
||||
const int NUM_FILTERS = 3;
|
||||
const int OPAQUE_SHAPE_BUCKET = 0;
|
||||
const int TRANSPARENT_SHAPE_BUCKET = 1;
|
||||
const int LIGHT_BUCKET = 2;
|
||||
const int BACKGROUND_BUCKET = 2;
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray spatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::light()
|
||||
} };
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray nonspatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::background()
|
||||
} };
|
||||
const auto filteredSpatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
const auto filteredNonspatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
|
||||
// Extract / Sort opaques / Transparents / Lights / Overlays
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto lights = filteredSpatialBuckets[LIGHT_BUCKET];
|
||||
|
||||
const auto overlayOpaques = addJob<DepthSortItems>("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET];
|
||||
// Extract opaques / transparents / lights / overlays
|
||||
const auto opaques = items[0];
|
||||
const auto transparents = items[1];
|
||||
const auto lights = items[2];
|
||||
const auto overlayOpaques = items[3];
|
||||
const auto overlayTransparents = items[4];
|
||||
const auto background = items[5];
|
||||
const auto spatialSelection = items[6];
|
||||
|
||||
// Prepare deferred, generate the shared Deferred Frame Transform
|
||||
const auto deferredFrameTransform = addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform");
|
||||
|
@ -200,7 +170,7 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
addJob<DebugAmbientOcclusion>("DebugAmbientOcclusion", debugAmbientOcclusionInputs);
|
||||
|
||||
|
||||
// Scene Octree Debuging job
|
||||
// Scene Octree Debugging job
|
||||
{
|
||||
addJob<DrawSceneOctree>("DrawSceneOctree", spatialSelection);
|
||||
addJob<DrawItemSelection>("DrawItemSelection", spatialSelection);
|
||||
|
@ -228,26 +198,6 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
|
||||
}
|
||||
|
||||
void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
// sanity checks
|
||||
assert(sceneContext);
|
||||
if (!sceneContext->_scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Is it possible that we render without a viewFrustum ?
|
||||
if (!(renderContext->args && renderContext->args->hasViewFrustum())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
|
||||
|
||||
for (auto job : _jobs) {
|
||||
job.run(sceneContext, renderContext);
|
||||
}
|
||||
}
|
||||
|
||||
void BeginGPURangeTimer::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) {
|
||||
timer = _gpuTimer;
|
||||
gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) {
|
||||
|
|
|
@ -13,18 +13,18 @@
|
|||
#define hifi_RenderDeferredTask_h
|
||||
|
||||
#include <gpu/Pipeline.h>
|
||||
#include <render/CullTask.h>
|
||||
#include <render/RenderFetchCullSortTask.h>
|
||||
#include "LightingModel.h"
|
||||
|
||||
|
||||
class BeginGPURangeTimer {
|
||||
public:
|
||||
using JobModel = render::Job::ModelO<BeginGPURangeTimer, gpu::RangeTimerPointer>;
|
||||
|
||||
|
||||
BeginGPURangeTimer(const std::string& name) : _gpuTimer(std::make_shared<gpu::RangeTimer>(name)) {}
|
||||
|
||||
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer);
|
||||
|
||||
|
||||
protected:
|
||||
gpu::RangeTimerPointer _gpuTimer;
|
||||
};
|
||||
|
@ -35,12 +35,12 @@ class EndGPURangeTimer {
|
|||
public:
|
||||
using Config = GPURangeTimerConfig;
|
||||
using JobModel = render::Job::ModelI<EndGPURangeTimer, gpu::RangeTimerPointer, Config>;
|
||||
|
||||
|
||||
EndGPURangeTimer() {}
|
||||
|
||||
|
||||
void configure(const Config& config) {}
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer);
|
||||
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
|
@ -192,20 +192,11 @@ public:
|
|||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer);
|
||||
};
|
||||
|
||||
using RenderDeferredTaskConfig = render::GPUTaskConfig;
|
||||
|
||||
class RenderDeferredTask : public render::Task {
|
||||
public:
|
||||
using Config = RenderDeferredTaskConfig;
|
||||
RenderDeferredTask(render::CullFunctor cullFunctor);
|
||||
using JobModel = Model<RenderDeferredTask>;
|
||||
|
||||
void configure(const Config& config) {}
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||
|
||||
using JobModel = Model<RenderDeferredTask, Config>;
|
||||
|
||||
protected:
|
||||
gpu::RangeTimerPointer _gpuTimer;
|
||||
RenderDeferredTask(RenderFetchCullSortTask::Output items);
|
||||
};
|
||||
|
||||
#endif // hifi_RenderDeferredTask_h
|
||||
|
|
|
@ -19,9 +19,6 @@
|
|||
#include <ViewFrustum.h>
|
||||
#include <gpu/Context.h>
|
||||
|
||||
#include <render/CullTask.h>
|
||||
#include <render/SortTask.h>
|
||||
|
||||
#include "FramebufferCache.h"
|
||||
#include "TextureCache.h"
|
||||
|
||||
|
@ -32,45 +29,14 @@
|
|||
|
||||
using namespace render;
|
||||
|
||||
RenderForwardTask::RenderForwardTask(CullFunctor cullFunctor) {
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
const auto spatialSelection = addJob<FetchSpatialTree>("FetchSceneSelection");
|
||||
|
||||
cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&){ return true; };
|
||||
auto spatialFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
|
||||
const auto culledSpatialSelection = addJob<CullSpatialSelection>("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter);
|
||||
|
||||
// Overlays are not culled
|
||||
const auto nonspatialSelection = addJob<FetchNonspatialItems>("FetchOverlaySelection");
|
||||
|
||||
// Multi filter visible items into different buckets
|
||||
const int NUM_FILTERS = 3;
|
||||
const int OPAQUE_SHAPE_BUCKET = 0;
|
||||
const int TRANSPARENT_SHAPE_BUCKET = 1;
|
||||
const int LIGHT_BUCKET = 2;
|
||||
const int BACKGROUND_BUCKET = 2;
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray spatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::light()
|
||||
} };
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray nonspatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::background()
|
||||
} };
|
||||
const auto filteredSpatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
const auto filteredNonspatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
|
||||
// Extract / Sort opaques / Transparents / Lights / Overlays
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto lights = filteredSpatialBuckets[LIGHT_BUCKET];
|
||||
|
||||
const auto overlayOpaques = addJob<DepthSortItems>("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET];
|
||||
RenderForwardTask::RenderForwardTask(RenderFetchCullSortTask::Output items) {
|
||||
// Extract opaques / transparents / lights / overlays
|
||||
const auto opaques = items[0];
|
||||
const auto transparents = items[1];
|
||||
const auto lights = items[2];
|
||||
const auto overlayOpaques = items[3];
|
||||
const auto overlayTransparents = items[4];
|
||||
const auto background = items[5];
|
||||
|
||||
const auto framebuffer = addJob<PrepareFramebuffer>("PrepareFramebuffer");
|
||||
|
||||
|
@ -83,26 +49,6 @@ RenderForwardTask::RenderForwardTask(CullFunctor cullFunctor) {
|
|||
addJob<Blit>("Blit", framebuffer);
|
||||
}
|
||||
|
||||
void RenderForwardTask::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
// sanity checks
|
||||
assert(sceneContext);
|
||||
if (!sceneContext->_scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Is it possible that we render without a viewFrustum ?
|
||||
if (!(renderContext->args && renderContext->args->hasViewFrustum())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
|
||||
|
||||
for (auto job : _jobs) {
|
||||
job.run(sceneContext, renderContext);
|
||||
}
|
||||
}
|
||||
|
||||
void PrepareFramebuffer::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, gpu::FramebufferPointer& framebuffer) {
|
||||
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||
auto framebufferSize = framebufferCache->getFrameBufferSize();
|
||||
|
|
|
@ -13,20 +13,14 @@
|
|||
#define hifi_RenderForwardTask_h
|
||||
|
||||
#include <gpu/Pipeline.h>
|
||||
#include <render/CullTask.h>
|
||||
#include <render/RenderFetchCullSortTask.h>
|
||||
#include "LightingModel.h"
|
||||
|
||||
using RenderForwardTaskConfig = render::GPUTaskConfig;
|
||||
|
||||
class RenderForwardTask : public render::Task {
|
||||
public:
|
||||
using Config = RenderForwardTaskConfig;
|
||||
RenderForwardTask(render::CullFunctor cullFunctor);
|
||||
using JobModel = Model<RenderForwardTask>;
|
||||
|
||||
void configure(const Config& config) {}
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||
|
||||
using JobModel = Model<RenderForwardTask, Config>;
|
||||
RenderForwardTask(RenderFetchCullSortTask::Output items);
|
||||
};
|
||||
|
||||
class PrepareFramebuffer {
|
||||
|
|
|
@ -115,6 +115,8 @@ RenderShadowTask::RenderShadowTask(CullFunctor cullFunctor) {
|
|||
skinProgram, state);
|
||||
}
|
||||
|
||||
const auto cachedMode = addJob<RenderShadowSetup>("Setup");
|
||||
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
auto shadowFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
|
||||
|
@ -127,6 +129,8 @@ RenderShadowTask::RenderShadowTask(CullFunctor cullFunctor) {
|
|||
|
||||
// GPU jobs: Render to shadow map
|
||||
addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber);
|
||||
|
||||
addJob<RenderShadowTeardown>("Teardown", cachedMode);
|
||||
}
|
||||
|
||||
void RenderShadowTask::configure(const Config& configuration) {
|
||||
|
@ -135,25 +139,13 @@ void RenderShadowTask::configure(const Config& configuration) {
|
|||
Task::configure(configuration);
|
||||
}
|
||||
|
||||
void RenderShadowTask::run(const SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) {
|
||||
assert(sceneContext);
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
// sanity checks
|
||||
if (!sceneContext->_scene || !args) {
|
||||
return;
|
||||
}
|
||||
|
||||
void RenderShadowSetup::run(const SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, Output& output) {
|
||||
auto lightStage = DependencyManager::get<DeferredLightingEffect>()->getLightStage();
|
||||
const auto globalShadow = lightStage->getShadow(0);
|
||||
|
||||
// If the global light is not set, bail
|
||||
if (!globalShadow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache old render args
|
||||
RenderArgs::RenderMode mode = args->_renderMode;
|
||||
RenderArgs* args = renderContext->args;
|
||||
output = args->_renderMode;
|
||||
|
||||
auto nearClip = args->getViewFrustum().getNearClip();
|
||||
float nearDepth = -args->_boomOffset.z;
|
||||
|
@ -163,14 +155,12 @@ void RenderShadowTask::run(const SceneContextPointer& sceneContext, const render
|
|||
// Set the keylight render args
|
||||
args->pushViewFrustum(*(globalShadow->getFrustum()));
|
||||
args->_renderMode = RenderArgs::SHADOW_RENDER_MODE;
|
||||
}
|
||||
|
||||
// TODO: Allow runtime manipulation of culling ShouldRenderFunctor
|
||||
|
||||
for (auto job : _jobs) {
|
||||
job.run(sceneContext, renderContext);
|
||||
}
|
||||
void RenderShadowTeardown::run(const SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Input& input) {
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
// Reset the render args
|
||||
args->popViewFrustum();
|
||||
args->_renderMode = mode;
|
||||
args->_renderMode = input;
|
||||
};
|
||||
|
|
|
@ -49,7 +49,20 @@ public:
|
|||
RenderShadowTask(render::CullFunctor shouldRender);
|
||||
|
||||
void configure(const Config& configuration);
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||
};
|
||||
|
||||
class RenderShadowSetup {
|
||||
public:
|
||||
using Output = RenderArgs::RenderMode;
|
||||
using JobModel = render::Job::ModelO<RenderShadowSetup, Output>;
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, Output& output);
|
||||
};
|
||||
|
||||
class RenderShadowTeardown {
|
||||
public:
|
||||
using Input = RenderArgs::RenderMode;
|
||||
using JobModel = render::Job::ModelI<RenderShadowTeardown, Input>;
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Input& input);
|
||||
};
|
||||
|
||||
#endif // hifi_RenderShadowTask_h
|
||||
|
|
|
@ -52,11 +52,3 @@ void Engine::load() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::run() {
|
||||
for (auto job : _jobs) {
|
||||
job.run(_sceneContext, _renderContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ namespace render {
|
|||
RenderContextPointer getRenderContext() const { return _renderContext; }
|
||||
|
||||
// Render a frame
|
||||
// A frame must have a scene registered and a context set to render
|
||||
void run();
|
||||
// Must have a scene registered and a context set
|
||||
void run() { assert(_sceneContext && _renderContext); Task::run(_sceneContext, _renderContext); }
|
||||
|
||||
protected:
|
||||
SceneContextPointer _sceneContext;
|
||||
|
|
61
libraries/render/src/render/RenderFetchCullSortTask.cpp
Normal file
61
libraries/render/src/render/RenderFetchCullSortTask.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// RenderFetchCullSortTask.cpp
|
||||
// render/src/
|
||||
//
|
||||
// Created by Zach Pomerantz on 12/22/2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RenderFetchCullSortTask.h"
|
||||
|
||||
#include "CullTask.h"
|
||||
#include "SortTask.h"
|
||||
|
||||
using namespace render;
|
||||
|
||||
RenderFetchCullSortTask::RenderFetchCullSortTask(CullFunctor cullFunctor) {
|
||||
cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&){ return true; };
|
||||
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
auto spatialFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
|
||||
const auto spatialSelection = addJob<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
|
||||
const auto culledSpatialSelection = addJob<CullSpatialSelection>("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter);
|
||||
|
||||
// Overlays are not culled
|
||||
const auto nonspatialSelection = addJob<FetchNonspatialItems>("FetchOverlaySelection");
|
||||
|
||||
// Multi filter visible items into different buckets
|
||||
const int NUM_FILTERS = 3;
|
||||
const int OPAQUE_SHAPE_BUCKET = 0;
|
||||
const int TRANSPARENT_SHAPE_BUCKET = 1;
|
||||
const int LIGHT_BUCKET = 2;
|
||||
const int BACKGROUND_BUCKET = 2;
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray spatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::light()
|
||||
} };
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray nonspatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::background()
|
||||
} };
|
||||
const auto filteredSpatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
const auto filteredNonspatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
|
||||
// Extract opaques / transparents / lights / overlays
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto lights = filteredSpatialBuckets[LIGHT_BUCKET];
|
||||
|
||||
const auto overlayOpaques = addJob<DepthSortItems>("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET];
|
||||
|
||||
setOutput(Output{{
|
||||
opaques, transparents, lights, overlayOpaques, overlayTransparents, background, spatialSelection }});
|
||||
}
|
28
libraries/render/src/render/RenderFetchCullSortTask.h
Normal file
28
libraries/render/src/render/RenderFetchCullSortTask.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// RenderFetchCullSortTask.h
|
||||
// render/src/
|
||||
//
|
||||
// Created by Zach Pomerantz on 12/22/2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_RenderFetchCullSortTask_h
|
||||
#define hifi_RenderFetchCullSortTask_h
|
||||
|
||||
#include <gpu/Pipeline.h>
|
||||
|
||||
#include "Task.h"
|
||||
#include "CullTask.h"
|
||||
|
||||
class RenderFetchCullSortTask : public render::Task {
|
||||
public:
|
||||
using Output = std::array<render::Varying, 7>;
|
||||
using JobModel = ModelO<RenderFetchCullSortTask>;
|
||||
|
||||
RenderFetchCullSortTask(render::CullFunctor cullFunctor);
|
||||
};
|
||||
|
||||
#endif // hifi_RenderFetchCullSortTask_h
|
|
@ -45,8 +45,9 @@ public:
|
|||
}
|
||||
template <class T> Varying(const T& data) : _concept(std::make_shared<Model<T>>(data)) {}
|
||||
|
||||
template <class T> T& edit() { return std::static_pointer_cast<Model<T>>(_concept)->_data; }
|
||||
template <class T> bool canCast() const { return !!std::dynamic_pointer_cast<Model<T>>(_concept); }
|
||||
template <class T> const T& get() const { return std::static_pointer_cast<const Model<T>>(_concept)->_data; }
|
||||
template <class T> T& edit() { return std::static_pointer_cast<Model<T>>(_concept)->_data; }
|
||||
|
||||
|
||||
// access potential sub varyings contained in this one.
|
||||
|
@ -440,6 +441,9 @@ template <class T, class C> void jobConfigure(T& data, const C& configuration) {
|
|||
template<class T> void jobConfigure(T&, const JobConfig&) {
|
||||
// nop, as the default JobConfig was used, so the data does not need a configure method
|
||||
}
|
||||
template<class T> void jobConfigure(T&, const TaskConfig&) {
|
||||
// nop, as the default TaskConfig was used, so the data does not need a configure method
|
||||
}
|
||||
template <class T> void jobRun(T& data, const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const JobNoIO& input, JobNoIO& output) {
|
||||
data.run(sceneContext, renderContext);
|
||||
}
|
||||
|
@ -591,24 +595,20 @@ class Task {
|
|||
public:
|
||||
using Config = TaskConfig;
|
||||
using QConfigPointer = Job::QConfigPointer;
|
||||
using None = Job::None;
|
||||
|
||||
template <class T, class C = Config, class I = None, class O = None> class Model : public Job::Concept {
|
||||
template <class T, class C = Config> class Model : public Job::Concept {
|
||||
public:
|
||||
using Data = T;
|
||||
using Input = I;
|
||||
using Output = O;
|
||||
using Config = C;
|
||||
using Input = Job::None;
|
||||
|
||||
Data _data;
|
||||
Varying _input;
|
||||
Varying _output;
|
||||
|
||||
const Varying getInput() const override { return _input; }
|
||||
const Varying getOutput() const override { return _output; }
|
||||
const Varying getOutput() const override { return _data._output; }
|
||||
|
||||
template <class... A>
|
||||
Model(const Varying& input, A&&... args) :
|
||||
Concept(nullptr), _data(Data(std::forward<A>(args)...)), _input(input), _output(Output()) {
|
||||
Concept(nullptr), _data(Data(std::forward<A>(args)...)) {
|
||||
// Recreate the Config to use the templated type
|
||||
_data.template createConfiguration<C>();
|
||||
_config = _data.getConfiguration();
|
||||
|
@ -620,16 +620,15 @@ public:
|
|||
}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) override {
|
||||
renderContext->jobConfig = std::static_pointer_cast<Config>(_config);
|
||||
if (renderContext->jobConfig->alwaysEnabled || renderContext->jobConfig->enabled) {
|
||||
jobRun(_data, sceneContext, renderContext, _input.get<I>(), _output.edit<O>());
|
||||
auto config = std::static_pointer_cast<Config>(_config);
|
||||
if (config->alwaysEnabled || config->enabled) {
|
||||
for (auto job : _data._jobs) {
|
||||
job.run(sceneContext, renderContext);
|
||||
}
|
||||
}
|
||||
renderContext->jobConfig.reset();
|
||||
}
|
||||
};
|
||||
template <class T, class I, class C = Config> using ModelI = Model<T, C, I, None>;
|
||||
template <class T, class O, class C = Config> using ModelO = Model<T, C, None, O>;
|
||||
template <class T, class I, class O, class C = Config> using ModelIO = Model<T, C, I, O>;
|
||||
template <class T, class C = Config> using ModelO = Model<T, C>;
|
||||
|
||||
using Jobs = std::vector<Job>;
|
||||
|
||||
|
@ -655,6 +654,10 @@ public:
|
|||
return addJob<T>(name, input, std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
template <class O> void setOutput(O&& output) {
|
||||
_output = Varying(output);
|
||||
}
|
||||
|
||||
template <class C> void createConfiguration() {
|
||||
auto config = std::make_shared<C>();
|
||||
if (_config) {
|
||||
|
@ -688,11 +691,18 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
for (auto job : _jobs) {
|
||||
job.run(sceneContext, renderContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
template <class T, class C, class I, class O> friend class Model;
|
||||
template <class T, class C> friend class Model;
|
||||
|
||||
QConfigPointer _config;
|
||||
Jobs _jobs;
|
||||
Varying _output;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -908,19 +908,11 @@ void ScriptEngine::run() {
|
|||
break;
|
||||
}
|
||||
|
||||
// determine how long before the next timer should fire, we'd ideally like to sleep just
|
||||
// that long, so the next processEvents() will allow the timers to fire on time.
|
||||
const std::chrono::microseconds minTimerTimeRemaining(USECS_PER_MSEC * getTimersRemainingTime());
|
||||
|
||||
// However, if we haven't yet slept at least as long as our average timer per frame, then we will
|
||||
// punish the timers to at least wait as long as the average run time of the timers.
|
||||
auto untilTimer = std::max(minTimerTimeRemaining, averageTimerPerFrame);
|
||||
|
||||
// choose the closest time point, our
|
||||
auto remainingSleepUntil = std::chrono::duration_cast<std::chrono::microseconds>(sleepUntil - clock::now());
|
||||
auto closestUntil = std::min(remainingSleepUntil, untilTimer);
|
||||
auto thisSleepUntil = std::min(sleepUntil, clock::now() + closestUntil);
|
||||
std::this_thread::sleep_until(thisSleepUntil);
|
||||
// We only want to sleep a small amount so that any pending events (like timers or invokeMethod events)
|
||||
// will be able to process quickly.
|
||||
static const int SMALL_SLEEP_AMOUNT = 100;
|
||||
auto smallSleepUntil = clock::now() + static_cast<std::chrono::microseconds>(SMALL_SLEEP_AMOUNT);
|
||||
std::this_thread::sleep_until(smallSleepUntil);
|
||||
}
|
||||
|
||||
#ifdef SCRIPT_DELAY_DEBUG
|
||||
|
@ -1010,21 +1002,6 @@ void ScriptEngine::run() {
|
|||
emit doneRunning();
|
||||
}
|
||||
|
||||
quint64 ScriptEngine::getTimersRemainingTime() {
|
||||
quint64 minimumTime = USECS_PER_SECOND; // anything larger than this can be ignored
|
||||
QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
QTimer* timer = i.key();
|
||||
int remainingTime = timer->remainingTime();
|
||||
if (remainingTime >= 0) {
|
||||
minimumTime = std::min((quint64)remainingTime, minimumTime);
|
||||
}
|
||||
}
|
||||
return minimumTime;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
||||
// we want to only call it in our own run "shutdown" processing.
|
||||
void ScriptEngine::stopAllTimers() {
|
||||
|
@ -1035,6 +1012,7 @@ void ScriptEngine::stopAllTimers() {
|
|||
stopTimer(timer);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) {
|
||||
// We could maintain a separate map of entityID => QTimer, but someone will have to prove to me that it's worth the complexity. -HRS
|
||||
QVector<QTimer*> toDelete;
|
||||
|
|
|
@ -218,7 +218,6 @@ protected:
|
|||
void init();
|
||||
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
quint64 getTimersRemainingTime();
|
||||
void timerFired();
|
||||
void stopAllTimers();
|
||||
void stopAllTimersForEntityScript(const EntityItemID& entityID);
|
||||
|
|
|
@ -21,9 +21,25 @@ UsersScriptingInterface::UsersScriptingInterface() {
|
|||
connect(nodeList.data(), &NodeList::usernameFromIDReply, this, &UsersScriptingInterface::usernameFromIDReply);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::ignore(const QUuid& nodeID) {
|
||||
void UsersScriptingInterface::ignore(const QUuid& nodeID, bool ignoreEnabled) {
|
||||
// ask the NodeList to ignore this user (based on the session ID of their node)
|
||||
DependencyManager::get<NodeList>()->ignoreNodeBySessionID(nodeID);
|
||||
DependencyManager::get<NodeList>()->ignoreNodeBySessionID(nodeID, ignoreEnabled);
|
||||
}
|
||||
|
||||
bool UsersScriptingInterface::getIgnoreStatus(const QUuid& nodeID) {
|
||||
// ask the NodeList for the Ignore status associated with the given session ID
|
||||
return DependencyManager::get<NodeList>()->isIgnoringNode(nodeID);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::personalMute(const QUuid& nodeID, bool muteEnabled) {
|
||||
// ask the NodeList to mute the user with the given session ID
|
||||
// "Personal Mute" only applies one way and is not global
|
||||
DependencyManager::get<NodeList>()->personalMuteNodeBySessionID(nodeID, muteEnabled);
|
||||
}
|
||||
|
||||
bool UsersScriptingInterface::getPersonalMuteStatus(const QUuid& nodeID) {
|
||||
// ask the NodeList for the Personal Mute status associated with the given session ID
|
||||
return DependencyManager::get<NodeList>()->isPersonalMutingNode(nodeID);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
||||
|
@ -61,3 +77,10 @@ void UsersScriptingInterface::disableIgnoreRadius() {
|
|||
bool UsersScriptingInterface::getIgnoreRadiusEnabled() {
|
||||
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
|
||||
}
|
||||
|
||||
bool UsersScriptingInterface::getRequestsDomainListData() {
|
||||
return DependencyManager::get<NodeList>()->getRequestsDomainListData();
|
||||
}
|
||||
void UsersScriptingInterface::setRequestsDomainListData(bool isRequesting) {
|
||||
DependencyManager::get<NodeList>()->setRequestsDomainListData(isRequesting);
|
||||
}
|
|
@ -24,6 +24,7 @@ class UsersScriptingInterface : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
Q_PROPERTY(bool canKick READ getCanKick)
|
||||
Q_PROPERTY(bool requestsDomainListData READ getRequestsDomainListData WRITE setRequestsDomainListData)
|
||||
|
||||
public:
|
||||
UsersScriptingInterface();
|
||||
|
@ -34,8 +35,31 @@ public slots:
|
|||
* Ignore another user.
|
||||
* @function Users.ignore
|
||||
* @param {nodeID} nodeID The node or session ID of the user you want to ignore.
|
||||
* @param {bool} enable True for ignored; false for un-ignored.
|
||||
*/
|
||||
void ignore(const QUuid& nodeID);
|
||||
void ignore(const QUuid& nodeID, bool ignoreEnabled);
|
||||
|
||||
/**jsdoc
|
||||
* Gets a bool containing whether you have ignored the given Avatar UUID.
|
||||
* @function Users.getIgnoreStatus
|
||||
* @param {nodeID} nodeID The node or session ID of the user whose ignore status you want.
|
||||
*/
|
||||
bool getIgnoreStatus(const QUuid& nodeID);
|
||||
|
||||
/**jsdoc
|
||||
* Mute another user for you and you only.
|
||||
* @function Users.personalMute
|
||||
* @param {nodeID} nodeID The node or session ID of the user you want to mute.
|
||||
* @param {bool} enable True for enabled; false for disabled.
|
||||
*/
|
||||
void personalMute(const QUuid& nodeID, bool muteEnabled);
|
||||
|
||||
/**jsdoc
|
||||
* Requests a bool containing whether you have personally muted the given Avatar UUID.
|
||||
* @function Users.requestPersonalMuteStatus
|
||||
* @param {nodeID} nodeID The node or session ID of the user whose personal mute status you want.
|
||||
*/
|
||||
bool getPersonalMuteStatus(const QUuid& nodeID);
|
||||
|
||||
/**jsdoc
|
||||
* Kick another user.
|
||||
|
@ -45,7 +69,7 @@ public slots:
|
|||
void kick(const QUuid& nodeID);
|
||||
|
||||
/**jsdoc
|
||||
* Mute another user.
|
||||
* Mute another user for everyone.
|
||||
* @function Users.mute
|
||||
* @param {nodeID} nodeID The node or session ID of the user you want to mute.
|
||||
*/
|
||||
|
@ -105,6 +129,11 @@ signals:
|
|||
* @function Users.usernameFromIDReply
|
||||
*/
|
||||
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
|
||||
|
||||
private:
|
||||
bool getRequestsDomainListData();
|
||||
void setRequestsDomainListData(bool requests);
|
||||
bool _requestsDomainListData;
|
||||
};
|
||||
|
||||
|
||||
|
|
244
scripts/developer/tests/testInterval.js
Normal file
244
scripts/developer/tests/testInterval.js
Normal file
|
@ -0,0 +1,244 @@
|
|||
// Tester, try testing these different settings.
|
||||
//
|
||||
// Changing the TIMER_HZ will show different performance results. It's expected that at 90hz you'll see a fair
|
||||
// amount of variance, as Qt Timers simply aren't accurate enough. In general RPC peformance should match the timer
|
||||
// without significant difference in variance.
|
||||
|
||||
var TIMER_HZ = 50; // Change this for different values
|
||||
var TIMER_INTERVAL = 1000 / TIMER_HZ;
|
||||
var TIMER_WORK_EFFORT = 0; // 1000 is light work, 1000000 ~= 30ms
|
||||
|
||||
var UPDATE_HZ = 60; // standard script update rate
|
||||
var UPDATE_INTERVAL = 1000/UPDATE_HZ; // standard script update interval
|
||||
var UPDATE_WORK_EFFORT = 0; // 1000 is light work, 1000000 ~= 30ms
|
||||
|
||||
var basePosition = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
|
||||
var timerBox = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: basePosition,
|
||||
dimensions: { x: 0.1, y: 0.1, z: 0.1 },
|
||||
color: { red: 255, green: 0, blue: 255 },
|
||||
dynamic: false,
|
||||
collisionless: true
|
||||
});
|
||||
|
||||
var lastTick = Date.now();
|
||||
var deltaTick = 0;
|
||||
var tickSamples = 0;
|
||||
var totalVariance = 0;
|
||||
var tickCount = 0;
|
||||
var totalWork = 0;
|
||||
var highVarianceCount = 0;
|
||||
var varianceCount = 0;
|
||||
|
||||
print("Set interval = " + TIMER_INTERVAL);
|
||||
|
||||
var timerTime = 0.0;
|
||||
var range = 0.5;
|
||||
var rotationFactor = 0.5; // smaller == faster
|
||||
var omega = 2.0 * Math.PI / rotationFactor;
|
||||
|
||||
var ticker = Script.setInterval(function() {
|
||||
|
||||
tickCount++;
|
||||
var tickNow = Date.now();
|
||||
deltaTick = tickNow - lastTick;
|
||||
|
||||
var variance = Math.abs(deltaTick - TIMER_INTERVAL);
|
||||
totalVariance += variance;
|
||||
|
||||
if (variance > 1) {
|
||||
varianceCount++;
|
||||
}
|
||||
if (variance > 5) {
|
||||
highVarianceCount++;
|
||||
}
|
||||
|
||||
var preWork = Date.now();
|
||||
var y = 2;
|
||||
for (var x = 0; x < TIMER_WORK_EFFORT; x++) {
|
||||
y = y * y;
|
||||
}
|
||||
var postWork = Date.now();
|
||||
deltaWork = postWork - preWork;
|
||||
totalWork += deltaWork;
|
||||
|
||||
// move a box
|
||||
var deltaTime = deltaTick / 1000;
|
||||
timerTime += deltaTime;
|
||||
rotation = Quat.angleAxis(timerTime * omega / Math.PI * 180.0, { x: 0, y: 1, z: 0 });
|
||||
Entities.editEntity(timerBox,
|
||||
{
|
||||
position: { x: basePosition.x + Math.sin(timerTime * omega) / 2.0 * range,
|
||||
y: basePosition.y,
|
||||
z: basePosition.z },
|
||||
//rotation: rotation
|
||||
});
|
||||
|
||||
tickSamples = tickSamples + deltaTick;
|
||||
lastTick = tickNow;
|
||||
|
||||
// report about every 5 seconds
|
||||
if(tickCount == (TIMER_HZ * 5)) {
|
||||
print("TIMER -- For " + tickCount + " samples average interval = " + tickSamples/tickCount + " ms"
|
||||
+ " average variance:" + totalVariance/tickCount + " ms"
|
||||
+ " min variance:" + varianceCount + " [" + (varianceCount/tickCount) * 100 + " %] "
|
||||
+ " high variance:" + highVarianceCount + " [" + (highVarianceCount/tickCount) * 100 + " %] "
|
||||
+ " average work:" + totalWork/tickCount + " ms");
|
||||
tickCount = 0;
|
||||
tickSamples = 0;
|
||||
totalWork = 0;
|
||||
totalVariance = 0;
|
||||
varianceCount = 0;
|
||||
highVarianceCount = 0;
|
||||
}
|
||||
}, TIMER_INTERVAL);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var rpcPosition = Vec3.sum(basePosition, { x:0, y: 0.2, z: 0});
|
||||
|
||||
var theRpcFunctionInclude = Script.resolvePath("testIntervalRpcFunction.js");
|
||||
|
||||
print("theRpcFunctionInclude:" + theRpcFunctionInclude);
|
||||
|
||||
var rpcBox = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: rpcPosition,
|
||||
dimensions: { x: 0.1, y: 0.1, z: 0.1 },
|
||||
color: { red: 255, green: 255, blue: 0 },
|
||||
dynamic: false,
|
||||
collisionless: true,
|
||||
script: theRpcFunctionInclude
|
||||
});
|
||||
|
||||
var rpcLastTick = Date.now();
|
||||
var rpcTotalTicks = 0;
|
||||
var rpcCount = 0;
|
||||
var rpcTotalVariance = 0;
|
||||
var rpcTickCount = 0;
|
||||
var rpcVarianceCount = 0;
|
||||
var rpcHighVarianceCount = 0;
|
||||
|
||||
var rpcTicker = Script.setInterval(function() {
|
||||
|
||||
rpcTickCount++;
|
||||
var tickNow = Date.now();
|
||||
var deltaTick = tickNow - rpcLastTick;
|
||||
var variance = Math.abs(deltaTick - TIMER_INTERVAL);
|
||||
rpcTotalVariance += variance;
|
||||
|
||||
if (variance > 1) {
|
||||
rpcVarianceCount++;
|
||||
}
|
||||
if (variance > 5) {
|
||||
rpcHighVarianceCount++;
|
||||
}
|
||||
|
||||
rpcTotalTicks += deltaTick;
|
||||
rpcLastTick = tickNow;
|
||||
|
||||
var args = [range, rotationFactor, omega, TIMER_INTERVAL, TIMER_HZ];
|
||||
|
||||
Entities.callEntityMethod(rpcBox, "doRPC", args);
|
||||
|
||||
// report about every 5 seconds
|
||||
if(rpcTickCount == (TIMER_HZ * 5)) {
|
||||
print("RPCTIMER- For " + rpcTickCount + " samples average interval = " + rpcTotalTicks/rpcTickCount + " ms"
|
||||
+ " average variance:" + rpcTotalVariance/rpcTickCount + " ms"
|
||||
+ " min variance:" + rpcVarianceCount + " [" + (rpcVarianceCount/rpcTickCount) * 100 + " %] "
|
||||
+ " high variance:" + rpcHighVarianceCount + " [" + (rpcHighVarianceCount/rpcTickCount) * 100 + " %] "
|
||||
);
|
||||
rpcTickCount = 0;
|
||||
rpcTotalTicks = 0;
|
||||
rpcTotalVariance = 0;
|
||||
rpcVarianceCount = 0;
|
||||
rpcHighVarianceCount = 0;
|
||||
}
|
||||
}, TIMER_INTERVAL);
|
||||
|
||||
|
||||
var updateCount = 0;
|
||||
var updateTotalElapsed = 0;
|
||||
var lastUpdate = Date.now();
|
||||
var updateTotalWork = 0;
|
||||
var updateTotalVariance = 0;
|
||||
var updateVarianceCount = 0;
|
||||
var updateHighVarianceCount = 0;
|
||||
|
||||
var updatePosition = Vec3.sum(basePosition, { x:0, y: -0.2, z: 0});
|
||||
|
||||
var updateBox = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: updatePosition,
|
||||
dimensions: { x: 0.1, y: 0.1, z: 0.1 },
|
||||
color: { red: 0, green: 255, blue: 255 },
|
||||
dynamic: false,
|
||||
collisionless: true
|
||||
});
|
||||
|
||||
|
||||
var updateTime = 0;
|
||||
var updateFunction = function(deltaTime){
|
||||
updateCount++;
|
||||
var updateAt = Date.now();
|
||||
deltaUpdate = updateAt - lastUpdate;
|
||||
updateTotalElapsed += deltaUpdate;
|
||||
lastUpdate = updateAt;
|
||||
|
||||
var variance = Math.abs(deltaUpdate - UPDATE_INTERVAL);
|
||||
updateTotalVariance += variance;
|
||||
|
||||
if (variance > 1) {
|
||||
updateVarianceCount++;
|
||||
}
|
||||
|
||||
if (variance > 5) {
|
||||
updateHighVarianceCount++;
|
||||
}
|
||||
|
||||
var preWork = Date.now();
|
||||
var y = 2;
|
||||
for (var x = 0; x < UPDATE_WORK_EFFORT; x++) {
|
||||
y = y * y;
|
||||
}
|
||||
var postWork = Date.now();
|
||||
deltaWork = postWork - preWork;
|
||||
updateTotalWork += deltaWork;
|
||||
|
||||
// move a box
|
||||
|
||||
updateTime += deltaTime;
|
||||
rotation = Quat.angleAxis(updateTime * omega / Math.PI * 180.0, { x: 0, y: 1, z: 0 });
|
||||
Entities.editEntity(updateBox,
|
||||
{
|
||||
position: { x: updatePosition.x + Math.sin(updateTime * omega) / 2.0 * range,
|
||||
y: updatePosition.y,
|
||||
z: updatePosition.z },
|
||||
});
|
||||
|
||||
|
||||
if(updateCount == (UPDATE_HZ * 5)) {
|
||||
print("UPDATE -- For " + updateCount + " samples average update = " + updateTotalElapsed/updateCount + " ms"
|
||||
+ " average variance:" + updateTotalVariance/updateCount + " ms"
|
||||
+ " min variance:" + updateVarianceCount + " [" + (updateVarianceCount/updateCount) * 100 + " %] "
|
||||
+ " high variance:" + updateHighVarianceCount + " [" + (updateHighVarianceCount/updateCount) * 100 + " %] "
|
||||
+ " average work:" + updateTotalWork/updateCount + " ms");
|
||||
|
||||
updateCount = 0;
|
||||
updateTotalElapsed = 0;
|
||||
updateTotalWork = 0;
|
||||
updateTotalVariance = 0;
|
||||
updateVarianceCount = 0;
|
||||
updateHighVarianceCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
Script.update.connect(updateFunction);
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
Entities.deleteEntity(timerBox);
|
||||
Entities.deleteEntity(rpcBox);
|
||||
Entities.deleteEntity(updateBox);
|
||||
});
|
77
scripts/developer/tests/testIntervalRpcFunction.js
Normal file
77
scripts/developer/tests/testIntervalRpcFunction.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
(function() {
|
||||
var x = false;
|
||||
var y = false;
|
||||
var z = false;
|
||||
|
||||
var entityLastTick = Date.now();
|
||||
var entityTotalTicks = 0;
|
||||
var entityCount = 0;
|
||||
var entityTotalVariance = 0;
|
||||
var entityTickCount = 0;
|
||||
var entityVarianceCount = 0;
|
||||
var entityHighVarianceCount = 0;
|
||||
var _entityID;
|
||||
var time = 0;
|
||||
|
||||
function Foo() { return; };
|
||||
Foo.prototype = {
|
||||
preload: function(entityID) { print('Foo preload'); _entityID = entityID; },
|
||||
doRPC: function(entityID, args) {
|
||||
var range = args[0];
|
||||
var rotationFactor = args[1];
|
||||
var omega = args[2];
|
||||
var TIMER_INTERVAL = args[3];
|
||||
var TIMER_HZ = args[4];
|
||||
|
||||
// first time, set our x,y,z
|
||||
if (x === false) {
|
||||
var position = Entities.getEntityProperties(_entityID, "position").position;
|
||||
x = position.x;
|
||||
y = position.y;
|
||||
z = position.z;
|
||||
}
|
||||
entityTickCount++;
|
||||
var tickNow = Date.now();
|
||||
var deltaTick = tickNow - entityLastTick;
|
||||
var variance = Math.abs(deltaTick - TIMER_INTERVAL);
|
||||
entityTotalVariance += variance;
|
||||
|
||||
if (variance > 1) {
|
||||
entityVarianceCount++;
|
||||
}
|
||||
if (variance > 5) {
|
||||
entityHighVarianceCount++;
|
||||
}
|
||||
|
||||
entityTotalTicks += deltaTick;
|
||||
entityLastTick = tickNow;
|
||||
|
||||
// move self!!
|
||||
var deltaTime = deltaTick / 1000;
|
||||
time += deltaTime;
|
||||
rotation = Quat.angleAxis(time * omega / Math.PI * 180.0, { x: 0, y: 1, z: 0 });
|
||||
Entities.editEntity(_entityID,
|
||||
{
|
||||
position: { x: x + Math.sin(time * omega) / 2.0 * range,
|
||||
y: y,
|
||||
z: z },
|
||||
});
|
||||
|
||||
|
||||
if(entityTickCount == (TIMER_HZ * 5)) {
|
||||
print("ENTITY -- For " + entityTickCount + " samples average interval = " + entityTotalTicks/entityTickCount + " ms"
|
||||
+ " average variance:" + entityTotalVariance/entityTickCount + " ms"
|
||||
+ " min variance:" + entityVarianceCount + " [" + (entityVarianceCount/entityTickCount) * 100 + " %] "
|
||||
+ " high variance:" + entityHighVarianceCount + " [" + (entityHighVarianceCount/entityTickCount) * 100 + " %] "
|
||||
);
|
||||
entityTickCount = 0;
|
||||
entityTotalTicks = 0;
|
||||
entityTotalVariance = 0;
|
||||
entityVarianceCount = 0;
|
||||
entityHighVarianceCount = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Foo();
|
||||
});
|
|
@ -816,7 +816,6 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.update = function(deltaTime, timestamp) {
|
||||
|
||||
this.updateSmoothedTrigger();
|
||||
|
||||
// If both trigger and grip buttons squeezed and nothing is held, rescale my avatar!
|
||||
|
@ -2141,7 +2140,6 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.nearGrabbing = function(deltaTime, timestamp) {
|
||||
|
||||
this.grabPointSphereOff();
|
||||
|
||||
if (this.state == STATE_NEAR_GRABBING && (!this.triggerClicked && this.secondaryReleased())) {
|
||||
|
@ -3066,9 +3064,65 @@ var handleHandMessages = function(channel, message, sender) {
|
|||
|
||||
Messages.messageReceived.connect(handleHandMessages);
|
||||
|
||||
var BASIC_TIMER_INTERVAL_MS = 20; // 20ms = 50hz good enough
|
||||
var TARGET_UPDATE_HZ = 50; // 50hz good enough (no change in logic)
|
||||
var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ;
|
||||
var lastInterval = Date.now();
|
||||
|
||||
var intervalCount = 0;
|
||||
var totalDelta = 0;
|
||||
var totalVariance = 0;
|
||||
var highVarianceCount = 0;
|
||||
var veryhighVarianceCount = 0;
|
||||
var updateTotalWork = 0;
|
||||
|
||||
var UPDATE_PERFORMANCE_DEBUGGING = false;
|
||||
|
||||
var updateIntervalTimer = Script.setInterval(function(){
|
||||
update(BASIC_TIMER_INTERVAL_MS / 1000);
|
||||
|
||||
intervalCount++;
|
||||
var thisInterval = Date.now();
|
||||
var deltaTimeMsec = thisInterval - lastInterval;
|
||||
var deltaTime = deltaTimeMsec / 1000;
|
||||
lastInterval = thisInterval;
|
||||
|
||||
totalDelta += deltaTimeMsec;
|
||||
|
||||
var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS);
|
||||
totalVariance += variance;
|
||||
|
||||
if (variance > 1) {
|
||||
highVarianceCount++;
|
||||
}
|
||||
|
||||
if (variance > 5) {
|
||||
veryhighVarianceCount++;
|
||||
}
|
||||
|
||||
// will call update for both hands
|
||||
var preWork = Date.now();
|
||||
update(deltaTime);
|
||||
var postWork = Date.now();
|
||||
var workDelta = postWork - preWork;
|
||||
updateTotalWork += workDelta;
|
||||
|
||||
if (intervalCount == 100) {
|
||||
|
||||
if (UPDATE_PERFORMANCE_DEBUGGING) {
|
||||
print("handControllerGrab.js -- For " + intervalCount + " samples average= " + totalDelta/intervalCount + " ms"
|
||||
+ " average variance:" + totalVariance/intervalCount + " ms"
|
||||
+ " high variance count:" + highVarianceCount + " [ " + (highVarianceCount/intervalCount) * 100 + "% ] "
|
||||
+ " VERY high variance count:" + veryhighVarianceCount + " [ " + (veryhighVarianceCount/intervalCount) * 100 + "% ] "
|
||||
+ " average work:" + updateTotalWork/intervalCount + " ms");
|
||||
}
|
||||
|
||||
intervalCount = 0;
|
||||
totalDelta = 0;
|
||||
totalVariance = 0;
|
||||
highVarianceCount = 0;
|
||||
veryhighVarianceCount = 0;
|
||||
updateTotalWork = 0;
|
||||
}
|
||||
|
||||
}, BASIC_TIMER_INTERVAL_MS);
|
||||
|
||||
function cleanup() {
|
||||
|
|
|
@ -132,10 +132,14 @@ function populateUserList() {
|
|||
// Request the username from the given UUID
|
||||
Users.requestUsernameFromID(id);
|
||||
}
|
||||
data.push(avatarPalDatum);
|
||||
if (id) { // No overlay for ourself.
|
||||
addAvatarNode(id);
|
||||
// Request personal mute status and ignore status
|
||||
// from NodeList (as long as we're not requesting it for our own ID)
|
||||
if (id) {
|
||||
avatarPalDatum['personalMute'] = Users.getPersonalMuteStatus(id);
|
||||
avatarPalDatum['ignore'] = Users.getIgnoreStatus(id);
|
||||
addAvatarNode(id); // No overlay for ourselves
|
||||
}
|
||||
data.push(avatarPalDatum);
|
||||
print('PAL data:', JSON.stringify(avatarPalDatum));
|
||||
});
|
||||
pal.sendToQml({method: 'users', params: data});
|
||||
|
@ -252,9 +256,11 @@ function off() {
|
|||
}
|
||||
triggerMapping.disable(); // It's ok if we disable twice.
|
||||
removeOverlays();
|
||||
Users.requestsDomainListData = false;
|
||||
}
|
||||
function onClicked() {
|
||||
if (!pal.visible) {
|
||||
Users.requestsDomainListData = true;
|
||||
populateUserList();
|
||||
pal.raise();
|
||||
isWired = true;
|
||||
|
@ -326,6 +332,14 @@ button.clicked.connect(onClicked);
|
|||
pal.visibleChanged.connect(onVisibleChanged);
|
||||
pal.closed.connect(off);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
function clearIgnoredInQMLAndClosePAL() {
|
||||
pal.sendToQml({ method: 'clearIgnored' });
|
||||
if (pal.visible) {
|
||||
onClicked(); // Close the PAL
|
||||
}
|
||||
}
|
||||
Window.domainChanged.connect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearIgnoredInQMLAndClosePAL);
|
||||
|
||||
//
|
||||
// Cleanup.
|
||||
|
@ -336,6 +350,8 @@ Script.scriptEnding.connect(function () {
|
|||
pal.visibleChanged.disconnect(onVisibleChanged);
|
||||
pal.closed.disconnect(off);
|
||||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL);
|
||||
off();
|
||||
});
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include <gl/Config.h>
|
||||
#include <gl/Context.h>
|
||||
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
@ -23,7 +25,6 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QThreadPool>
|
||||
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QResizeEvent>
|
||||
#include <QtGui/QWindow>
|
||||
|
@ -33,7 +34,6 @@
|
|||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
|
||||
#include <shared/RateCounter.h>
|
||||
#include <shared/NetworkUtils.h>
|
||||
#include <shared/FileLogger.h>
|
||||
|
@ -60,8 +60,10 @@
|
|||
#include <model-networking/ModelCache.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <render/RenderFetchCullSortTask.h>
|
||||
#include <RenderShadowTask.h>
|
||||
#include <RenderDeferredTask.h>
|
||||
#include <RenderForwardTask.h>
|
||||
#include <OctreeConstants.h>
|
||||
|
||||
#include <EntityTreeRenderer.h>
|
||||
|
@ -537,7 +539,14 @@ public:
|
|||
_initContext.makeCurrent();
|
||||
// Render engine init
|
||||
_renderEngine->addJob<RenderShadowTask>("RenderShadowTask", _cullFunctor);
|
||||
_renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", _cullFunctor);
|
||||
const auto items = _renderEngine->addJob<RenderFetchCullSortTask>("FetchCullSort", _cullFunctor);
|
||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
||||
if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) {
|
||||
_renderEngine->addJob<RenderForwardTask>("RenderForwardTask", items.get<RenderFetchCullSortTask::Output>());
|
||||
} else {
|
||||
_renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", items.get<RenderFetchCullSortTask::Output>());
|
||||
}
|
||||
_renderEngine->load();
|
||||
_renderEngine->registerScene(_main3DScene);
|
||||
|
||||
|
|
Loading…
Reference in a new issue