#include "RoundRobinPhysicsSender.hpp" #include "Replicator.hpp" #include "Compressor.hpp" #include "Player.hpp" #include "RakNet/GetTime.hpp" #include "RakNet/RakPeerInterface.hpp" #include "NetworkSettings.hpp" #include "ConcurrentRakPeer.hpp" #include "Client.hpp" #include "World/SendPhysics.hpp" #include "World/World.hpp" #include "World/Primitive.hpp" #include "World/Assembly.hpp" #include "DataModel/PartInstance.hpp" #include "DataModel/ModelInstance.hpp" #include "DataModel/Workspace.hpp" #include "DataModel/PhysicsService.hpp" #include "RakNet/RakNetStatistics.hpp" using namespace Aya; using namespace Aya::Network; SYNCHRONIZED_FASTFLAG(PhysicsPacketSendWorldStepTimestamp) ///////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////// RoundRobinPhysicsSender::RoundRobinPhysicsSender(Replicator& replicator) : PhysicsSender(replicator) , lastCharacterSendTime(Time::now()) { } void RoundRobinPhysicsSender::sendPhysicsData(RakNet::BitStream& bitStream, const Assembly* assembly) { AYAASSERT(assembly); if (assembly) { senderStats = NULL; if (replicator.settings().trackPhysicsDetails) { senderStats = &(replicator.replicatorStats.physicsSenderStats); } const PartInstance* part = PartInstance::fromConstPrimitive(assembly->getConstAssemblyPrimitive()); if (canSend(part, assembly, bitStream)) { Player* player = replicator.findTargetPlayer(); ModelInstance* m = player ? player->getCharacter() : 0; bool detailed = m && m->isAncestorOf(part); if (replicator.isStreamingEnabled()) { bitStream << false; // token: not done with packet bitStream << false; // not CFrame only if (replicator.trySerializeId(bitStream, part)) { RakNet::BitSize_t characterBytes = bitStream.GetNumberOfBytesUsed(); CoordinateFrame cFrame = part->getCoordinateFrame(); float accumulatedError; sendMechanism(bitStream, part, detailed, currentStepTimestamp, cFrame, accumulatedError, NULL); if (senderStats && detailed) { senderStats->details.characterAnim.increment(); senderStats->details.characterAnimSize.sample(bitStream.GetNumberOfBytesUsed() - characterBytes); } } else { replicator.serializeId(bitStream, NULL); // token: done with this mechanism } } else if (replicator.trySerializeId(bitStream, part)) { RakNet::BitSize_t characterBytes = bitStream.GetNumberOfBytesUsed(); CoordinateFrame cFrame = part->getCoordinateFrame(); float accumulatedError; sendMechanism(bitStream, part, detailed, currentStepTimestamp, cFrame, accumulatedError, NULL); if (senderStats && detailed) { senderStats->details.characterAnim.increment(); senderStats->details.characterAnimSize.sample(bitStream.GetNumberOfBytesUsed() - characterBytes); } } } } } const SimJob* RoundRobinPhysicsSender::findTargetPlayerCharacterSimJob() { if (const Player* player = replicator.findTargetPlayer()) { if (const Primitive* primitive = player->getConstCharacterRoot()) { return SimJob::getConstSimJobFromPrimitive(primitive); } } return NULL; } class RoundRobinPhysicsSender::JobSender : public boost::noncopyable { // Hacky way to keep track of the first object sent, since it may be sent again after reportClosestMechanism const Assembly* firstA; const SimJob* firstM; RoundRobinPhysicsSender& sender; ConcurrentRakPeer* rakPeer; const int maxStreamSize; shared_ptr bitStream; PacketPriority packetPriority; unsigned int emptySize; void openPacket() { bitStream.reset(new RakNet::BitStream(maxStreamSize)); *bitStream << (unsigned char)ID_TIMESTAMP; RakNet::Time now = RakNet::GetTime(); #if !defined(__linux) && !defined(__APPLE__) *bitStream << now; #else *bitStream << static_cast(now); #endif *bitStream << (unsigned char)ID_PHYSICS; emptySize = bitStream->GetNumberOfBitsUsed(); ++packetCount; itemCount = 0; } void openPacketPhysicsOffset(RakNet::Time& newTimeStamp) { bitStream.reset(new RakNet::BitStream(maxStreamSize)); *bitStream << (unsigned char)ID_TIMESTAMP; RakNet::Time now = RakNet::GetTime(); #if !defined(__linux) && !defined(__APPLE__) *bitStream << now; #else *bitStream << static_cast(now); #endif *bitStream << (unsigned char)ID_PHYSICS; #if !defined(__linux) && !defined(__APPLE__) *bitStream << newTimeStamp; #else *bitStream << static_cast(newTimeStamp); #endif emptySize = bitStream->GetNumberOfBitsUsed(); ++packetCount; itemCount = 0; } void closePacket() { if (bitStream->GetNumberOfBitsUsed() > emptySize) { // Packet "end" tag if (sender.replicator.isStreamingEnabled()) *bitStream << true; else sender.replicator.serializeId(*bitStream, NULL); // Send ID_PHYSICS ReplicatorStats::PhysicsSenderStats& physStats = sender.replicator.physicsSenderStats(); rakPeer->Send(bitStream, packetPriority, UNRELIABLE, PHYSICS_CHANNEL, sender.replicator.remotePlayerId, false); physStats.physicsPacketsSent.sample(); physStats.physicsPacketsSentSmooth.sample(); physStats.physicsPacketsSentSize.sample(bitStream->GetNumberOfBytesUsed()); sender.itemsPerPacket.sample(itemCount); } bitStream.reset(); } public: int packetCount; unsigned int itemCount; JobSender(RoundRobinPhysicsSender& sender, ConcurrentRakPeer* peer) : sender(sender) , rakPeer(peer) , firstA(NULL) , firstM(NULL) , packetPriority(sender.replicator.settings().getPhysicsSendPriority()) , packetCount(0) , itemCount(0) , maxStreamSize(sender.replicator.getPhysicsMtuSize()) { } void close() { if (bitStream) closePacket(); } ~JobSender() { close(); } // Returns true if it wants another sample bool report(const SimJob& simJob) { if (SFFlag::getPhysicsPacketSendWorldStepTimestamp()) { float timeOffsetDueToPhysics = 0.0f; Workspace* w = ServiceProvider::find(&sender.replicator); if (w) { timeOffsetDueToPhysics = w->getWorld()->getUpdateExpectedStepDelta(); } // Generates timestamp for this frame, minus a small offset based on // missed or extra world steps taken. RakNet::Time passStamp = (RakNet::Time)((TaskScheduler::singleton().lastCyclcTimestamp + Time::Interval(sender.replicator.getRakOffsetTime() - timeOffsetDueToPhysics)) .timestampSeconds() * 1000.0); if (!bitStream) openPacketPhysicsOffset(passStamp); } else { if (!bitStream) openPacket(); } sender.sendPhysicsData(*bitStream, simJob.getConstAssembly()); const int newSize = bitStream->GetNumberOfBytesUsed(); itemCount++; return newSize < maxStreamSize; } bool operator()(SimJob& m) { return report(m); } }; void RoundRobinPhysicsSender::step() { if (Instance::fastDynamicCast(replicator.getParent())) { float bufferHealth = replicator.rakPeer->GetBufferHealth(); if ((Time::nowFast() - lastBufferCheckTime).seconds() > 3.0) { if (bufferHealth <= 0.5) { // buffer is growing, reduce packets per step if (sendPacketsPerStep > 1) // don't go below 1 sendPacketsPerStep--; } else if (bufferHealth >= 0.9) { // buffer health is good, push more data Workspace* w = ServiceProvider::find(&replicator); if (w) { if ((itemsPerPacket.value() * sendPacketsPerStep) < w->getWorld()->getSendPhysics()->getNumSimJobs()) sendPacketsPerStep++; } } lastBufferCheckTime = Time::nowFast(); } } } int RoundRobinPhysicsSender::sendPacket(int maxPackets, PacketPriority packetPriority, ReplicatorStats::PhysicsSenderStats* stats) { if (replicator.getBufferCountAvailable(maxPackets, packetPriority) <= 0) return 0; JobSender jobSender(*this, replicator.rakPeer.get()); // TODO: Check timer and feed these gradually? Workspace* w = ServiceProvider::find(&replicator); if (w) { const SimJob* characterSimJob = findTargetPlayerCharacterSimJob(); if (characterSimJob) { // For a minimal level of responsiveness, occasionally send the character with a high priority const Time t = Time::now(); if (t > lastCharacterSendTime + Time::Interval(0.1)) { jobSender.report(*characterSimJob); lastCharacterSendTime = t; } else { characterSimJob = NULL; // don't ignore me below } } int numSimJobs = w->getWorld()->getSendPhysics()->getNumSimJobs(); int numSimJobsReported = 0; for (int i = 0; i < maxPackets; i++) { numSimJobsReported += w->getWorld()->getSendPhysics()->reportSimJobs(jobSender, jobStagePos, characterSimJob, numSimJobs - numSimJobsReported); if (numSimJobsReported >= numSimJobs) break; jobSender.close(); } } // jobSend close automatically on destruction return jobSender.packetCount; };