avatar mixer can relay "client-only" entities between interfaces -- the entity server wont know about them.

This commit is contained in:
Seth Alves 2016-05-07 14:48:31 -07:00
parent b234d94d25
commit 0e6d9a1eec
16 changed files with 336 additions and 26 deletions

View file

@ -796,6 +796,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// Tell our entity edit sender about our known jurisdictions
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
_entityEditSender.setMyAvatar(getMyAvatar());
// For now we're going to set the PPS for outbound packets to be super high, this is
// probably not the right long term solution. But for now, we're going to do this to
@ -1087,6 +1088,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender();
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
entityPacketSender->setMyAvatar(getMyAvatar());
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
@ -4186,6 +4191,7 @@ void Application::clearDomainOctreeDetails() {
qCDebug(interfaceapp) << "Clearing domain octree details...";
resetPhysicsReadyInformation();
getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities
// reset our node to stats and node to jurisdiction maps... since these must be changing...
_entityServerJurisdictions.withWriteLock([&] {

View file

@ -30,6 +30,7 @@
#include <SharedUtil.h>
#include <TextRenderer3D.h>
#include <TextureCache.h>
#include <VariantMapToScriptValue.h>
#include "Application.h"
#include "Avatar.h"
@ -160,6 +161,89 @@ void Avatar::animateScaleChanges(float deltaTime) {
}
}
void Avatar::updateAvatarEntities() {
// - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity()
// - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited
// - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket
// - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces
// - AvatarHashMap::processAvatarIdentityPacket's on other interfaces call avatar->setAvatarEntityData()
// - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true
// - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are...
quint64 now = usecTimestampNow();
const static quint64 refreshTime = 3 * USECS_PER_SECOND;
if (!_avatarEntityDataChanged && now - _avatarEntityChangedTime < refreshTime) {
return;
}
EntityTreeRenderer* treeRenderer = qApp->getEntities();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
bool success = true;
QScriptEngine scriptEngine;
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
// see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties
// and either add or update the entity.
QByteArray jsonByteArray = avatarEntities.value(entityID);
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray);
if (!jsonProperties.isObject()) {
qDebug() << "got bad avatarEntity json";
continue;
}
QVariant variantProperties = jsonProperties.toVariant();
QVariantMap asMap = variantProperties.toMap();
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties properties;
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties);
properties.setClientOnly(true);
properties.setOwningAvatarID(getID());
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) {
if (!entityTree->updateEntity(entityID, properties)) {
qDebug() << "AVATAR-ENTITES -- updateEntity failed: " << properties.getType();
success = false;
}
} else {
entity = entityTree->addEntity(entityID, properties);
if (!entity) {
qDebug() << "AVATAR-ENTITES -- addEntity failed: " << properties.getType();
success = false;
}
}
}
});
AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs();
foreach (auto entityID, recentlyDettachedAvatarEntities) {
if (!_avatarEntityData.contains(entityID)) {
EntityItemPointer dettachedEntity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (dettachedEntity) {
// this will cause this interface to listen to data from the entity-server about this entity.
dettachedEntity->setClientOnly(false);
}
}
}
if (success) {
setAvatarEntityDataChanged(false);
_avatarEntityChangedTime = now;
}
}
void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
@ -228,6 +312,7 @@ void Avatar::simulate(float deltaTime) {
simulateAttachments(deltaTime);
updatePalms();
updateAvatarEntities();
}
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {

View file

@ -64,6 +64,7 @@ public:
typedef std::shared_ptr<render::Item::PayloadInterface> PayloadPointer;
void init();
void updateAvatarEntities();
void simulate(float deltaTime);
virtual void simulateAttachments(float deltaTime);

View file

@ -311,6 +311,10 @@ void MyAvatar::update(float deltaTime) {
head->setAudioLoudness(audio->getLastInputLoudness());
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
if (_avatarEntityDataLocallyEdited) {
sendIdentityPacket();
}
simulate(deltaTime);
currentEnergy += energyChargeRate;
@ -443,7 +447,7 @@ void MyAvatar::simulate(float deltaTime) {
EntityItemProperties properties = entity->getProperties();
properties.setQueryAACubeDirty();
properties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
entity->setLastBroadcast(usecTimestampNow());
}
}
@ -455,6 +459,8 @@ void MyAvatar::simulate(float deltaTime) {
}
});
}
updateAvatarEntities();
}
// thread-safe
@ -696,6 +702,16 @@ void MyAvatar::saveData() {
}
settings.endArray();
settings.beginWriteArray("avatarEntityData");
int avatarEntityIndex = 0;
for (auto entityID : _avatarEntityData.keys()) {
settings.setArrayIndex(avatarEntityIndex);
settings.setValue("id", entityID);
settings.setValue("properties", _avatarEntityData.value(entityID));
avatarEntityIndex++;
}
settings.endArray();
settings.setValue("displayName", _displayName);
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
@ -807,6 +823,16 @@ void MyAvatar::loadData() {
settings.endArray();
setAttachmentData(attachmentData);
int avatarEntityCount = settings.beginReadArray("avatarEntityData");
for (int i = 0; i < avatarEntityCount; i++) {
settings.setArrayIndex(i);
QUuid entityID = settings.value("id").toUuid();
QByteArray properties = settings.value("properties").toByteArray();
updateAvatarEntity(entityID, properties);
}
settings.endArray();
setAvatarEntityDataChanged(true);
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());

View file

@ -962,8 +962,9 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) {
QUrl unusedModelURL; // legacy faceModel support
QUrl skeletonModelURL;
QVector<AttachmentData> attachmentData;
AvatarEntityMap avatarEntityData;
QString displayName;
packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName;
packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName >> avatarEntityData;
bool hasIdentityChanged = false;
@ -983,6 +984,11 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) {
hasIdentityChanged = true;
}
if (avatarEntityData != _avatarEntityData) {
setAvatarEntityData(avatarEntityData);
hasIdentityChanged = true;
}
return hasIdentityChanged;
}
@ -994,7 +1000,7 @@ QByteArray AvatarData::identityByteArray() {
QUrl unusedModelURL; // legacy faceModel support
identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName;
identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName << _avatarEntityData;
return identityData;
}
@ -1202,6 +1208,8 @@ void AvatarData::sendIdentityPacket() {
[&](const SharedNodePointer& node) {
nodeList->sendPacketList(std::move(packetList), *node);
});
_avatarEntityDataLocallyEdited = false;
}
void AvatarData::sendBillboardPacket() {
@ -1389,6 +1397,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel");
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");
static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
static const QString JSON_AVATAR_SCALE = QStringLiteral("scale");
QJsonValue toJsonValue(const JointData& joint) {
@ -1427,6 +1436,17 @@ QJsonObject AvatarData::toJson() const {
root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson;
}
if (!_avatarEntityData.empty()) {
QJsonArray avatarEntityJson;
for (auto entityID : _avatarEntityData.keys()) {
QVariantMap entityData;
entityData.insert("id", entityID);
entityData.insert("properties", _avatarEntityData.value(entityID));
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
}
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
}
auto recordingBasis = getRecordingBasis();
bool success;
Transform avatarTransform = getTransform(success);
@ -1526,6 +1546,13 @@ void AvatarData::fromJson(const QJsonObject& json) {
setAttachmentData(attachments);
}
// if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) {
// QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray();
// for (auto attachmentJson : attachmentsJson) {
// // TODO -- something
// }
// }
// Joint rotations are relative to the avatar, so they require no basis correction
if (json.contains(JSON_AVATAR_JOINT_ARRAY)) {
QVector<JointData> jointArray;
@ -1678,9 +1705,69 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
QVector<AttachmentData> newAttachments;
newAttachments.reserve(variant.size());
for (const auto& attachmentVar : variant) {
AttachmentData attachment;
AttachmentData attachment;
attachment.fromVariant(attachmentVar);
newAttachments.append(attachment);
}
setAttachmentData(newAttachments);
}
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData));
return;
}
_avatarEntityData.insert(entityID, entityData);
_avatarEntityDataLocallyEdited = true;
}
void AvatarData::clearAvatarEntity(const QUuid& entityID) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearAvatarEntity", Q_ARG(const QUuid&, entityID));
return;
}
_avatarEntityData.remove(entityID);
_avatarEntityDataLocallyEdited = true;
}
AvatarEntityMap AvatarData::getAvatarEntityData() const {
if (QThread::currentThread() != thread()) {
AvatarEntityMap result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getAvatarEntityData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AvatarEntityMap, result));
return result;
}
return _avatarEntityData;
}
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData));
return;
}
if (_avatarEntityData != avatarEntityData) {
// keep track of entities that were attached to this avatar but no longer are
AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys());
_avatarEntityData = avatarEntityData;
setAvatarEntityDataChanged(true);
foreach (auto entityID, previousAvatarEntityIDs) {
if (!_avatarEntityData.contains(entityID)) {
_avatarEntityDetached.insert(entityID);
}
}
}
}
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
if (QThread::currentThread() != thread()) {
AvatarEntityIDs result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AvatarEntityIDs, result));
return result;
}
AvatarEntityIDs result = _avatarEntityDetached;
_avatarEntityDetached.clear();
return result;
}

View file

@ -61,6 +61,8 @@ typedef unsigned long long quint64;
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
using AvatarEntityMap = QMap<QUuid, QByteArray>;
using AvatarEntityIDs = QSet<QUuid>;
using AvatarDataSequenceNumber = uint16_t;
@ -135,6 +137,10 @@ class AttachmentData;
class Transform;
using TransformPointer = std::shared_ptr<Transform>;
// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows
// the value to be reset when the sessionID changes.
const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}");
class AvatarData : public QObject, public SpatiallyNestable {
Q_OBJECT
@ -274,6 +280,9 @@ public:
Q_INVOKABLE QVariantList getAttachmentsVariant() const;
Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant);
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID);
void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }
// key state
@ -333,6 +342,11 @@ public:
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -405,6 +419,12 @@ protected:
// updates about one avatar to another.
glm::vec3 _globalPosition;
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
AvatarEntityMap _avatarEntityData;
bool _avatarEntityDataLocallyEdited { false };
bool _avatarEntityDataChanged { false };
quint64 _avatarEntityChangedTime { 0 };
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
static QUrl _defaultFullAvatarModelUrl;

View file

@ -111,17 +111,18 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
QDataStream identityStream(message->getMessage());
QUuid sessionUUID;
while (!identityStream.atEnd()) {
QUrl faceMeshURL, skeletonURL;
QVector<AttachmentData> attachmentData;
AvatarEntityMap avatarEntityData;
QString displayName;
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName;
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName >> avatarEntityData;
// mesh URL for a UUID, find avatar in our list
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) {
avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire
}
@ -130,6 +131,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
avatar->setAttachmentData(attachmentData);
}
avatar->setAvatarEntityData(avatarEntityData);
if (avatar->getDisplayName() != displayName) {
avatar->setDisplayName(displayName);
}

View file

@ -174,9 +174,14 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
#endif
if (!_webSurface) {
#if defined(Q_OS_LINUX)
// these don't seem to work on Linux
return;
#else
if (!buildWebSurface(static_cast<EntityTreeRenderer*>(args->_renderer))) {
return;
}
#endif
}
_lastRenderTime = usecTimestampNow();

View file

@ -10,6 +10,7 @@
//
#include <assert.h>
#include <QJsonDocument>
#include <PerfStat.h>
#include <OctalCode.h>
#include <udt/PacketHeaders.h>
@ -35,18 +36,54 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte
}
}
void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemID modelID,
const EntityItemProperties& properties) {
void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
EntityTreePointer entityTree,
EntityItemID entityItemID,
const EntityItemProperties& properties) {
if (!_shouldSend) {
return; // bail early
}
if (properties.getClientOnly()) {
// this is an avatar-based entity. update our avatar-data rather than sending to the entity-server
assert(_myAvatar);
if (!entityTree) {
qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree.";
return;
}
EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID);
if (!entity) {
qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity.";
return;
}
// the properties that get serialized into the avatar identity packet should be the entire set
// rather than just the ones being edited.
entity->setProperties(properties);
EntityItemProperties entityProperties = entity->getProperties();
QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties);
QVariant variantProperties = scriptProperties.toVariant();
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
// the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar
QJsonObject jsonObject = jsonProperties.object();
if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) {
jsonObject["parentID"] = AVATAR_SELF_ID.toString();
}
jsonProperties = QJsonDocument(jsonObject);
QByteArray binaryProperties = jsonProperties.toBinaryData();
_myAvatar->updateAvatarEntity(entityItemID, binaryProperties);
return;
}
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, bufferOut)) {
if (EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut)) {
#ifdef WANT_DEBUG
qCDebug(entities) << "calling queueOctreeEditMessage()...";
qCDebug(entities) << " id:" << modelID;
qCDebug(entities) << " id:" << entityItemID;
qCDebug(entities) << " properties:" << properties;
#endif
queueOctreeEditMessage(type, bufferOut);

View file

@ -15,6 +15,7 @@
#include <OctreeEditPacketSender.h>
#include "EntityItem.h"
#include "AvatarData.h"
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
class EntityEditPacketSender : public OctreeEditPacketSender {
@ -22,11 +23,17 @@ class EntityEditPacketSender : public OctreeEditPacketSender {
public:
EntityEditPacketSender();
void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; }
AvatarData* getMyAvatar() { return _myAvatar; }
void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); }
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
/// NOTE: EntityItemProperties assumes that all distances are in meter units
void queueEditEntityMessage(PacketType type, EntityItemID modelID, const EntityItemProperties& properties);
void queueEditEntityMessage(PacketType type, EntityTreePointer entityTree,
EntityItemID entityItemID, const EntityItemProperties& properties);
void queueEraseEntityMessage(const EntityItemID& entityItemID);
@ -40,5 +47,7 @@ public slots:
private:
bool _shouldProcessNack = true;
AvatarData* _myAvatar { nullptr };
QScriptEngine _scriptEngine;
};
#endif // hifi_EntityEditPacketSender_h

View file

@ -420,12 +420,19 @@ public:
/// entity to definitively state if the preload signal should be sent.
///
/// We only want to preload if:
/// there is some script, and either the script value or the scriptTimestamp
/// there is some script, and either the script value or the scriptTimestamp
/// value have changed since our last preload
bool shouldPreloadScript() const { return !_script.isEmpty() &&
bool shouldPreloadScript() const { return !_script.isEmpty() &&
((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); }
void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; }
bool getClientOnly() const { return _clientOnly; }
void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
// if this entity is client-only, which avatar is it associated with?
QUuid getOwningAvatarID() const { return _owningAvatarID; }
void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
protected:
void setSimulated(bool simulated) { _simulated = simulated; }
@ -537,6 +544,9 @@ protected:
mutable QHash<QUuid, quint64> _previouslyDeletedActions;
QUuid _sourceUUID; /// the server node UUID we came from
bool _clientOnly { false };
QUuid _owningAvatarID;
};
#endif // hifi_EntityItem_h

View file

@ -276,6 +276,12 @@ public:
void setJointRotationsDirty() { _jointRotationsSetChanged = true; _jointRotationsChanged = true; }
void setJointTranslationsDirty() { _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; }
bool getClientOnly() const { return _clientOnly; }
void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
// if this entity is client-only, which avatar is it associated with?
QUuid getOwningAvatarID() const { return _owningAvatarID; }
void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
protected:
QString getCollisionMaskAsString() const;
void setCollisionMaskFromString(const QString& maskString);
@ -302,6 +308,9 @@ private:
glm::vec3 _naturalPosition;
EntityPropertyFlags _desiredProperties; // if set will narrow scopes of copy/to/from to just these properties
bool _clientOnly { false };
QUuid _owningAvatarID;
};
Q_DECLARE_METATYPE(EntityItemProperties);

View file

@ -36,7 +36,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
EntityItemID entityID, const EntityItemProperties& properties) {
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties);
}
bool EntityScriptingInterface::canAdjustLocks() {
@ -123,9 +123,10 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti
}
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) {
EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
propertiesWithSimID = clientOnly;
auto dimensions = propertiesWithSimID.getDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
@ -272,13 +273,15 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
bool updatedEntity = false;
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
return;
}
if (scriptSideProperties.parentRelatedPropertyChanged()) {
// All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them.
// If any of these changed, pull any missing properties from the entity.
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
return;
}
//existing entity, retrieve old velocity for check down below
oldVelocity = entity->getVelocity().length();
@ -296,6 +299,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
}
}
properties = convertLocationFromScriptSemantics(properties);
properties.setClientOnly(entity->getClientOnly());
properties.setOwningAvatarID(entity->getOwningAvatarID());
float cost = calculateCost(density * volume, oldVelocity, newVelocity);
cost *= costMultiplier;

View file

@ -82,7 +82,7 @@ public slots:
Q_INVOKABLE bool canRez();
/// adds a model with the specific properties
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties);
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false);
/// temporary method until addEntity can be used from QJSEngine
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position);

View file

@ -1373,8 +1373,11 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra
}
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
EntityTreePointer tree = entityTreeElement->getTree();
// queue the packet to send to the server
args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties);
args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, tree, newID, properties);
// also update the local tree instantly (note: this is not our tree, but an alternate tree)
if (args->otherTree) {

View file

@ -547,8 +547,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties);
_entity->setLastBroadcast(usecTimestampNow());
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties);
_entity->setLastBroadcast(now);
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
// if they've changed.
@ -559,8 +562,9 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties);
entityDescendant->setLastBroadcast(usecTimestampNow());
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree,
descendant->getID(), newQueryCubeProperties);
entityDescendant->setLastBroadcast(now);
}
}
});