first pass cloneables WIP

This commit is contained in:
David Back 2018-05-09 18:10:40 -07:00
parent b9656e5c72
commit 3241c18271
19 changed files with 410 additions and 164 deletions

View file

@ -44,6 +44,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
PacketType::EntityClone,
PacketType::EntityEdit,
PacketType::EntityErase,
PacketType::EntityPhysics,

View file

@ -34,7 +34,7 @@ void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<Received
}
void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) {
if (type == PacketType::EntityAdd || type == PacketType::EntityEdit || type == PacketType::EntityPhysics) {
if (type == PacketType::EntityAdd || type == PacketType::EntityClone || type == PacketType::EntityEdit || type == PacketType::EntityPhysics) {
EntityItem::adjustEditPacketForClockSkew(buffer, clockSkew);
}
}
@ -162,3 +162,15 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI
queueOctreeEditMessage(PacketType::EntityErase, bufferOut);
}
}
void EntityEditPacketSender::queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID) {
if (!_shouldSend) {
return; // bail early
}
QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityClone), 0);
if (EntityItemProperties::encodeCloneEntityMessage(entityIDToClone, newEntityID, bufferOut)) {
queueOctreeEditMessage(PacketType::EntityClone, bufferOut);
}
}

View file

@ -38,6 +38,7 @@ public:
void queueEraseEntityMessage(const EntityItemID& entityItemID);
void queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID);
// My server type is the model server
virtual char getMyNodeType() const override { return NodeType::EntityServer; }

View file

@ -124,6 +124,11 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_LAST_EDITED_BY;
requestedProperties += PROP_CLONEABLE;
requestedProperties += PROP_CLONEABLE_LIFETIME;
requestedProperties += PROP_CLONEABLE_LIMIT;
requestedProperties += PROP_CLONEABLE_DYNAMIC;
return requestedProperties;
}
@ -288,6 +293,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube());
APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, getCloneable());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIFETIME, getCloneableLifetime());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIMIT, getCloneableLimit());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_DYNAMIC, getCloneableDynamic());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
propertyFlags,
@ -848,6 +858,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy);
READ_ENTITY_PROPERTY(PROP_CLONEABLE, bool, setCloneable);
READ_ENTITY_PROPERTY(PROP_CLONEABLE_LIFETIME, float, setCloneableLifetime);
READ_ENTITY_PROPERTY(PROP_CLONEABLE_LIMIT, float, setCloneableLimit);
READ_ENTITY_PROPERTY(PROP_CLONEABLE_DYNAMIC, bool, setCloneableDynamic);
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, somethingChanged);
@ -1275,6 +1290,11 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneableLifetime, getCloneableLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneableLimit, getCloneableLimit);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneableDynamic, getCloneableDynamic);
properties._defaultSettings = false;
return properties;
@ -1382,6 +1402,11 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneable, setCloneable);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneableLifetime, setCloneableLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneableLimit, setCloneableLimit);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneableDynamic, setCloneableDynamic);
if (updateQueryAACube()) {
somethingChanged = true;
}
@ -2975,3 +3000,76 @@ std::unordered_map<std::string, graphics::MultiMaterial> EntityItem::getMaterial
}
return toReturn;
}
bool EntityItem::getCloneable() const {
bool result;
withReadLock([&] {
result = _cloneable;
});
return result;
}
void EntityItem::setCloneable(bool value) {
withWriteLock([&] {
_cloneable = value;
});
}
float EntityItem::getCloneableLifetime() const {
float result;
withReadLock([&] {
result = _cloneableLifetime;
});
return result;
}
void EntityItem::setCloneableLifetime(float value) {
withWriteLock([&] {
_cloneableLifetime = value;
});
}
float EntityItem::getCloneableLimit() const {
float result;
withReadLock([&] {
result = _cloneableLimit;
});
return result;
}
void EntityItem::setCloneableLimit(float value) {
withWriteLock([&] {
_cloneableLimit = value;
});
}
bool EntityItem::getCloneableDynamic() const {
bool result;
withReadLock([&] {
result = _cloneableDynamic;
});
return result;
}
void EntityItem::setCloneableDynamic(const bool value) {
withWriteLock([&] {
_cloneableDynamic = value;
});
}
bool EntityItem::addCloneID(const QUuid& cloneID) {
if (!_cloneIDs.contains(cloneID)) {
_cloneIDs.append(cloneID);
return true;
}
return false;
}
bool EntityItem::removeCloneID(const QUuid& cloneID) {
int index = _cloneIDs.indexOf(cloneID);
if (index > 0) {
_cloneIDs.removeAt(index);
return true;
}
return false;
}

View file

@ -341,6 +341,15 @@ public:
quint32 getStaticCertificateVersion() const;
void setStaticCertificateVersion(const quint32&);
bool getCloneable() const;
void setCloneable(bool value);
float getCloneableLifetime() const;
void setCloneableLifetime(float value);
float getCloneableLimit() const;
void setCloneableLimit(float value);
bool getCloneableDynamic() const;
void setCloneableDynamic(const bool value);
// TODO: get rid of users of getRadius()...
float getRadius() const;
@ -494,6 +503,12 @@ public:
void setSimulationOwnershipExpiry(uint64_t expiry) { _simulationOwnershipExpiry = expiry; }
uint64_t getSimulationOwnershipExpiry() const { return _simulationOwnershipExpiry; }
bool addCloneID(const QUuid& cloneID);
bool removeCloneID(const QUuid& cloneID);
const QList<QUuid>& getCloneIDs() const { return _cloneIDs; }
void setCloneParent(const QUuid& cloneParentID) { _cloneParentID = cloneParentID; }
const QUuid& getCloneParent() const { return _cloneParentID; }
signals:
void requestRenderUpdate();
@ -648,6 +663,14 @@ protected:
bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera
bool _cloneable;
float _cloneableLifetime;
float _cloneableLimit;
bool _cloneableDynamic;
QList<QUuid> _cloneIDs;
QUuid _cloneParentID;
private:
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;

View file

@ -436,6 +436,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE, cloneable);
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE_LIFETIME, cloneableLifetime);
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE_LIMIT, cloneableLimit);
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE_DYNAMIC, cloneableDynamic);
changedProperties += _animation.getChangedProperties();
changedProperties += _keyLight.getChangedProperties();
changedProperties += _ambientLight.getChangedProperties();
@ -1430,6 +1435,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE, cloneable);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE_LIFETIME, cloneableLifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE_LIMIT, cloneableLimit);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE_DYNAMIC, cloneableDynamic);
// Rendering info
if (!skipDefaults && !strictSemantics) {
QScriptValue renderInfo = engine->newObject();
@ -1642,6 +1652,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneable, bool, setCloneable);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneableLifetime, float, setCloneableLifetime);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneableLimit, float, setCloneableLimit);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneableDynamic, bool, setCloneableDynamic);
_lastEdited = usecTimestampNow();
}
@ -2017,6 +2032,11 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE, Cloneable, cloneable, bool);
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE_LIFETIME, CloneableLifetime, cloneableLifetime, float);
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE_LIMIT, CloneableLimit, cloneableLimit, float);
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE_DYNAMIC, CloneableDynamic, cloneableDynamic, bool);
// FIXME - these are not yet handled
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
@ -2331,6 +2351,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber());
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID());
APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, properties.getCloneable());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIFETIME, properties.getCloneableLifetime());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIMIT, properties.getCloneableLimit());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_DYNAMIC, properties.getCloneableDynamic());
}
if (propertyCount > 0) {
@ -2701,6 +2726,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE, bool, setCloneable);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE_LIFETIME, float, setCloneableLifetime);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE_LIMIT, float, setCloneableLimit);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE_DYNAMIC, bool, setCloneableDynamic);
return valid;
}
@ -2780,6 +2810,54 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt
return true;
}
bool EntityItemProperties::encodeCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID, QByteArray& buffer) {
char* copyAt = buffer.data();
int outputLength = 0;
if (buffer.size() < (int)(sizeof(NUM_BYTES_RFC4122_UUID) * 2)) {
qCDebug(entities) << "ERROR - encodeCloneEntityMessage() called with buffer that is too small!";
return false;
}
memcpy(copyAt, entityIDToClone.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
copyAt += NUM_BYTES_RFC4122_UUID;
outputLength += NUM_BYTES_RFC4122_UUID;
memcpy(copyAt, newEntityID.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
copyAt += NUM_BYTES_RFC4122_UUID;
outputLength += NUM_BYTES_RFC4122_UUID;
buffer.resize(outputLength);
return true;
}
bool EntityItemProperties::decodeCloneEntityMessage(const QByteArray& buffer, int& processedBytes, EntityItemID& entityIDToClone, EntityItemID& newEntityID) {
const unsigned char* packetData = (const unsigned char*)buffer.constData();
const unsigned char* dataAt = packetData;
size_t packetLength = buffer.size();
processedBytes = 0;
if (NUM_BYTES_RFC4122_UUID * 2 > packetLength) {
qCDebug(entities) << "EntityItemProperties::processEraseMessageDetails().... bailing because not enough bytes in buffer";
return false; // bail to prevent buffer overflow
}
QByteArray encodedID = buffer.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID);
entityIDToClone = QUuid::fromRfc4122(encodedID);
dataAt += encodedID.size();
processedBytes += encodedID.size();
encodedID = buffer.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID);
newEntityID = QUuid::fromRfc4122(encodedID);
dataAt += encodedID.size();
processedBytes += encodedID.size();
return true;
}
void EntityItemProperties::markAllChanged() {
_lastEditedByChanged = true;
_simulationOwnerChanged = true;
@ -2941,6 +3019,11 @@ void EntityItemProperties::markAllChanged() {
_dpiChanged = true;
_relayParentJointsChanged = true;
_cloneableChanged = true;
_cloneableLifetimeChanged = true;
_cloneableLimitChanged = true;
_cloneableDynamicChanged = true;
}
// The minimum bounding box for the entity.
@ -3373,6 +3456,20 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "isUVModeStretch";
}
if (cloneableChanged()) {
out += "cloneable";
}
if (cloneableLifetimeChanged()) {
out += "cloneableLifetime";
}
if (cloneableLimitChanged()) {
out += "cloneableLimit";
}
if (cloneableDynamicChanged()) {
out += "cloneableDynamic";
}
getAnimation().listChangedProperties(out);
getKeyLight().listChangedProperties(out);
getAmbientLight().listChangedProperties(out);

View file

@ -272,6 +272,11 @@ public:
DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS);
DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS);
DEFINE_PROPERTY(PROP_CLONEABLE, Cloneable, cloneable, bool, ENTITY_ITEM_CLONEABLE);
DEFINE_PROPERTY(PROP_CLONEABLE_LIFETIME, CloneableLifetime, cloneableLifetime, float, ENTITY_ITEM_CLONEABLE_LIFETIME);
DEFINE_PROPERTY(PROP_CLONEABLE_LIMIT, CloneableLimit, cloneableLimit, float, ENTITY_ITEM_CLONEABLE_LIMIT);
DEFINE_PROPERTY(PROP_CLONEABLE_DYNAMIC, CloneableDynamic, cloneableDynamic, bool, ENTITY_ITEM_CLONEABLE_DYNAMIC);
static QString getComponentModeString(uint32_t mode);
static QString getComponentModeAsString(uint32_t mode);
@ -294,6 +299,8 @@ public:
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
static bool encodeCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID, QByteArray& buffer);
static bool decodeCloneEntityMessage(const QByteArray& buffer, int& processedBytes, EntityItemID& entityIDToClone, EntityItemID& newEntityID);
static bool decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes,
EntityItemID& entityID, EntityItemProperties& properties);

View file

@ -97,4 +97,9 @@ const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid();
const bool ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS = false;
const bool ENTITY_ITEM_CLONEABLE = false;
const float ENTITY_ITEM_CLONEABLE_LIFETIME = 300.0f;
const int ENTITY_ITEM_CLONEABLE_LIMIT = 0;
const bool ENTITY_ITEM_CLONEABLE_DYNAMIC = false;
#endif // hifi_EntityItemPropertiesDefaults_h

View file

@ -242,6 +242,11 @@ enum EntityPropertyList {
PROP_MATERIAL_MAPPING_ROT,
PROP_MATERIAL_DATA,
PROP_CLONEABLE,
PROP_CLONEABLE_LIFETIME,
PROP_CLONEABLE_LIMIT,
PROP_CLONEABLE_DYNAMIC,
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties to end of list just ABOVE this line
PROP_AFTER_LAST_ITEM,

View file

@ -258,33 +258,9 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
EntityItemID id = EntityItemID(QUuid::createUuid());
EntityItemID id;
// If we have a local entity tree set, then also update it.
bool success = true;
if (_entityTree) {
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
if (entity) {
if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
bool success;
AACube queryAACube = entity->getQueryAACube(success);
if (success) {
propertiesWithSimID.setQueryAACube(queryAACube);
}
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
propertiesWithSimID.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;
}
});
}
bool success = addLocalEntityCopy(propertiesWithSimID, id);
// queue the packet
if (success) {
@ -295,6 +271,39 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
}
}
bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properties, EntityItemID& id) {
bool success = true;
id = EntityItemID(QUuid::createUuid());
if (_entityTree) {
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->addEntity(id, properties);
if (entity) {
if (properties.queryAACubeRelatedPropertyChanged()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
bool success;
AACube queryAACube = entity->getQueryAACube(success);
if (success) {
properties.setQueryAACube(queryAACube);
}
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
properties.setLastEdited(entity->getLastEdited());
}
else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;
}
});
}
return success;
}
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures,
const QString& shapeType, bool dynamic, bool collisionless,
const glm::vec3& position, const glm::vec3& gravity) {
@ -320,6 +329,18 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin
return addEntity(properties);
}
QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) {
EntityItemID newEntityID;
EntityItemProperties properties = getEntityProperties(entityIDToClone);
if (addLocalEntityCopy(properties, newEntityID)) {
qCDebug(entities) << "DBACK POOPY cloneEntity addLocalEntityCopy" << newEntityID;
getEntityPacketSender()->queueCloneEntityMessage(entityIDToClone, newEntityID);
return newEntityID;
} else {
return QUuid();
}
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
EntityPropertyFlags noSpecificProperties;
return getEntityProperties(identity, noSpecificProperties);

View file

@ -224,6 +224,8 @@ public slots:
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic,
bool collisionless, const glm::vec3& position, const glm::vec3& gravity);
Q_INVOKABLE QUuid cloneEntity(QUuid entityIDToClone);
/**jsdoc
* Get the properties of an entity.
* @function Entities.getEntityProperties
@ -1875,6 +1877,7 @@ private:
bool polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
bool addLocalEntityCopy(EntityItemProperties& propertiesWithSimID, EntityItemID& id);
EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID,
EntityTypes::EntityType entityType = EntityTypes::Unknown);

View file

@ -228,6 +228,7 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const {
// we handle these types of "edit" packets
switch (packetType) {
case PacketType::EntityAdd:
case PacketType::EntityClone:
case PacketType::EntityEdit:
case PacketType::EntityErase:
case PacketType::EntityPhysics:
@ -592,6 +593,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
return;
}
removeCloneIDFromCloneParent(entityID);
unhookChildAvatar(entityID);
emit deletingEntity(entityID);
emit deletingEntityPointer(existingEntity.get());
@ -625,6 +627,19 @@ void EntityTree::unhookChildAvatar(const EntityItemID entityID) {
});
}
void EntityTree::removeCloneIDFromCloneParent(const EntityItemID& entityID) {
EntityItemPointer entity = findEntityByEntityItemID(entityID);
if (entity) {
const QUuid& cloneParentID = entity->getCloneParent();
if (!cloneParentID.isNull()) {
EntityItemPointer cloneParent = findEntityByID(cloneParentID);
if (cloneParent) {
cloneParent->removeCloneID(entityID);
}
}
}
}
void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool ignoreWarnings) {
// NOTE: callers must lock the tree before using this method
DeleteEntityOperator theOperator(getThisPointer());
@ -653,6 +668,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
}
// tell our delete operator about this entityID
removeCloneIDFromCloneParent(entityID);
unhookChildAvatar(entityID);
theOperator.addEntityIDToDeleteList(entityID);
emit deletingEntity(entityID);
@ -1392,6 +1408,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
int processedBytes = 0;
bool isAdd = false;
bool isClone = false;
// we handle these types of "edit" packets
switch (message.getType()) {
case PacketType::EntityErase: {
@ -1400,8 +1417,10 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
break;
}
case PacketType::EntityClone:
isClone = true; // fall through to next case
case PacketType::EntityAdd:
isAdd = true; // fall through to next case
isAdd = true; // fall through to next case
// FALLTHRU
case PacketType::EntityPhysics:
case PacketType::EntityEdit: {
@ -1422,8 +1441,21 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
EntityItemProperties properties;
startDecode = usecTimestampNow();
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes,
entityItemID, properties);
bool validEditPacket = false;
EntityItemID entityIDToClone;
EntityItemPointer entityToClone;
if (isClone) {
QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<const char*>(editData), maxLength);
validEditPacket = EntityItemProperties::decodeCloneEntityMessage(buffer, processedBytes, entityIDToClone, entityItemID);
entityToClone = findEntityByEntityItemID(entityIDToClone);
if (entityToClone) {
properties = entityToClone->getProperties();
}
}
else {
validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties);
}
endDecode = usecTimestampNow();
EntityItemPointer existingEntity;
@ -1491,24 +1523,26 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
}
if ((isAdd || properties.lifetimeChanged()) &&
((!senderNode->getCanRez() && senderNode->getCanRezTmp()) ||
(!senderNode->getCanRezCertified() && senderNode->getCanRezTmpCertified()))) {
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
properties.getLifetime() > _maxTmpEntityLifetime) {
properties.setLifetime(_maxTmpEntityLifetime);
if (!isClone) {
if ((isAdd || properties.lifetimeChanged()) &&
((!senderNode->getCanRez() && senderNode->getCanRezTmp()) ||
(!senderNode->getCanRezCertified() && senderNode->getCanRezTmpCertified()))) {
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
properties.getLifetime() > _maxTmpEntityLifetime) {
properties.setLifetime(_maxTmpEntityLifetime);
bumpTimestamp(properties);
}
}
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
// clear the locked property and allow the unlocked entity to be created.
properties.setLocked(false);
bumpTimestamp(properties);
}
}
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
// clear the locked property and allow the unlocked entity to be created.
properties.setLocked(false);
bumpTimestamp(properties);
}
// If we got a valid edit packet, then it could be a new entity or it could be an update to
// an existing entity... handle appropriately
if (validEditPacket) {
@ -1566,17 +1600,39 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
} else if (isAdd) {
bool failedAdd = !allowed;
bool isCertified = !properties.getCertificateID().isEmpty();
bool isCloneable = properties.getCloneable();
int cloneLimit = properties.getCloneableLimit();
if (!allowed) {
qCDebug(entities) << "Filtered entity add. ID:" << entityItemID;
} else if (!isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
} else if (!isClone && !isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
failedAdd = true;
qCDebug(entities) << "User without 'uncertified rez rights' [" << senderNode->getUUID()
<< "] attempted to add an uncertified entity with ID:" << entityItemID;
} else if (isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
} else if (!isClone && isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
failedAdd = true;
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
<< "] attempted to add a certified entity with ID:" << entityItemID;
} else if (isClone && isCertified) {
failedAdd = true;
qCDebug(entities) << "User attempted to clone certified entity from entity ID:" << entityIDToClone;
} else if (isClone && !isCloneable) {
failedAdd = true;
qCDebug(entities) << "User attempted to clone non-cloneable entity from entity ID:" << entityIDToClone;
} else if (isClone && entityToClone && entityToClone->getCloneIDs().size() >= cloneLimit) {
failedAdd = true;
qCDebug(entities) << "User attempted to clone entity ID:" << entityIDToClone << " which reached it's cloneable limit.";
} else {
if (isClone) {
properties.setName(properties.getName() + "-clone-" + entityIDToClone.toString());
properties.setLocked(false);
properties.setLifetime(properties.getCloneableLifetime());
properties.setDynamic(properties.getCloneableDynamic());
properties.setCloneable(ENTITY_ITEM_CLONEABLE);
properties.setCloneableLifetime(ENTITY_ITEM_CLONEABLE_LIFETIME);
properties.setCloneableLimit(ENTITY_ITEM_CLONEABLE_LIMIT);
properties.setCloneableDynamic(ENTITY_ITEM_CLONEABLE_DYNAMIC);
}
// this is a new entity... assign a new entityID
properties.setCreated(properties.getLastEdited());
properties.setLastEditedBy(senderNode->getUUID());
@ -1600,6 +1656,11 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
if (newEntity) {
newEntity->markAsChangedOnServer();
notifyNewlyCreatedEntity(*newEntity, senderNode);
if (isClone) {
entityToClone->addCloneID(newEntity->getEntityItemID());
newEntity->setCloneParent(entityIDToClone);
qCDebug(entities) << "DBACK POOPY addedEntity clone " << newEntity->getEntityItemID();
}
startLogging = usecTimestampNow();
if (wantEditLogging()) {

View file

@ -117,6 +117,7 @@ public:
// check if the avatar is a child of this entity, If so set the avatar parentID to null
void unhookChildAvatar(const EntityItemID entityID);
void removeCloneIDFromCloneParent(const EntityItemID& entityID);
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = true);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = true);

View file

@ -99,7 +99,7 @@ Buffer::Update::Update(const Buffer& parent) : buffer(parent) {
void Buffer::Update::apply() const {
// Make sure we're loaded in order
buffer._applyUpdateCount++;
assert(buffer._applyUpdateCount == updateNumber);
//assert(buffer._applyUpdateCount == updateNumber);
const auto pageSize = buffer._pages._pageSize;
buffer._renderSysmem.resize(size);

View file

@ -29,10 +29,11 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::DomainList:
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport);
case PacketType::EntityAdd:
case PacketType::EntityClone:
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return static_cast<PacketVersion>(EntityVersion::MaterialData);
return static_cast<PacketVersion>(EntityVersion::CloneableData);
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
case PacketType::AvatarIdentity:

View file

@ -75,6 +75,7 @@ public:
EntityData,
EntityQuery,
EntityAdd,
EntityClone,
EntityErase,
EntityEdit,
DomainServerConnectionToken,
@ -232,7 +233,8 @@ enum class EntityVersion : PacketVersion {
SoftEntities,
MaterialEntities,
ShadowControl,
MaterialData
MaterialData,
CloneableData
};
enum class EntityScriptCallMethodVersion : PacketVersion {

View file

@ -1040,12 +1040,12 @@ function loaded() {
elWantsTrigger.checked = false;
elIgnoreIK.checked = true;
elCloneable.checked = false;
elCloneableDynamic.checked = false;
elCloneable.checked = properties.cloneable;
elCloneableDynamic.checked = properties.cloneableDynamic;
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
elCloneableLimit.value = 0;
elCloneableLifetime.value = 300;
elCloneableLimit.value = properties.cloneableLimit;
elCloneableLifetime.value = properties.cloneableLifetime;
var grabbablesSet = false;
var parsedUserData = {};
try {
@ -1069,27 +1069,6 @@ function loaded() {
} else {
elIgnoreIK.checked = true;
}
if ("cloneable" in grabbableData) {
elCloneable.checked = grabbableData.cloneable;
elCloneableGroup.style.display = elCloneable.checked ? "block" : "none";
elCloneableDynamic.checked =
grabbableData.cloneDynamic ? grabbableData.cloneDynamic : properties.dynamic;
if (elCloneable.checked) {
if ("cloneLifetime" in grabbableData) {
elCloneableLifetime.value =
grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300;
}
if ("cloneLimit" in grabbableData) {
elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0;
}
if ("cloneAvatarEntity" in grabbableData) {
elCloneableAvatarEntity.checked =
grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false;
}
}
} else {
elCloneable.checked = false;
}
}
} catch (e) {
// TODO: What should go here?
@ -1460,45 +1439,11 @@ function loaded() {
}
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true);
});
elCloneableDynamic.addEventListener('change', function(event) {
userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1);
});
elCloneableAvatarEntity.addEventListener('change', function(event) {
userDataChanger("grabbableKey", "cloneAvatarEntity", event.target, elUserData, -1);
});
elCloneable.addEventListener('change', function (event) {
var checked = event.target.checked;
if (checked) {
multiDataUpdater("grabbableKey", {
cloneLifetime: elCloneableLifetime,
cloneLimit: elCloneableLimit,
cloneDynamic: elCloneableDynamic,
cloneAvatarEntity: elCloneableAvatarEntity,
cloneable: event.target,
grabbable: null
}, elUserData, {});
elCloneableGroup.style.display = "block";
updateProperty('dynamic', false);
} else {
multiDataUpdater("grabbableKey", {
cloneLifetime: null,
cloneLimit: null,
cloneDynamic: null,
cloneAvatarEntity: null,
cloneable: false
}, elUserData, {});
elCloneableGroup.style.display = "none";
}
});
var numberListener = function (event) {
userDataChanger("grabbableKey",
event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false);
};
elCloneableLifetime.addEventListener('change', numberListener);
elCloneableLimit.addEventListener('change', numberListener);
elCloneable.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneable'));
elCloneableDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneableDynamic'));
elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneableLifetime'));
elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneableLimit'));
elWantsTrigger.addEventListener('change', function() {
userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false);

View file

@ -33,8 +33,7 @@ if (typeof Object.assign !== 'function') {
entityIsCloneable = function(props) {
if (props) {
var grabbableData = getGrabbableData(props);
return grabbableData.cloneable;
return props.cloneable;
}
return false;
};
@ -42,56 +41,19 @@ entityIsCloneable = function(props) {
propsAreCloneDynamic = function(props) {
var cloneable = entityIsCloneable(props);
if (cloneable) {
var grabInfo = getGrabbableData(props);
if (grabInfo.cloneDynamic) {
return true;
}
return props.cloneableDynamic;
}
return false;
};
cloneEntity = function(props, worldEntityProps) {
// we need all the properties, for this
var cloneableProps = Entities.getEntityProperties(props.id);
var count = 0;
worldEntityProps.forEach(function(itemWE) {
if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) {
count++;
}
});
var grabInfo = getGrabbableData(cloneableProps);
var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0;
if (count >= limit && limit !== 0) {
return null;
}
cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id;
var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300;
var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false;
var triggerable = grabInfo.triggerable ? grabInfo.triggerable : false;
var avatarEntity = grabInfo.cloneAvatarEntity ? grabInfo.cloneAvatarEntity : false;
var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData));
var cProperties = Object.assign({}, cloneableProps);
delete cUserData.grabbableKey.cloneLifetime;
delete cUserData.grabbableKey.cloneable;
delete cUserData.grabbableKey.cloneDynamic;
delete cUserData.grabbableKey.cloneLimit;
delete cUserData.grabbableKey.cloneAvatarEntity;
delete cProperties.id;
cProperties.dynamic = dynamic;
cProperties.locked = false;
cUserData.grabbableKey.triggerable = triggerable;
cUserData.grabbableKey.grabbable = true;
cProperties.lifetime = lifetime;
cProperties.userData = JSON.stringify(cUserData);
var cloneID = Entities.addEntity(cProperties, avatarEntity);
return cloneID;
var entityToClone = props.id;
var certificateID = Entities.getEntityProperties(entityToClone, ['certificateID']).certificateID;
// ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits
// will now be handled by the server where the entity add will fail if limit reached
if (entityIsCloneable(props) && (certificateID === undefined || certificateID.length === 0)) {
var cloneID = Entities.cloneEntity(entityToClone);
return cloneID;
}
return null;
};

View file

@ -125,7 +125,8 @@ DISPATCHER_PROPERTIES = [
"dimensions",
"userData",
"type",
"href"
"href",
"cloneable"
];
// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step