mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 16:38:27 +02:00
send traits in bulk to avatar mixer client
This commit is contained in:
parent
f23a036f4a
commit
26a1f03314
8 changed files with 188 additions and 11 deletions
|
@ -17,7 +17,8 @@
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
|
|
||||||
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
|
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
|
||||||
NodeData(nodeID)
|
NodeData(nodeID),
|
||||||
|
_receivedSimpleTraitVersions(AvatarTraits::SimpleTraitTypes.size())
|
||||||
{
|
{
|
||||||
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
|
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
|
||||||
_avatar->setID(nodeID);
|
_avatar->setID(nodeID);
|
||||||
|
@ -92,7 +93,41 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) {
|
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) {
|
||||||
qDebug() << "Pulling a traits message of" << message.getSize();
|
// pull the trait version from the message
|
||||||
|
AvatarTraits::TraitVersion packetTraitVersion;
|
||||||
|
message.readPrimitive(&packetTraitVersion);
|
||||||
|
|
||||||
|
bool anyTraitsChanged = false;
|
||||||
|
|
||||||
|
while (message.getBytesLeftToRead() > 0) {
|
||||||
|
// for each trait in the packet, apply it if the trait version is newer than what we have
|
||||||
|
|
||||||
|
AvatarTraits::TraitType traitType;
|
||||||
|
message.readPrimitive(&traitType);
|
||||||
|
|
||||||
|
AvatarTraits::TraitWireSize traitSize;
|
||||||
|
message.readPrimitive(&traitSize);
|
||||||
|
|
||||||
|
if (packetTraitVersion > _receivedSimpleTraitVersions[traitType]) {
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||||
|
// get the URL from the binary data
|
||||||
|
auto skeletonModelURL = QUrl::fromEncoded(message.read(traitSize));
|
||||||
|
_avatar->setSkeletonModelURL(skeletonModelURL);
|
||||||
|
|
||||||
|
qDebug() << "Set skeleton URL to" << skeletonModelURL << "for trait packet version" << packetTraitVersion;
|
||||||
|
|
||||||
|
_receivedSimpleTraitVersions[traitType] = packetTraitVersion;
|
||||||
|
|
||||||
|
anyTraitsChanged = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.seek(message.getPosition() + traitSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyTraitsChanged) {
|
||||||
|
_lastReceivedTraitsChange = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
|
uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
|
||||||
|
@ -172,3 +207,39 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
||||||
jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView;
|
jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView;
|
||||||
jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView;
|
jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const {
|
||||||
|
auto it = _lastSentTraitsTimestamps.find(otherAvatar);
|
||||||
|
|
||||||
|
if (it != _lastSentTraitsTimestamps.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return TraitsCheckTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersion AvatarMixerClientData::getLastSentSimpleTraitVersion(Node::LocalID otherAvatar,
|
||||||
|
AvatarTraits::TraitType traitType) const {
|
||||||
|
auto it = _sentSimpleTraitVersions.find(otherAvatar);
|
||||||
|
|
||||||
|
if (it != _sentSimpleTraitVersions.end()) {
|
||||||
|
return it->second[traitType];
|
||||||
|
}
|
||||||
|
|
||||||
|
return AvatarTraits::DEFAULT_TRAIT_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarMixerClientData::setLastSentSimpleTraitVersion(Node::LocalID otherAvatar, AvatarTraits::TraitType traitType, AvatarTraits::TraitVersion traitVersion) {
|
||||||
|
|
||||||
|
auto it = _sentSimpleTraitVersions.find(otherAvatar);
|
||||||
|
|
||||||
|
if (it == _sentSimpleTraitVersions.end()) {
|
||||||
|
auto pair = _sentSimpleTraitVersions.insert({
|
||||||
|
otherAvatar, { AvatarTraits::TotalTraitTypes, AvatarTraits::DEFAULT_TRAIT_VERSION }
|
||||||
|
});
|
||||||
|
|
||||||
|
it = pair.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
it->second[traitType] = traitVersion;
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
|
#include <AvatarTraits.h>
|
||||||
#include <NodeData.h>
|
#include <NodeData.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
|
@ -122,6 +123,20 @@ public:
|
||||||
|
|
||||||
void processSetTraitsMessage(ReceivedMessage& message);
|
void processSetTraitsMessage(ReceivedMessage& message);
|
||||||
|
|
||||||
|
using TraitsCheckTimestamp = std::chrono::steady_clock::time_point;
|
||||||
|
|
||||||
|
TraitsCheckTimestamp getLastReceivedTraitsChange() const { return _lastReceivedTraitsChange; }
|
||||||
|
AvatarTraits::TraitVersion getLastReceivedSimpleTraitVersion(AvatarTraits::TraitType traitType) const
|
||||||
|
{ return _receivedSimpleTraitVersions[traitType]; }
|
||||||
|
|
||||||
|
TraitsCheckTimestamp getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const;
|
||||||
|
void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint)
|
||||||
|
{ _lastSentTraitsTimestamps[otherAvatar] = sendPoint; }
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersion getLastSentSimpleTraitVersion(Node::LocalID otherAvatar, AvatarTraits::TraitType traitType) const;
|
||||||
|
void setLastSentSimpleTraitVersion(Node::LocalID otherAvatar, AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitVersion traitVersion);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
||||||
QWeakPointer<Node> node;
|
QWeakPointer<Node> node;
|
||||||
|
@ -158,6 +173,12 @@ private:
|
||||||
int _recentOtherAvatarsOutOfView { 0 };
|
int _recentOtherAvatarsOutOfView { 0 };
|
||||||
QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary.
|
QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary.
|
||||||
bool _requestsDomainListData { false };
|
bool _requestsDomainListData { false };
|
||||||
|
|
||||||
|
AvatarTraits::SimpleTraitVersions _receivedSimpleTraitVersions;
|
||||||
|
TraitsCheckTimestamp _lastReceivedTraitsChange;
|
||||||
|
|
||||||
|
std::unordered_map<Node::LocalID, TraitsCheckTimestamp> _lastSentTraitsTimestamps;
|
||||||
|
std::unordered_map<Node::LocalID, AvatarTraits::SimpleTraitVersions> _sentSimpleTraitVersions;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarMixerClientData_h
|
#endif // hifi_AvatarMixerClientData_h
|
||||||
|
|
|
@ -79,6 +79,61 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
|
||||||
|
const AvatarMixerClientData* sendingNodeData,
|
||||||
|
NLPacketList& traitsPacketList) {
|
||||||
|
|
||||||
|
auto otherNodeLocalID = sendingNodeData->getNodeLocalID();
|
||||||
|
|
||||||
|
// Perform a simple check with two server clock time points
|
||||||
|
// to see if there is any new traits data for this avatar that we need to send
|
||||||
|
auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID);
|
||||||
|
auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange();
|
||||||
|
|
||||||
|
if (timeOfLastTraitsChange > timeOfLastTraitsSent) {
|
||||||
|
// there is definitely new traits data to send
|
||||||
|
|
||||||
|
// add the avatar ID to mark the beginning of traits for this avatar
|
||||||
|
traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122());
|
||||||
|
|
||||||
|
auto sendingAvatar = sendingNodeData->getAvatarSharedPointer();
|
||||||
|
|
||||||
|
// compare trait versions so we can see what exactly needs to go out
|
||||||
|
for (int i = 0; i < AvatarTraits::TotalTraitTypes; ++i) {
|
||||||
|
AvatarTraits::TraitType traitType = static_cast<AvatarTraits::TraitType>(i);
|
||||||
|
|
||||||
|
auto lastSentVersion = listeningNodeData->getLastSentSimpleTraitVersion(otherNodeLocalID, traitType);
|
||||||
|
auto lastReceivedVersion = sendingNodeData->getLastReceivedSimpleTraitVersion(traitType);
|
||||||
|
|
||||||
|
if (lastReceivedVersion > lastSentVersion) {
|
||||||
|
// there is an update to this trait, add it to the traits packet
|
||||||
|
|
||||||
|
// write the trait type and the trait version
|
||||||
|
traitsPacketList.writePrimitive(traitType);
|
||||||
|
traitsPacketList.writePrimitive(lastReceivedVersion);
|
||||||
|
|
||||||
|
// update the last sent version since we're adding this to the packet
|
||||||
|
listeningNodeData->setLastSentSimpleTraitVersion(otherNodeLocalID, traitType, lastReceivedVersion);
|
||||||
|
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||||
|
// get an encoded version of the URL, write its size and then the data itself
|
||||||
|
auto encodedSkeletonURL = sendingAvatar->getSkeletonModelURL().toEncoded();
|
||||||
|
|
||||||
|
traitsPacketList.writePrimitive(uint16_t(encodedSkeletonURL.size()));
|
||||||
|
traitsPacketList.write(encodedSkeletonURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write a null trait type to mark the end of trait data for this avatar
|
||||||
|
traitsPacketList.writePrimitive(AvatarTraits::NullTrait);
|
||||||
|
|
||||||
|
// since we send all traits for this other avatar, update the time of last traits sent
|
||||||
|
// to match the time of last traits change
|
||||||
|
listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) {
|
int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) {
|
||||||
if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) {
|
if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) {
|
||||||
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true);
|
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true);
|
||||||
|
@ -326,6 +381,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
||||||
|
|
||||||
int remainingAvatars = (int)sortedAvatars.size();
|
int remainingAvatars = (int)sortedAvatars.size();
|
||||||
|
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
||||||
while (!sortedAvatars.empty()) {
|
while (!sortedAvatars.empty()) {
|
||||||
const auto avatarData = sortedAvatars.top().getAvatar();
|
const auto avatarData = sortedAvatars.top().getAvatar();
|
||||||
sortedAvatars.pop();
|
sortedAvatars.pop();
|
||||||
|
@ -392,11 +448,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||||
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
|
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition,
|
||||||
|
&lastSentJointsForOther);
|
||||||
quint64 end = usecTimestampNow();
|
quint64 end = usecTimestampNow();
|
||||||
_stats.toByteArrayElapsedTime += (end - start);
|
_stats.toByteArrayElapsedTime += (end - start);
|
||||||
|
|
||||||
auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
||||||
if (bytes.size() > maxAvatarDataBytes) {
|
if (bytes.size() > maxAvatarDataBytes) {
|
||||||
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
||||||
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
|
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
|
||||||
|
@ -445,6 +502,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
quint64 endAvatarDataPacking = usecTimestampNow();
|
quint64 endAvatarDataPacking = usecTimestampNow();
|
||||||
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
||||||
|
|
||||||
|
// use helper to add any changed traits to our packet list
|
||||||
|
addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 startPacketSending = usecTimestampNow();
|
quint64 startPacketSending = usecTimestampNow();
|
||||||
|
@ -461,6 +521,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||||
nodeData->recordSentAvatarData(numAvatarDataBytes);
|
nodeData->recordSentAvatarData(numAvatarDataBytes);
|
||||||
|
|
||||||
|
// close the current traits packet list
|
||||||
|
traitsPacketList->closeCurrentPacket();
|
||||||
|
|
||||||
|
if (traitsPacketList->getNumPackets() >= 1) {
|
||||||
|
// send the traits packet list
|
||||||
|
nodeList->sendPacketList(std::move(traitsPacketList), *node);
|
||||||
|
}
|
||||||
|
|
||||||
// record the number of avatars held back this frame
|
// record the number of avatars held back this frame
|
||||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||||
|
|
|
@ -99,6 +99,10 @@ private:
|
||||||
int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||||
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
||||||
|
|
||||||
|
void addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
|
||||||
|
const AvatarMixerClientData* sendingNodeData,
|
||||||
|
NLPacketList& traitsPacketList);
|
||||||
|
|
||||||
void broadcastAvatarDataToAgent(const SharedNodePointer& node);
|
void broadcastAvatarDataToAgent(const SharedNodePointer& node);
|
||||||
void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node);
|
void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node);
|
||||||
|
|
||||||
|
|
|
@ -13,15 +13,25 @@
|
||||||
#define hifi_AvatarTraits_h
|
#define hifi_AvatarTraits_h
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace AvatarTraits {
|
namespace AvatarTraits {
|
||||||
enum Trait : uint8_t {
|
enum TraitType : int8_t {
|
||||||
|
NullTrait = -1,
|
||||||
SkeletonModelURL,
|
SkeletonModelURL,
|
||||||
TotalTraits
|
TotalTraitTypes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using TraitTypeSet = std::set<TraitType>;
|
||||||
|
const TraitTypeSet SimpleTraitTypes = { SkeletonModelURL };
|
||||||
|
|
||||||
using TraitVersion = uint32_t;
|
using TraitVersion = uint32_t;
|
||||||
const TraitVersion DEFAULT_TRAIT_VERSION = 0;
|
const TraitVersion DEFAULT_TRAIT_VERSION = 0;
|
||||||
|
|
||||||
|
using TraitWireSize = uint16_t;
|
||||||
|
|
||||||
|
using SimpleTraitVersions = std::vector<TraitVersion>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // hifi_AvatarTraits_h
|
#endif // hifi_AvatarTraits_h
|
||||||
|
|
|
@ -68,9 +68,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
||||||
|
|
||||||
auto encodedSkeletonURL = _owningAvatar->getSkeletonModelURL().toEncoded();
|
auto encodedSkeletonURL = _owningAvatar->getSkeletonModelURL().toEncoded();
|
||||||
|
|
||||||
uint16_t encodedURLSize = encodedSkeletonURL.size();
|
AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
|
||||||
traitsPacketList->writePrimitive(encodedURLSize);
|
traitsPacketList->writePrimitive(encodedURLSize);
|
||||||
|
|
||||||
|
qDebug() << "Sending trait of size" << encodedURLSize;
|
||||||
|
|
||||||
traitsPacketList->write(encodedSkeletonURL);
|
traitsPacketList->write(encodedSkeletonURL);
|
||||||
|
|
||||||
traitsPacketList->endSegment();
|
traitsPacketList->endSegment();
|
||||||
|
|
|
@ -25,16 +25,16 @@ public:
|
||||||
void sendChangedTraitsToMixer();
|
void sendChangedTraitsToMixer();
|
||||||
|
|
||||||
bool hasChangedTraits() { return _changedTraits.size(); }
|
bool hasChangedTraits() { return _changedTraits.size(); }
|
||||||
void markTraitChanged(AvatarTraits::Trait changedTrait) { _changedTraits.insert(changedTrait); }
|
void markTraitChanged(AvatarTraits::TraitType changedTrait) { _changedTraits.insert(changedTrait); }
|
||||||
|
|
||||||
bool hasTraitChanged(AvatarTraits::Trait checkTrait) { return _changedTraits.count(checkTrait) > 0; }
|
bool hasTraitChanged(AvatarTraits::TraitType checkTrait) { return _changedTraits.count(checkTrait) > 0; }
|
||||||
|
|
||||||
void resetForNewMixer();
|
void resetForNewMixer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AvatarData* _owningAvatar;
|
AvatarData* _owningAvatar;
|
||||||
|
|
||||||
std::set<AvatarTraits::Trait> _changedTraits;
|
AvatarTraits::TraitTypeSet _changedTraits;
|
||||||
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
|
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
|
||||||
bool _performInitialSend { false };
|
bool _performInitialSend { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -133,6 +133,7 @@ public:
|
||||||
|
|
||||||
EntityClone,
|
EntityClone,
|
||||||
EntityQueryInitialResultsComplete,
|
EntityQueryInitialResultsComplete,
|
||||||
|
BulkAvatarTraits,
|
||||||
|
|
||||||
NUM_PACKET_TYPE
|
NUM_PACKET_TYPE
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue