Merge branch 'master' of github.com:highfidelity/hifi into rework-visual-physics-debug

This commit is contained in:
Seth Alves 2015-11-14 14:00:20 -08:00
commit 89ed3751ae
16 changed files with 272 additions and 190 deletions

View file

@ -96,29 +96,130 @@ bool EntityServer::hasSpecialPacketsToSend(const SharedNodePointer& node) {
return shouldSendDeletedEntities;
}
// FIXME - most of the old code for this was encapsulated in EntityTree, I liked that design from a data
// hiding and object oriented perspective. But that didn't really allow us to handle the case of lots
// of entities being deleted at the same time. I'd like to look to move this back into EntityTree but
// for now this works and addresses the bug.
int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) {
int totalBytes = 0;
EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
if (nodeData) {
quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt();
quint64 considerEntitiesSince = EntityTree::getAdjustedConsiderSince(deletedEntitiesSentAt);
quint64 deletePacketSentAt = usecTimestampNow();
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
auto recentlyDeleted = tree->getRecentlyDeletedEntityIDs();
bool hasMoreToSend = true;
packetsSent = 0;
while (hasMoreToSend) {
auto specialPacket = tree->encodeEntitiesDeletedSince(queryNode->getSequenceNumber(), deletedEntitiesSentAt,
hasMoreToSend);
// create a new special packet
std::unique_ptr<NLPacket> deletesPacket = NLPacket::create(PacketType::EntityErase);
queryNode->packetSent(*specialPacket);
// pack in flags
OCTREE_PACKET_FLAGS flags = 0;
deletesPacket->writePrimitive(flags);
totalBytes += specialPacket->getDataSize();
packetsSent++;
// pack in sequence number
auto sequenceNumber = queryNode->getSequenceNumber();
deletesPacket->writePrimitive(sequenceNumber);
DependencyManager::get<NodeList>()->sendPacket(std::move(specialPacket), *node);
}
// pack in timestamp
OCTREE_PACKET_SENT_TIME now = usecTimestampNow();
deletesPacket->writePrimitive(now);
// figure out where we are now and pack a temporary number of IDs
uint16_t numberOfIDs = 0;
qint64 numberOfIDsPos = deletesPacket->pos();
deletesPacket->writePrimitive(numberOfIDs);
// we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been
// deleted since we last sent to this node
auto it = recentlyDeleted.constBegin();
while (it != recentlyDeleted.constEnd()) {
// if the timestamp is more recent then out last sent time, include it
if (it.key() > considerEntitiesSince) {
// get all the IDs for this timestamp
const auto& entityIDsFromTime = recentlyDeleted.values(it.key());
for (const auto& entityID : entityIDsFromTime) {
// check to make sure we have room for one more ID, if we don't have more
// room, then send out this packet and create another one
if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) {
// replace the count for the number of included IDs
deletesPacket->seek(numberOfIDsPos);
deletesPacket->writePrimitive(numberOfIDs);
// Send the current packet
queryNode->packetSent(*deletesPacket);
auto thisPacketSize = deletesPacket->getDataSize();
totalBytes += thisPacketSize;
packetsSent++;
DependencyManager::get<NodeList>()->sendPacket(std::move(deletesPacket), *node);
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize;
#endif
// create another packet
deletesPacket = NLPacket::create(PacketType::EntityErase);
// pack in flags
deletesPacket->writePrimitive(flags);
// pack in sequence number
sequenceNumber = queryNode->getSequenceNumber();
deletesPacket->writePrimitive(sequenceNumber);
// pack in timestamp
deletesPacket->writePrimitive(now);
// figure out where we are now and pack a temporary number of IDs
numberOfIDs = 0;
numberOfIDsPos = deletesPacket->pos();
deletesPacket->writePrimitive(numberOfIDs);
}
// FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server
// to the client. These were causing "lost" entities like flashlights and laser pointers
// now that we keep around some additional history of the erased entities and resend that
// history for a longer time window, these entities are not "lost". But we haven't yet
// found/fixed the underlying issue that caused bad UUIDs to be sent to some users.
deletesPacket->write(entityID.toRfc4122());
++numberOfIDs;
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID;
#endif
} // end for (ids)
} // end if (it.val > sinceLast)
++it;
} // end while
// replace the count for the number of included IDs
deletesPacket->seek(numberOfIDsPos);
deletesPacket->writePrimitive(numberOfIDs);
// Send the current packet
queryNode->packetSent(*deletesPacket);
auto thisPacketSize = deletesPacket->getDataSize();
totalBytes += thisPacketSize;
packetsSent++;
DependencyManager::get<NodeList>()->sendPacket(std::move(deletesPacket), *node);
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize;
#endif
nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt);
}
@ -134,6 +235,7 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN
return totalBytes;
}
void EntityServer::pruneDeletedEntities() {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
if (tree->hasAnyDeletedEntities()) {

View file

@ -240,6 +240,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
auto nodeList = DependencyManager::get<NodeList>();
int packetsSent = 0;
int totalBytesSent = 0;
NodeToSenderStatsMapIterator i = _singleSenderStats.begin();
while (i != _singleSenderStats.end()) {
@ -291,12 +292,15 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
packetsSent += nackPacketList->getNumPackets();
// send the list of nack packets
nodeList->sendPacketList(std::move(nackPacketList), *destinationNode);
totalBytesSent += nodeList->sendPacketList(std::move(nackPacketList), *destinationNode);
}
++i;
}
OctreeSendThread::_totalPackets += packetsSent;
OctreeSendThread::_totalBytes += totalBytesSent;
return packetsSent;
}

View file

@ -119,6 +119,10 @@ AtomicUIntStat OctreeSendThread::_totalBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalWastedBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalPackets { 0 };
AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 };
int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
OctreeServer::didHandlePacketSend(this);
@ -579,11 +583,17 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// send the environment packet
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketsToSend(_node) && !nodeData->isShuttingDown()) {
int specialPacketsSent;
int specialPacketsSent = 0;
trueBytesSent += _myServer->sendSpecialPackets(_node, nodeData, specialPacketsSent);
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
truePacketsSent += specialPacketsSent;
packetsSentThisInterval += specialPacketsSent;
_totalPackets += specialPacketsSent;
_totalBytes += trueBytesSent;
_totalSpecialPackets += specialPacketsSent;
_totalSpecialBytes += trueBytesSent;
}
// Re-send packets that were nacked by the client

View file

@ -38,6 +38,9 @@ public:
static AtomicUIntStat _totalWastedBytes;
static AtomicUIntStat _totalPackets;
static AtomicUIntStat _totalSpecialBytes;
static AtomicUIntStat _totalSpecialPackets;
static AtomicUIntStat _usleepTime;
static AtomicUIntStat _usleepCalls;

View file

@ -415,6 +415,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
quint64 totalOutboundSpecialPackets = OctreeSendThread::_totalSpecialPackets;
quint64 totalOutboundSpecialBytes = OctreeSendThread::_totalSpecialBytes;
statsString += QString(" Total Clients Connected: %1 clients\r\n")
.arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' '));
@ -606,6 +609,13 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
.arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Special Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalOutboundSpecialPackets).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Special Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalOutboundSpecialBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Wasted Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString().sprintf(" Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n",

View file

@ -33,15 +33,6 @@ var SHOW = 4;
var HIDE = 5;
var LOAD = 6;
var COLORS = [];
COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 };
COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 };
COLORS[STOP] = { red: STOP, green: 0, blue: 0 };
COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 };
COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 };
COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 };
var windowDimensions = Controller.getViewportDimensions();
var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/";
@ -138,6 +129,7 @@ function setupToolBars() {
}
function sendCommand(id, action) {
if (action === SHOW) {
toolBars[id].selectTool(onOffIcon[id], false);
toolBars[id].setAlpha(ALPHA_ON, playIcon[id]);
@ -154,24 +146,29 @@ function sendCommand(id, action) {
return;
}
if (id === (toolBars.length - 1)) {
for (i = 0; i < NUM_AC; i++) {
sendCommand(i, action);
}
return;
}
if (id === (toolBars.length - 1))
id = -1;
var position = { x: controlEntityPosition.x + id * controlEntitySize,
y: controlEntityPosition.y, z: controlEntityPosition.z };
Entities.addEntity({
name: "Actor Controller",
userData: clip_url,
var controlEntity = Entities.addEntity({
name: 'New Actor Controller',
type: "Box",
position: position,
dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize },
color: COLORS[action],
lifetime: 5
});
color: { red: 0, green: 0, blue: 0 },
position: controlEntityPosition,
dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize },
visible: false,
lifetime: 10,
userData: JSON.stringify({
idKey: {
uD_id: id
},
actionKey: {
uD_action: action
},
clipKey: {
uD_url: clip_url
}
})
});
}
function mousePressEvent(event) {
@ -191,8 +188,12 @@ function mousePressEvent(event) {
sendCommand(i, PLAY_LOOP);
} else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
sendCommand(i, STOP);
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
sendCommand(i, LOAD);
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
input_text = Window.prompt("Insert the url of the clip: ","");
if (!(input_text === "" || input_text === null)) {
clip_url = input_text;
sendCommand(i, LOAD);
}
} else {
// Check individual controls
for (i = 0; i < NUM_AC; i++) {
@ -210,7 +211,7 @@ function mousePressEvent(event) {
sendCommand(i, STOP);
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
input_text = Window.prompt("Insert the url of the clip: ","");
if(!(input_text === "" || input_text === null)){
if (!(input_text === "" || input_text === null)) {
clip_url = input_text;
sendCommand(i, LOAD);
}

View file

@ -38,18 +38,6 @@ var SHOW = 4;
var HIDE = 5;
var LOAD = 6;
var COLORS = [];
COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 };
COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 };
COLORS[STOP] = { red: STOP, green: 0, blue: 0 };
COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 };
COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 };
COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 };
controlEntityPosition.x += id * controlEntitySize;
Avatar.loadRecording(clip_url);
Avatar.setPlayFromCurrentLocation(playFromCurrentLocation);
Avatar.setPlayerUseDisplayName(useDisplayName);
Avatar.setPlayerUseAttachments(useAttachments);
@ -67,27 +55,27 @@ function setupEntityViewer() {
EntityViewer.queryOctree();
}
function getAction(controlEntity) {
clip_url = controlEntity.userData;
function getAction(controlEntity) {
if (controlEntity === null) {
return DO_NOTHING;
}
var userData = JSON.parse(Entities.getEntityProperties(controlEntity, ["userData"]).userData);
if (controlEntity === null ||
controlEntity.position.x !== controlEntityPosition.x ||
controlEntity.position.y !== controlEntityPosition.y ||
controlEntity.position.z !== controlEntityPosition.z ||
controlEntity.dimensions.x !== controlEntitySize) {
var uD_id = userData.idKey.uD_id;
var uD_action = userData.actionKey.uD_action;
var uD_url = userData.clipKey.uD_url;
Entities.deleteEntity((Entities.getEntityProperties(controlEntity)).id);
if (uD_id === id || uD_id === -1) {
if (uD_action === 6)
clip_url = uD_url;
return uD_action;
} else {
return DO_NOTHING;
}
for (i in COLORS) {
if (controlEntity.color.red === COLORS[i].red &&
controlEntity.color.green === COLORS[i].green &&
controlEntity.color.blue === COLORS[i].blue) {
Entities.deleteEntity(controlEntity.id);
return parseInt(i);
}
}
return DO_NOTHING;
}
}
count = 100; // This is necessary to wait for the audio mixer to connect
@ -100,7 +88,7 @@ function update(event) {
var controlEntity = Entities.findClosestEntity(controlEntityPosition, controlEntitySize);
var action = getAction(Entities.getEntityProperties(controlEntity));
var action = getAction(controlEntity);
switch(action) {
case PLAY:

View file

@ -176,14 +176,13 @@ function formatTime(time) {
var SEC_PER_MIN = 60;
var MSEC_PER_SEC = 1000;
var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR);
var hours = Math.floor(time / (SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (SEC_PER_MIN * MIN_PER_HOUR);
var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN));
time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN);
var minutes = Math.floor(time / (SEC_PER_MIN));
time -= minutes * (SEC_PER_MIN);
var seconds = Math.floor(time / MSEC_PER_SEC);
seconds = time / MSEC_PER_SEC;
var seconds = time;
var text = "";
text += (hours > 0) ? hours + ":" :

View file

@ -608,7 +608,7 @@ float MyAvatar::recorderElapsed() {
if (!_recorder) {
return 0;
}
return (float)_recorder->position() / MSECS_PER_SECOND;
return (float)_recorder->position() / (float) MSECS_PER_SECOND;
}
QMetaObject::Connection _audioClientRecorderConnection;

View file

@ -804,12 +804,12 @@ float AvatarData::playerElapsed() {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
float result;
QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
Q_RETURN_ARG(float, result));
return result;
}
return (float)_player->position() / MSECS_PER_SECOND;
return (float)_player->position() / (float) MSECS_PER_SECOND;
}
float AvatarData::playerLength() {
@ -817,12 +817,12 @@ float AvatarData::playerLength() {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
float result;
QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
Q_RETURN_ARG(float, result));
return result;
}
return _player->length() / MSECS_PER_SECOND;
return (float)_player->length() / (float) MSECS_PER_SECOND;
}
void AvatarData::loadRecording(const QString& filename) {
@ -1513,7 +1513,8 @@ void AvatarData::setRecordingBasis(std::shared_ptr<Transform> recordingBasis) {
recordingBasis = std::make_shared<Transform>();
recordingBasis->setRotation(getOrientation());
recordingBasis->setTranslation(getPosition());
recordingBasis->setScale(getTargetScale());
// TODO: find a different way to record/playback the Scale of the avatar
//recordingBasis->setScale(getTargetScale());
}
_recordingBasis = recordingBasis;
}
@ -1532,7 +1533,7 @@ Transform AvatarData::getTransform() const {
static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform");
static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform");
static const QString JSON_AVATAR_JOINT_ROTATIONS = QStringLiteral("jointRotations");
static const QString JSON_AVATAR_JOINT_ARRAY = QStringLiteral("jointArray");
static const QString JSON_AVATAR_HEAD = QStringLiteral("head");
static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation");
static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes");
@ -1544,6 +1545,24 @@ static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel");
static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName");
static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments");
QJsonValue toJsonValue(const JointData& joint) {
QJsonArray result;
result.push_back(toJsonValue(joint.rotation));
result.push_back(toJsonValue(joint.translation));
return result;
}
JointData jointDataFromJsonValue(const QJsonValue& json) {
JointData result;
if (json.isArray()) {
QJsonArray array = json.toArray();
result.rotation = quatFromJsonValue(array[0]);
result.rotationSet = true;
result.translation = vec3FromJsonValue(array[1]);
result.translationSet = false;
}
return result;
}
// Every frame will store both a basis for the recording and a relative transform
// This allows the application to decide whether playback should be relative to an avatar's
@ -1575,13 +1594,16 @@ QByteArray avatarStateToFrame(const AvatarData* _avatar) {
root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform);
root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis);
}
} else {
root[JSON_AVATAR_RELATIVE] = Transform::toJson(_avatar->getTransform());
}
QJsonArray jointRotations;
for (const auto& jointRotation : _avatar->getJointRotations()) {
jointRotations.push_back(toJsonValue(jointRotation));
// Skeleton pose
QJsonArray jointArray;
for (const auto& joint : _avatar->getRawJointData()) {
jointArray.push_back(toJsonValue(joint));
}
root[JSON_AVATAR_JOINT_ROTATIONS] = jointRotations;
root[JSON_AVATAR_JOINT_ARRAY] = jointArray;
const HeadData* head = _avatar->getHeadData();
if (head) {
@ -1643,24 +1665,34 @@ void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar) {
auto worldTransform = currentBasis->worldTransform(relativeTransform);
_avatar->setPosition(worldTransform.getTranslation());
_avatar->setOrientation(worldTransform.getRotation());
_avatar->setTargetScale(worldTransform.getScale().x);
// TODO: find a way to record/playback the Scale of the avatar
//_avatar->setTargetScale(worldTransform.getScale().x);
}
#if 0
if (root.contains(JSON_AVATAR_ATTACHEMENTS)) {
// FIXME de-serialize attachment data
}
// Joint rotations are relative to the avatar, so they require no basis correction
if (root.contains(JSON_AVATAR_JOINT_ROTATIONS)) {
QVector<quat> jointRotations;
QJsonArray jointRotationsJson = root[JSON_AVATAR_JOINT_ROTATIONS].toArray();
jointRotations.reserve(jointRotationsJson.size());
for (const auto& jointRotationJson : jointRotationsJson) {
jointRotations.push_back(quatFromJsonValue(jointRotationJson));
if (root.contains(JSON_AVATAR_JOINT_ARRAY)) {
QVector<JointData> jointArray;
QJsonArray jointArrayJson = root[JSON_AVATAR_JOINT_ARRAY].toArray();
jointArray.reserve(jointArrayJson.size());
for (const auto& jointJson : jointArrayJson) {
jointArray.push_back(jointDataFromJsonValue(jointJson));
}
QVector<glm::quat> jointRotations;
jointRotations.reserve(jointArray.size());
for (const auto& joint : jointArray) {
jointRotations.push_back(joint.rotation);
}
_avatar->setJointRotations(jointRotations);
}
#if 0
// Most head data is relative to the avatar, and needs no basis correction,
// but the lookat vector does need correction
HeadData* head = _avatar->_headData;

View file

@ -457,6 +457,9 @@ public:
bool translationSet = false;
};
QJsonValue toJsonValue(const JointData& joint);
JointData jointDataFromJsonValue(const QJsonValue& q);
class AttachmentData {
public:
QUrl modelURL;

View file

@ -887,8 +887,13 @@ void EntityTree::update() {
}
}
quint64 EntityTree::getAdjustedConsiderSince(quint64 sinceTime) {
return (sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER);
}
bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) {
quint64 considerEntitiesSince = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER;
quint64 considerEntitiesSince = getAdjustedConsiderSince(sinceTime);
// we can probably leverage the ordered nature of QMultiMap to do this quickly...
bool hasSomethingNewer = false;
@ -915,88 +920,6 @@ bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) {
return hasSomethingNewer;
}
// sinceTime is an in/out parameter - it will be side effected with the last time sent out
std::unique_ptr<NLPacket> EntityTree::encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime,
bool& hasMore) {
quint64 considerEntitiesSince = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER;
auto deletesPacket = NLPacket::create(PacketType::EntityErase);
// pack in flags
OCTREE_PACKET_FLAGS flags = 0;
deletesPacket->writePrimitive(flags);
// pack in sequence number
deletesPacket->writePrimitive(sequenceNumber);
// pack in timestamp
OCTREE_PACKET_SENT_TIME now = usecTimestampNow();
deletesPacket->writePrimitive(now);
// figure out where we are now and pack a temporary number of IDs
uint16_t numberOfIDs = 0;
qint64 numberOfIDsPos = deletesPacket->pos();
deletesPacket->writePrimitive(numberOfIDs);
// we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been
// deleted since we last sent to this node
{
QReadLocker locker(&_recentlyDeletedEntitiesLock);
bool hasFilledPacket = false;
auto it = _recentlyDeletedEntityItemIDs.constBegin();
while (it != _recentlyDeletedEntityItemIDs.constEnd()) {
QList<QUuid> values = _recentlyDeletedEntityItemIDs.values(it.key());
for (int valueItem = 0; valueItem < values.size(); ++valueItem) {
// if the timestamp is more recent then out last sent time, include it
if (it.key() > considerEntitiesSince) {
QUuid entityID = values.at(valueItem);
// FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server
// to the client. These were causing "lost" entities like flashlights and laser pointers
// now that we keep around some additional history of the erased entities and resend that
// history for a longer time window, these entities are not "lost". But we haven't yet
// found/fixed the underlying issue that caused bad UUIDs to be sent to some users.
deletesPacket->write(entityID.toRfc4122());
++numberOfIDs;
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID;
#endif
// check to make sure we have room for one more ID
if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) {
hasFilledPacket = true;
break;
}
}
}
// check to see if we're about to return
if (hasFilledPacket) {
// let our caller know how far we got
sinceTime = it.key();
break;
}
++it;
}
// if we got to the end, then we're done sending
if (it == _recentlyDeletedEntityItemIDs.constEnd()) {
hasMore = false;
}
}
// replace the count for the number of included IDs
deletesPacket->seek(numberOfIDsPos);
deletesPacket->writePrimitive(numberOfIDs);
return deletesPacket;
}
// called by the server when it knows all nodes have been sent deleted packets
void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) {
quint64 considerSinceTime = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER;

View file

@ -147,10 +147,19 @@ public:
void addNewlyCreatedHook(NewlyCreatedEntityHook* hook);
void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook);
bool hasAnyDeletedEntities() const { return _recentlyDeletedEntityItemIDs.size() > 0; }
bool hasAnyDeletedEntities() const {
QReadLocker locker(&_recentlyDeletedEntitiesLock);
return _recentlyDeletedEntityItemIDs.size() > 0;
}
bool hasEntitiesDeletedSince(quint64 sinceTime);
std::unique_ptr<NLPacket> encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime,
bool& hasMore);
static quint64 getAdjustedConsiderSince(quint64 sinceTime);
QMultiMap<quint64, QUuid> getRecentlyDeletedEntityIDs() const {
QReadLocker locker(&_recentlyDeletedEntitiesLock);
return _recentlyDeletedEntityItemIDs;
}
void forgetEntitiesDeletedBefore(quint64 sinceTime);
int processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode);
@ -243,7 +252,7 @@ private:
QReadWriteLock _newlyCreatedHooksLock;
QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedEntitiesLock;
mutable QReadWriteLock _recentlyDeletedEntitiesLock;
QMultiMap<quint64, QUuid> _recentlyDeletedEntityItemIDs;
EntityItemFBXService* _fbxService;

View file

@ -25,6 +25,8 @@ void Deck::queueClip(ClipPointer clip, Time timeOffset) {
// FIXME if the time offset is not zero, wrap the clip in a OffsetClip wrapper
_clips.push_back(clip);
_length = std::max(_length, clip->duration());
}
void Deck::play() {

View file

@ -27,7 +27,7 @@ public:
static const FrameType TYPE_INVALID = 0xFFFF;
static const FrameType TYPE_HEADER = 0x0;
FrameType type { TYPE_INVALID };
Time timeOffset { 0 };
Time timeOffset { 0 }; // milliseconds
QByteArray data;
Frame() {}

View file

@ -89,11 +89,7 @@ void Model::setScale(const glm::vec3& scale) {
}
void Model::setScaleInternal(const glm::vec3& scale) {
float scaleLength = glm::length(_scale);
float relativeDeltaScale = glm::length(_scale - scale) / scaleLength;
const float ONE_PERCENT = 0.01f;
if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
if (glm::distance(_scale, scale) > METERS_PER_MILLIMETER) {
_scale = scale;
initJointTransforms();
}