From 9b1ac8b71456b33bf652b9f15b5bbc16a9acb5c7 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Fri, 6 Mar 2026 15:49:24 +0200 Subject: [PATCH 01/13] more Network --- Client/App/App.vcproj | 9 +- Client/App/include/util/Events.h | 8 +- Client/App/include/util/HitTestFilter.h | 7 +- Client/App/include/util/Object.h | 15 +- Client/App/include/util/Profiling.h | 2 +- Client/App/include/v8datamodel/Filters.h | 37 ++ Client/App/include/v8world/Mechanism.h | 31 +- Client/App/include/v8world/SimJobStage.h | 5 +- Client/App/util/Profiling.cpp | 2 +- Client/App/v8datamodel/Filters.cpp | 32 + Client/Network/API.cpp | 19 + Client/Network/Client.h | 4 +- Client/Network/IdManager.h | 6 +- Client/Network/Network.vcproj | 16 + Client/Network/NetworkSettings.cpp | 37 ++ Client/Network/NetworkSettings.h | 29 + Client/Network/Replicator.cpp | 593 +++++++++++++++++- Client/Network/Replicator.h | 306 ++++++++- Client/Network/Server.cpp | 80 +++ Client/Network/Server.h | 75 +++ Client/Network/Streaming.cpp | 12 + Client/Network/Streaming.h | 14 +- Client/Network/include/Network/Player.h | 9 - Client/Network/include/Network/Players.h | 26 - .../include/Network/SuperSafeChanged.h | 2 + 25 files changed, 1286 insertions(+), 90 deletions(-) create mode 100644 Client/App/include/v8datamodel/Filters.h create mode 100644 Client/App/v8datamodel/Filters.cpp create mode 100644 Client/Network/NetworkSettings.cpp create mode 100644 Client/Network/NetworkSettings.h create mode 100644 Client/Network/Server.cpp create mode 100644 Client/Network/Server.h diff --git a/Client/App/App.vcproj b/Client/App/App.vcproj index d37b5c48..d6b6f7fb 100644 --- a/Client/App/App.vcproj +++ b/Client/App/App.vcproj @@ -809,6 +809,10 @@ RelativePath=".\include\v8datamodel\Feature.h" > + + @@ -1314,6 +1318,10 @@ RelativePath=".\v8datamodel\Feature.cpp" > + + @@ -1388,7 +1396,6 @@ diff --git a/Client/App/include/util/Events.h b/Client/App/include/util/Events.h index 72c62ef8..2d1ea7da 100644 --- a/Client/App/include/util/Events.h +++ b/Client/App/include/util/Events.h @@ -17,18 +17,14 @@ namespace RBX }; template - class Listener + class __declspec(novtable) Listener { friend class Notifier; protected: virtual void onEvent(const Class*, Event) = 0; Listener& operator=(const Listener&); - virtual ~Listener(); - - public: - //Listener(const Listener&); - Listener(); + virtual ~Listener() {} }; template diff --git a/Client/App/include/util/HitTestFilter.h b/Client/App/include/util/HitTestFilter.h index 8f15d2d9..6427e03e 100644 --- a/Client/App/include/util/HitTestFilter.h +++ b/Client/App/include/util/HitTestFilter.h @@ -15,11 +15,6 @@ namespace RBX }; public: - virtual Result filterResult(const Primitive*) const = 0; - public: - //HitTestFilter(const HitTestFilter&); - HitTestFilter(); - public: - //HitTestFilter& operator=(const HitTestFilter&); + virtual Result filterResult(const Primitive* testMe) const = 0; }; } diff --git a/Client/App/include/util/Object.h b/Client/App/include/util/Object.h index 8b98e521..43afa4ff 100644 --- a/Client/App/include/util/Object.h +++ b/Client/App/include/util/Object.h @@ -5,11 +5,22 @@ namespace RBX { - // TODO: check if matches template boost::shared_ptr shared_from(Class* r) { - return boost::shared_static_cast(r->shared_from_this()); + return r ? boost::shared_static_cast(r->shared_from_this()) : boost::shared_ptr(); + } + + template + boost::shared_ptr shared_from_dynamic_cast(boost::enable_shared_from_this* r) + { + return r ? boost::shared_dynamic_cast(r->shared_from_this()) : boost::shared_ptr(); + } + + template + boost::shared_ptr shared_from_polymorphic_downcast(boost::enable_shared_from_this* r) + { + return r ? boost::shared_polymorphic_downcast(r->shared_from_this()) : boost::shared_ptr(); } class Object diff --git a/Client/App/include/util/Profiling.h b/Client/App/include/util/Profiling.h index 50c3ba52..4ed5bd57 100644 --- a/Client/App/include/util/Profiling.h +++ b/Client/App/include/util/Profiling.h @@ -59,7 +59,7 @@ namespace RBX //ThreadProfiler(const ThreadProfiler&); ThreadProfiler(const char* name); public: - void sample(void* thread); + void sample(HANDLE thread); public: ~ThreadProfiler() {} public: diff --git a/Client/App/include/v8datamodel/Filters.h b/Client/App/include/v8datamodel/Filters.h new file mode 100644 index 00000000..198fc7f0 --- /dev/null +++ b/Client/App/include/v8datamodel/Filters.h @@ -0,0 +1,37 @@ +#pragma once +#include "util/HitTestFilter.h" +#include "v8datamodel/ModelInstance.h" +#include "v8datamodel/PartInstance.h" +#include + +namespace RBX +{ + class Unlocked : public HitTestFilter + { + public: + virtual HitTestFilter::Result filterResult(const Primitive* testMe) const + { + return unlocked(testMe) ? HitTestFilter::INCLUDE_PRIM : HitTestFilter::STOP_TEST; + } + + static bool unlocked(const Primitive* testMe); + }; + + class PartByLocalCharacter : public HitTestFilter + { + protected: + boost::shared_ptr character; + boost::shared_ptr head; + + public: + PartByLocalCharacter(Instance* root); + virtual HitTestFilter::Result filterResult(const Primitive* testMe) const; + }; + + class UnlockedPartByLocalCharacter : public PartByLocalCharacter + { + public: + UnlockedPartByLocalCharacter(Instance*); + virtual HitTestFilter::Result filterResult(const Primitive* testMe) const; + }; +} diff --git a/Client/App/include/v8world/Mechanism.h b/Client/App/include/v8world/Mechanism.h index d7a147a8..65e45bae 100644 --- a/Client/App/include/v8world/Mechanism.h +++ b/Client/App/include/v8world/Mechanism.h @@ -18,9 +18,22 @@ namespace RBX std::vector trackers; public: std::list::iterator myIt; - Mechanism::~Mechanism() { RBXASSERT(this->trackers.size() == 0); } - const std::set& getAssemblies() const {return assemblies;} - std::set& getAssemblies() {return assemblies;} + + Mechanism::~Mechanism() + { + RBXASSERT(this->trackers.size() == 0); + } + + const std::set& getAssemblies() const + { + return assemblies; + } + + std::set& getAssemblies() + { + return assemblies; + } + void notifyMovingPrimitives(); void insertAssembly(Assembly* a); void removeAssembly(Assembly* a); @@ -38,8 +51,16 @@ namespace RBX bool containedBy(Mechanism*); void stopTracking(); public: - //MechanismTracker(); - ~MechanismTracker() {this->stopTracking();} + MechanismTracker() + : mechanism(NULL) + { + } + + ~MechanismTracker() + { + this->stopTracking(); + } + bool tracking(); void setMechanism(Mechanism*); Mechanism* getMechanism(); diff --git a/Client/App/include/v8world/SimJobStage.h b/Client/App/include/v8world/SimJobStage.h index a4066d7e..936590f0 100644 --- a/Client/App/include/v8world/SimJobStage.h +++ b/Client/App/include/v8world/SimJobStage.h @@ -23,7 +23,6 @@ namespace RBX public: Mechanism* nextMechanism(std::list& list, const Mechanism* current); public: - //SimJobStage(const SimJobStage&); SimJobStage(IStage* upstream, World* world); virtual ~SimJobStage(); public: @@ -36,6 +35,8 @@ namespace RBX void onAssemblyAdded(Assembly* a); void onAssemblyRemoving(Assembly* a); void notifyMovingPrimitives(); - //SimJobStage& operator=(const SimJobStage&); + + template + void reportMechanisms(Class& callback, MechanismTracker& tracker, const Mechanism* ignore); }; } diff --git a/Client/App/util/Profiling.cpp b/Client/App/util/Profiling.cpp index 7f4b9b03..16c8107c 100644 --- a/Client/App/util/Profiling.cpp +++ b/Client/App/util/Profiling.cpp @@ -41,7 +41,7 @@ namespace RBX { } - void ThreadProfiler::sample(void* thread) + void ThreadProfiler::sample(HANDLE thread) { double time = G3D::System::getTick(); if (bucketTimeSpan + lastSampleTime <= time) diff --git a/Client/App/v8datamodel/Filters.cpp b/Client/App/v8datamodel/Filters.cpp new file mode 100644 index 00000000..364569a1 --- /dev/null +++ b/Client/App/v8datamodel/Filters.cpp @@ -0,0 +1,32 @@ +#include "v8datamodel/Filters.h" +#include "humanoid/Humanoid.h" +#include "Network/Players.h" + +namespace RBX +{ + bool Unlocked::unlocked(const Primitive* testMe) + { + return !PartInstance::fromPrimitiveConst(testMe)->getPartLocked(); + } + + PartByLocalCharacter::PartByLocalCharacter(Instance* root) + { + character = shared_from(Network::Players::findLocalCharacter(root)); + head = shared_from(Humanoid::getHeadFromCharacter(character.get())); + } + + HitTestFilter::Result PartByLocalCharacter::filterResult(const Primitive* testMe) const + { + if (character && head) + { + const PartInstance* p = PartInstance::fromPrimitiveConst(testMe); + + if (p->isDescendentOf(character.get())) + { + return head->getIsTransparent() ? HitTestFilter::IGNORE_PRIM : HitTestFilter::STOP_TEST; + } + } + + return HitTestFilter::INCLUDE_PRIM; + } +} diff --git a/Client/Network/API.cpp b/Client/Network/API.cpp index 543372bf..9c9443b2 100644 --- a/Client/Network/API.cpp +++ b/Client/Network/API.cpp @@ -1,8 +1,27 @@ #include "API.h" +#include "Server.h" +#include "Client.h" +#include "IdManager.h" +#include "NetworkSettings.h" +#include "Network/Players.h" namespace RBX { namespace Network { + void API::init(const char* version) + { + API::version = version; + + Client::classDescriptor(); + Server::classDescriptor(); + Player::classDescriptor(); + Players::classDescriptor(); + IdManager::classDescriptor(); + GlobalSettings::classDescriptor(); + NetworkSettings::classDescriptor(); + + NetworkSettings::singleton(); + } } } diff --git a/Client/Network/Client.h b/Client/Network/Client.h index 30622d37..c4ccc56a 100644 --- a/Client/Network/Client.h +++ b/Client/Network/Client.h @@ -30,7 +30,6 @@ namespace RBX static Reflection::SignalDesc event_ConnectionRejected; public: - //Client(const Client&); Client(); virtual ~Client(); void connect(std::string, int, int, int); @@ -43,9 +42,8 @@ namespace RBX protected: virtual void onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider); virtual void onEvent(const ServiceProvider* source, Closing event); - public: - //Client& operator=(const Client&); + public: static bool clientIsPresent(const Instance* context, bool testInDatamodel); }; } diff --git a/Client/Network/IdManager.h b/Client/Network/IdManager.h index 1f9b1a9e..25db3504 100644 --- a/Client/Network/IdManager.h +++ b/Client/Network/IdManager.h @@ -1,3 +1,4 @@ +#pragma once #include "reflection/reflection.h" #include "v8tree/Instance.h" #include "v8tree/Service.h" @@ -23,10 +24,5 @@ namespace RBX virtual void onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider); private: void removeInstance(boost::shared_ptr instance); - public: - //IdManager(const IdManager&); - IdManager(); - virtual ~IdManager(); - //IdManager& operator=(const IdManager&); }; } diff --git a/Client/Network/Network.vcproj b/Client/Network/Network.vcproj index f7c93d45..a88904cf 100644 --- a/Client/Network/Network.vcproj +++ b/Client/Network/Network.vcproj @@ -222,6 +222,10 @@ RelativePath=".\IdManager.cpp" > + + @@ -234,6 +238,10 @@ RelativePath=".\Replicator.cpp" > + + @@ -500,6 +508,10 @@ RelativePath=".\IdManager.h" > + + @@ -512,6 +524,10 @@ RelativePath=".\Replicator.h" > + + diff --git a/Client/Network/NetworkSettings.cpp b/Client/Network/NetworkSettings.cpp new file mode 100644 index 00000000..53d1ec52 --- /dev/null +++ b/Client/Network/NetworkSettings.cpp @@ -0,0 +1,37 @@ +#include "NetworkSettings.h" + +namespace RBX +{ + Reflection::BoundProp prop_LogPackets("LogPackets", "Diagnostics", &NetworkSettings::logPackets, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_PrintInstances("PrintInstances", "Diagnostics", &NetworkSettings::printInstances, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_PrintProperties("PrintProperties", "Diagnostics", &NetworkSettings::printProperties, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_PrintPacketBuffer("PrintPacketBuffer", "Diagnostics", &NetworkSettings::printPacketBuffer, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_PrintPhysicsErrors("PrintPhysicsErrors", "Diagnostics", &NetworkSettings::printPhysicsErrors, Reflection::PropertyDescriptor::STANDARD); + + Reflection::BoundProp prop_MaxSendBPS("MaxSendBPS", "Simulator", &NetworkSettings::maxSendBPS, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_MinExtraPing("MinExtraPing", "Simulator", &NetworkSettings::minExtraPing, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_ExtraPingVariance("ExtraPingVariance", "Simulator", &NetworkSettings::extraPingVariance, Reflection::PropertyDescriptor::STANDARD); + + Reflection::BoundProp prop_MaxDataModelSendBuffer("MaxDataModelSendBuffer", "Replication", &NetworkSettings::maxDataModelSendBuffer, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_DataPacketSize("DataPacketSize", "Replication", &NetworkSettings::dataPacketSize, Reflection::PropertyDescriptor::STANDARD); + Reflection::BoundProp prop_SendRate("SendRate", "Replication", &NetworkSettings::sendRate, Reflection::PropertyDescriptor::STANDARD); + + NetworkSettings::NetworkSettings() + : clientPhysicsSpeed(1.0f), + clientPhysicsLifetime(0.5f), + dataPacketSize(0.75f), + sendRate(40.0f), + clientPhysics(false), + printPhysicsErrors(false), + printInstances(false), + printProperties(false), + printPacketBuffer(false), + logPackets(false), + maxDataModelSendBuffer(4), + maxSendBPS(0), + minExtraPing(0), + extraPingVariance(0) + { + setName("Network"); + } +} diff --git a/Client/Network/NetworkSettings.h b/Client/Network/NetworkSettings.h new file mode 100644 index 00000000..cc594137 --- /dev/null +++ b/Client/Network/NetworkSettings.h @@ -0,0 +1,29 @@ +#pragma once +#include "v8datamodel/GlobalSettings.h" + +namespace RBX +{ + extern const char* sNetworkSettings; + + class NetworkSettings : public GlobalSettingsItem + { + public: + bool clientPhysics; + float clientPhysicsSpeed; + float clientPhysicsLifetime; + bool printPhysicsErrors; + bool printInstances; + bool printProperties; + bool printPacketBuffer; + bool logPackets; + int maxDataModelSendBuffer; + float dataPacketSize; + float sendRate; + int maxSendBPS; + int minExtraPing; + int extraPingVariance; + + public: + NetworkSettings(); + }; +} diff --git a/Client/Network/Replicator.cpp b/Client/Network/Replicator.cpp index f7e56b24..705c5f06 100644 --- a/Client/Network/Replicator.cpp +++ b/Client/Network/Replicator.cpp @@ -1,12 +1,108 @@ #include "Replicator.h" +#include "NetworkSettings.h" +#include "v8datamodel/PartInstance.h" +#include "v8world/SimJobStage.h" +#include "util/Log.h" +#include "util/standardout.h" +#include #include -void checkDisconnect(RBX::Instance* instance); +enum ValueType // NOTE: may not be intended for this file +{ + ValueType_nil, + ValueType_string, + ValueType_bool, + ValueType_int, + ValueType_float, + ValueType_BrickColor, + ValueType_Color3, + ValueType_Vector3, + ValueType_BrickVector, + ValueType_CoordinateFrame, + ValueType_Enum, + ValueType_Ref, + ValueType_ContentId +}; + +class ExtractorHack : public RakPeer +{ +public: + HANDLE getProcessPacketsThreadHandle() // the lengths you have to go through to get a protected member + { + return processPacketsThreadHandle; + } +}; + +static void checkDisconnect(RBX::Instance* instance) +{ + RBX::Network::Replicator* repl = RBX::Instance::fastDynamicCast(instance); + + if (repl && repl->disconnected) + { + repl->setParent(NULL); + } +} + +void writeValueType(bool includeLength, ValueType vt, RakNet::BitStream& outBitStream) +{ + if (includeLength) + { + unsigned char c = (unsigned char)vt; + outBitStream.WriteBits(&c, 4, true); + } +} + +FilePacketLogger::FilePacketLogger(RBX::Network::Peer* peer) + : packetLogFile(NULL) +{ + char filename[256]; + + std::string pathName = RBX::Log::current()->logFile; + pathName.erase(pathName.rfind('\\') + 1); + + sprintf(filename, "PacketLog%i.csv", RakNet::GetTime()); + pathName += filename; + + packetLogFile = fopen(pathName.c_str(), "wt"); + + if (packetLogFile) + { + RBX::StandardOut::singleton()->print(RBX::MESSAGE_INFO, "Logging packets to %s", pathName.c_str()); + } + else + { + RBX::StandardOut::singleton()->print(RBX::MESSAGE_WARNING, "Failed to create log file %s", pathName.c_str()); + } +} + +void FilePacketLogger::OnAttach(RakPeerInterface* peer) +{ + PacketLogger::OnAttach(peer); + LogHeader(); +} + +void FilePacketLogger::WriteLog(const char* str) +{ + if (packetLogFile) + fputs(str, packetLogFile); +} namespace RBX { namespace Network { + Peer::Peer() + : profilePacketsThread(new Profiling::ThreadProfiler("Packets Thread")), + rakPeer(new RakPeer) + { + rakPeer->AttachPlugin(this); + } + + Peer::~Peer() + { + rakPeer->DetachPlugin(this); + } + void Peer::onEvent(const RunService* source, Heartbeat event) { double tick = G3D::System::getTick() + event.step / 5.0; @@ -28,5 +124,500 @@ namespace RBX { return rakPeer.get(); } + + void Peer::Update(RakPeerInterface* peer) + { + RBXASSERT(peer == rakPeer.get()); + + profilePacketsThread->sample(static_cast(peer)->getProcessPacketsThreadHandle()); + PluginInterface::Update(peer); // TODO: blank function shared by multiple symbols. is this the correct one? + } + + void Peer::updateLogger() + { + if (!NetworkSettings::singleton().logPackets) + { + logger.reset(); + } + else if (!logger) + { + logger.reset(new FilePacketLogger(this)); + rakPeer->AttachPlugin(logger.get()); + } + } + + void Peer::updateNetworkSimulator() + { + rakPeer->ApplyNetworkSimulator( + NetworkSettings::singleton().maxSendBPS, + NetworkSettings::singleton().minExtraPing, + NetworkSettings::singleton().extraPingVariance + ); + } + + Replicator::Replicator(SystemAddress remotePlayerId, RakPeerInterface* peer) + : lastSendTime(G3D::System::getLocalTime()), + lastCharacterSendTime(G3D::System::getLocalTime()), + profileReplication(new Profiling::CodeProfiler("Replication")), + profileDataListening(new Profiling::CodeProfiler("Data Listening")), + profileDataIn(new Profiling::CodeProfiler("Data In")), + profileDataOut(new Profiling::CodeProfiler("Data Out")), + profilePhysicsIn(new Profiling::CodeProfiler("Physics In")), + profilePhysicsOut(new Profiling::CodeProfiler("Physics Out")), + remotePlayerId(remotePlayerId), + peer(peer), + disconnected(false), + receivedGlobals(false), + sendPhysicsEnabled(false), + players(NULL), + deserializeProperty(NULL), + removingInstance(NULL) + { + peer->AttachPlugin(this); + setName(remotePlayerId.ToString(true)); + + profileDataListening->parent = profileReplication.get(); + profileDataIn->parent = profileReplication.get(); + profileDataOut->parent = profileReplication.get(); + profilePhysicsIn->parent = profileReplication.get(); + profilePhysicsOut->parent = profileReplication.get(); + } + + Replicator::~Replicator() + { + if (peer) + peer->DetachPlugin(this); + } + + bool Replicator::wantReplicate(const Instance* source) const + { + return fastDynamicCast(source) == NULL ? true : false; + } + + bool Replicator::canSendItems() + { + return receivedGlobals; + } + + void Replicator::closeConnection() + { + peer->CloseConnection(remotePlayerId, true); + setParent(NULL); + } + + Player* Replicator::findTargetPlayer() + { + return players ? players->getLocalPlayer() : NULL; + } + + Mechanism* Replicator::findTargetPlayerCharacterMechanism() + { + Player* player = findTargetPlayer(); + + if (player && player->getCharacter()) + { + PartInstance* part = player->getCharacter()->getPrimaryPart(); + + if (part) + return Mechanism::getMechanismFromPrimitive(part->getPrimitive()); + } + + return NULL; + } + + bool Replicator::isSerializePending(const Instance* instance) const + { + return pendingNewInstances.find(instance) != pendingNewInstances.end(); + } + + void Replicator::disconnectAllPropertyChangedConnections() + { + while (propertyChangedConnections.size() > 0) + { + propertyChangedConnections.begin()->second.disconnect(); + propertyChangedConnections.erase(propertyChangedConnections.begin()); + } + } + + void Replicator::requestCharacter() + { + RakNet::BitStream bitStream; + + bitStream << 'L'; + + Player* player = findTargetPlayer(); + if (!player) + throw std::runtime_error("Attempting to send a Character request without a local Player"); + + serializeId(bitStream, player); + + if (NetworkSettings::singleton().printInstances) + { + Guid::Data id; + player->getGuid().extract(id); + + StandardOut::singleton()->print(MESSAGE_INFO, "Replicator: Requesting character for %s", id.readableString(4).c_str()); + } + + peer->Send(&bitStream, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, remotePlayerId, false); + } + + void Replicator::disconnectPropertyChanged(boost::shared_ptr instance) + { + std::map, boost::signals::connection>::iterator iter = propertyChangedConnections.find(instance); + + if (iter != propertyChangedConnections.end()) + { + iter->second.disconnect(); + propertyChangedConnections.erase(iter); + } + } + + void Replicator::deleteDisconnectInstances() + { + std::set>::iterator iter = deleteOnDisconnectInstances.begin(); + std::set>::iterator end = deleteOnDisconnectInstances.end(); + + for (; iter != end; iter++) + { + boost::shared_ptr instance = iter->lock(); + if (instance) + instance->setParent(NULL); + } + + deleteOnDisconnectInstances.clear(); + } + + bool Replicator::isInReplicationScope(const Instance* instance) const + { + std::vector::const_iterator begin = replicationContainers.begin(); + std::vector::const_iterator end = replicationContainers.end(); + + if (std::find_if(begin, end, boost::bind(&Instance::isDescendentOf, instance, _1)) != end) + { + return true; + } + else + { + return std::find(begin, end, instance) != end ? true : false; + } + } + + void Replicator::readMarker(RakNet::BitStream& inBitstream) + { + intptr_t marker; + inBitstream >> marker; + + if (NetworkSettings::singleton().printInstances) + { + StandardOut::singleton()->print(MESSAGE_INFO, "Received marker %d from %s", (intptr_t)marker, remotePlayerId.ToString()); + } + + RBXASSERT(reinterpret_cast(marker) == incomingMarkers.front().get()); + + Marker::event_Returned.fire(incomingMarkers.front().get()); + incomingMarkers.pop(); + } + + boost::shared_ptr Replicator::sendMarker() + { + boost::shared_ptr marker = Marker::newMarker(); + + RakNet::BitStream bitStream; + bitStream << 'N'; + bitStream << (intptr_t)marker.get(); + + if (NetworkSettings::singleton().printInstances) + { + StandardOut::singleton()->print(MESSAGE_INFO, "Replicator: Requesting Marker %d of %s", (intptr_t)marker.get(), remotePlayerId.ToString()); + } + + incomingMarkers.push(marker); + peer->Send(&bitStream, HIGH_PRIORITY, RELIABLE, 0, remotePlayerId, false); + + return marker; + } + + bool Replicator::sendPhysicsPacket() + { + if (!sendPhysicsEnabled) + { + return false; + } + else if (!peer->GetStatistics(remotePlayerId)) // TODO: vtable offset does not match due to missing function in RakPeerInterface + { + return false; + } + else + { + Profiling::Mark mark(*profilePhysicsOut, false); + JobSender jobSender(*this, peer); + + Workspace* workspace = ServiceProvider::find(this); + if (workspace) + { + SimJobStage& sim = workspace->getWorld()->getSimJobStage(); + Mechanism* mech = findTargetPlayerCharacterMechanism(); + if (mech) + { + G3D::RealTime t = G3D::System::getLocalTime(); + if (t > lastCharacterSendTime + 0.1) + { + jobSender.setPacketPriority(MEDIUM_PRIORITY); + jobSender.report(*mech); + jobSender.setPacketPriority(MEDIUM_PRIORITY); + + lastCharacterSendTime = t; + } + else + { + mech = NULL; + } + } + + sim.reportMechanisms(jobSender, jobStagePos, mech); + } + + return jobSender.sentPacket; + } + } + + bool Replicator::sendItems() + { + if (canSendItems()) + { + Profiling::Mark mark(*profileDataOut, false); + ItemSender sender(*this, peer); + + while (pendingItems.size() > 0) + { + boost::shared_ptr item = *pendingItems.begin(); + + if (item) + { + if (!sender.send(*item)) + break; + } + + pendingItems.pop_front(); + } + + return sender.sentItems; + } + else + { + // 100% match when this path returns nothing, although it generates a warning and is undefined behavior + // On VC8 this should not cause a bug as the result of canSendItems() is in the AL register when this path is executed, which at that point must be equal to false + return false; + } + } + + const Instance* Replicator::getDefault(const Name& className) + { + Security::Impersonator impersonate(Security::Replicator); + + std::map>::iterator iter = defaultObjects.find(&className); + + if (iter == defaultObjects.end()) + { + boost::shared_ptr instance = AbstractFactoryProduct::create(className); + + if (!instance) + { + StandardOut::singleton()->print(MESSAGE_ERROR, "Replication: Can\'t create default object of type %s", className.c_str()); + } + + defaultObjects[&className] = instance; + return instance.get(); + } + else + { + return iter->second.get(); + } + } + + SharedStringDictionary& Replicator::getSharedDictionary(const Reflection::PropertyDescriptor& descriptor) + { + std::map>::iterator iter = strings.find(&descriptor); + + if (iter == strings.end()) + { + boost::shared_ptr result(new SharedStringDictionary); + + strings[&descriptor] = result; + return *result; + } + else + { + return *iter->second; + } + } + + void Replicator::receiveData(Packet* packet) + { + Profiling::Mark mark(*profileDataIn, false); + + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + + Security::Impersonator impersonate(Security::Replicator); + + while (true) + { + Item::ItemType itemType; + Item::readItemType(inBitstream, itemType); + + switch (itemType) + { + case Item::ItemTypeDelete: + readInstanceDelete(inBitstream); + break; + case Item::ItemTypeNew: + readInstanceNew(inBitstream); + break; + case Item::ItemTypeChangeProperty: + readChangedProperty(inBitstream); + break; + case Item::ItemTypeMarker: + readMarker(inBitstream); + break; + case Item::ItemTypeEnd: + return; + } + } + } + + void Replicator::Item::readItemType(RakNet::BitStream& stream, ItemType& value) + { + value = ItemTypeEnd; + + stream.ReadBits((unsigned char*)&value, 2, true); + + if (value < 1 || value > 3) + { + stream.Read(value); + } + } + + void Replicator::Item::writeItemType(RakNet::BitStream& stream, ItemType value) + { + if ((unsigned)value - 1 <= 2) // value == ItemTypeDelete || value == ItemTypeNew + { + stream.WriteBits((unsigned char*)&value, 2, true); + } + else + { + unsigned char zeroes = 0; + stream.WriteBits(&zeroes, 2, true); + stream.Write(value); + } + } + + Replicator::ItemSender::ItemSender(Replicator& replicator, RakPeerInterface* peer) + : replicator(replicator), + peer(peer), + maxStreamSize(G3D::iRound(NetworkSettings::singleton().dataPacketSize * (peer->GetMTUSize(replicator.remotePlayerId) - 128))), + sentItems(false) + { + } + + Replicator::ItemSender::~ItemSender() + { + if (bitStream.GetNumberOfBitsUsed() > 0) + { + Item::writeItemType(bitStream, Item::ItemTypeEnd); + peer->Send(&bitStream, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, replicator.remotePlayerId, false); + bitStream.Reset(); + } + } + + bool Replicator::ItemSender::send(Item& item) + { + if (bitStream.GetNumberOfBytesUsed() < maxStreamSize) + { + if (bitStream.GetNumberOfBitsUsed() == 0) + { + bitStream << 'M'; + } + + item.write(bitStream); + sentItems = true; + + return true; + } + else + { + return false; + } + } + + Replicator::JobSender::JobSender(Replicator& replicator, RakPeerInterface* peer) + : replicator(replicator), + peer(peer), + firstA(NULL), + firstM(NULL), + packetPriority(MEDIUM_PRIORITY), + sentPacket(false), + hasOpenPacket(false), + bitStream(1364) // what + { + } + + Replicator::JobSender::~JobSender() + { + if (hasOpenPacket) + { + closePacket(); + } + } + + void Replicator::JobSender::setPacketPriority(PacketPriority packetPriority) + { + if (this->packetPriority != packetPriority) + { + closePacket(); + this->packetPriority = packetPriority; + } + } + + void Replicator::JobSender::closePacket() + { + replicator.serializeId(bitStream, NULL); + peer->Send(&bitStream, packetPriority, UNRELIABLE, 0, replicator.remotePlayerId, false); + hasOpenPacket = false; + } + + bool Replicator::JobSender::report(const Mechanism& m) + { + if (!hasOpenPacket) + { + bitStream.Reset(); + + bitStream << (char)0x18; + bitStream << RakNet::GetTime(); + bitStream << 'O'; + + hasOpenPacket = true; + sentPacket = true; + } + + replicator.sendPhysicsData(bitStream, m); + replicator.serializeId(bitStream, NULL); + + return bitStream.GetNumberOfBytesUsed() < 1364u; + } + + void Replicator::MarkerItem::write(RakNet::BitStream& bitStream) + { + writeItemType(bitStream, ItemTypeMarker); + bitStream << (int)id; + + if (NetworkSettings::singleton().printInstances) + { + StandardOut::singleton()->print(MESSAGE_INFO, "Replication: Sending marker %d to %s", id, replicator.remotePlayerId.ToString()); + } + + replicator.onSentMarker(id); + } } } diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index 921e6689..d1fb0787 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -1,20 +1,292 @@ #pragma once #include -#include "v8tree/Instance.h" -#include "v8tree/Service.h" -#include "util/Events.h" +#include "Streaming.h" +#include "Network/Players.h" +#include "v8world/Assembly.h" +#include "v8world/Mechanism.h" #include "util/Profiling.h" #include "util/RunStateOwner.h" #include -#include #include -#include #include namespace RBX { + namespace Stats + { + class Item; + class StatsService; + } + namespace Network { + extern const char* sMarker; + + class Marker : public DescribedNonCreatable, private boost::noncopyable + { + private: + bool returned; + public: + static Reflection::SignalDesc event_Returned; + static Reflection::SignalDesc event_Progress; + + private: + Marker() + : returned(false) + { + } + public: + long id() const; + void fireReturned(); + + public: + static boost::shared_ptr newMarker() + { + return boost::shared_ptr(new Marker); + } + static Marker* fromId(long); + }; + + extern const char* sReplicator; + + class Replicator : public DescribedNonCreatable, public PluginInterface + { + class Item : private boost::noncopyable + { + public: + enum ItemType + { + ItemTypeEnd, + ItemTypeDelete, + ItemTypeNew, + ItemTypeChangeProperty, + ItemTypeMarker + }; + + protected: + Replicator& replicator; + + protected: + Item(Replicator& replicator) + : replicator(replicator) + { + } + public: + virtual ~Item(); + virtual void write(RakNet::BitStream& bitStream) = 0; + + public: + static void writeItemType(RakNet::BitStream& stream, ItemType value); + static void readItemType(RakNet::BitStream& stream, ItemType& value); + }; + + class ItemSender : private boost::noncopyable + { + private: + Replicator& replicator; + RakPeerInterface* peer; + RakNet::BitStream bitStream; + const int maxStreamSize; + public: + bool sentItems; + + private: + void openPacket(); + void closePacket(); + public: + ItemSender(Replicator& replicator, RakPeerInterface* peer); + ~ItemSender(); + bool send(Item& item); + }; + + class JobSender : public boost::noncopyable + { + private: + const Assembly* firstA; + const Mechanism* firstM; + Replicator& replicator; + RakPeerInterface* peer; + bool hasOpenPacket; + RakNet::BitStream bitStream; + PacketPriority packetPriority; + public: + bool sentPacket; + private: + static const size_t maxStreamSize; + + private: + void openPacket(); + void closePacket(); + public: + JobSender(Replicator& replicator, RakPeerInterface* peer); + void close(); + ~JobSender(); + void setPacketPriority(PacketPriority packetPriority); + bool report(const Mechanism& m); + bool operator()(Mechanism&); + }; + + class MarkerItem : public Item + { + private: + int id; + + public: + MarkerItem(Replicator&, int); + virtual void write(RakNet::BitStream& bitStream); + }; + + class NewInstanceItem : public Item + { + private: + boost::shared_ptr instance; + + public: + NewInstanceItem(Replicator&, const boost::shared_ptr&); + virtual void write(RakNet::BitStream&); + }; + + class ReplicatorStatsItem : public Item // TODO (PR #124 has implementations of RBX::Stats classes) + { + private: + Stats::Item* waitingRefs; + Stats::Item* packetLoss; + Stats::Item* ping; + Stats::Item* instanceSize; + Stats::Item* bps; + const boost::shared_ptr replicator; + const RakNetStatistics* statistics; + public: + size_t instanceCount; + size_t instancBits; + + public: + ReplicatorStatsItem(const boost::shared_ptr&, const RakNetStatistics*); + virtual void update(); + }; + + class ChangePropertyItem : public Item + { + private: + const Reflection::PropertyDescriptor& desc; + const boost::shared_ptr instance; + + public: + ChangePropertyItem(Replicator&, const boost::shared_ptr&, const Reflection::PropertyDescriptor&); + virtual void write(RakNet::BitStream&); + }; + + class DeleteInstanceItem : public Item + { + private: + const boost::shared_ptr instance; + + public: + DeleteInstanceItem(Replicator& replicator, const boost::shared_ptr& instance); + virtual void write(RakNet::BitStream&); + }; + + private: + G3D::RealTime lastSendTime; + MechanismTracker jobStagePos; + G3D::RealTime lastCharacterSendTime; + boost::shared_ptr statsItem; + std::map> strings; + SharedStringDictionary classNames; + SharedStringDictionary propNames; + protected: + std::vector replicationContainers; + std::vector replicationContainerConnections; + Players* players; + std::list> pendingItems; + std::set pendingNewInstances; + std::set pendingDeleteInstances; + bool sendPhysicsEnabled; + const Reflection::Property* deserializeProperty; + Instance* removingInstance; + std::set> deleteOnDisconnectInstances; + std::map, boost::signals::connection> propertyChangedConnections; + std::queue> incomingMarkers; + std::map> defaultObjects; + boost::scoped_ptr profileReplication; + boost::scoped_ptr profileDataListening; + boost::scoped_ptr profileDataIn; + boost::scoped_ptr profileDataOut; + boost::scoped_ptr profilePhysicsIn; + boost::scoped_ptr profilePhysicsOut; + private: + bool receivedGlobals; + public: + bool disconnected; + RakPeerInterface* peer; + const SystemAddress remotePlayerId; + + public: + static Reflection::BoundProp prop_maxDataModelSendBuffer; + static Reflection::SignalDesc event_Disconnection; + + protected: + virtual Player* findTargetPlayer(); + Mechanism* findTargetPlayerCharacterMechanism(); + public: + Replicator(SystemAddress remotePlayerId, RakPeerInterface* peer); + virtual ~Replicator(); + boost::shared_ptr getPlayer() + { + return shared_from(findTargetPlayer()); + } + virtual void Update(RakPeerInterface*); + virtual PluginReceiveResult OnReceive(RakPeerInterface*, Packet*); + virtual const Name& getClassName() const + { + return Name::getNullName(); + } + virtual XmlElement* write(); + boost::shared_ptr sendMarker(); + bool isSerializePending(const Instance* instance) const; + bool isPropertyChangedPending(const Reflection::Property&) const; + void requestCharacter(); + void closeConnection(); + protected: + virtual void onServiceProvider(const ServiceProvider*, const ServiceProvider*); + void onDescendentAdded(boost::shared_ptr); + void onDescendentRemoving(boost::shared_ptr); + void onPropertyChanged(boost::shared_ptr, const Reflection::PropertyDescriptor*); + const Instance* getDefault(const Name& className); + void deleteDisconnectInstances(); + void connectPropertyChanged(boost::shared_ptr instance); + void disconnectPropertyChanged(boost::shared_ptr instance); + virtual bool wantReplicate(const Instance* source) const; + virtual void onSentMarker(int); + private: + void disconnectAllPropertyChangedConnections(); + void connectToReplicationContainers(); + void removeFromPendingProperties(const Instance*); + bool removeFromPendingNewInstances(const Instance*); + bool removeFromPendingDeleteInstances(const Instance*); + bool isChildOfPendingDeleteInstance(const Instance* instance) const; + bool isInReplicationScope(const Instance* instance) const; + bool sendItems(); + bool sendPhysicsPacket(); + void sendPhysicsData(RakNet::BitStream&, const Mechanism&); + void sendPhysicsData(RakNet::BitStream&, const Assembly&); + void createStatsItems(Stats::StatsService*); + virtual bool remoteDeleteOnDisconnect(const Instance* instance) const; + void serializeValue(const Reflection::ConstProperty&, bool, RakNet::BitStream&); + void deserializeValue(RakNet::BitStream&, bool, Reflection::Property&); + void skipValue(RakNet::BitStream&); + void receiveData(Packet* packet); + void readInstanceNew(RakNet::BitStream&); + void readInstanceDelete(RakNet::BitStream&); + void readChangedProperty(RakNet::BitStream&); + void readMarker(RakNet::BitStream& inBitstream); + bool getCameFromRemotePlayer(const Instance*) const; + void setCameFromRemotePlayer(Instance*); + SharedStringDictionary& getSharedDictionary(const Reflection::PropertyDescriptor& descriptor); + PluginReceiveResult OnReceive_ID_TIMESTAMP(Packet*); + virtual bool canSendItems(); + }; + extern const char* sPeer; class Peer : public Reflection::Described, public PluginInterface, public Listener @@ -24,20 +296,32 @@ namespace RBX boost::scoped_ptr profilePacketsThread; protected: const boost::scoped_ptr rakPeer; - public: - //Peer(const Peer&); + protected: Peer(); virtual ~Peer(); - virtual bool askAddChild(const Instance*) const; + virtual bool askAddChild(const Instance* instance) const + { + return fastDynamicCast(instance) != NULL; + } RakPeerInterface* peerInterface(); void updateLogger(); void updateNetworkSimulator(); virtual void onEvent(const RunService* source, Heartbeat event); - virtual void Update(RakPeerInterface*); + virtual void Update(RakPeerInterface* peer); virtual void onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider); - public: - //Peer& operator=(const Peer&); }; } } + +class FilePacketLogger : public PacketLogger +{ +private: + FILE* packetLogFile; + +public: + FilePacketLogger(RBX::Network::Peer*); + virtual ~FilePacketLogger(); + virtual void OnAttach(RakPeerInterface* peer); + virtual void WriteLog(const char*); +}; \ No newline at end of file diff --git a/Client/Network/Server.cpp b/Client/Network/Server.cpp new file mode 100644 index 00000000..2390eb59 --- /dev/null +++ b/Client/Network/Server.cpp @@ -0,0 +1,80 @@ +#include "Server.h" +#include "API.h" +#include "util/standardout.h" + +static bool isReplicator(boost::shared_ptr instance) +{ + return RBX::Instance::fastDynamicCast(instance.get()) != NULL; +} + +namespace RBX +{ + namespace Network + { + Server::Server() + : outgoingPort(0) + { + setName("NetworkServer"); + rakPeer->SetMaximumIncomingConnections(32); + rakPeer->SetIncomingPassword(API::version.c_str(), (int)API::version.size()); + updateLogger(); + } + + Server::~Server() {} + + void Server::stop(int blockDuration) + { + if (rakPeer->IsActive()) + rakPeer->Shutdown(blockDuration); + + removeAllChildren(); + } + + void Server::start(int port, int threadSleepTime) + { + SocketDescriptor d(port, ""); + + if (!rakPeer->Startup(32, threadSleepTime, &d, 1)) + throw std::runtime_error("Failed to start network server"); + + outgoingPort = port; + StandardOut::singleton()->print(MESSAGE_INFO, "Starting network server on port %d", port); + + unsigned numAddresses = rakPeer->GetNumberOfAddresses(); + + StandardOut::singleton()->print(MESSAGE_INFO, "IP addresses:"); + + for (unsigned i = 0; i < numAddresses; i++) + { + StandardOut::singleton()->print(MESSAGE_INFO, "%s", rakPeer->GetLocalIP(i)); + } + + updateNetworkSimulator(); + } + + int Server::getClientCount() + { + if (&(*getChildren())) + { + return std::count_if(getChildren()->begin(), getChildren()->end(), &isReplicator); + } + else + { + return 0; + } + } + + Server::ClientProxy::ClientProxy(SystemAddress systemAddress, Server* server) + : Replicator(systemAddress, server->peerInterface()), + server(server) + { + } + + Server::ClientProxy::~ClientProxy() {} + + void Server::ClientProxy::onSentMarker(long id) + { + sendPhysicsEnabled = true; + } + } +} diff --git a/Client/Network/Server.h b/Client/Network/Server.h new file mode 100644 index 00000000..52a4b7fe --- /dev/null +++ b/Client/Network/Server.h @@ -0,0 +1,75 @@ +#pragma once +#include "Network/Players.h" +#include "Replicator.h" + +namespace RBX +{ + namespace Network + { + extern const char* sServer; + + class Server : public DescribedCreatable + { + class ClientProxy : public Replicator + { + private: + boost::shared_ptr remotePlayer; + Server* server; + + public: + ClientProxy(SystemAddress systemAddress, Server* server); + virtual ~ClientProxy(); + void sendTop(); + virtual PluginReceiveResult OnReceive(RakPeerInterface*, Packet*); + protected: + virtual Player* findTargetPlayer() + { + return remotePlayer.get(); + } + + virtual void onSentMarker(long id); + private: + virtual bool remoteDeleteOnDisconnect(const Instance* instance) const + { + std::vector::const_iterator begin = replicationContainers.begin(); + std::vector::const_iterator end = replicationContainers.end(); + + return std::find(begin, end, instance) == end; + } + + virtual bool canSendItems(); + }; + + private: + int outgoingPort; + boost::scoped_ptr pingThread; + boost::shared_ptr players; + + static int lastId; + public: + static Reflection::SignalDesc)> event_IncommingConnection; + + public: + Server(); + virtual ~Server(); + + void start(int port, int threadSleepTime); + void stop(int blockDuration); + int getClientCount(); + virtual XmlElement* write(); + virtual PluginReceiveResult OnReceive(RakPeerInterface*, Packet*); + void setServerManagerPing(std::string, std::string, int); + protected: + virtual void onServiceProvider(const ServiceProvider*, const ServiceProvider*); + virtual bool askAddChild(const Instance* instance) const + { + return fastDynamicCast(instance) != NULL; + } + + private: + static worker_thread::work_result ping(boost::weak_ptr, std::string, int, std::string); + public: + static bool serverIsPresent(const Instance* context, bool testInDatamodel); + }; + } +} diff --git a/Client/Network/Streaming.cpp b/Client/Network/Streaming.cpp index ee2f34ea..ccd6edf0 100644 --- a/Client/Network/Streaming.cpp +++ b/Client/Network/Streaming.cpp @@ -19,6 +19,12 @@ namespace RBX return stream; } + RakNet::BitStream& operator<<(RakNet::BitStream& stream, long value) + { + stream.Write(value); + return stream; + } + RakNet::BitStream& operator<<(RakNet::BitStream& stream, unsigned int value) { stream.Write(value); @@ -72,6 +78,12 @@ namespace RBX return stream; } + RakNet::BitStream& operator<<(RakNet::BitStream& stream, long& value) + { + stream.Write(value); + return stream; + } + RakNet::BitStream& operator>>(RakNet::BitStream& stream, unsigned int& value) { stream.Read(value); diff --git a/Client/Network/Streaming.h b/Client/Network/Streaming.h index ead9e8d6..68ea8042 100644 --- a/Client/Network/Streaming.h +++ b/Client/Network/Streaming.h @@ -1,3 +1,4 @@ +#pragma once #include "v8datamodel/BrickColor.h" #include "v8tree/Instance.h" #include "util/ContentProvider.h" @@ -26,6 +27,7 @@ namespace RBX RakNet::BitStream& operator<<(RakNet::BitStream& stream, bool value); RakNet::BitStream& operator<<(RakNet::BitStream& stream, int value); + RakNet::BitStream& operator<<(RakNet::BitStream& stream, long value); RakNet::BitStream& operator<<(RakNet::BitStream& stream, unsigned int value); RakNet::BitStream& operator<<(RakNet::BitStream& stream, unsigned char value); RakNet::BitStream& operator<<(RakNet::BitStream& stream, float value); @@ -36,6 +38,7 @@ namespace RBX RakNet::BitStream& operator>>(RakNet::BitStream& stream, bool& value); RakNet::BitStream& operator>>(RakNet::BitStream& stream, int& value); + RakNet::BitStream& operator>>(RakNet::BitStream& stream, long& value); RakNet::BitStream& operator>>(RakNet::BitStream& stream, unsigned int& value); RakNet::BitStream& operator>>(RakNet::BitStream& stream, unsigned char& value); RakNet::BitStream& operator>>(RakNet::BitStream& stream, float& value); @@ -60,10 +63,6 @@ namespace RBX void deserializeString(Reflection::Property& property, RakNet::BitStream& bitStream); void receive(RakNet::BitStream& stream, const Name*& value); void receive(RakNet::BitStream& stream, std::string& value); - //StringReceiver(const StringReceiver&); - StringReceiver(); - ~StringReceiver(); - //StringReceiver& operator=(const StringReceiver&); }; class StringSender @@ -73,7 +72,6 @@ namespace RBX std::string strings[128]; int lastIndex; public: - //StringSender(const StringSender&); StringSender() : lastIndex(0) { @@ -88,9 +86,6 @@ namespace RBX bool trySend(RakNet::BitStream&, const char*); bool trySend(RakNet::BitStream&, const Name&); bool trySend(RakNet::BitStream&, const std::string&); - - ~StringSender(); - //StringSender& operator=(const StringSender&); }; class SharedStringDictionary : public StringSender, public StringReceiver, private boost::noncopyable @@ -111,7 +106,6 @@ namespace RBX protected: std::map> waitItems; public: - //IdSerializer(const IdSerializer&); IdSerializer() {} void serializeId(RakNet::BitStream& stream, const Instance* instance); @@ -123,8 +117,6 @@ namespace RBX size_t numWaitingRefs() const; void serializeRef(const Reflection::ConstProperty& property, RakNet::BitStream& bitStream); void deserializeRef(Reflection::Property&, RakNet::BitStream&); - virtual ~IdSerializer(); - //IdSerializer& operator=(const IdSerializer&); protected: static void setRefValue(WaitItem& wi, Instance* instance); }; diff --git a/Client/Network/include/Network/Player.h b/Client/Network/include/Network/Player.h index 3a49c18e..b85b3e2b 100644 --- a/Client/Network/include/Network/Player.h +++ b/Client/Network/include/Network/Player.h @@ -3,8 +3,6 @@ #include "Network/Players.h" #include "security/SecurityContext.h" #include "humanoid/Humanoid.h" -#include "v8tree/Instance.h" -#include "v8tree/Service.h" #include "v8datamodel/BrickColor.h" #include "v8datamodel/Backpack.h" #include "v8datamodel/Workspace.h" @@ -19,9 +17,7 @@ namespace RBX const boost::shared_ptr character; public: - //CharacterAdded(const CharacterAdded&); CharacterAdded(Instance*); - ~CharacterAdded(); }; struct CharacterRemoving @@ -30,9 +26,7 @@ namespace RBX const boost::shared_ptr character; public: - //CharacterRemoving(const CharacterRemoving&); CharacterRemoving(Instance*); - ~CharacterRemoving(); }; extern const char* sPlayer; @@ -63,7 +57,6 @@ namespace RBX void onCharacterChangedFrontend(); void registerLocalPlayerNotIdle(); public: - //Player(const Player&); Player(); virtual ~Player(); public: @@ -112,8 +105,6 @@ namespace RBX private: void onCharacterDied(); void doPeriodicIdleCheck(); - public: - //Player& operator=(const Player&); public: static void onLocalPlayerNotIdle(ServiceProvider*); diff --git a/Client/Network/include/Network/Players.h b/Client/Network/include/Network/Players.h index bb8eb557..e2af6c89 100644 --- a/Client/Network/include/Network/Players.h +++ b/Client/Network/include/Network/Players.h @@ -25,10 +25,6 @@ namespace RBX const std::string message; const boost::shared_ptr source; const boost::shared_ptr destination; - - public: - ~ChatMessage(); - //ChatMessage(const ChatMessage&); }; struct AbuseReport @@ -48,12 +44,6 @@ namespace RBX public: void addMessage(const ChatMessage& cm); - public: - //AbuseReport(const AbuseReport&); - AbuseReport(); - ~AbuseReport(); - public: - //AbuseReport& operator=(const AbuseReport&); }; class AbuseReporter @@ -64,13 +54,6 @@ namespace RBX public: std::queue queue; boost::mutex requestSync; - - public: - //data(const data&); - data(); - ~data(); - public: - //data& operator=(const data&); }; private: @@ -78,14 +61,8 @@ namespace RBX boost::scoped_ptr requestProcessor; public: - //AbuseReporter(const AbuseReporter&); AbuseReporter(std::string abuseUrl); - public: void add(AbuseReport& r, const std::list& chatHistory); - public: - ~AbuseReporter(); - public: - //AbuseReporter& operator=(const AbuseReporter&); private: static worker_thread::work_result processRequests(boost::shared_ptr, std::string); @@ -117,7 +94,6 @@ namespace RBX virtual void onEvent(const Player*, CharacterAdded); virtual void onChildChanged(Instance*, const PropertyChanged&); public: - //Players(const Players&); Players(); virtual ~Players(); public: @@ -158,8 +134,6 @@ namespace RBX virtual void onChildRemoving(Instance*); private: void addChatMessage(const ChatMessage&); - public: - //Players& operator=(const Players&); public: static Player* getPlayerFromCharacter(Instance* character); diff --git a/Client/Network/include/Network/SuperSafeChanged.h b/Client/Network/include/Network/SuperSafeChanged.h index 3e54297d..e85f0d10 100644 --- a/Client/Network/include/Network/SuperSafeChanged.h +++ b/Client/Network/include/Network/SuperSafeChanged.h @@ -1,3 +1,5 @@ +#pragma once + namespace RBX { class SuperSafeChanged From ab93e11856fa7ce7ff037e69a4b3ef83775d7609 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Fri, 6 Mar 2026 16:31:52 +0200 Subject: [PATCH 02/13] add newline --- Client/Network/Replicator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index d1fb0787..0726a002 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -324,4 +324,4 @@ class FilePacketLogger : public PacketLogger virtual ~FilePacketLogger(); virtual void OnAttach(RakPeerInterface* peer); virtual void WriteLog(const char*); -}; \ No newline at end of file +}; From c2fcc58e3b594e2f268b6314ad33ba95fe10f5eb Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Wed, 18 Mar 2026 18:29:04 +0200 Subject: [PATCH 03/13] what the typo --- Client/Network/Replicator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index 0726a002..88312bb0 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -158,7 +158,7 @@ namespace RBX const RakNetStatistics* statistics; public: size_t instanceCount; - size_t instancBits; + size_t instanceBits; public: ReplicatorStatsItem(const boost::shared_ptr&, const RakNetStatistics*); From 4a27b818f4d6410a01c8154dce2f7cd29e9662d9 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Sat, 28 Mar 2026 13:20:01 +0200 Subject: [PATCH 04/13] that works --- Client/Network/Replicator.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Client/Network/Replicator.cpp b/Client/Network/Replicator.cpp index 705c5f06..e3a6fa1b 100644 --- a/Client/Network/Replicator.cpp +++ b/Client/Network/Replicator.cpp @@ -384,7 +384,11 @@ namespace RBX bool Replicator::sendItems() { - if (canSendItems()) + if (!canSendItems()) + { + return false; + } + else { Profiling::Mark mark(*profileDataOut, false); ItemSender sender(*this, peer); @@ -404,12 +408,6 @@ namespace RBX return sender.sentItems; } - else - { - // 100% match when this path returns nothing, although it generates a warning and is undefined behavior - // On VC8 this should not cause a bug as the result of canSendItems() is in the AL register when this path is executed, which at that point must be equal to false - return false; - } } const Instance* Replicator::getDefault(const Name& className) @@ -493,7 +491,7 @@ namespace RBX stream.ReadBits((unsigned char*)&value, 2, true); - if (value < 1 || value > 3) + if (value < 1 || value > 3) // neither of Delete, New, ChangeProperty { stream.Read(value); } @@ -501,7 +499,7 @@ namespace RBX void Replicator::Item::writeItemType(RakNet::BitStream& stream, ItemType value) { - if ((unsigned)value - 1 <= 2) // value == ItemTypeDelete || value == ItemTypeNew + if (value >= 1 && value <= 3) // Delete, New, ChangeProperty { stream.WriteBits((unsigned char*)&value, 2, true); } From b23f1440e3ab62136ba4560e4aaef845b7f20a56 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Mon, 4 May 2026 18:52:39 +0300 Subject: [PATCH 05/13] i forgor : skull: --- Client/Network/API.cpp | 2 +- Client/Network/Replicator.h | 10 ++++------ Client/Network/Server.cpp | 1 + Client/Network/Streaming.h | 7 ------- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Client/Network/API.cpp b/Client/Network/API.cpp index 9c9443b2..7d1ac00d 100644 --- a/Client/Network/API.cpp +++ b/Client/Network/API.cpp @@ -1,6 +1,6 @@ #include "API.h" -#include "Server.h" #include "Client.h" +#include "Server.h" #include "IdManager.h" #include "NetworkSettings.h" #include "Network/Players.h" diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index f722fb82..dd387d18 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -1,10 +1,13 @@ #pragma once #include +#include +#include +#include +#include "Streaming.h" #include "Network/Players.h" #include "v8world/Assembly.h" #include "v8world/Mechanism.h" #include "util/Profiling.h" -#include "util/RunStateOwner.h" #include "util/Events.h" #include @@ -13,11 +16,6 @@ class RakPeerInterface; class PacketLogger; -namespace RakNet -{ - class BitStream; -} - namespace RBX { namespace Stats diff --git a/Client/Network/Server.cpp b/Client/Network/Server.cpp index 2390eb59..0e449a9a 100644 --- a/Client/Network/Server.cpp +++ b/Client/Network/Server.cpp @@ -1,3 +1,4 @@ +#include #include "Server.h" #include "API.h" #include "util/standardout.h" diff --git a/Client/Network/Streaming.h b/Client/Network/Streaming.h index ea171cfd..75e4f984 100644 --- a/Client/Network/Streaming.h +++ b/Client/Network/Streaming.h @@ -1,15 +1,8 @@ #pragma once #include "v8datamodel/BrickColor.h" #include "v8tree/Instance.h" -#include "util/ContentProvider.h" -#include "util/Name.h" -#include "util/Guid.h" -#include "reflection/property.h" #include -#include #include -#include -#include namespace RakNet { From 8496901c5a36ef777aab07d60f4d8e5e80577a84 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Mon, 4 May 2026 22:18:39 +0300 Subject: [PATCH 06/13] more functions --- Client/App/include/v8datamodel/Stats.h | 3 + Client/Network/Replicator.cpp | 117 ++++++++++++++++++++++++- Client/Network/Replicator.h | 8 +- Client/Network/Streaming.h | 5 +- 4 files changed, 127 insertions(+), 6 deletions(-) diff --git a/Client/App/include/v8datamodel/Stats.h b/Client/App/include/v8datamodel/Stats.h index 2db38144..4f06fd17 100644 --- a/Client/App/include/v8datamodel/Stats.h +++ b/Client/App/include/v8datamodel/Stats.h @@ -40,6 +40,9 @@ namespace RBX template Item* createChildItem(const char* name, boost::function0 func); + + template + void formatValue(const T& value); }; template diff --git a/Client/Network/Replicator.cpp b/Client/Network/Replicator.cpp index 60851e11..04e440ad 100644 --- a/Client/Network/Replicator.cpp +++ b/Client/Network/Replicator.cpp @@ -1,12 +1,13 @@ #include #include +#include #include "Replicator.h" #include "NetworkSettings.h" +#include "v8datamodel/Stats.h" #include "v8datamodel/PartInstance.h" #include "v8world/SimJobStage.h" #include "util/Log.h" #include "util/standardout.h" -#include #include enum ValueType // NOTE: may not be intended for this file @@ -487,6 +488,73 @@ namespace RBX } } + //99% match + void Replicator::Update(RakPeerInterface* peer) + { + PluginInterface::Update(peer); + + if (getParent()) + { + Profiling::Mark mark(*profileReplication, false); + + RakNetStatistics* statistics = peer->GetStatistics(remotePlayerId); + + if (statistics) + { + G3D::RealTime t = G3D::System::getLocalTime(); + + if (1.0f / NetworkSettings::singleton().sendRate + lastSendTime < t) + { + int maxBuffer = NetworkSettings::singleton().maxDataModelSendBuffer; + int sendBuffer = statistics->messageSendBuffer[MEDIUM_PRIORITY] + statistics->messageSendBuffer[HIGH_PRIORITY]; + + if (maxBuffer - sendBuffer >= 2 && !statistics->bandwidthExceeded) + { + sendPhysicsPacket(); + sendItems(); + lastSendTime = t; + } + } + + if (NetworkSettings::singleton().printPacketBuffer) + { + size_t sendBuffer = statistics->messageSendBuffer[MEDIUM_PRIORITY] + statistics->messageSendBuffer[HIGH_PRIORITY] * 2; + size_t maxBuffer = NetworkSettings::singleton().maxDataModelSendBuffer; + + if (sendBuffer > maxBuffer) + { + StandardOut::singleton()->print(MESSAGE_WARNING, "SendBuffer = %d", sendBuffer); + } + else if (sendBuffer > 0) + { + StandardOut::singleton()->print(MESSAGE_INFO, "SendBuffer = %d", sendBuffer); + } + } + } + } + } + + void Replicator::createStatsItems(Stats::StatsService* stats) + { + if (statsItem) + { + statsItem->setParent(NULL); + statsItem.reset(); + } + + if (stats) + { + boost::shared_ptr network = shared_from_polymorphic_downcast(stats->findFirstChildByName("Network")); + + if (network) + { + statsItem = Creatable::create(shared_from(this), peer->GetStatistics(remotePlayerId)); + statsItem->setName(getName()); + statsItem->setParent2(network); + } + } + } + void Replicator::Item::readItemType(RakNet::BitStream& stream, ItemType& value) { value = ItemTypeEnd; @@ -619,5 +687,52 @@ namespace RBX replicator.onSentMarker(id); } + + Replicator::ReplicatorStatsItem::ReplicatorStatsItem(const boost::shared_ptr& replicator, const RakNetStatistics* statistics) + : replicator(replicator), + statistics(statistics), + instanceCount(0), + instanceBits(0) + { + bps = createChildItem("Bytes/s"); + bps->createBoundChildItem("Bandwidth Exceeded", statistics->bandwidthExceeded); + + packetLoss = bps->createChildItem("Packet Loss"); + ping = createChildItem("Ping"); + + Item* rep = createBoundChildItem(*replicator->profileReplication); + + rep->createBoundChildItem(*replicator->profileDataListening); + rep->createBoundChildItem(*replicator->profileDataOut); + rep->createBoundChildItem(*replicator->profilePhysicsOut); + rep->createBoundChildItem(*replicator->profileDataIn); + rep->createBoundChildItem(*replicator->profilePhysicsIn); + + instanceSize = rep->createChildItem("Instance size (In)"); + waitingRefs = createChildItem("Waiting Refs"); + } + + void Replicator::ReplicatorStatsItem::update() + { + if (replicator->peer) + { + bps->formatMem((size_t)statistics->bitsPerSecond / 8); + + ping->formatValue( + replicator->peer->GetLastPing(replicator->remotePlayerId), + "%d avg:%d best:%d", + replicator->peer->GetLastPing(replicator->remotePlayerId), + replicator->peer->GetAveragePing(replicator->remotePlayerId), + replicator->peer->GetLowestPing(replicator->remotePlayerId) + ); + + waitingRefs->formatValue(replicator->numWaitingRefs()); + + float ratio = (float)statistics->messagesTotalBitsResent / statistics->totalBitsSent; + packetLoss->formatValue(ratio, "%.1g%%", ratio * 100.0); + + instanceSize->formatMem(instanceCount != 0 ? instanceBits / (instanceCount * 8) : 0); + } + } } } diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index dd387d18..9ef4d6b1 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -152,7 +152,7 @@ namespace RBX virtual void write(RakNet::BitStream&); }; - class ReplicatorStatsItem : public Item // TODO (PR #124 has implementations of RBX::Stats classes) + class ReplicatorStatsItem : public Stats::Item { private: Stats::Item* waitingRefs; @@ -167,7 +167,7 @@ namespace RBX size_t instanceBits; public: - ReplicatorStatsItem(const boost::shared_ptr&, const RakNetStatistics*); + ReplicatorStatsItem(const boost::shared_ptr& replicator, const RakNetStatistics* statistics); virtual void update(); }; @@ -241,7 +241,7 @@ namespace RBX { return shared_from(findTargetPlayer()); } - virtual void Update(RakPeerInterface*); + virtual void Update(RakPeerInterface* peer); virtual PluginReceiveResult OnReceive(RakPeerInterface*, Packet*); virtual const Name& getClassName() const { @@ -276,7 +276,7 @@ namespace RBX bool sendPhysicsPacket(); void sendPhysicsData(RakNet::BitStream&, const Mechanism&); void sendPhysicsData(RakNet::BitStream&, const Assembly&); - void createStatsItems(Stats::StatsService*); + void createStatsItems(Stats::StatsService* stats); virtual bool remoteDeleteOnDisconnect(const Instance* instance) const; void serializeValue(const Reflection::ConstProperty&, bool, RakNet::BitStream&); void deserializeValue(RakNet::BitStream&, bool, Reflection::Property&); diff --git a/Client/Network/Streaming.h b/Client/Network/Streaming.h index 75e4f984..b51bc992 100644 --- a/Client/Network/Streaming.h +++ b/Client/Network/Streaming.h @@ -111,7 +111,10 @@ namespace RBX void resolvePendingBindings(Instance* instance, Guid::Data id); bool deserializeInstanceRef(RakNet::BitStream&, Instance*&); bool deserializeInstanceRef(RakNet::BitStream&, Instance*&, Guid::Data&); - size_t numWaitingRefs() const; + size_t numWaitingRefs() const + { + return waitItems.size(); + } void serializeRef(const Reflection::ConstProperty& property, RakNet::BitStream& bitStream); void deserializeRef(Reflection::Property&, RakNet::BitStream&); protected: From 48bb7e8ecf99026ed85c77e41f4b444b391aca35 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Tue, 5 May 2026 22:57:04 +0300 Subject: [PATCH 07/13] rahhhh --- Client/App/App.vcproj | 1 - Client/Network/Replicator.cpp | 80 ++++++++++++++++++++++++++++++++++- Client/Network/Replicator.h | 8 ++-- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/Client/App/App.vcproj b/Client/App/App.vcproj index fe9f63f1..ad2859df 100644 --- a/Client/App/App.vcproj +++ b/Client/App/App.vcproj @@ -1888,7 +1888,6 @@ diff --git a/Client/Network/Replicator.cpp b/Client/Network/Replicator.cpp index 04e440ad..7a8030d6 100644 --- a/Client/Network/Replicator.cpp +++ b/Client/Network/Replicator.cpp @@ -544,7 +544,7 @@ namespace RBX if (stats) { - boost::shared_ptr network = shared_from_polymorphic_downcast(stats->findFirstChildByName("Network")); + boost::shared_ptr network = shared_from_polymorphic_downcast(stats->findFirstChildByName("Network")); if (network) { @@ -734,5 +734,83 @@ namespace RBX instanceSize->formatMem(instanceCount != 0 ? instanceBits / (instanceCount * 8) : 0); } } + + Replicator::ChangePropertyItem::ChangePropertyItem(Replicator& replicator, const boost::shared_ptr& instance, const Reflection::PropertyDescriptor& desc) + : Item(replicator), + instance(instance), + desc(desc) + { + } + + void Replicator::ChangePropertyItem::write(RakNet::BitStream& bitStream) + { + if (replicator.isInReplicationScope(instance.get())) + { + writeItemType(bitStream, ItemTypeChangeProperty); + + const Name& descName = desc.name; + + replicator.serializeId(bitStream, instance.get()); + + SharedStringDictionary& r = replicator.propNames; + + r.send(bitStream, descName.toString()); + + if (NetworkSettings::singleton().printProperties) + { + StandardOut::singleton()->print( + MESSAGE_INFO, + "Replication: %s << %s:%s.%s", + replicator.remotePlayerId.ToString(true), + instance->getClassName().c_str(), + instance->getGuid().readableString(4).c_str(), + descName.c_str() + ); + } + + replicator.serializeValue(Reflection::ConstProperty(desc, instance.get()), true, bitStream); + } + } + + void Replicator::DeleteInstanceItem::write(RakNet::BitStream& bitStream) + { + size_t erased = replicator.pendingDeleteInstances.erase(instance.get()); + + if (erased) + { + if (!instance.get()) + { + StandardOut::singleton()->print(MESSAGE_ERROR, "Replication: %s << ~NULL", replicator.remotePlayerId.ToString(true)); + return; + } + + try + { + writeItemType(bitStream, ItemTypeDelete); + replicator.serializeId(bitStream, instance.get()); + + if (NetworkSettings::singleton().printInstances) + { + StandardOut::singleton()->print( + MESSAGE_INFO, + "Replication: %s << ~%s:%s", + replicator.remotePlayerId.ToString(true), + instance->getClassName().c_str(), + instance->getGuid().readableString(4).c_str() + ); + } + } + catch (std::runtime_error& e) + { + StandardOut::singleton()->print( + MESSAGE_ERROR, + "Replication: %s << ~%s, %s", + replicator.remotePlayerId.ToString(true), + instance->getClassName().c_str(), + e.what() + ); + } + } + } } } diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index 9ef4d6b1..1cb5d416 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -178,18 +178,18 @@ namespace RBX const boost::shared_ptr instance; public: - ChangePropertyItem(Replicator&, const boost::shared_ptr&, const Reflection::PropertyDescriptor&); - virtual void write(RakNet::BitStream&); + ChangePropertyItem(Replicator& replicator, const boost::shared_ptr& instance, const Reflection::PropertyDescriptor& desc); + virtual void write(RakNet::BitStream& bitStream); }; class DeleteInstanceItem : public Item { private: - const boost::shared_ptr instance; + boost::shared_ptr instance; public: DeleteInstanceItem(Replicator& replicator, const boost::shared_ptr& instance); - virtual void write(RakNet::BitStream&); + virtual void write(RakNet::BitStream& bitStream); }; private: From 79c1a73e3ad3cf2b0c7647d6dd5dcfb0f3220868 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Tue, 5 May 2026 23:48:28 +0300 Subject: [PATCH 08/13] more from Client, IdManager --- Client/Network/Client.cpp | 54 ++++++++++++++++++++++++++++++++++++ Client/Network/IdManager.cpp | 7 ----- Client/Network/IdManager.h | 20 +++++++++++-- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/Client/Network/Client.cpp b/Client/Network/Client.cpp index 5c597ea2..090b969e 100644 --- a/Client/Network/Client.cpp +++ b/Client/Network/Client.cpp @@ -14,6 +14,17 @@ namespace RBX { namespace Network { + Client::Client() + { + setName("NetworkClient"); + updateLogger(); + } + + Client::~Client() + { + rakPeer->CloseConnection(serverId, true, 0); + } + void Client::disconnect(int blockDuration) { removeAllChildren(); @@ -39,5 +50,48 @@ namespace RBX updateNetworkSimulator(); } + + void Client::onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider) + { + Listener* oldListener = this; + + if (oldProvider) + { + oldProvider->Notifier::removeListener(oldListener); + } + + if (oldProvider) + { + RunService* runService = oldProvider->find(); + + if (runService) + runService->runDisabled = false; + + disconnect(3000); + + Players* p = oldProvider->find(); + p->setConnection(NULL); + } + + Instance::onServiceProvider(oldProvider, newProvider); + + if (newProvider) + { + Players* p = newProvider->create(); + p->setConnection(rakPeer.get()); + + RunService* runService = newProvider->find(); + + if (runService) + runService->runDisabled = true; + } + + Listener* newListener = this; + + if (newProvider) + { + newProvider->Notifier::addListener(newListener); + } + } } } diff --git a/Client/Network/IdManager.cpp b/Client/Network/IdManager.cpp index 4ac908e8..4182d662 100644 --- a/Client/Network/IdManager.cpp +++ b/Client/Network/IdManager.cpp @@ -5,13 +5,6 @@ namespace RBX { - void IdManager::addInstance(Instance* instance, Guid::Data explicitId) - { - RBXASSERT(getInstance(explicitId) == NULL); - - instance->assignGuid(explicitId); - addInstance(instance); - } void IdManager::addInstance(Instance* instance) { diff --git a/Client/Network/IdManager.h b/Client/Network/IdManager.h index 25db3504..960f8a4f 100644 --- a/Client/Network/IdManager.h +++ b/Client/Network/IdManager.h @@ -16,9 +16,25 @@ namespace RBX private: std::map items; boost::signals::scoped_connection removeInstanceConnection; + public: - Instance* getInstance(Guid::Data id); - void addInstance(Instance* instance, Guid::Data explicitId); + Instance* getInstance(Guid::Data id) + { + if (*id.scope == Name::getNullName()) + return NULL; + + std::map::const_iterator found = items.find(id); + return found != items.end() ? found->second : NULL; + } + + void addInstance(Instance* instance, Guid::Data explicitId) + { + RBXASSERT(getInstance(explicitId) == NULL); + + instance->assignGuid(explicitId); + addInstance(instance); + } + void addInstance(Instance* instance); protected: virtual void onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider); From ee9b70e8f950ce380c40964b0dbd13e1762201bd Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Wed, 6 May 2026 18:03:12 +0300 Subject: [PATCH 09/13] Player --- Client/App/include/util/Events.h | 7 ++- Client/Network/Player.cpp | 63 +++++++++++++++++++++++-- Client/Network/include/Network/Player.h | 13 +++-- 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/Client/App/include/util/Events.h b/Client/App/include/util/Events.h index 4dc6810c..ed7e24a4 100644 --- a/Client/App/include/util/Events.h +++ b/Client/App/include/util/Events.h @@ -39,13 +39,18 @@ namespace RBX protected: Notifier(const Notifier&); + Notifier() : listeners(), raiseRange(NULL) { } + Notifier& operator=(const Notifier&); - virtual ~Notifier(); + + virtual ~Notifier() + { + } public: void addListener(Listener*) const; diff --git a/Client/Network/Player.cpp b/Client/Network/Player.cpp index f49ddcca..d1338dbe 100644 --- a/Client/Network/Player.cpp +++ b/Client/Network/Player.cpp @@ -1,16 +1,41 @@ #include "Client.h" #include "Player.h" +#include "security/SecurityContext.h" #include "v8datamodel/TimerService.h" +RBX::Reflection::PropDescriptor prop_teamColor("TeamColor", "Team", &RBX::Network::Player::getTeamColor, &RBX::Network::Player::setTeamColor, RBX::Reflection::PropertyDescriptor::STANDARD); +RBX::Reflection::PropDescriptor prop_neutral("Neutral", "Team", &RBX::Network::Player::getNeutral, &RBX::Network::Player::setNeutral, RBX::Reflection::PropertyDescriptor::STANDARD); +RBX::Reflection::PropDescriptor prop_characterAppearance("CharacterAppearance", "Data", &RBX::Network::Player::getCharacterAppearance, &RBX::Network::Player::setCharacterAppearance, RBX::Reflection::PropertyDescriptor::STANDARD); + +RBX::Reflection::RefPropDescriptor prop_Character("Character", "Data", &RBX::Network::Player::getCharacter, &RBX::Network::Player::setCharacter, RBX::Reflection::PropertyDescriptor::STANDARD); + +RBX::Reflection::SignalDesc event_Idled("Idled", "time"); + +static void addChild(const boost::shared_ptr& parent, const boost::shared_ptr& child) +{ + child->setParent(parent.get()); +} + namespace RBX { namespace Network { - Reflection::PropDescriptor prop_teamColor("TeamColor", "Team", &Player::getTeamColor, &Player::setTeamColor, Reflection::PropertyDescriptor::STANDARD); - Reflection::PropDescriptor prop_neutral("Neutral", "Team", &Player::getNeutral, &Player::setNeutral, Reflection::PropertyDescriptor::STANDARD); - Reflection::PropDescriptor prop_characterAppearance("CharacterAppearance", "Data", &Player::getCharacterAppearance, &Player::setCharacterAppearance, Reflection::PropertyDescriptor::STANDARD); + Player::Player() + : teamColor(BrickColor::lego_1), + neutral(true), + under13(false), + superSafeChat(false), + userId(0), + lastActivityTime(0.0) + { + Security::Context::current().requirePermission(Security::Administrator, "create a Player"); + setName("Player"); + } - Reflection::SignalDesc event_Idled("Idled", "time"); + Player::~Player() + { + setCharacter(NULL); + } Backpack* Player::getPlayerBackpack() const { @@ -133,5 +158,35 @@ namespace RBX timerService->delay(boost::bind(&Player::doPeriodicIdleCheck, shared_from(this)), 30.0); } } + + void Player::setCharacter(ModelInstance* value) + { + if (value != character.get()) + { + if (character.get()) + { + characterDiedConnection.disconnect(); + + Notifier::raise(character.get()); + + if (Players::backendProcessing(this, false)) + character->setParent(NULL); + + character.reset(); + } + + if (value) + { + character = shared_from(value); + + Notifier::raise(character.get()); + + if (Players::frontendProcessing(this, false)) + onCharacterChangedFrontend(); + } + + raisePropertyChanged(prop_Character); + } + } } } diff --git a/Client/Network/include/Network/Player.h b/Client/Network/include/Network/Player.h index cdb0da8f..0bf66a36 100644 --- a/Client/Network/include/Network/Player.h +++ b/Client/Network/include/Network/Player.h @@ -1,6 +1,5 @@ #pragma once #include "Network/Players.h" -#include "security/SecurityContext.h" #include "humanoid/Humanoid.h" #include "v8datamodel/BrickColor.h" #include "v8datamodel/Backpack.h" @@ -16,7 +15,10 @@ namespace RBX const boost::shared_ptr character; public: - CharacterAdded(Instance*); + CharacterAdded(Instance* character) + : character(shared_from(character)) + { + } }; struct CharacterRemoving @@ -25,7 +27,10 @@ namespace RBX const boost::shared_ptr character; public: - CharacterRemoving(Instance*); + CharacterRemoving(Instance* character) + : character(shared_from(character)) + { + } }; extern const char* sPlayer; @@ -65,7 +70,7 @@ namespace RBX { return character.get(); } - void setCharacter(ModelInstance*); + void setCharacter(ModelInstance* value); BrickColor getTeamColor() const { return teamColor; From bc9773aab3d4946b8c6fec87abbc91514ed6b176 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Wed, 6 May 2026 19:08:14 +0300 Subject: [PATCH 10/13] Players dtor, setMaxPlayers --- Client/Network/Players.cpp | 32 ++++++++++++++++++++++-- Client/Network/include/Network/Players.h | 2 +- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Client/Network/Players.cpp b/Client/Network/Players.cpp index d1076bbd..6539339c 100644 --- a/Client/Network/Players.cpp +++ b/Client/Network/Players.cpp @@ -10,20 +10,39 @@ class PluginInterfaceAdapter : public PluginInterface private: Class* c; protected: - PluginInterfaceAdapter(Class*); + PluginInterfaceAdapter(Class* c) + : PluginInterface(), + c(c) + { + } public: virtual PluginReceiveResult OnReceive(RakPeerInterface* peer, Packet* packet); }; +RBX::Reflection::PropDescriptor propPlayerCount("NumPlayers", "Data", &RBX::Network::Players::numPlayers, NULL, RBX::Reflection::PropertyDescriptor::UI); +RBX::Reflection::PropDescriptor propPlayerMaxCount("MaxPlayers", "Data", &RBX::Network::Players::getMaxPlayers, &RBX::Network::Players::setMaxPlayers, RBX::Reflection::PropertyDescriptor::STANDARD); + namespace RBX { namespace Network { class Players::Plugin : public PluginInterfaceAdapter { - Plugin(Players*); + public: + Plugin(Players* players) + : PluginInterfaceAdapter(players) + { + } }; + Players::~Players() + { + if (peer) + peer->DetachPlugin(plugin.get()); + + peer = NULL; + } + bool Players::clientIsPresent(const Instance* context, bool testInDatamodel) { return Client::clientIsPresent(context, testInDatamodel); @@ -65,6 +84,15 @@ namespace RBX abuseReporter.reset(new AbuseReporter(value)); } + void Players::setMaxPlayers(int value) + { + if (value != maxPlayers) + { + maxPlayers = value; + raisePropertyChanged(propPlayerMaxCount); + } + } + void Players::reportAbuse(boost::shared_ptr player, std::string comment) { reportAbuse(fastDynamicCast(player.get()), comment); diff --git a/Client/Network/include/Network/Players.h b/Client/Network/include/Network/Players.h index e2af6c89..d62df585 100644 --- a/Client/Network/include/Network/Players.h +++ b/Client/Network/include/Network/Players.h @@ -112,7 +112,7 @@ namespace RBX { return maxPlayers; } - void setMaxPlayers(int); + void setMaxPlayers(int value); boost::shared_ptr>> getPlayers() { return players.read(); From 826e84bdd61801a5b5165fedbeee1a4187ea5294 Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Wed, 6 May 2026 20:02:59 +0300 Subject: [PATCH 11/13] Server functions --- Client/Network/Server.cpp | 49 +++++++++++++++++++++++++++++++++++++++ Client/Network/Server.h | 4 ++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Client/Network/Server.cpp b/Client/Network/Server.cpp index 0e449a9a..91af2cab 100644 --- a/Client/Network/Server.cpp +++ b/Client/Network/Server.cpp @@ -1,5 +1,6 @@ #include #include "Server.h" +#include "IdManager.h" #include "API.h" #include "util/standardout.h" @@ -65,6 +66,35 @@ namespace RBX } } + void Server::onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider) + { + if (oldProvider) + { + players->setConnection(NULL); + + if (rakPeer->IsActive()) + rakPeer->Shutdown(1000); + + removeAllChildren(); + + players.reset(); + } + + Peer::onServiceProvider(oldProvider, newProvider); + + if (newProvider) + { + players = shared_from(newProvider->create()); + + players->setConnection(peerInterface()); + } + } + + void Server::setServerManagerPing(std::string pingUrl, std::string publicIP, int thumbnailId) + { + pingThread.reset(new worker_thread(boost::bind(&Server::ping, boost::weak_ptr(shared_from(this)), publicIP, thumbnailId, pingUrl), "rbx_serverping")); + } + Server::ClientProxy::ClientProxy(SystemAddress systemAddress, Server* server) : Replicator(systemAddress, server->peerInterface()), server(server) @@ -77,5 +107,24 @@ namespace RBX { sendPhysicsEnabled = true; } + + void Server::ClientProxy::sendTop() + { + RakNet::BitStream bitStream; + + bitStream << 'K'; + + std::vector::iterator end = replicationContainers.end(); + + for (std::vector::iterator iter = replicationContainers.begin(); iter != end; iter++) + { + RBXASSERT(*iter != NULL); + + serializeId(bitStream, *iter); + ServiceProvider::create(this)->addInstance(*iter); + } + + peer->Send(&bitStream, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, remotePlayerId, false); + } } } diff --git a/Client/Network/Server.h b/Client/Network/Server.h index 52a4b7fe..d71e63bb 100644 --- a/Client/Network/Server.h +++ b/Client/Network/Server.h @@ -58,9 +58,9 @@ namespace RBX int getClientCount(); virtual XmlElement* write(); virtual PluginReceiveResult OnReceive(RakPeerInterface*, Packet*); - void setServerManagerPing(std::string, std::string, int); + void setServerManagerPing(std::string pingUrl, std::string publicIP, int thumbnailId); protected: - virtual void onServiceProvider(const ServiceProvider*, const ServiceProvider*); + virtual void onServiceProvider(const ServiceProvider* oldProvider, const ServiceProvider* newProvider); virtual bool askAddChild(const Instance* instance) const { return fastDynamicCast(instance) != NULL; From 05a006d1547e3c95b60891b5e758ba59420977ce Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Thu, 7 May 2026 13:21:09 +0300 Subject: [PATCH 12/13] isChildOfPendingDeleteInstance, onPropertyChanged --- Client/App/include/reflection/property.h | 5 ++- Client/Network/Replicator.cpp | 50 ++++++++++++++++++++++++ Client/Network/Replicator.h | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Client/App/include/reflection/property.h b/Client/App/include/reflection/property.h index 5d2e6197..286fc2b0 100644 --- a/Client/App/include/reflection/property.h +++ b/Client/App/include/reflection/property.h @@ -39,7 +39,10 @@ namespace RBX public: bool isPublic() const; virtual bool isReadOnly() const = 0; - bool canStreamWrite() const; + bool canStreamWrite() const + { + return bCanStreamWrite; + } bool operator==(const PropertyDescriptor& other) const { return this == &other; diff --git a/Client/Network/Replicator.cpp b/Client/Network/Replicator.cpp index 7a8030d6..67c867fa 100644 --- a/Client/Network/Replicator.cpp +++ b/Client/Network/Replicator.cpp @@ -555,6 +555,56 @@ namespace RBX } } + //95.65% match + //functionally accurate, *slightly* different instruction ordering + bool Replicator::isChildOfPendingDeleteInstance(const Instance* instance) const + { + for (const Instance* current = instance->getParent(); current != NULL; current = current->getParent()) + { + if (pendingDeleteInstances.find(current) != pendingDeleteInstances.end()) + return true; + } + + return false; + } + + void Replicator::onPropertyChanged(boost::shared_ptr instance, const Reflection::PropertyDescriptor* descriptor) + { + Profiling::Mark mark(*profileReplication, false); + Profiling::Mark mark2(*profileDataListening, false); + + if (instance.get() != removingInstance) + { + bool isDeserializeProperty = false; + + if (deserializeProperty) + { + const DescribedBase* inst = static_cast(instance.get()); // force static cast outside of the RBXASSERT block + RBXASSERT(descriptor->isMemberOf(inst)); + + if (descriptor == &deserializeProperty->getDescriptor() && inst == deserializeProperty->getInstance()) + isDeserializeProperty = true; + } + + if (!isDeserializeProperty && (descriptor->canStreamWrite() || descriptor == &Instance::propParent) && !isSerializePending(instance.get())) + { + const DescribedBase* inst = static_cast(instance.get()); + RBXASSERT(descriptor->isMemberOf(inst)); + + Player* targetPlayer = findTargetPlayer(); + + if (targetPlayer && targetPlayer->getCharacter() && instance->isDescendentOf(targetPlayer->getCharacter())) + { + pendingItems.push_front(boost::shared_ptr(new ChangePropertyItem(*this, instance, *descriptor))); + } + else + { + pendingItems.push_back(boost::shared_ptr(new ChangePropertyItem(*this, instance, *descriptor))); + } + } + } + } + void Replicator::Item::readItemType(RakNet::BitStream& stream, ItemType& value) { value = ItemTypeEnd; diff --git a/Client/Network/Replicator.h b/Client/Network/Replicator.h index 1cb5d416..b986b6d9 100644 --- a/Client/Network/Replicator.h +++ b/Client/Network/Replicator.h @@ -257,7 +257,7 @@ namespace RBX virtual void onServiceProvider(const ServiceProvider*, const ServiceProvider*); void onDescendentAdded(boost::shared_ptr); void onDescendentRemoving(boost::shared_ptr); - void onPropertyChanged(boost::shared_ptr, const Reflection::PropertyDescriptor*); + void onPropertyChanged(boost::shared_ptr instance, const Reflection::PropertyDescriptor* descriptor); const Instance* getDefault(const Name& className); void deleteDisconnectInstances(); void connectPropertyChanged(boost::shared_ptr instance); From ab01ab8d0dacce7d0c043d7e728f15320befb1bb Mon Sep 17 00:00:00 2001 From: luasoft10 Date: Sun, 17 May 2026 20:22:51 +0300 Subject: [PATCH 13/13] Merge remote-tracking branch 'upstream/master' into more-network --- Client/App/App.vcproj | 4 + Client/App/include/tool/Dragger.h | 6 +- Client/App/include/tool/MegaDragger.h | 17 ++- Client/App/include/util/Extents.h | 8 +- Client/App/include/v8datamodel/ICameraOwner.h | 5 +- Client/App/include/v8datamodel/RootInstance.h | 5 +- Client/App/include/v8world/World.h | 5 +- Client/App/tool/MegaDragger.cpp | 135 ++++++++++++++++++ 8 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 Client/App/tool/MegaDragger.cpp diff --git a/Client/App/App.vcproj b/Client/App/App.vcproj index f5eec253..2cd03e80 100644 --- a/Client/App/App.vcproj +++ b/Client/App/App.vcproj @@ -1898,6 +1898,10 @@ RelativePath=".\tool\DragUtilities.cpp" > + + diff --git a/Client/App/include/tool/Dragger.h b/Client/App/include/tool/Dragger.h index 63e99e56..917fb65a 100644 --- a/Client/App/include/tool/Dragger.h +++ b/Client/App/include/tool/Dragger.h @@ -25,7 +25,11 @@ namespace RBX static void searchUpGross(const G3D::Array&, G3D::Vector3&, ContactManager&); static void searchDownGross(const G3D::Array&, G3D::Vector3&, ContactManager&); public: - static const G3D::Vector3& dragSnap(); + static const G3D::Vector3& dragSnap() + { + static G3D::Vector3 v(1, 0.1, 1); + return v; + } static G3D::Vector3 toGrid(const G3D::Vector3&); static G3D::Vector3 safeMoveNoDrop(const G3D::Array&, const G3D::Vector3&, ContactManager&); static G3D::Vector3 safeMoveYDrop(const G3D::Array&, const G3D::Vector3&, ContactManager&); diff --git a/Client/App/include/tool/MegaDragger.h b/Client/App/include/tool/MegaDragger.h index 9876f5c7..ecc82b8b 100644 --- a/Client/App/include/tool/MegaDragger.h +++ b/Client/App/include/tool/MegaDragger.h @@ -4,6 +4,7 @@ #include #include #include "util/UIEvent.h" +#include "util/Debug.h" namespace RBX { @@ -23,8 +24,8 @@ namespace RBX public: //MegaDragger(const MegaDragger&); - MegaDragger(PartInstance*, const std::vector>&, RootInstance*); - MegaDragger(PartInstance*, const std::vector&, RootInstance*); + MegaDragger(PartInstance* mousePartPtr, const std::vector>& partArray, RootInstance* rootInstance); + MegaDragger(PartInstance* mousePartPtr, const std::vector& pvInstances, RootInstance* rootInstance); ~MegaDragger(); public: void startDragging(); @@ -32,12 +33,16 @@ namespace RBX void finishDragging(); bool mousePartAlive(); bool anyDragPartAlive(); - boost::weak_ptr getMousePart(); + boost::weak_ptr getMousePart() + { + RBXASSERT(!mousePart.expired()); + return mousePart; + } void alignAndCleanParts(); G3D::Vector3 hitObjectOrPlane(const UIEvent&); - G3D::Vector3 safeMoveYDrop(const G3D::Vector3&); + G3D::Vector3 safeMoveYDrop(const G3D::Vector3& tryDrag); G3D::Vector3 safeMoveAlongLine(const G3D::Vector3&); - G3D::Vector3 safeMoveNoDrop(const G3D::Vector3&); - void safeRotate(const G3D::Matrix3&); + G3D::Vector3 safeMoveNoDrop(const G3D::Vector3& tryDrag); + void safeRotate(const G3D::Matrix3& rotMatrix); }; } diff --git a/Client/App/include/util/Extents.h b/Client/App/include/util/Extents.h index b711c765..4ea1c2ce 100644 --- a/Client/App/include/util/Extents.h +++ b/Client/App/include/util/Extents.h @@ -28,7 +28,13 @@ namespace RBX G3D::Vector3 size() const { return this->high - this->low; } G3D::Vector3 center() const { return (this->high + this->low) * 0.5f; } G3D::Vector3 bottomCenter() const; - G3D::Vector3 topCenter() const; + G3D::Vector3 topCenter() const + { + G3D::Vector3 v = (this->low + this->high) * 0.5f; + v.y = high.y; + + return v; + } float longestSide() const; float volume() const; float areaXZ() const; diff --git a/Client/App/include/v8datamodel/ICameraOwner.h b/Client/App/include/v8datamodel/ICameraOwner.h index 7aadd7c3..c3b5e366 100644 --- a/Client/App/include/v8datamodel/ICameraOwner.h +++ b/Client/App/include/v8datamodel/ICameraOwner.h @@ -28,7 +28,10 @@ namespace RBX public: void setCameraIgnoreParts(const std::vector&); void setCameraIgnoreParts(PartInstance*); - void clearCameraIgnoreParts(); + void clearCameraIgnoreParts() + { + cameraIgnoreParts.clear(); + } void getCameraIgnorePrimitives(std::vector&); }; } diff --git a/Client/App/include/v8datamodel/RootInstance.h b/Client/App/include/v8datamodel/RootInstance.h index 7c840b9a..7fc470b3 100644 --- a/Client/App/include/v8datamodel/RootInstance.h +++ b/Client/App/include/v8datamodel/RootInstance.h @@ -65,7 +65,10 @@ namespace RBX void setInsertPoint(const G3D::Vector3& topCenter); void moveToPoint(PVInstance* pv, G3D::Vector3 point); ControllerTypeArray getControllersUsed() const; - World* getWorld(); + World* getWorld() + { + return world.get(); + } const G3D::Rect2D& getViewPort(); public: //RootInstance& operator=(RootInstance&); diff --git a/Client/App/include/v8world/World.h b/Client/App/include/v8world/World.h index 9c5ecd1a..96f7a11f 100644 --- a/Client/App/include/v8world/World.h +++ b/Client/App/include/v8world/World.h @@ -84,7 +84,10 @@ namespace RBX } void addedBodyForce(); void setCanThrottle(bool); - ContactManager& getContactManager(); + ContactManager& getContactManager() + { + return *contactManager; + } ClumpStage* getClumpStage(); const CollisionStage* getCollisionStage() const; CollisionStage* getCollisionStage(); diff --git a/Client/App/tool/MegaDragger.cpp b/Client/App/tool/MegaDragger.cpp new file mode 100644 index 00000000..2054e7ad --- /dev/null +++ b/Client/App/tool/MegaDragger.cpp @@ -0,0 +1,135 @@ +#include "tool/MegaDragger.h" +#include "tool/DragUtilities.h" +#include "tool/Dragger.h" +#include "v8world/ContactManager.h" +#include "v8datamodel/PartInstance.h" +#include "v8datamodel/RootInstance.h" + +namespace RBX +{ + MegaDragger::MegaDragger(PartInstance* mousePartPtr, const std::vector& pvInstances, RootInstance* rootInstance) + : mousePart(shared_from(mousePartPtr)), + dragParts(), + joined(true), + rootInstance(rootInstance), + contactManager(rootInstance->getWorld()->getContactManager()) + { + DragUtilities::pvsToParts(pvInstances, dragParts); + } + + MegaDragger::MegaDragger(PartInstance* mousePartPtr, const std::vector>& partArray, RootInstance* rootInstance) + : mousePart(shared_from(mousePartPtr)), + dragParts(partArray), + rootInstance(rootInstance), + joined(true), + contactManager(rootInstance->getWorld()->getContactManager()) + { + } + + + MegaDragger::~MegaDragger() + { + if (!joined) + { + RBXASSERT(0); + DragUtilities::joinAndStopDragging(dragParts); + } + } + + void MegaDragger::startDragging() + { + DragUtilities::unJoinAndSetDragging(dragParts); + + if (PartInstance::nonNullInWorkspace(mousePart.lock())) + { + rootInstance->setCameraIgnoreParts(mousePart.lock().get()); + } + joined = false; + } + + void MegaDragger::continueDragging() + { + RBXASSERT(!joined); + DragUtilities::unJoinAndSetDragging(dragParts); + } + + void MegaDragger::finishDragging() + { + RBXASSERT(!joined); + rootInstance->clearCameraIgnoreParts(); + DragUtilities::joinAndStopDragging(dragParts); + if (DragUtilities::anyPartAlive(dragParts)) + { + rootInstance->setInsertPoint(DragUtilities::computeExtents(dragParts).topCenter()); + } + joined = true; + } + + void MegaDragger::alignAndCleanParts() + { + RBXASSERT(!joined); + if (PartInstance::nonNullInWorkspace(mousePart.lock())) + { + PartInstance* part = mousePart.lock().get(); + + if (!part->aligned()) + { + G3D::CoordinateFrame snap = part->worldSnapLocation(); + G3D::CoordinateFrame snapToGrid = Math::snapToGrid(snap, Dragger::dragSnap()); + DragUtilities::move(dragParts, snap, snapToGrid); + } + } + DragUtilities::clean(dragParts); + } + + G3D::Vector3 MegaDragger::safeMoveYDrop(const G3D::Vector3& tryDrag) + { + RBXASSERT(!joined); + RBXASSERT(tryDrag == Math::toGrid(tryDrag, 0.1f)); + + G3D::Array primitives; + DragUtilities::partsToPrimitives(dragParts, primitives); + + RBXASSERT(primitives.size() > 0); + + G3D::Vector3 result = Dragger::safeMoveYDrop(primitives, tryDrag, contactManager); + + RBXASSERT(!contactManager.intersectingOthers(primitives, 0.01f)); + return result; + } + + G3D::Vector3 MegaDragger::safeMoveNoDrop(const G3D::Vector3& tryDrag) + { + RBXASSERT(!joined); + RBXASSERT(tryDrag == Math::toGrid(tryDrag, 0.1f)); + + G3D::Array primitives; + DragUtilities::partsToPrimitives(dragParts, primitives); + + RBXASSERT(primitives.size() > 0); + + G3D::Vector3 result = Dragger::safeMoveNoDrop(primitives, tryDrag, contactManager); + + RBXASSERT(!contactManager.intersectingOthers(primitives, 0.01f)); + return result; + } + + void MegaDragger::safeRotate(const G3D::Matrix3& rotMatrix) + { + RBXASSERT(!joined); + + G3D::Array primitives; + DragUtilities::partsToPrimitives(dragParts, primitives); + + RBXASSERT(primitives.size() > 0); + + Dragger::safeRotate(primitives, rotMatrix, contactManager); + + RBXASSERT(!contactManager.intersectingOthers(primitives, 0.01f)); + } + + bool MegaDragger::mousePartAlive() + { + return PartInstance::nonNullInWorkspace(mousePart.lock()); + } +} \ No newline at end of file