forked from aya/aya
Initial commit
This commit is contained in:
156
engine/network/CMakeLists.txt
Normal file
156
engine/network/CMakeLists.txt
Normal file
@@ -0,0 +1,156 @@
|
||||
add_library(NetworkPlayer STATIC $<TARGET_OBJECTS:RakNet>)
|
||||
add_library(NetworkStudio STATIC $<TARGET_OBJECTS:RakNet>)
|
||||
add_library(NetworkServer STATIC $<TARGET_OBJECTS:RakNet>)
|
||||
|
||||
set(SOURCES
|
||||
src/API.cpp
|
||||
src/BoostAppend.cpp
|
||||
src/BoostAppend.hpp
|
||||
src/ChatFilter.cpp
|
||||
src/ChatFilter.hpp
|
||||
src/Client.cpp
|
||||
src/Client.hpp
|
||||
src/ClientReplicator.cpp
|
||||
src/ClientReplicator.hpp
|
||||
src/ClusterUpdateBuffer.cpp
|
||||
src/ClusterUpdateBuffer.hpp
|
||||
src/Compressor.cpp
|
||||
src/Compressor.hpp
|
||||
src/CrispProxy.hpp
|
||||
src/ConcurrentRakPeer.cpp
|
||||
src/ConcurrentRakPeer.hpp
|
||||
src/DataBlockEncryptor.cpp
|
||||
src/DataBlockEncryptor.hpp
|
||||
src/Dictionary.cpp
|
||||
src/Dictionary.hpp
|
||||
src/DirectPhysicsReceiver.cpp
|
||||
src/DirectPhysicsReceiver.hpp
|
||||
src/ErrorCompPhysicsSender.cpp
|
||||
src/ErrorCompPhysicsSender.hpp
|
||||
src/ErrorCompPhysicsSender2.cpp
|
||||
src/ErrorCompPhysicsSender2.hpp
|
||||
src/GameConfigurer.cpp
|
||||
src/GameConfigurer.hpp
|
||||
src/GamePerfMonitor.cpp
|
||||
src/GamePerfMonitor.hpp
|
||||
src/GuidRegistryService.cpp
|
||||
src/GuidRegistryService.hpp
|
||||
src/InterpolatingPhysicsReceiver.cpp
|
||||
src/InterpolatingPhysicsReceiver.hpp
|
||||
src/Item.cpp
|
||||
src/Item.hpp
|
||||
src/Marker.cpp
|
||||
src/Marker.hpp
|
||||
src/MechanismItem.cpp
|
||||
src/MechanismItem.hpp
|
||||
src/MovementHistoryJob.cpp
|
||||
src/MovementHistoryJob.hpp
|
||||
src/NetworkClusterPacketCache.cpp
|
||||
src/NetworkFilter.cpp
|
||||
src/NetworkFilter.hpp
|
||||
src/NetworkOwnerJob.cpp
|
||||
src/NetworkOwnerJob.hpp
|
||||
src/NetworkPacketCache.cpp
|
||||
src/NetworkProfiler.cpp
|
||||
src/NetworkProfiler.hpp
|
||||
src/NetworkSettings.cpp
|
||||
src/NetworkSettings.hpp
|
||||
src/PacketIds.hpp
|
||||
src/Peer.cpp
|
||||
src/Peer.hpp
|
||||
src/PersistentDataStore.cpp
|
||||
src/PersistentDataStore.hpp
|
||||
src/PhysicsReceiver.cpp
|
||||
src/PhysicsReceiver.hpp
|
||||
src/PhysicsSender.cpp
|
||||
src/PhysicsSender.hpp
|
||||
src/Player.cpp
|
||||
src/Players.cpp
|
||||
src/PropertySynchronization.hpp
|
||||
src/Replicator.ChangePropertyItem.cpp
|
||||
src/Replicator.ChangePropertyItem.hpp
|
||||
src/Replicator.cpp
|
||||
src/Replicator.DeleteInstanceItem.cpp
|
||||
src/Replicator.DeleteInstanceItem.hpp
|
||||
src/Replicator.EventInvocationItem.cpp
|
||||
src/Replicator.EventInvocationItem.hpp
|
||||
src/Replicator.GCJob.cpp
|
||||
src/Replicator.GCJob.hpp
|
||||
src/Replicator.hpp
|
||||
src/Replicator.ItemSender.cpp
|
||||
src/Replicator.ItemSender.hpp
|
||||
src/Replicator.JoinDataItem.cpp
|
||||
src/Replicator.JoinDataItem.hpp
|
||||
src/Replicator.MarkerItem.cpp
|
||||
src/Replicator.MarkerItem.hpp
|
||||
src/Replicator.NewInstanceItem.cpp
|
||||
src/Replicator.NewInstanceItem.hpp
|
||||
src/Replicator.PingBackItem.cpp
|
||||
src/Replicator.PingBackItem.hpp
|
||||
src/Replicator.PingItem.cpp
|
||||
src/Replicator.PingItem.hpp
|
||||
src/Replicator.PingJob.hpp
|
||||
src/Replicator.ProcessPacketsJob.hpp
|
||||
src/Replicator.ReferencePropertyChangedItem.cpp
|
||||
src/Replicator.ReferencePropertyChangedItem.hpp
|
||||
src/Replicator.SendDataJob.hpp
|
||||
src/Replicator.StatsItem.cpp
|
||||
src/Replicator.StatsItem.hpp
|
||||
src/Replicator.StreamJob.cpp
|
||||
src/Replicator.StreamJob.hpp
|
||||
src/Replicator.TagItem.cpp
|
||||
src/Replicator.TagItem.hpp
|
||||
src/ReplicatorStats.cpp
|
||||
src/ReplicatorStats.hpp
|
||||
src/Rijndael-Boxes.hpp
|
||||
src/rijndael.cpp
|
||||
src/Rijndael.hpp
|
||||
src/RoundRobinPhysicsSender.cpp
|
||||
src/RoundRobinPhysicsSender.hpp
|
||||
src/Server.cpp
|
||||
src/Server.hpp
|
||||
src/ServerReplicator.cpp
|
||||
src/ServerReplicator.hpp
|
||||
src/Streaming.cpp
|
||||
src/Streaming.hpp
|
||||
src/StreamingUtil.hpp
|
||||
src/TopNErrorsPhysicsSender.cpp
|
||||
src/TopNErrorsPhysicsSender.hpp
|
||||
src/Util.cpp
|
||||
src/Util.hpp
|
||||
src/WebChatFilter.cpp
|
||||
src/WebChatFilter.hpp
|
||||
)
|
||||
|
||||
set(PRECOMPILED_HEADERS
|
||||
src/API.hpp
|
||||
src/NetworkClusterPacketCache.hpp
|
||||
src/NetworkOwner.hpp
|
||||
src/NetworkPacketCache.hpp
|
||||
src/Player.hpp
|
||||
src/Players.hpp
|
||||
)
|
||||
|
||||
if(AYA_OS_WINDOWS)
|
||||
list(APPEND SOURCES
|
||||
src/CrashReporter.cpp
|
||||
src/CrashReporter.hpp
|
||||
src/RakNetFast.hpp
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(NetworkPlayer PUBLIC src PRIVATE ${THIRD_PARTY_DIR}/BulletPhysics/src ${THIRD_PARTY_DIR}/RakNet/src ${ENGINE_DIR}/app/src ${ENGINE_DIR}/core/src ${ENGINE_DIR}/3d/src ${ENGINE_DIR}/gfx/src)
|
||||
target_include_directories(NetworkStudio PUBLIC src PRIVATE ${THIRD_PARTY_DIR}/BulletPhysics/src ${THIRD_PARTY_DIR}/RakNet/src ${ENGINE_DIR}/app/src ${ENGINE_DIR}/core/src ${ENGINE_DIR}/3d/src ${ENGINE_DIR}/gfx/src)
|
||||
target_include_directories(NetworkServer PUBLIC src PRIVATE ${THIRD_PARTY_DIR}/BulletPhysics/src ${THIRD_PARTY_DIR}/RakNet/src ${ENGINE_DIR}/app/src ${ENGINE_DIR}/core/src ${ENGINE_DIR}/3d/src ${ENGINE_DIR}/gfx/src)
|
||||
|
||||
target_precompile_headers(NetworkPlayer PUBLIC ${PRECOMPILED_HEADERS})
|
||||
target_precompile_headers(NetworkStudio PUBLIC ${PRECOMPILED_HEADERS})
|
||||
target_precompile_headers(NetworkServer PUBLIC ${PRECOMPILED_HEADERS})
|
||||
|
||||
target_sources(NetworkPlayer PRIVATE ${SOURCES})
|
||||
target_sources(NetworkStudio PRIVATE ${SOURCES})
|
||||
target_sources(NetworkServer PRIVATE ${SOURCES})
|
||||
|
||||
target_link_libraries(NetworkPlayer PRIVATE $<TARGET_OBJECTS:AppPlayer>)
|
||||
target_link_libraries(NetworkStudio PRIVATE $<TARGET_OBJECTS:AppStudio>)
|
||||
target_link_libraries(NetworkServer PRIVATE $<TARGET_OBJECTS:AppServer>)
|
||||
263
engine/network/src/API.cpp
Normal file
263
engine/network/src/API.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
|
||||
|
||||
|
||||
// third time's the charm
|
||||
#include "API.hpp"
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "Server.hpp"
|
||||
#include "ServerReplicator.hpp"
|
||||
#include "ClientReplicator.hpp"
|
||||
#include "Players.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "Marker.hpp"
|
||||
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/GlobalSettings.hpp"
|
||||
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "GuidRegistryService.hpp"
|
||||
#include "RakNet/RakNetVersion.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
#include "Utility/URL.hpp"
|
||||
#include "Utility/Utilities.hpp"
|
||||
|
||||
#include "FastLog.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
#elif defined(__unix__) || defined(__posix__)
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#endif
|
||||
|
||||
// RakNet
|
||||
#include "RakNet/StringCompressor.hpp"
|
||||
#include "RakNet/StringTable.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
FASTSTRINGVARIABLE(ClientExternalBrowserUserAgent, "Roblox/WinInet")
|
||||
|
||||
std::string Aya::Network::password;
|
||||
|
||||
#if RAKNET_PROTOCOL_VERSION != 5
|
||||
#error
|
||||
#endif
|
||||
|
||||
|
||||
AYA_REGISTER_CLASS(Aya::Network::Client);
|
||||
AYA_REGISTER_CLASS(Aya::Network::Server);
|
||||
AYA_REGISTER_CLASS(Aya::Network::Player);
|
||||
AYA_REGISTER_CLASS(Aya::Network::Players);
|
||||
AYA_REGISTER_CLASS(Aya::NetworkSettings);
|
||||
AYA_REGISTER_CLASS(Aya::Network::Peer);
|
||||
AYA_REGISTER_CLASS(Aya::Network::Marker);
|
||||
AYA_REGISTER_CLASS(Aya::Network::Replicator);
|
||||
AYA_REGISTER_CLASS(Aya::Network::ServerReplicator);
|
||||
AYA_REGISTER_CLASS(Aya::Network::ClientReplicator);
|
||||
AYA_REGISTER_CLASS(Aya::Network::GuidRegistryService);
|
||||
|
||||
AYA_REGISTER_ENUM(PacketPriority);
|
||||
AYA_REGISTER_ENUM(PacketReliability);
|
||||
AYA_REGISTER_ENUM(Aya::Network::FilterResult);
|
||||
AYA_REGISTER_ENUM(Aya::Network::Player::MembershipType);
|
||||
AYA_REGISTER_ENUM(Aya::Network::Player::ChatMode);
|
||||
AYA_REGISTER_ENUM(Aya::Network::Players::PlayerChatType);
|
||||
AYA_REGISTER_ENUM(Aya::Network::Players::ChatOption);
|
||||
AYA_REGISTER_ENUM(Aya::NetworkSettings::PhysicsSendMethod);
|
||||
AYA_REGISTER_ENUM(Aya::NetworkSettings::PhysicsReceiveMethod);
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
// Prevent string compressors from being created at the same time
|
||||
class SafeInitFree
|
||||
{
|
||||
public:
|
||||
SafeInitFree()
|
||||
{
|
||||
RakNet::StringCompressor::AddReference();
|
||||
RakNet::StringTable::AddReference();
|
||||
}
|
||||
~SafeInitFree()
|
||||
{
|
||||
RakNet::StringCompressor::RemoveReference();
|
||||
RakNet::StringTable::RemoveReference();
|
||||
}
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
static bool _isPlayerAuthenticationEnabled;
|
||||
|
||||
bool Aya::Network::isPlayerAuthenticationEnabled()
|
||||
{
|
||||
return _isPlayerAuthenticationEnabled;
|
||||
}
|
||||
|
||||
bool Aya::Network::isNetworkClient(const Instance* context)
|
||||
{
|
||||
return ServiceProvider::find<Client>(context) != NULL;
|
||||
}
|
||||
|
||||
#if defined(AYA_SERVER)
|
||||
static shared_ptr<Aya::Network::ServerReplicator> createSecureReplicator(
|
||||
RakNet::SystemAddress a, Aya::Network::Server* s, Aya::NetworkSettings* networkSettings)
|
||||
{
|
||||
return Aya::Creatable<Aya::Instance>::create<Aya::Network::CheatHandlingServerReplicator>(a, s, networkSettings);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Aya::Network::init()
|
||||
{
|
||||
static SafeInitFree safeInitFree;
|
||||
|
||||
Client::classDescriptor();
|
||||
Server::classDescriptor();
|
||||
Player::classDescriptor();
|
||||
Players::classDescriptor();
|
||||
GlobalAdvancedSettings::classDescriptor();
|
||||
NetworkSettings::classDescriptor();
|
||||
NetworkSettings::singleton();
|
||||
|
||||
#if defined(AYA_SERVER)
|
||||
_isPlayerAuthenticationEnabled = true;
|
||||
Server::createReplicator = createSecureReplicator;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Aya::Network::setPassword(const char* _password)
|
||||
{
|
||||
if (_password)
|
||||
password = _password;
|
||||
}
|
||||
bool Aya::Network::isTrustedContent(const char* url)
|
||||
{
|
||||
if (!Aya::ContentProvider::isUrl(url))
|
||||
return false;
|
||||
|
||||
bool kSkipNetworkTrustedContentCheck = true;
|
||||
if (kSkipNetworkTrustedContentCheck)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string urlString(url);
|
||||
boost::algorithm::to_lower(urlString);
|
||||
|
||||
bool isRobloxLabsUrl = false;
|
||||
|
||||
const Aya::Url baseUrlParsed = Aya::Url::fromString(GetBaseURL());
|
||||
|
||||
size_t foundPos = urlString.find(baseUrlParsed.host().c_str() + '/');
|
||||
if (foundPos == std::string::npos)
|
||||
{
|
||||
foundPos = urlString.find('.' + baseUrlParsed.host().c_str() + '/');
|
||||
if (foundPos == std::string::npos)
|
||||
return false;
|
||||
|
||||
isRobloxLabsUrl = true;
|
||||
}
|
||||
|
||||
urlString = urlString.substr(foundPos, std::string::npos); // remove all of string before URL_IDENTIFIER
|
||||
|
||||
// put our iterator at end of URL_IDENTIFIER
|
||||
if (isRobloxLabsUrl)
|
||||
foundPos = sizeof('.' + baseUrlParsed.host().c_str() + '/') - 1;
|
||||
else
|
||||
foundPos = sizeof(baseUrlParsed.host().c_str()) - 1;
|
||||
|
||||
while (foundPos < urlString.size() && (urlString[foundPos] == '\\' || urlString[foundPos] == '/'))
|
||||
++foundPos;
|
||||
|
||||
if (foundPos >= urlString.size())
|
||||
return false;
|
||||
|
||||
return urlString.substr(foundPos, 5) == "asset" || urlString.substr(foundPos, 4) == "game" || urlString.substr(foundPos, 9) == "analytics" ||
|
||||
urlString.substr(foundPos, 3) == "ide" || urlString.substr(foundPos, 6) == "images" || urlString.substr(foundPos, 6) == "thumbs" ||
|
||||
urlString.substr(foundPos, 2) == "ui" || urlString.substr(foundPos, 11) == "persistence" || urlString.substr(foundPos, 8) == "rolesets" ||
|
||||
urlString.substr(foundPos, 4) == "auth" || urlString.substr(foundPos, 8) == "currency" ||
|
||||
urlString.substr(foundPos, 11) == "marketplace" || urlString.substr(foundPos, 9) == "ownership" ||
|
||||
urlString.substr(foundPos, 13) == "placerolesets";
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isDebuggerPresent()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return ::IsDebuggerPresent() != 0;
|
||||
#elif defined(__APPLE__)
|
||||
// macOS/iOS
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
|
||||
struct kinfo_proc info = {};
|
||||
size_t size = sizeof(info);
|
||||
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) == 0)
|
||||
return (info.kp_proc.p_flag & P_TRACED) != 0;
|
||||
|
||||
return false;
|
||||
#elif defined(__linux__)
|
||||
// Linux - check /proc/self/status for TracerPid
|
||||
std::ifstream statusFile("/proc/self/status");
|
||||
std::string line;
|
||||
|
||||
while (std::getline(statusFile, line))
|
||||
{
|
||||
if (line.compare(0, 10, "TracerPid:") == 0)
|
||||
{
|
||||
int tracerPid = std::stoi(line.substr(10));
|
||||
return tracerPid != 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#elif defined(__unix__) || defined(__posix__)
|
||||
// Generic Unix fallback using ptrace
|
||||
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0)
|
||||
return true; // Already being traced
|
||||
|
||||
ptrace(PTRACE_DETACH, 0, 1, 0);
|
||||
return false;
|
||||
#else
|
||||
return false; // Unknown platform
|
||||
#endif
|
||||
}
|
||||
|
||||
void isDebuggedDirectThreadFunc(weak_ptr<Aya::DataModel> weakDataModel)
|
||||
{
|
||||
if (IsInsecureMode())
|
||||
return;
|
||||
|
||||
static const int kSleepBetweenChecksMillis = 1500;
|
||||
|
||||
while (true)
|
||||
{
|
||||
shared_ptr<Aya::DataModel> dataModel = weakDataModel.lock();
|
||||
if (!dataModel)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned int mask = static_cast<unsigned int>(isDebuggerPresent()) * HATE_DEBUGGER;
|
||||
dataModel->addHackFlag(mask);
|
||||
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(kSleepBetweenChecksMillis));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Aya::spawnDebugCheckThreads(weak_ptr<Aya::DataModel> dataModel)
|
||||
{
|
||||
boost::thread t(boost::bind(&isDebuggedDirectThreadFunc, dataModel));
|
||||
}
|
||||
36
engine/network/src/API.hpp
Normal file
36
engine/network/src/API.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/weak_ptr.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class Instance;
|
||||
class DataModel;
|
||||
namespace Network
|
||||
{
|
||||
typedef enum
|
||||
{
|
||||
Accept,
|
||||
Reject
|
||||
} FilterResult;
|
||||
|
||||
extern std::string password;
|
||||
extern std::string securityKey;
|
||||
bool isPlayerAuthenticationEnabled();
|
||||
void init();
|
||||
bool isNetworkClient(const Instance* context);
|
||||
bool getSystemUrlLocal(DataModel* dataModel);
|
||||
|
||||
// Used for debugging and development:
|
||||
void setPassword(const char* password);
|
||||
|
||||
bool isTrustedContent(const char* url);
|
||||
} // namespace Network
|
||||
|
||||
void spawnDebugCheckThreads(boost::weak_ptr<Aya::DataModel> weakDataModel);
|
||||
} // namespace Aya
|
||||
15
engine/network/src/BoostAppend.cpp
Normal file
15
engine/network/src/BoostAppend.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
#include "BoostAppend.hpp"
|
||||
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
|
||||
std::size_t boost::hash_value(const shared_ptr<Aya::PartInstance>& b)
|
||||
{
|
||||
boost::hash<void*> hasher;
|
||||
return hasher(b.get());
|
||||
}
|
||||
15
engine/network/src/BoostAppend.hpp
Normal file
15
engine/network/src/BoostAppend.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "boost.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class PartInstance;
|
||||
}
|
||||
|
||||
|
||||
namespace boost
|
||||
{
|
||||
std::size_t hash_value(const shared_ptr<Aya::PartInstance>& b);
|
||||
}
|
||||
26
engine/network/src/ChatFilter.cpp
Normal file
26
engine/network/src/ChatFilter.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "ChatFilter.hpp"
|
||||
|
||||
#include "Utility/Http.hpp"
|
||||
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
DYNAMIC_LOGGROUP(WebChatFiltering)
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
const char* const Aya::Network::sChatFilter = "ChatFilter";
|
||||
|
||||
bool ChatFilter::filterMessageBase(shared_ptr<Player> sourcePlayer, shared_ptr<Instance> receiver, const std::string& message, const ChatFilter::FilteredChatMessageCallback callback)
|
||||
{
|
||||
// no filter
|
||||
Result result;
|
||||
result.whitelistFilteredMessage = message;
|
||||
result.blacklistFilteredMessage = message;
|
||||
boost::thread t(boost::bind(callback, result));
|
||||
return true;
|
||||
}
|
||||
38
engine/network/src/ChatFilter.hpp
Normal file
38
engine/network/src/ChatFilter.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Service.hpp"
|
||||
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class CustomChatFilter;
|
||||
|
||||
extern const char* const sChatFilter;
|
||||
class ChatFilter
|
||||
: public DescribedNonCreatable<ChatFilter, Instance, sChatFilter, Reflection::ClassDescriptor::INTERNAL_LOCAL>
|
||||
, public Service
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
std::string whitelistFilteredMessage;
|
||||
std::string blacklistFilteredMessage;
|
||||
};
|
||||
typedef boost::function<void(const Result&)> FilteredChatMessageCallback;
|
||||
|
||||
ChatFilter() {}
|
||||
|
||||
bool filterMessageBase(
|
||||
shared_ptr<Player> sourcePlayer, shared_ptr<Instance> receiver, const std::string& message, const FilteredChatMessageCallback callback);
|
||||
|
||||
virtual void filterMessage(
|
||||
shared_ptr<Player> sourcePlayer, shared_ptr<Instance> receiver, const std::string& message, const FilteredChatMessageCallback callback) = 0;
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
419
engine/network/src/Client.cpp
Normal file
419
engine/network/src/Client.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "ClientReplicator.hpp"
|
||||
|
||||
#include "RakNet/PacketLogger.hpp"
|
||||
#include "Util.hpp"
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "Players.hpp"
|
||||
|
||||
#include "NetworkOwner.hpp"
|
||||
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
#include "Utility/ProtectedString.hpp"
|
||||
|
||||
#include "Utility/RbxStringTable.hpp"
|
||||
|
||||
#include "CPUCount.hpp"
|
||||
#include "FastLog.hpp"
|
||||
#include "AyaDbgInfo.hpp"
|
||||
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
|
||||
#include "DataModel/TeleportService.hpp"
|
||||
|
||||
|
||||
#include "Script/ScriptContext.hpp"
|
||||
|
||||
#include "RakNet/RakNetStatistics.hpp"
|
||||
|
||||
#include "Utility/AyaService.hpp"
|
||||
|
||||
LOGGROUP(US14116)
|
||||
DYNAMIC_FASTFLAG(DebugDisableTimeoutDisconnect)
|
||||
FASTFLAG(DebugLocalRccServerConnection)
|
||||
|
||||
DYNAMIC_LOGGROUP(NetworkJoin)
|
||||
FASTFLAG(DebugProtocolSynchronization)
|
||||
|
||||
#ifndef _WIN32
|
||||
// For inet_addr() call used below
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
const char* const Aya::Network::sClient = "NetworkClient";
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
extern const char* const sHopper;
|
||||
class Instance;
|
||||
} // namespace Aya
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
using namespace RakNet;
|
||||
|
||||
|
||||
Reflection::BoundProp<std::string> Client::prop_Ticket("Ticket", "Authentication", &Client::ticket);
|
||||
static Reflection::BoundFuncDesc<Client, shared_ptr<Instance>(int, std::string, int, int, int)> f_connect(
|
||||
&Client::playerConnect, "PlayerConnect", "userId", "server", "serverPort", "clientPort", 0, "threadSleepTime", 30, Security::Plugin);
|
||||
static Reflection::BoundFuncDesc<Client, void(int)> f_disconnect(&Client::disconnect, "Disconnect", "blockDuration", 3000, Security::LocalUser);
|
||||
static Reflection::BoundFuncDesc<Client, void(std::string)> func_setGameSessionID(
|
||||
&Client::setGameSessionID, "SetGameSessionID", "gameSessionID", Security::Roblox);
|
||||
static Reflection::EventDesc<Client, void(std::string, shared_ptr<Aya::Instance>)> event_ConnectionAccepted(
|
||||
&Client::connectionAcceptedSignal, "ConnectionAccepted", "peer", "replicator");
|
||||
static Reflection::EventDesc<Client, void(std::string)> event_ConnectionRejected(&Client::connectionRejectedSignal, "ConnectionRejected", "peer");
|
||||
static Reflection::EventDesc<Client, void(std::string, int, std::string)> event_ConnectionFailed(
|
||||
&Client::connectionFailedSignal, "ConnectionFailed", "peer", "code", "reason");
|
||||
REFLECTION_END();
|
||||
|
||||
Client::Client()
|
||||
: userId(-1)
|
||||
, networkSettings(&NetworkSettings::singleton())
|
||||
{
|
||||
Aya::Security::Context::current().requirePermission(Aya::Security::Plugin, "create a NetworkClient");
|
||||
setName(sClient);
|
||||
|
||||
FASTLOG(FLog::Network, "NetworkClient:Create");
|
||||
}
|
||||
|
||||
Client::~Client(void)
|
||||
{
|
||||
FASTLOG(FLog::Network, "NetworkClient:Remove");
|
||||
}
|
||||
|
||||
Client* Client::findClient(const Aya::Instance* context, bool testInDatamodel)
|
||||
{
|
||||
const ServiceProvider* serviceProvider = ServiceProvider::findServiceProvider(context);
|
||||
AYAASSERT(!testInDatamodel || serviceProvider != NULL);
|
||||
return ServiceProvider::find<Client>(serviceProvider);
|
||||
}
|
||||
|
||||
bool Client::clientIsPresent(const Aya::Instance* context, bool testInDatamodel)
|
||||
{
|
||||
return findClient(context, testInDatamodel) != NULL;
|
||||
}
|
||||
|
||||
bool Client::physicsOutBandwidthExceeded(const Aya::Instance* context)
|
||||
{
|
||||
if (Client* client = Client::findClient(context))
|
||||
{
|
||||
if (ClientReplicator* clientRep = client->findFirstChildOfType<ClientReplicator>())
|
||||
{
|
||||
return clientRep->isLimitedByOutgoingBandwidthLimit();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
double Client::getNetworkBufferHealth(const Aya::Instance* context)
|
||||
{
|
||||
if (Client* client = Client::findClient(context))
|
||||
{
|
||||
return client->rakPeer->GetBufferHealth();
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const Aya::SystemAddress Client::findLocalSimulatorAddress(const Aya::Instance* context)
|
||||
{
|
||||
if (Client* client = Client::findClient(context, false))
|
||||
{
|
||||
if (ClientReplicator* clientRep = client->findFirstChildOfType<ClientReplicator>())
|
||||
{
|
||||
return RakNetToRbxAddress(clientRep->getClientAddress());
|
||||
}
|
||||
}
|
||||
return Network::NetworkOwner::Unassigned();
|
||||
}
|
||||
|
||||
shared_ptr<Instance> Client::playerConnect(int userId, std::string server, int serverPort, int clientPort, int threadSleepTime)
|
||||
{
|
||||
FASTLOG3(FLog::Network, "Client:Connect serverPort(%d) clientPort(%d) threadSleepTime(%d)", serverPort, clientPort, threadSleepTime);
|
||||
|
||||
this->userId = userId;
|
||||
Players* players = ServiceProvider::create<Players>(this);
|
||||
if (!players)
|
||||
throw Aya::runtime_error("Cannot get players");
|
||||
|
||||
shared_ptr<Instance> player = players->createLocalPlayer(userId, TeleportService::getPreviousPlaceId() > 0);
|
||||
|
||||
if (clientPort == 0)
|
||||
{
|
||||
clientPort = networkSettings->preferredClientPort;
|
||||
}
|
||||
|
||||
RakNet::SocketDescriptor d(clientPort, "");
|
||||
StartupResult startRes = rakPeer->rawPeer()->Startup(1, &d, 1);
|
||||
if (startRes != RakNet::RAKNET_STARTED)
|
||||
{
|
||||
if (clientPort == 0)
|
||||
{
|
||||
throw Aya::runtime_error("Failed to start the network client");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Aya::runtime_error("Failed to start the network client on port %d", clientPort);
|
||||
}
|
||||
}
|
||||
|
||||
// allow local and LAN games only.
|
||||
if (server != "localhost")
|
||||
{
|
||||
bool lansubnet = false;
|
||||
for (int i = 0; !lansubnet && i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ++i)
|
||||
{
|
||||
RakNet::SystemAddress localAddress = rakPeer->rawPeer()->GetInternalID(RakNet::UNASSIGNED_SYSTEM_ADDRESS, i);
|
||||
RakNet::SystemAddress remoteAddress(server.c_str(), serverPort);
|
||||
|
||||
// match the a and b records
|
||||
lansubnet = (localAddress.GetBinaryAddress() & 0x00FF) == (remoteAddress.GetBinaryAddress() & 0x00FF);
|
||||
}
|
||||
if (!FFlag::DebugLocalRccServerConnection)
|
||||
{
|
||||
if (!lansubnet)
|
||||
{
|
||||
Aya::Security::Context::current().requirePermission(Aya::Security::Roblox, " connect to an extranet game");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RakNet::ConnectionAttemptResult connectRes =
|
||||
rakPeer->rawPeer()->Connect(server.c_str(), serverPort, Network::password.c_str(), Network::password.size());
|
||||
if (connectRes != RakNet::CONNECTION_ATTEMPT_STARTED)
|
||||
{
|
||||
throw Aya::runtime_error("Failed to connect to server, id %d", connectRes);
|
||||
}
|
||||
FASTLOG1F(DFLog::NetworkJoin, "playerConnect connecting to server @ %f s", Time::nowFastSec());
|
||||
|
||||
if (DFFlag::DebugDisableTimeoutDisconnect || AyaService::localServer)
|
||||
rakPeer->rawPeer()->SetTimeoutTime(10 * 60 * 1000, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
|
||||
// StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "Connecting to %s:%d", server.c_str(), serverPort);
|
||||
|
||||
FASTLOG2(FLog::Network, "Connecting to server, IP(inet_addr): %u Port: %u", inet_addr(server.c_str()), serverPort);
|
||||
|
||||
Aya::AyaDbgInfo::SetServerIP(server.c_str());
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Client::disconnect(int blockDuration)
|
||||
{
|
||||
FASTLOG(FLog::Network, "Client:Disconnect");
|
||||
|
||||
// The following line will remove the Replicator
|
||||
this->visitChildren(boost::bind(&Instance::unlockParent, _1));
|
||||
this->removeAllChildren();
|
||||
|
||||
if (rakPeer)
|
||||
{
|
||||
rakPeer->rawPeer()->CloseConnection(this->serverId, true);
|
||||
rakPeer->rawPeer()->Shutdown(blockDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::setGameSessionID(std::string value)
|
||||
{
|
||||
if (value != Http::gameSessionID)
|
||||
{
|
||||
Http::gameSessionID = value;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
||||
{
|
||||
if (oldProvider)
|
||||
{
|
||||
closingConnection.disconnect();
|
||||
|
||||
disconnect(); // We should have disconnected by now (in response to the Closing event)
|
||||
|
||||
Players* players = ServiceProvider::find<Players>(oldProvider);
|
||||
players->setConnection(NULL);
|
||||
}
|
||||
|
||||
Super::onServiceProvider(oldProvider, newProvider);
|
||||
|
||||
if (newProvider)
|
||||
{
|
||||
// We're in multiplayer mode, so burn out the studio tools
|
||||
if (Aya::DataModel* dataModel = Aya::DataModel::get(this))
|
||||
{
|
||||
if (dataModel->lockVerb.get())
|
||||
dataModel->lockVerb->doIt(NULL);
|
||||
}
|
||||
|
||||
Players* players = ServiceProvider::create<Players>(newProvider);
|
||||
players->setConnection(rakPeer.get());
|
||||
|
||||
// Disconnect now before we start getting DescendantRemoving events
|
||||
// If we don't disconnect first, then we'll send a shower of delete messages
|
||||
// to the Server
|
||||
closingConnection = newProvider->closingSignal.connect(boost::bind(&Client::disconnect, this));
|
||||
}
|
||||
}
|
||||
|
||||
void Client::sendVersionInfo()
|
||||
{
|
||||
RakNet::BitStream bitStream;
|
||||
bitStream << (unsigned char)ID_PROTOCOL_SYNC;
|
||||
bitStream << protocolVersion;
|
||||
|
||||
rakPeer->rawPeer()->Send(&bitStream, networkSettings->getDataSendPriority(), DATAMODEL_RELIABILITY, DATA_CHANNEL, serverId, false);
|
||||
}
|
||||
|
||||
void Client::sendTicket()
|
||||
{
|
||||
RakNet::BitStream bitStream;
|
||||
bitStream << (unsigned char)ID_SUBMIT_TICKET;
|
||||
|
||||
bitStream << userId;
|
||||
serializeStringCompressed(ticket, bitStream);
|
||||
|
||||
serializeStringCompressed(Aya::DataModel::hash, bitStream);
|
||||
|
||||
bitStream << protocolVersion;
|
||||
|
||||
serializeStringCompressed(password, bitStream);
|
||||
|
||||
// TODO: better way to track protocol changes between versions
|
||||
// Network Protocol version 2
|
||||
serializeStringCompressed(DebugSettings::singleton().osPlatform(), bitStream);
|
||||
serializeStringCompressed(DebugSettings::singleton().getRobloxProductName(), bitStream);
|
||||
|
||||
serializeStringCompressed(Http::gameSessionID, bitStream);
|
||||
|
||||
encryptDataPart(bitStream);
|
||||
|
||||
// Send ID_SUBMIT_TICKET
|
||||
rakPeer->rawPeer()->Send(&bitStream, networkSettings->getDataSendPriority(), DATAMODEL_RELIABILITY, DATA_CHANNEL, serverId, false);
|
||||
}
|
||||
|
||||
std::string rakIdToString(int id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case ID_INVALID_PASSWORD:
|
||||
case ID_HASH_MISMATCH:
|
||||
return "Aya version is out of date. Please uninstall and try again.";
|
||||
case ID_CONNECTION_ATTEMPT_FAILED:
|
||||
return "Connection attempt failed.";
|
||||
case ID_SECURITYKEY_MISMATCH:
|
||||
return "Version not compatible with server. Please uninstall and try again.";
|
||||
default:
|
||||
return Aya::format("Network error %d", id);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::OnFailedConnectionAttempt(RakNet::Packet* packet, RakNet::PI2_FailedConnectionAttemptReason failedConnectionAttemptReason)
|
||||
{
|
||||
std::string message = rakIdToString(packet->data[0]);
|
||||
StandardOut::singleton()->printf(
|
||||
MESSAGE_SENSITIVE, "Failed to connect to %s. %s\n", RakNetAddressToString(packet->systemAddress).c_str(), message.c_str());
|
||||
connectionFailedSignal(RakNetAddressToString(packet->systemAddress), (int)packet->data[0], message);
|
||||
}
|
||||
|
||||
void Client::sendPreferedSpawnName() const
|
||||
{
|
||||
RakNet::BitStream bitStream;
|
||||
|
||||
bitStream << (unsigned char)ID_SPAWN_NAME;
|
||||
|
||||
serializeStringCompressed(TeleportService::GetSpawnName(), bitStream);
|
||||
|
||||
FASTLOGS(FLog::Network, "serverId: %s", RakNetAddressToString(serverId).c_str());
|
||||
|
||||
rakPeer->rawPeer()->Send(&bitStream, networkSettings->getDataSendPriority(), DATAMODEL_RELIABILITY, DATA_CHANNEL, serverId, false);
|
||||
}
|
||||
|
||||
void Client::HandleConnection(RakNet::Packet* packet)
|
||||
{
|
||||
shared_ptr<Replicator> proxy;
|
||||
try
|
||||
{
|
||||
// send previous placeId
|
||||
RakNet::BitStream bitStream;
|
||||
bitStream << (unsigned char)ID_PLACEID_VERIFICATION;
|
||||
|
||||
bitStream << TeleportService::getPreviousPlaceId();
|
||||
|
||||
rakPeer->rawPeer()->Send(&bitStream, networkSettings->getDataSendPriority(), DATAMODEL_RELIABILITY, DATA_CHANNEL, serverId, false);
|
||||
|
||||
sendTicket();
|
||||
|
||||
sendPreferedSpawnName();
|
||||
|
||||
Workspace* workspace = Workspace::findWorkspace(this);
|
||||
workspace->clearTerrain();
|
||||
|
||||
proxy =
|
||||
Creatable<Instance>::create<ClientReplicator>(packet->systemAddress, this, rakPeer->rawPeer()->GetExternalID(serverId), networkSettings);
|
||||
|
||||
proxy->setAndLockParent(this);
|
||||
|
||||
connectionAcceptedSignal(RakNetAddressToString(packet->systemAddress), proxy);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "Error in ID_CONNECTION_REQUEST_ACCEPTED: %s", e.what());
|
||||
if (proxy)
|
||||
{
|
||||
// Disconnect
|
||||
proxy->unlockParent();
|
||||
proxy->setParent(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RakNet::PluginReceiveResult Client::OnReceive(RakNet::Packet* packet)
|
||||
{
|
||||
RakNet::PluginReceiveResult result = Super::OnReceive(packet);
|
||||
if (result != RR_CONTINUE_PROCESSING)
|
||||
return result;
|
||||
|
||||
switch ((unsigned char)packet->data[0])
|
||||
{
|
||||
case ID_CONNECTION_REQUEST_ACCEPTED:
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "Connection accepted from %s\n", RakNetAddressToString(packet->systemAddress).c_str());
|
||||
|
||||
serverId = packet->systemAddress;
|
||||
|
||||
HandleConnection(packet);
|
||||
sendVersionInfo();
|
||||
}
|
||||
return RR_CONTINUE_PROCESSING;
|
||||
|
||||
case ID_INVALID_PASSWORD:
|
||||
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "Invalid password from %s", RakNetAddressToString(packet->systemAddress).c_str());
|
||||
connectionFailedSignal(RakNetAddressToString(packet->systemAddress), (int)packet->data[0], rakIdToString(packet->data[0]));
|
||||
connectionRejectedSignal(RakNetAddressToString(packet->systemAddress));
|
||||
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
||||
|
||||
case ID_HASH_MISMATCH:
|
||||
case ID_SECURITYKEY_MISMATCH:
|
||||
connectionFailedSignal(RakNetAddressToString(packet->systemAddress), (int)packet->data[0], rakIdToString(packet->data[0]));
|
||||
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
||||
|
||||
case ID_DISCONNECTION_NOTIFICATION:
|
||||
case ID_CONNECTION_LOST:
|
||||
AYAASSERT(packet->systemAddress == serverId);
|
||||
serverId = UNASSIGNED_SYSTEM_ADDRESS;
|
||||
return RR_CONTINUE_PROCESSING;
|
||||
|
||||
default:
|
||||
return RR_CONTINUE_PROCESSING;
|
||||
}
|
||||
}
|
||||
72
engine/network/src/Client.hpp
Normal file
72
engine/network/src/Client.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Peer.hpp"
|
||||
#include "Utility/SystemAddress.hpp"
|
||||
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
extern const char* const sClient;
|
||||
class Client
|
||||
: public DescribedCreatable<Client, Peer, sClient, Reflection::ClassDescriptor::INTERNAL_LOCAL>
|
||||
, public Service
|
||||
{
|
||||
private:
|
||||
typedef DescribedCreatable<Client, Peer, sClient, Reflection::ClassDescriptor::INTERNAL_LOCAL> Super;
|
||||
|
||||
Aya::signals::scoped_connection closingConnection;
|
||||
RakNet::SystemAddress serverId;
|
||||
int userId;
|
||||
std::string ticket;
|
||||
NetworkSettings* networkSettings;
|
||||
|
||||
static Reflection::BoundProp<std::string> prop_Ticket;
|
||||
|
||||
public:
|
||||
Client();
|
||||
~Client();
|
||||
|
||||
Aya::signal<void(std::string, shared_ptr<Instance>)> connectionAcceptedSignal;
|
||||
Aya::signal<void(std::string, int, std::string)> connectionFailedSignal;
|
||||
Aya::signal<void(std::string)> connectionRejectedSignal;
|
||||
|
||||
void setTicket(const std::string& t)
|
||||
{
|
||||
ticket = t;
|
||||
}
|
||||
|
||||
shared_ptr<Instance> playerConnect(int userId, std::string server, int serverPort, int clientPort, int threadSleepTime);
|
||||
void disconnect(int blockDuration);
|
||||
void disconnect()
|
||||
{
|
||||
disconnect(3000);
|
||||
}
|
||||
void setGameSessionID(std::string gameSessionID);
|
||||
|
||||
/*override*/ RakNet::PluginReceiveResult OnReceive(RakNet::Packet* packet);
|
||||
/*override*/ void OnFailedConnectionAttempt(RakNet::Packet* packet, RakNet::PI2_FailedConnectionAttemptReason failedConnectionAttemptReason);
|
||||
|
||||
static Client* findClient(const Aya::Instance* context, bool testInDatamodel = true);
|
||||
static bool clientIsPresent(const Aya::Instance* context, bool testInDatamodel = true);
|
||||
static const Aya::SystemAddress findLocalSimulatorAddress(const Aya::Instance* context); // if Client == clientAddress, else == NULL;
|
||||
static bool physicsOutBandwidthExceeded(const Aya::Instance* context);
|
||||
static double getNetworkBufferHealth(const Aya::Instance* context);
|
||||
|
||||
protected:
|
||||
virtual void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
|
||||
private:
|
||||
void sendVersionInfo();
|
||||
void sendTicket();
|
||||
void sendPreferedSpawnName() const;
|
||||
void HandleConnection(RakNet::Packet* packet);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
2796
engine/network/src/ClientReplicator.cpp
Normal file
2796
engine/network/src/ClientReplicator.cpp
Normal file
File diff suppressed because it is too large
Load Diff
317
engine/network/src/ClientReplicator.hpp
Normal file
317
engine/network/src/ClientReplicator.hpp
Normal file
@@ -0,0 +1,317 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "PropertySynchronization.hpp"
|
||||
#include "threadsafe.hpp"
|
||||
|
||||
#include "Utility/MemoryStats.hpp"
|
||||
|
||||
#include "Replicator.StreamJob.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
const float kMaxIncomePacketWaitTime = 0.25f;
|
||||
|
||||
class Client;
|
||||
class StrictNetworkFilter;
|
||||
class DeserializedStatsItem;
|
||||
class DeserializedTagItem;
|
||||
class DeserializedStreamDataItem;
|
||||
|
||||
extern const char* const sClientReplicator;
|
||||
class ClientReplicator
|
||||
: public Aya::DescribedNonCreatable<ClientReplicator, Replicator, sClientReplicator, Reflection::ClassDescriptor::INTERNAL_LOCAL>
|
||||
{
|
||||
private:
|
||||
typedef Aya::DescribedNonCreatable<ClientReplicator, Replicator, sClientReplicator, Reflection::ClassDescriptor::INTERNAL_LOCAL> Super;
|
||||
|
||||
class RequestCharacterItem;
|
||||
class ClientCapacityUpdateItem;
|
||||
class ClientStatsItem;
|
||||
#ifdef ENABLE_VOICE_CHAT
|
||||
class SendOpusDataJob;
|
||||
#endif
|
||||
class GCJob;
|
||||
friend class ClientStatsItem;
|
||||
friend class StatsItem;
|
||||
friend class DeserializedStatsItem;
|
||||
friend class DeserializedTagItem;
|
||||
friend class DeserializedStreamDataItem;
|
||||
|
||||
PropSync::Slave propSync;
|
||||
RakNet::SystemAddress clientAddress;
|
||||
bool receivedGlobals;
|
||||
boost::scoped_ptr<Aya::AutoMemPool> cframePool;
|
||||
boost::shared_ptr<GCJob> gcJob;
|
||||
#ifdef ENABLE_VOICE_CHAT
|
||||
boost::shared_ptr<SendOpusDataJob> opusJob;
|
||||
#endif
|
||||
|
||||
RunningAverage<double> avgInstancesPerStreamData;
|
||||
RunningAverage<double> avgStreamDataReadTime;
|
||||
RunningAverage<int> avgRequestCount;
|
||||
Aya::MemoryStats::MemoryLevel memoryLevel;
|
||||
|
||||
Aya::signals::scoped_connection playerCharacterAddedConnection;
|
||||
void onPlayerCharacterAdded();
|
||||
|
||||
shared_ptr<Reflection::ValueTable> readStats(RakNet::BitStream& bitStream);
|
||||
|
||||
// streaming
|
||||
double sampleTimer;
|
||||
int clientInstanceQuota;
|
||||
StreamRegion::Id lastReadStreamId;
|
||||
|
||||
int pendingInstanceRequests;
|
||||
int numInstancesRead;
|
||||
bool loggedLowMemWarning;
|
||||
void readStreamData(RakNet::BitStream& bitStream);
|
||||
void readStreamDataItem(DeserializedStreamDataItem* item);
|
||||
void processStreamDataRegionId(Replicator::StreamJob::RegionIteratorSuccessor successorBitMask, StreamRegion::Id id);
|
||||
bool canUpdateClientCapacity();
|
||||
void streamOutTerrain(const Vector3int16& cellPos);
|
||||
void streamOutInstance(Instance* part,
|
||||
bool deleteImmediately = true); // NOTE: passing false to deleteImmediately requires handling auto joints and deleting the instance externally
|
||||
void streamOutPartHelper(const Guid::Data& data, PartInstance* part, shared_ptr<Instance> descendant);
|
||||
void streamOutAutoJointHelper(std::vector<shared_ptr<PartInstance>> pendingRemovalParts, shared_ptr<Instance> instance);
|
||||
|
||||
void markerReceived();
|
||||
|
||||
protected:
|
||||
/*override*/ bool isProtectedStringEnabled();
|
||||
/*override*/ std::string encodeProtectedString(
|
||||
const ProtectedString& value, const Instance* instance, const Reflection::PropertyDescriptor& desc);
|
||||
/*override*/ boost::optional<ProtectedString> decodeProtectedString(
|
||||
const std::string& value, const Instance* instance, const Reflection::PropertyDescriptor& desc);
|
||||
|
||||
/*override*/ bool checkDistributedReceive(PartInstance* part);
|
||||
/*override*/ bool checkDistributedSend(const PartInstance* part);
|
||||
/*override*/ bool checkDistributedSendFast(const PartInstance* part);
|
||||
/*override*/ void processPacket(RakNet::Packet* packet);
|
||||
/*override*/ bool canSendItems();
|
||||
|
||||
void readTag(RakNet::BitStream& inBitstream);
|
||||
void readTagItem(DeserializedTagItem* item);
|
||||
void processTag(int tag);
|
||||
|
||||
/*override*/ void readItem(RakNet::BitStream& inBitstream, Aya::Network::Item::ItemType itemType);
|
||||
/*override*/ shared_ptr<DeserializedItem> deserializeItem(RakNet::BitStream& inBitstream, Aya::Network::Item::ItemType itemType);
|
||||
/*override*/ bool isLegalSendInstance(const Instance* instance);
|
||||
/*override*/ bool isLegalSendProperty(Instance* instance, const Reflection::PropertyDescriptor& desc);
|
||||
/*override*/ bool isLegalSendEvent(Instance* instance, const Reflection::EventDescriptor& desc);
|
||||
|
||||
/*override*/ void readChangedProperty(RakNet::BitStream& bitStream, Reflection::Property prop);
|
||||
/*override*/ void readChangedPropertyItem(DeserializedChangePropertyItem* item, Reflection::Property prop);
|
||||
bool processChangedParentPropertyForStreaming(const Guid::Data& parentId, Reflection::Property prop);
|
||||
|
||||
/*override*/ void writeChangedProperty(const Instance* instance, const Reflection::PropertyDescriptor& desc, RakNet::BitStream& outBitStream);
|
||||
/*override*/ void writeChangedRefProperty(
|
||||
const Instance* instance, const Reflection::RefPropertyDescriptor& desc, const Guid::Data& newRefGuid, RakNet::BitStream& outBitStream);
|
||||
/*override*/ void writeProperties(const Instance* instance, RakNet::BitStream& outBitstream, PropertyCacheType cacheType, bool useDictionary);
|
||||
|
||||
/*override*/ Player* findTargetPlayer() const;
|
||||
/*override*/ Player* getRemotePlayer() const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/*override*/ void dataOutStep();
|
||||
|
||||
/*override*/ void setPropSyncExpiration(double value)
|
||||
{
|
||||
propSync.setExpiration(Aya::Time::Interval(value));
|
||||
}
|
||||
|
||||
/*override*/ shared_ptr<Stats> createStatsItem();
|
||||
|
||||
/*override*/ void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
|
||||
/*override*/ FilterResult filterChangedProperty(Instance* instance, const Reflection::PropertyDescriptor& desc);
|
||||
/*override*/ FilterResult filterReceivedParent(Instance* instance, Instance* parent);
|
||||
|
||||
/*override*/ bool wantReplicate(const Instance* source) const;
|
||||
/*override*/ void terrainCellChanged(const Voxel::CellChangeInfo& info);
|
||||
/*override*/ void onTerrainRegionChanged(const Voxel2::Region& region);
|
||||
|
||||
// protocol schema
|
||||
/*override*/ bool ProcessOutdatedChangedProperty(RakNet::BitStream& inBitstream, const Aya::Guid::Data& id, const Instance* instance,
|
||||
const Reflection::PropertyDescriptor* propertyDescriptor, unsigned int propId);
|
||||
/*override*/ bool ProcessOutdatedProperties(RakNet::BitStream& inBitstream, Instance* instance, PropertyCacheType cacheType, bool useDictionary,
|
||||
bool preventBounceBack, std::vector<PropValuePair>* valueArray);
|
||||
/*override*/ bool ProcessOutdatedInstance(RakNet::BitStream& inBitstream, bool isJoinData, const Aya::Guid::Data& id,
|
||||
const Reflection::ClassDescriptor* classDescriptor, unsigned int classId);
|
||||
/*override*/ bool ProcessOutdatedEventInvocation(RakNet::BitStream& inBitstream, const Aya::Guid::Data& id, const Instance* instance,
|
||||
const Reflection::EventDescriptor* eventDescriptor, unsigned int eventId);
|
||||
/*override*/ bool ProcessOutdatedEnumSerialization(
|
||||
const Reflection::Type& type, const Reflection::Variant& value, RakNet::BitStream& outBitStream);
|
||||
/*override*/ bool ProcessOutdatedEnumDeserialization(RakNet::BitStream& inBitStream, const Reflection::Type& type, Reflection::Variant& value);
|
||||
/*override*/ bool ProcessOutdatedPropertyEnumSerialization(const Reflection::ConstProperty& property, RakNet::BitStream& outBitStream);
|
||||
/*override*/ bool ProcessOutdatedPropertyEnumDeserialization(Reflection::Property& property, RakNet::BitStream& inBitStream);
|
||||
/*override*/ bool isClassRemoved(const Instance* instance);
|
||||
/*override*/ bool isPropertyRemoved(const Instance* instance, const Name& propertyName);
|
||||
/*override*/ bool isEventRemoved(const Instance* instance, const Name& eventName);
|
||||
|
||||
bool needGC();
|
||||
bool hasEnoughMemoryToReceiveInstances();
|
||||
|
||||
public:
|
||||
Aya::signal<void()> receivedGlobalsSignal;
|
||||
Aya::signal<void()> gameLoadedSignal;
|
||||
Aya::signal<void(shared_ptr<const Reflection::ValueTable>)> statsReceivedSignal;
|
||||
|
||||
ClientReplicator(RakNet::SystemAddress systemAddress, Client* client, RakNet::SystemAddress clientAddress, NetworkSettings* networkSettings);
|
||||
~ClientReplicator();
|
||||
|
||||
void requestServerStats(bool request);
|
||||
void requestCharacterImpl();
|
||||
/*override*/ void requestCharacter();
|
||||
/*override*/ void updateClientCapacity();
|
||||
/*override*/ RakNet::PluginReceiveResult OnReceive(RakNet::Packet* packet);
|
||||
/*override*/ bool canUseProtocolVersion(int protocolVersion) const;
|
||||
/*override*/ void receiveCluster(RakNet::BitStream& inBitstream, Instance* instance, bool usingOneQuarterIterator);
|
||||
/*override*/ void postProcessPacket();
|
||||
/*override*/ shared_ptr<Instance> sendMarker();
|
||||
|
||||
const RakNet::SystemAddress getClientAddress() const
|
||||
{
|
||||
return clientAddress;
|
||||
}
|
||||
|
||||
void writePropAcknowledgementIfNeeded(const Instance* instance, const Reflection::PropertyDescriptor& desc, RakNet::BitStream& outBitStream);
|
||||
|
||||
bool isLimitedByOutgoingBandwidthLimit() const;
|
||||
virtual void deserializeSFFlags(RakNet::BitStream& inBitStream);
|
||||
|
||||
void renderStreamedRegions(Adorn* adorn);
|
||||
|
||||
void renderPartMovementPath(Adorn* adorn);
|
||||
|
||||
Aya::MemoryStats::MemoryLevel getMemoryLevel()
|
||||
{
|
||||
return memoryLevel;
|
||||
}
|
||||
void updateMemoryStats();
|
||||
|
||||
// streaming debug
|
||||
int getNumRegionsToGC() const;
|
||||
short getGCDistance() const;
|
||||
int getNumStreamedRegions() const;
|
||||
|
||||
private:
|
||||
// ------------- protocol schema --------------
|
||||
class ReflectionPropertyContainer
|
||||
{
|
||||
public:
|
||||
bool needSync;
|
||||
unsigned int id;
|
||||
const Name& name;
|
||||
unsigned int typeId;
|
||||
std::string typeName;
|
||||
const Reflection::Type* type;
|
||||
bool canReplicate;
|
||||
size_t enumMSB;
|
||||
ReflectionPropertyContainer(unsigned int _id, const Name& _name, unsigned int _typeId, std::string _typeName, const Reflection::Type* _type,
|
||||
bool _canReplicate, size_t _enumMSB)
|
||||
: id(_id)
|
||||
, name(_name)
|
||||
, typeId(_typeId)
|
||||
, typeName(_typeName)
|
||||
, type(_type)
|
||||
, canReplicate(_canReplicate)
|
||||
, enumMSB(_enumMSB)
|
||||
, needSync(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
typedef std::vector<shared_ptr<ReflectionPropertyContainer>> ReflectionPropertyList;
|
||||
|
||||
class ReflectionEventContainer
|
||||
{
|
||||
public:
|
||||
bool needSync;
|
||||
unsigned int id;
|
||||
const Name& name;
|
||||
std::vector<const Reflection::Type*> argTypes;
|
||||
ReflectionEventContainer(unsigned int _id, const Name& _name)
|
||||
: id(_id)
|
||||
, name(_name)
|
||||
, needSync(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
typedef std::vector<shared_ptr<ReflectionEventContainer>> ReflectionEventList;
|
||||
|
||||
class ReflectionClassContainer
|
||||
{
|
||||
public:
|
||||
bool needSync;
|
||||
unsigned int id;
|
||||
const Name& name;
|
||||
Reflection::ReplicationLevel replicationLevel;
|
||||
ReflectionPropertyList properties;
|
||||
ReflectionEventList events;
|
||||
ReflectionClassContainer(unsigned int _id, const Name& _name, Reflection::ReplicationLevel _repLevel)
|
||||
: id(_id)
|
||||
, name(_name)
|
||||
, replicationLevel(_repLevel)
|
||||
, needSync(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<const Aya::Name*, const shared_ptr<ReflectionClassContainer>> ReflectionClassMap;
|
||||
ReflectionClassMap serverClasses;
|
||||
|
||||
class ReflectionEnumContainer
|
||||
{
|
||||
public:
|
||||
size_t enumMSB;
|
||||
ReflectionEnumContainer(size_t _enumMSB)
|
||||
: enumMSB(_enumMSB)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<const Aya::Name*, ReflectionEnumContainer> ReflectionEnumMap;
|
||||
ReflectionEnumMap serverEnums;
|
||||
|
||||
typedef std::map<Aya::Guid::Data, const shared_ptr<ReflectionClassContainer>> InstanceClassMap;
|
||||
InstanceClassMap serverInstanceClassMap;
|
||||
|
||||
// Maintain a string dictionary for each Property/event type I don't recognize
|
||||
typedef std::map<unsigned int, shared_ptr<SharedStringDictionary>> DummyPropertyStrings;
|
||||
DummyPropertyStrings dummyStrings;
|
||||
|
||||
typedef std::map<unsigned int, shared_ptr<SharedStringProtectedDictionary>> DummyPropertyProtectedStrings;
|
||||
DummyPropertyProtectedStrings dummyProtectedStrings;
|
||||
|
||||
typedef std::map<unsigned int, shared_ptr<SharedStringDictionary>> DummyEventStrings;
|
||||
DummyEventStrings dummyEventStrings;
|
||||
|
||||
typedef std::map<unsigned int, shared_ptr<SharedBinaryStringDictionary>> DummyPropertyBinaryStrings;
|
||||
DummyPropertyBinaryStrings dummyBinaryStrings;
|
||||
|
||||
void learnSchema(RakNet::BitStream& bitStream);
|
||||
|
||||
void skipPropertyValue(RakNet::BitStream& inBitStream, const shared_ptr<ReflectionPropertyContainer>&, bool useDictionary);
|
||||
void skipPropertiesInternal(const shared_ptr<ReflectionPropertyContainer>&, RakNet::BitStream& inBitstream, bool useDictionary);
|
||||
void skipChangedProperty(RakNet::BitStream& bitStream, const shared_ptr<ReflectionPropertyContainer>& prop);
|
||||
void skipEventInvocation(RakNet::BitStream& bitStream, const shared_ptr<ReflectionEventContainer>& event);
|
||||
|
||||
bool getServerBasedProperty(const Name& className, int propertyId, shared_ptr<ReflectionPropertyContainer>& serverBasedProperty);
|
||||
bool getServerBasedEvent(const Name& className, int eventId, shared_ptr<ReflectionEventContainer>& serverBasedEvent);
|
||||
|
||||
SharedStringProtectedDictionary& getSharedPropertyProtectedDictionaryById(unsigned int propertyId);
|
||||
SharedStringDictionary& getSharedPropertyDictionaryById(unsigned int propertyId);
|
||||
SharedStringDictionary& getSharedEventDictionaryById(unsigned int eventId);
|
||||
SharedBinaryStringDictionary& getSharedPropertyBinaryDictionaryById(unsigned int propertyId);
|
||||
|
||||
bool hasEnumChanged(const Reflection::EnumDescriptor& enumDesc, size_t& newMSB);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
92
engine/network/src/ClusterUpdateBuffer.cpp
Normal file
92
engine/network/src/ClusterUpdateBuffer.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "ClusterUpdateBuffer.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "Utility/G3DCore.hpp"
|
||||
|
||||
#include "DataModel/MegaCluster.hpp"
|
||||
|
||||
#include "boost/cstdint.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
void ClusterUpdateBuffer::computeUintRepresentingLocationInChunk(const ClusterCellUpdate& update, unsigned int* out)
|
||||
{
|
||||
|
||||
(*out) = (update.x & kXZ_CHUNK_SIZE_AS_BITMASK) + ((update.z & kXZ_CHUNK_SIZE_AS_BITMASK) << kXZ_CHUNK_SIZE_AS_BITSHIFT) +
|
||||
((update.y & kY_CHUNK_SIZE_AS_BITMASK) << (2 * kXZ_CHUNK_SIZE_AS_BITSHIFT));
|
||||
}
|
||||
|
||||
void ClusterUpdateBuffer::computeGlobalLocationFromUintRepresentation(
|
||||
const unsigned int& index, const Vector3int16& baseCellOffset, ClusterCellUpdate* out)
|
||||
{
|
||||
|
||||
(*out) = baseCellOffset;
|
||||
out->x += index & kXZ_CHUNK_SIZE_AS_BITMASK;
|
||||
out->z += (index >> kXZ_CHUNK_SIZE_AS_BITSHIFT) & kXZ_CHUNK_SIZE_AS_BITMASK;
|
||||
out->y += (index >> (2 * kXZ_CHUNK_SIZE_AS_BITSHIFT)) & kY_CHUNK_SIZE_AS_BITMASK;
|
||||
}
|
||||
|
||||
ClusterUpdateBuffer::ClusterUpdateBuffer()
|
||||
: internalSize(0)
|
||||
{
|
||||
lastFound = bitSetUpdates.begin();
|
||||
}
|
||||
|
||||
size_t ClusterUpdateBuffer::size() const
|
||||
{
|
||||
return internalSize;
|
||||
}
|
||||
|
||||
void ClusterUpdateBuffer::push(const ClusterCellUpdate& inputData)
|
||||
{
|
||||
unsigned int info;
|
||||
computeUintRepresentingLocationInChunk(inputData, &info);
|
||||
|
||||
internalSize += bitSetUpdates[SpatialRegion::regionContainingVoxel(inputData)].insert(info);
|
||||
|
||||
// insert potentially invalidates iterators; also this preserves the invariant that no updates can be before lastFound in the map
|
||||
lastFound = bitSetUpdates.begin();
|
||||
}
|
||||
|
||||
bool ClusterUpdateBuffer::chk(const ClusterCellUpdate& test)
|
||||
{
|
||||
unsigned int info;
|
||||
computeUintRepresentingLocationInChunk(test, &info);
|
||||
|
||||
BitSetUpdateMap::iterator it = bitSetUpdates.find(SpatialRegion::regionContainingVoxel(test));
|
||||
|
||||
return it != bitSetUpdates.end() && it->second.contains(info);
|
||||
}
|
||||
|
||||
void ClusterUpdateBuffer::pop(ClusterCellUpdate* out)
|
||||
{
|
||||
AYAASSERT(internalSize > 0);
|
||||
|
||||
for (BitSetUpdateMap::iterator it = lastFound; it != bitSetUpdates.end(); ++it)
|
||||
{
|
||||
UintSet& updateVector = it->second;
|
||||
|
||||
if (updateVector.size() > 0)
|
||||
{
|
||||
lastFound = it;
|
||||
|
||||
unsigned int intVal;
|
||||
updateVector.pop_smallest(&intVal);
|
||||
computeGlobalLocationFromUintRepresentation(intVal, SpatialRegion::inclusiveVoxelExtentsOfRegion(it->first).getMinPos(), out);
|
||||
internalSize--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AYAASSERT(false);
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
62
engine/network/src/ClusterUpdateBuffer.hpp
Normal file
62
engine/network/src/ClusterUpdateBuffer.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Utility/G3DCore.hpp"
|
||||
|
||||
#include "Utility/UintSet.hpp"
|
||||
|
||||
#include "boost/cstdint.hpp"
|
||||
#include "Utility/ClusterCellIterator.hpp"
|
||||
|
||||
#include "boost/unordered_map.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// these constants visible for testing
|
||||
const int kXZ_CHUNK_SIZE_AS_BITSHIFT = 5;
|
||||
const int kXZ_CHUNK_SIZE_AS_BITMASK = 0x1f;
|
||||
const int kY_CHUNK_SIZE_AS_BITSHIFT = 4;
|
||||
const int kY_CHUNK_SIZE_AS_BITMASK = 0x0f;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
typedef Vector3int16 ClusterCellUpdate;
|
||||
typedef unsigned int ChunkIndex;
|
||||
|
||||
|
||||
struct ClusterUpdateBuffer
|
||||
{
|
||||
|
||||
private:
|
||||
typedef boost::unordered_map<SpatialRegion::Id, UintSet, SpatialRegion::Id::boost_compatible_hash_value> BitSetUpdateMap;
|
||||
BitSetUpdateMap::iterator lastFound;
|
||||
BitSetUpdateMap bitSetUpdates;
|
||||
|
||||
size_t internalSize;
|
||||
|
||||
public:
|
||||
static void computeUintRepresentingLocationInChunk(const ClusterCellUpdate& update, unsigned int* out);
|
||||
static void computeGlobalLocationFromUintRepresentation(const unsigned int& info, const Vector3int16& baseCellOffset, ClusterCellUpdate* out);
|
||||
|
||||
ClusterUpdateBuffer();
|
||||
|
||||
size_t size() const;
|
||||
|
||||
void push(const ClusterCellUpdate& inputData);
|
||||
|
||||
bool chk(const ClusterCellUpdate& test);
|
||||
|
||||
void pop(ClusterCellUpdate* out);
|
||||
|
||||
static inline void nextCellInIterationOrder(const Vector3int16& cellpos, Vector3int16* out)
|
||||
{
|
||||
ClusterChunksIterator::nextCellInIterationOrder(cellpos, out);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
|
||||
} // namespace Aya
|
||||
214
engine/network/src/Compressor.cpp
Normal file
214
engine/network/src/Compressor.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
|
||||
|
||||
#include "Compressor.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Utility/Quaternion.hpp"
|
||||
|
||||
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/iostreams/filter/gzip.hpp>
|
||||
#include <boost/iostreams/device/array.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
enum
|
||||
{
|
||||
translationBits0 = 15,
|
||||
translationBits1 = 14,
|
||||
translationBits2 = 15
|
||||
};
|
||||
|
||||
static const float translationMin[] = {-1024, -512, -1024};
|
||||
static const float translationMax[] = {1024, 512, 1024};
|
||||
|
||||
void Compressor::writeRotation(RakNet::BitStream& bitStream, const Matrix3& rotation, CompressionType compressionType)
|
||||
{
|
||||
writeCompressionType(bitStream, compressionType);
|
||||
|
||||
Quaternion q(rotation);
|
||||
q.normalize();
|
||||
|
||||
switch (compressionType)
|
||||
{
|
||||
case Compressor::UNCOMPRESSED:
|
||||
{
|
||||
bitStream << q.w;
|
||||
bitStream << q.x;
|
||||
bitStream << q.y;
|
||||
bitStream << q.z;
|
||||
break;
|
||||
}
|
||||
case Compressor::RAKNET_COMPRESSED:
|
||||
{
|
||||
bitStream.WriteNormQuat(q.w, q.x, q.y, q.z);
|
||||
break;
|
||||
}
|
||||
case Compressor::HEAVILY_COMPRESSED:
|
||||
{
|
||||
bitStream.WriteNormQuat(q.w, q.x, q.y, q.z);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
AYAASSERT(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Compressor::writeTranslation(RakNet::BitStream& bitStream, const Vector3& translation, CompressionType compressionType)
|
||||
{
|
||||
if ((compressionType == Compressor::HEAVILY_COMPRESSED) && !canHeavilyCompressTranslation(translation))
|
||||
{
|
||||
compressionType = Compressor::RAKNET_COMPRESSED;
|
||||
}
|
||||
|
||||
writeCompressionType(bitStream, compressionType);
|
||||
|
||||
switch (compressionType)
|
||||
{
|
||||
case Compressor::UNCOMPRESSED:
|
||||
{
|
||||
bitStream << translation.x;
|
||||
bitStream << translation.y;
|
||||
bitStream << translation.z;
|
||||
break;
|
||||
}
|
||||
case Compressor::RAKNET_COMPRESSED:
|
||||
{
|
||||
bitStream.WriteVector(translation.x, translation.y, translation.z);
|
||||
break;
|
||||
}
|
||||
case Compressor::HEAVILY_COMPRESSED:
|
||||
{
|
||||
// p = (p_i - min)*shift/(max-min)
|
||||
unsigned short x = (unsigned short)((translation.x - translationMin[0]) * (1 << translationBits0) / (translationMax[0] - translationMin[0]));
|
||||
unsigned short y = (unsigned short)((translation.y - translationMin[1]) * (1 << translationBits1) / (translationMax[1] - translationMin[1]));
|
||||
unsigned short z = (unsigned short)((translation.z - translationMin[2]) * (1 << translationBits2) / (translationMax[2] - translationMin[2]));
|
||||
|
||||
// Check for overflow (underflow shouldn't be possible)
|
||||
if (x >= 1 << translationBits0)
|
||||
x = 0xFFFF;
|
||||
if (y >= 1 << translationBits1)
|
||||
y = 0xFFFF;
|
||||
if (z >= 1 << translationBits2)
|
||||
z = 0xFFFF;
|
||||
|
||||
bitStream.WriteBits((const unsigned char*)&x, translationBits0);
|
||||
bitStream.WriteBits((const unsigned char*)&y, translationBits1);
|
||||
bitStream.WriteBits((const unsigned char*)&z, translationBits2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
AYAASSERT(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Compressor::canHeavilyCompressTranslation(const Vector3& translation)
|
||||
{
|
||||
return !(translation.x > translationMax[0] || translation.x < translationMin[0] || translation.y > translationMax[1] ||
|
||||
translation.y < translationMin[1] || translation.z > translationMax[2] || translation.z < translationMin[2]);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
void Compressor::readRotation(RakNet::BitStream& bitStream, Matrix3& rotation)
|
||||
{
|
||||
CompressionType compressionType = readCompressionType(bitStream);
|
||||
|
||||
Quaternion q;
|
||||
|
||||
switch (compressionType)
|
||||
{
|
||||
case UNCOMPRESSED:
|
||||
{
|
||||
bitStream >> q.w;
|
||||
bitStream >> q.x;
|
||||
bitStream >> q.y;
|
||||
bitStream >> q.z;
|
||||
break;
|
||||
}
|
||||
case RAKNET_COMPRESSED:
|
||||
{
|
||||
if (!bitStream.ReadNormQuat(q.w, q.x, q.y, q.z))
|
||||
throw std::runtime_error("Failed to read Quaternion");
|
||||
break;
|
||||
}
|
||||
case HEAVILY_COMPRESSED:
|
||||
{
|
||||
if (!bitStream.ReadNormQuat(q.w, q.x, q.y, q.z))
|
||||
throw std::runtime_error("Failed to read Quaternion");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
AYAASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
q.toRotationMatrix(rotation);
|
||||
}
|
||||
|
||||
void Compressor::readTranslation(RakNet::BitStream& bitStream, Vector3& translation)
|
||||
{
|
||||
|
||||
CompressionType compressionType = readCompressionType(bitStream);
|
||||
|
||||
switch (compressionType)
|
||||
{
|
||||
case UNCOMPRESSED:
|
||||
{
|
||||
bitStream >> translation.x;
|
||||
bitStream >> translation.y;
|
||||
bitStream >> translation.z;
|
||||
break;
|
||||
}
|
||||
case RAKNET_COMPRESSED:
|
||||
{
|
||||
readVectorFast(bitStream, translation.x, translation.y, translation.z);
|
||||
break;
|
||||
}
|
||||
case HEAVILY_COMPRESSED:
|
||||
{
|
||||
unsigned short x, y, z = 0;
|
||||
|
||||
readFastN<translationBits0>(bitStream, x);
|
||||
readFastN<translationBits1>(bitStream, y);
|
||||
readFastN<translationBits2>(bitStream, z);
|
||||
|
||||
// p_i = p/shift * (max-min) + min
|
||||
translation.x = (float)x * (1.0f / (1 << translationBits0) * (translationMax[0] - translationMin[0])) + translationMin[0];
|
||||
translation.y = (float)y * (1.0f / (1 << translationBits1) * (translationMax[1] - translationMin[1])) + translationMin[1];
|
||||
translation.z = (float)z * (1.0f / (1 << translationBits2) * (translationMax[2] - translationMin[2])) + translationMin[2];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
AYAASSERT(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Compressor::writeCompressionType(RakNet::BitStream& bitStream, CompressionType compressionType)
|
||||
{
|
||||
unsigned short temp = compressionType;
|
||||
bitStream.WriteBits((const unsigned char*)&temp, 2);
|
||||
}
|
||||
|
||||
Compressor::CompressionType Compressor::readCompressionType(RakNet::BitStream& bitStream)
|
||||
{
|
||||
unsigned short temp = 0;
|
||||
readFastN<2>(bitStream, temp);
|
||||
|
||||
CompressionType answer = static_cast<CompressionType>(temp);
|
||||
return answer;
|
||||
}
|
||||
42
engine/network/src/Compressor.hpp
Normal file
42
engine/network/src/Compressor.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utility/G3DCore.hpp"
|
||||
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Compressor
|
||||
{
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
UNCOMPRESSED = 0,
|
||||
RAKNET_COMPRESSED,
|
||||
HEAVILY_COMPRESSED
|
||||
} CompressionType;
|
||||
|
||||
private:
|
||||
static bool canHeavilyCompressTranslation(const Vector3& translation);
|
||||
|
||||
static void writeCompressionType(RakNet::BitStream& bitStream, CompressionType compressionType);
|
||||
static CompressionType readCompressionType(RakNet::BitStream& bitStream);
|
||||
|
||||
public:
|
||||
static void writeTranslation(RakNet::BitStream& bitStream, const Vector3& translation, CompressionType compressionType);
|
||||
static void writeRotation(RakNet::BitStream& bitStream, const Matrix3& rotation, CompressionType compressionType);
|
||||
|
||||
static void readTranslation(RakNet::BitStream& bitStream, Vector3& translation);
|
||||
static void readRotation(RakNet::BitStream& bitStream, Matrix3& rotation);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
295
engine/network/src/ConcurrentRakPeer.cpp
Normal file
295
engine/network/src/ConcurrentRakPeer.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "RakNet/RakNetStatistics.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "Utility/SystemAddress.hpp"
|
||||
|
||||
|
||||
LOGGROUP(NetworkStatsReport)
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
// Used by ConcurrentRakPeer to synchronize the sending of packets in a thread-safe way
|
||||
class ConcurrentRakPeer::PacketJob : public Aya::DataModelJob
|
||||
{
|
||||
public:
|
||||
struct SendData
|
||||
{
|
||||
boost::shared_ptr<const RakNet::BitStream> bitStream;
|
||||
PacketPriority priority;
|
||||
PacketReliability reliability;
|
||||
char orderingChannel;
|
||||
RakNet::SystemAddress systemAddress;
|
||||
bool broadcast;
|
||||
};
|
||||
Aya::timestamped_safe_queue<SendData> sendQueue;
|
||||
bool isQueueErrorComputed;
|
||||
|
||||
weak_ptr<RakNet::RakPeerInterface> peer;
|
||||
PacketJob(shared_ptr<RakNet::RakPeerInterface> peer, DataModel* dataModel)
|
||||
: DataModelJob("Net Peer Send", DataModelJob::RaknetPeer, false, shared_from(dataModel), Time::Interval(0))
|
||||
, peer(peer)
|
||||
, isQueueErrorComputed(NetworkSettings::singleton().isQueueErrorComputed)
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ReceiveIncoming;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return sendQueue.empty() ? Time::Interval::max() : Time::Interval::zero();
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
Error result;
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
if (sendQueue.size())
|
||||
{
|
||||
result.error = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.error = 0.0f;
|
||||
}
|
||||
}
|
||||
else if (isQueueErrorComputed)
|
||||
{
|
||||
double waitTime = sendQueue.head_waittime_sec(stats.timeNow);
|
||||
result.error = waitTime * 100.0;
|
||||
result.error *= std::min<int>(10, sendQueue.size() + 1) / 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double size = sendQueue.size();
|
||||
|
||||
if (size > 0)
|
||||
result.error = stats.timespanSinceLastStep.seconds() * 100.0 * (double)size;
|
||||
else
|
||||
result.error = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (shared_ptr<RakNet::RakPeerInterface> safePeer = peer.lock())
|
||||
{
|
||||
SendData data;
|
||||
while (sendQueue.pop_if_present(data))
|
||||
{
|
||||
bool result = safePeer->Send(data.bitStream.get(), data.priority, data.reliability, data.orderingChannel, data.systemAddress,
|
||||
data.broadcast) != 0;
|
||||
AYAASSERT(result);
|
||||
}
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ConcurrentRakPeer::StatsUpdateJob : public Aya::DataModelJob
|
||||
{
|
||||
private:
|
||||
struct SystemAddressHasher
|
||||
{
|
||||
size_t operator()(const RakNet::SystemAddress& key) const
|
||||
{
|
||||
return RakNet::SystemAddress::ToInteger(key);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
boost::mutex mapMutex;
|
||||
typedef boost::unordered_map<RakNet::SystemAddress, ConnectionStats, SystemAddressHasher> StatsMap;
|
||||
StatsMap statsMap;
|
||||
|
||||
typedef boost::unordered_map<RakNet::SystemAddress, boost::function<void(const ConnectionStats&)>> UpdateCallbackMap;
|
||||
UpdateCallbackMap updateCallbackMap;
|
||||
|
||||
// 0 to 1
|
||||
// 1: good, nothing in buffer, room to grow. 0: bad, buffer is increasing, should probably send less data
|
||||
RunningAverage<> bufferHealth;
|
||||
int prevBufferSize;
|
||||
|
||||
weak_ptr<RakNet::RakPeerInterface> peer;
|
||||
StatsUpdateJob(shared_ptr<RakNet::RakPeerInterface> peer, DataModel* dataModel)
|
||||
: DataModelJob("Net Peer Stats", DataModelJob::RaknetPeer, false, shared_from(dataModel), Time::Interval(0))
|
||||
, peer(peer)
|
||||
, prevBufferSize(0)
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessIncoming;
|
||||
}
|
||||
|
||||
void updateStats(StatsMap::value_type& x)
|
||||
{
|
||||
if (shared_ptr<RakNet::RakPeerInterface> safePeer = peer.lock())
|
||||
{
|
||||
updateStats(x, safePeer.get());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void updateStats(StatsMap::value_type& x, RakNet::RakPeerInterface* peer)
|
||||
{
|
||||
FASTLOG(FLog::NetworkStatsReport, "updateStatsJob::updateStats running");
|
||||
x.second.mtuSize = peer->GetMTUSize(x.first);
|
||||
x.second.averagePing = peer->GetAveragePing(x.first);
|
||||
x.second.lastPing = peer->GetLastPing(x.first);
|
||||
x.second.lowestPing = peer->GetLowestPing(x.first);
|
||||
RakNet::RakNetStatistics* stats = peer->GetStatistics(x.first);
|
||||
if (stats)
|
||||
{
|
||||
int ourstats = stats->runningTotal[RakNet::ACTUAL_BYTES_RECEIVED];
|
||||
FASTLOG1(FLog::NetworkStatsReport, "updateStatsJob rakStats ACTUAL_BYTES_RECEIVE: %d", ourstats);
|
||||
x.second.rakStats = *stats;
|
||||
x.second.maxPacketloss = std::max(x.second.maxPacketloss, stats->packetlossLastSecond);
|
||||
x.second.averageBandwidthExceeded.sample(stats->isLimitedByOutgoingBandwidthLimit ? 1.0 : 0.0);
|
||||
x.second.averageCongestionControlExceeded.sample(stats->isLimitedByCongestionControl ? 1.0 : 0.0);
|
||||
x.second.kiloBytesSentPerSecond.sample(stats->valueOverLastSecond[RakNet::USER_MESSAGE_BYTES_PUSHED] / 1000.0f);
|
||||
x.second.kiloBytesReceivedPerSecond.sample(stats->valueOverLastSecond[RakNet::USER_MESSAGE_BYTES_RECEIVED_PROCESSED] / 1000.0f);
|
||||
|
||||
x.second.updateBufferHealth(stats->messageInSendBuffer[IMMEDIATE_PRIORITY] + stats->messageInSendBuffer[HIGH_PRIORITY] +
|
||||
stats->messageInSendBuffer[MEDIUM_PRIORITY] + stats->messageInSendBuffer[LOW_PRIORITY]);
|
||||
}
|
||||
}
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, 30);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
return computeStandardErrorCyclicExecutiveSleeping(stats, 30);
|
||||
}
|
||||
|
||||
TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (shared_ptr<RakNet::RakPeerInterface> safePeer = peer.lock())
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mapMutex);
|
||||
|
||||
int bufferSize = 0;
|
||||
for (StatsMap::iterator iter = statsMap.begin(); iter != statsMap.end(); iter++)
|
||||
{
|
||||
updateStats(*iter, safePeer.get());
|
||||
RakNet::RakNetStatistics& stats = iter->second.rakStats;
|
||||
bufferSize += stats.messageInSendBuffer[IMMEDIATE_PRIORITY] + stats.messageInSendBuffer[HIGH_PRIORITY] +
|
||||
stats.messageInSendBuffer[MEDIUM_PRIORITY] + stats.messageInSendBuffer[LOW_PRIORITY];
|
||||
|
||||
UpdateCallbackMap::iterator callback = updateCallbackMap.find(iter->first);
|
||||
if (callback != updateCallbackMap.end())
|
||||
{
|
||||
callback->second(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
calcBufferHealth(bufferSize);
|
||||
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
|
||||
void calcBufferHealth(int bufferSize)
|
||||
{
|
||||
if (bufferSize > prevBufferSize)
|
||||
bufferHealth.sample(0.0);
|
||||
else if (bufferSize == 0)
|
||||
bufferHealth.sample(1);
|
||||
else
|
||||
bufferHealth.sample(0.5);
|
||||
|
||||
prevBufferSize = bufferSize;
|
||||
}
|
||||
};
|
||||
|
||||
void ConcurrentRakPeer::addStats(RakNet::SystemAddress address, boost::function<void(const ConnectionStats&)> callback)
|
||||
{
|
||||
AYAASSERT(dataModel->write_requested);
|
||||
StatsUpdateJob::StatsMap::value_type p(address, ConnectionStats());
|
||||
statsUpdateJob->updateStats(p);
|
||||
|
||||
boost::mutex::scoped_lock lock(statsUpdateJob->mapMutex);
|
||||
statsUpdateJob->statsMap.insert(p);
|
||||
statsUpdateJob->updateCallbackMap[address] = callback;
|
||||
|
||||
// We need to call this callback
|
||||
// Otherwise we send uninitialized data to Diag sometimes.
|
||||
callback(p.second);
|
||||
}
|
||||
|
||||
void ConcurrentRakPeer::removeStats(RakNet::SystemAddress address)
|
||||
{
|
||||
AYAASSERT(dataModel->write_requested);
|
||||
boost::mutex::scoped_lock lock(statsUpdateJob->mapMutex);
|
||||
statsUpdateJob->statsMap.erase(address);
|
||||
statsUpdateJob->updateCallbackMap.erase(address);
|
||||
}
|
||||
|
||||
ConcurrentRakPeer::ConcurrentRakPeer(RakNet::RakPeerInterface* peer, Aya::DataModel* dataModel)
|
||||
: peer(shared_ptr<RakNet::RakPeerInterface>(peer))
|
||||
, dataModel(dataModel)
|
||||
{
|
||||
packetJob.reset(new PacketJob(this->peer, dataModel));
|
||||
statsUpdateJob.reset(new StatsUpdateJob(this->peer, dataModel));
|
||||
Aya::TaskScheduler::singleton().add(packetJob);
|
||||
Aya::TaskScheduler::singleton().add(statsUpdateJob);
|
||||
}
|
||||
|
||||
|
||||
ConcurrentRakPeer::~ConcurrentRakPeer()
|
||||
{
|
||||
Aya::TaskScheduler::singleton().remove(packetJob);
|
||||
Aya::TaskScheduler::singleton().remove(statsUpdateJob);
|
||||
}
|
||||
|
||||
void ConcurrentRakPeer::Send(boost::shared_ptr<const RakNet::BitStream> bitStream, PacketPriority priority, PacketReliability reliability,
|
||||
char orderingChannel, RakNet::SystemAddress systemAddress, bool broadcast)
|
||||
{
|
||||
PacketJob::SendData data = {bitStream, priority, reliability, orderingChannel, systemAddress, broadcast};
|
||||
packetJob->sendQueue.push(data);
|
||||
TaskScheduler::singleton().reschedule(packetJob);
|
||||
}
|
||||
|
||||
double ConcurrentRakPeer::GetBufferHealth()
|
||||
{
|
||||
boost::mutex::scoped_lock lock(statsUpdateJob->mapMutex);
|
||||
return statsUpdateJob->bufferHealth.value();
|
||||
}
|
||||
|
||||
const RakNet::RakNetStatistics* ConcurrentRakPeer::GetStatistics(const RakNet::SystemAddress systemAddress) const
|
||||
{
|
||||
boost::mutex::scoped_lock lock(statsUpdateJob->mapMutex);
|
||||
return &statsUpdateJob->statsMap[systemAddress].rakStats;
|
||||
}
|
||||
double ConcurrentRakPeer::GetBandwidthExceeded(const RakNet::SystemAddress systemAddress)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(statsUpdateJob->mapMutex);
|
||||
return statsUpdateJob->statsMap[systemAddress].averageBandwidthExceeded.value();
|
||||
}
|
||||
double ConcurrentRakPeer::GetCongestionControlExceeded(const RakNet::SystemAddress systemAddress)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(statsUpdateJob->mapMutex);
|
||||
return statsUpdateJob->statsMap[systemAddress].averageCongestionControlExceeded.value();
|
||||
}
|
||||
|
||||
|
||||
RakNet::RakPeerInterface* ConcurrentRakPeer::rawPeer()
|
||||
{
|
||||
AYAASSERT(dataModel->write_requested);
|
||||
return peer.get();
|
||||
}
|
||||
const RakNet::RakPeerInterface* ConcurrentRakPeer::rawPeer() const
|
||||
{
|
||||
AYAASSERT(dataModel->write_requested);
|
||||
return peer.get();
|
||||
}
|
||||
116
engine/network/src/ConcurrentRakPeer.hpp
Normal file
116
engine/network/src/ConcurrentRakPeer.hpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#include "RakNet/RakPeerInterface.hpp"
|
||||
#include "RakNet/RakNetStatistics.hpp"
|
||||
#include "RakNet/MTUSize.hpp"
|
||||
#include "boost/noncopyable.hpp"
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "boost/shared_ptr.hpp"
|
||||
#include "DataModel/DataModelJob.hpp"
|
||||
|
||||
#include "threadsafe.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class DataModel;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
struct ConnectionStats
|
||||
{
|
||||
int mtuSize;
|
||||
int averagePing;
|
||||
int lastPing;
|
||||
int lowestPing;
|
||||
float maxPacketloss;
|
||||
|
||||
RunningAverage<> bufferHealth;
|
||||
int prevBufferSize;
|
||||
|
||||
Aya::RunningAverage<double> averageBandwidthExceeded;
|
||||
Aya::RunningAverage<double> averageCongestionControlExceeded;
|
||||
Aya::RunningAverage<double> kiloBytesSentPerSecond;
|
||||
Aya::RunningAverage<double> kiloBytesReceivedPerSecond;
|
||||
|
||||
RakNet::RakNetStatistics rakStats;
|
||||
|
||||
ConnectionStats()
|
||||
: mtuSize(MAXIMUM_MTU_SIZE)
|
||||
, averagePing(0)
|
||||
, lastPing(0)
|
||||
, lowestPing(0)
|
||||
, maxPacketloss(0.0f)
|
||||
, prevBufferSize(0)
|
||||
, averageBandwidthExceeded(0.1)
|
||||
, averageCongestionControlExceeded(0.1)
|
||||
, bufferHealth(0.1, 1.0)
|
||||
{
|
||||
}
|
||||
|
||||
void updateBufferHealth(int bufferSize)
|
||||
{
|
||||
if (bufferSize > prevBufferSize)
|
||||
bufferHealth.sample(0.0);
|
||||
else if (bufferSize == 0)
|
||||
bufferHealth.sample(1);
|
||||
else
|
||||
bufferHealth.sample(0.5);
|
||||
|
||||
prevBufferSize = bufferSize;
|
||||
}
|
||||
};
|
||||
|
||||
/// Guards RakPeerInterface agains concurrent access
|
||||
/// Any Job running in DataModelJob::Write can access
|
||||
/// the peer interface directly using rawPeer().
|
||||
/// All other threads should use the functions provided.
|
||||
class ConcurrentRakPeer : boost::noncopyable
|
||||
{
|
||||
boost::shared_ptr<RakNet::RakPeerInterface> peer;
|
||||
|
||||
class PacketJob;
|
||||
boost::shared_ptr<PacketJob> packetJob;
|
||||
class StatsUpdateJob;
|
||||
boost::shared_ptr<StatsUpdateJob> statsUpdateJob;
|
||||
Aya::DataModel* const dataModel;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
ConcurrentRakPeer(RakNet::RakPeerInterface* peer, Aya::DataModel* dataModel);
|
||||
~ConcurrentRakPeer();
|
||||
|
||||
void addStats(RakNet::SystemAddress address, boost::function<void(const ConnectionStats&)> updateCallback);
|
||||
void removeStats(RakNet::SystemAddress address);
|
||||
|
||||
// Call raw when you KNOW there are no concurrency issues
|
||||
RakNet::RakPeerInterface* rawPeer();
|
||||
const RakNet::RakPeerInterface* rawPeer() const;
|
||||
|
||||
bool equals(RakNet::RakPeerInterface* peer) const
|
||||
{
|
||||
return this->peer.get() == peer;
|
||||
}
|
||||
|
||||
void Send(boost::shared_ptr<const RakNet::BitStream> bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel,
|
||||
RakNet::SystemAddress systemAddress, bool broadcast);
|
||||
|
||||
void DeallocatePacket(RakNet::Packet* packet)
|
||||
{
|
||||
// Thread-safe
|
||||
peer->DeallocatePacket(packet);
|
||||
}
|
||||
|
||||
double GetBufferHealth();
|
||||
|
||||
// 0..1
|
||||
double GetBandwidthExceeded(const RakNet::SystemAddress systemAddress);
|
||||
double GetCongestionControlExceeded(const RakNet::SystemAddress systemAddress);
|
||||
const RakNet::RakNetStatistics* GetStatistics(const RakNet::SystemAddress systemAddress) const;
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
425
engine/network/src/CrashReporter.cpp
Normal file
425
engine/network/src/CrashReporter.cpp
Normal file
@@ -0,0 +1,425 @@
|
||||
#ifdef _WIN32
|
||||
// To compile link with Dbghelp.lib
|
||||
// The callstack in release is the same as usual, which means it isn't all that accurate.
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <intrusive_ptr_target.hpp>
|
||||
#include <intrusive_weak_ptr.hpp>
|
||||
|
||||
|
||||
#include "CrashReporter.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef _WIN32
|
||||
#include <DbgHelp.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
#include "RakNet/EmailSender.hpp"
|
||||
#include "RakNet/FileList.hpp"
|
||||
#include "RakNet/FileOperations.hpp"
|
||||
#include "TaskScheduler.hpp"
|
||||
|
||||
#include "Log.hpp"
|
||||
|
||||
#include "CEvent.hpp"
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "boost.hpp"
|
||||
#include "FastLog.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
|
||||
#include "boost/bind.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "StrSafe.h"
|
||||
#endif
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
CrashReporter* CrashReporter::singleton = NULL;
|
||||
|
||||
// More info at:
|
||||
// http://www.codeproject.com/debug/postmortemdebug_standalone1.asp
|
||||
// http://www.codeproject.com/debug/XCrashReportPt3.asp
|
||||
// http://www.codeproject.com/debug/XCrashReportPt1.asp
|
||||
// http://www.microsoft.com/msj/0898/bugslayer0898.aspx
|
||||
|
||||
#pragma warning(disable : 4996) // TODO - revisit this compiler warning
|
||||
|
||||
LOGGROUP(HangDetection)
|
||||
LOGGROUP(Crash)
|
||||
|
||||
DYNAMIC_FASTINTVARIABLE(WriteFullDmpPercent, 0)
|
||||
|
||||
CrashReporter::CrashReporter()
|
||||
: threadResult(0)
|
||||
, exceptionInfo(NULL)
|
||||
, reportCrashEvent(FALSE)
|
||||
, hangReportingEnabled(false)
|
||||
, isAlive(true)
|
||||
, deadlockCounter(0)
|
||||
, destructing(false)
|
||||
, immediateUploadEnabled(true)
|
||||
{
|
||||
assert(singleton == NULL);
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
CrashReporter::~CrashReporter()
|
||||
{
|
||||
destructing = true;
|
||||
|
||||
if (watcherThread)
|
||||
{
|
||||
FASTLOG(FLog::Crash, "Closing crashreporter thread on destroy");
|
||||
reportCrashEvent.Set();
|
||||
watcherThread->join();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HRESULT CrashReporter::GenerateDmpFileName(__out_ecount(cchdumpFilepath) char* dumpFilepath, int cchdumpFilepath, bool fastLog, bool fullDmp)
|
||||
{
|
||||
char appDescriptor[_MAX_PATH];
|
||||
char dumpFilename[_MAX_PATH];
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
StringCchPrintf(appDescriptor, _MAX_PATH, "%s %s", controls.appName, controls.appVersion);
|
||||
|
||||
if (FAILED(hr = StringCchCopy(dumpFilepath, cchdumpFilepath, controls.pathToMinidump)))
|
||||
return hr;
|
||||
WriteFileWithDirectories(dumpFilepath, 0, 0);
|
||||
AddSlash(dumpFilepath);
|
||||
unsigned i, dumpFilenameLen;
|
||||
StringCchCopy(dumpFilename, _MAX_PATH, appDescriptor);
|
||||
dumpFilenameLen = (unsigned)strlen(appDescriptor);
|
||||
for (i = 0; i < dumpFilenameLen; i++)
|
||||
if (dumpFilename[i] == ':')
|
||||
dumpFilename[i] = '.'; // Can't have : in a filename
|
||||
|
||||
if (FAILED(hr = StringCchCat(dumpFilepath, cchdumpFilepath, dumpFilename)))
|
||||
return hr;
|
||||
|
||||
if (fullDmp)
|
||||
{
|
||||
if (FAILED(hr = StringCchCat(dumpFilepath, cchdumpFilepath, ".Full")))
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (FAILED(hr = StringCchCat(dumpFilepath, cchdumpFilepath, fastLog ? ".txt" : controls.crashExtention)))
|
||||
return hr;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
LONG CrashReporter::ProcessExceptionHelper(struct _EXCEPTION_POINTERS* ExceptionInfo, bool writeFullDmp, bool noMsg, char* dumpFilepath)
|
||||
{
|
||||
int dumpType = controls.minidumpType;
|
||||
|
||||
if (writeFullDmp)
|
||||
dumpType |= MiniDumpWithFullMemory;
|
||||
|
||||
if (FAILED(GenerateDmpFileName(dumpFilepath, _MAX_PATH, false, writeFullDmp)))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
HANDLE hFile = CreateFile(dumpFilepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION eInfo;
|
||||
eInfo.ThreadId = GetCurrentThreadId();
|
||||
eInfo.ExceptionPointers = ExceptionInfo;
|
||||
eInfo.ClientPointers = FALSE;
|
||||
|
||||
LONG result = EXCEPTION_EXECUTE_HANDLER;
|
||||
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)dumpType, ExceptionInfo ? &eInfo : NULL, NULL, NULL))
|
||||
{
|
||||
HRESULT hr = GetLastError();
|
||||
DWORD bytesWritten = 0;
|
||||
TCHAR msg[1024];
|
||||
StringCchPrintf(msg, ARRAYSIZE(msg), "MiniDumpWriteDump() failed with hr = 0x%08x", hr);
|
||||
WriteFile(hFile, &msg, (strlen(msg) + 1) * sizeof(TCHAR), &bytesWritten, NULL);
|
||||
|
||||
result = EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
CloseHandle(hFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
std::string specialCrashType = "first"; // generic name in case there are multiple choices
|
||||
bool gCrashIsSpecial = false;
|
||||
} // namespace Aya
|
||||
|
||||
LONG CrashReporter::ProcessException(struct _EXCEPTION_POINTERS* ExceptionInfo, bool noMsg)
|
||||
{
|
||||
static bool allowSpecial = true;
|
||||
LONG result = EXCEPTION_EXECUTE_HANDLER;
|
||||
|
||||
char dumpFilepath[_MAX_PATH] = {};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LONG CrashReporter::ProcessExceptionInThead(struct _EXCEPTION_POINTERS* excpInfo)
|
||||
{
|
||||
FASTLOG(FLog::Crash, "Processing exception in thread");
|
||||
// stack collection happens in it's own thread.
|
||||
// that way, we can report stacks for stack overflow exceptions.
|
||||
if (watcherThread)
|
||||
{
|
||||
exceptionInfo = excpInfo;
|
||||
|
||||
reportCrashEvent.Set();
|
||||
|
||||
watcherThread->join();
|
||||
|
||||
watcherThread.reset(); // cleanup.
|
||||
|
||||
return this->threadResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no thread? no problem!
|
||||
return ProcessException(excpInfo, false);
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReporter::TheadFunc(struct _EXCEPTION_POINTERS* excpInfo)
|
||||
{
|
||||
this->threadResult = ProcessException(excpInfo, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static LONG ProcessExceptionStatic(PEXCEPTION_POINTERS excpInfo)
|
||||
{
|
||||
if (excpInfo == NULL)
|
||||
{
|
||||
LONG result;
|
||||
// Generate exception to get proper context in dump
|
||||
__try
|
||||
{
|
||||
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
|
||||
}
|
||||
__except (result = ProcessExceptionStatic(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CrashReporter::singleton->ProcessExceptionInThead(excpInfo);
|
||||
}
|
||||
}
|
||||
|
||||
LONG WINAPI CrashExceptionFilter(struct _EXCEPTION_POINTERS* excpInfo)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
AYAASSERT(false);
|
||||
#endif
|
||||
LONG result = ProcessExceptionStatic(excpInfo);
|
||||
|
||||
// Force a hard termination of the process.
|
||||
// exit() would call termination routines, flush buffers, etc.
|
||||
// The justification is that the program is in a bad state and
|
||||
// might deadlock rather than exiting properly.
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// http://blog.kalmbachnet.de/?postid=75
|
||||
/*
|
||||
|
||||
Many programs are setting an own Unhandled-Exception-Filter , for catching unhandled exceptions and do some reporting or logging (for example creating
|
||||
a mini-dump ).
|
||||
|
||||
Now, starting with VC8 (VS2005), MS changed the behaviour of the CRT is some security related and special situations.
|
||||
The CRT forces the call of the default-debugger (normally Dr.Watson) without informing the registered unhandled exception filter. The situations in
|
||||
which this happens are the following:
|
||||
|
||||
Calling abort if the abort-behaviour was set to _CALL_REPORTFAULT. This is the default setting for release builds!
|
||||
Failures detected by security checks (see also Compiler Security Checks In Depth ) This is also enabled by default!
|
||||
If no invalid parameter handler was defined (which is the default setting) and an invalid parameter was detected (this is done in many CRT functions
|
||||
by calling _invalid_parameter). So the conclusion is: The are many situations in which your user-defined Unhandled-Exception-Filter will never be
|
||||
called. This is a major change to the previous versions of the CRT and IMHO not very well documented.
|
||||
|
||||
The solution
|
||||
If you don<6F>t want this behavior and you will be sure that your handler will be called, you need to intercept the call to SetUnhandledExceptionFilter
|
||||
which is used by the CRT to disable all previously installed filters. You can achieve this for x86 with the following code:
|
||||
|
||||
*/
|
||||
#ifndef _M_IX86
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
BOOL PreventSetUnhandledExceptionFilter()
|
||||
{
|
||||
HMODULE hKernel32 = LoadLibrary("kernel32.dll");
|
||||
if (hKernel32 == NULL)
|
||||
return FALSE;
|
||||
void* pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
|
||||
if (pOrgEntry == NULL)
|
||||
return FALSE;
|
||||
unsigned char newJump[100];
|
||||
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
|
||||
dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
|
||||
void* pNewFunc = &MyDummySetUnhandledExceptionFilter;
|
||||
DWORD dwNewEntryAddr = (DWORD)pNewFunc;
|
||||
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
|
||||
|
||||
newJump[0] = 0xE9; // JMP absolute
|
||||
memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
|
||||
SIZE_T bytesWritten;
|
||||
BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);
|
||||
return bRet;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#define PROCESS_CALLBACK_FILTER_ENABLED 0x1
|
||||
|
||||
typedef BOOL(WINAPI* SetProcessUserModeExceptionPolicy)(__in DWORD dwFlags);
|
||||
typedef BOOL(WINAPI* GetProcessUserModeExceptionPolicy)(__out LPDWORD lpFlags);
|
||||
|
||||
void fixExceptionsThroughKernel()
|
||||
{
|
||||
// Try to make kernel not swallow exceptions on 64-bit os
|
||||
// http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/
|
||||
|
||||
HMODULE hKernelDll = LoadLibrary("kernel32.dll");
|
||||
if (hKernelDll)
|
||||
{
|
||||
SetProcessUserModeExceptionPolicy set = (SetProcessUserModeExceptionPolicy)GetProcAddress(hKernelDll, "SetProcessUserModeExceptionPolicy");
|
||||
GetProcessUserModeExceptionPolicy get = (GetProcessUserModeExceptionPolicy)GetProcAddress(hKernelDll, "GetProcessUserModeExceptionPolicy");
|
||||
|
||||
if (set != NULL && get != NULL)
|
||||
{
|
||||
FASTLOG(FLog::Crash, "Found Get/SetProcessUserModeExceptionPolicy");
|
||||
DWORD dwFlags;
|
||||
if (get(&dwFlags))
|
||||
{
|
||||
FASTLOG1(FLog::Crash, "UserMode Exception Flags: %u. Setting CALLBACK_FILER off", dwFlags);
|
||||
set(dwFlags & ~PROCESS_CALLBACK_FILTER_ENABLED); // turn off bit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReporter::Start()
|
||||
{
|
||||
SetUnhandledExceptionFilter(CrashExceptionFilter);
|
||||
#ifndef _M_IX86
|
||||
PreventSetUnhandledExceptionFilter();
|
||||
#endif
|
||||
|
||||
watcherThread.reset(new boost::thread(boost::bind(&CrashReporter::WatcherThreadFunc, CrashReporter::singleton)));
|
||||
}
|
||||
|
||||
|
||||
void CrashReporter::WatcherThreadFunc()
|
||||
{
|
||||
Aya::set_thread_name("CrashReporter_WatcherThreadFunc");
|
||||
|
||||
Aya::Log::current()->writeEntry(Aya::Log::Information, "WatcherThread Started");
|
||||
|
||||
bool quit = false;
|
||||
while (!quit && !destructing)
|
||||
{
|
||||
if (!reportCrashEvent.Wait(3 * 60 * 1000 /*3 minutes*/))
|
||||
{
|
||||
if (isAlive)
|
||||
{
|
||||
isAlive = false; // reset to test for next notify.
|
||||
}
|
||||
else if (hangReportingEnabled)
|
||||
{
|
||||
// not responsive!
|
||||
if (deadlockCounter++ == 0) // only report first hang in session.
|
||||
{
|
||||
/*
|
||||
logEvent("WatcherThread Detected hang.");
|
||||
FASTLOG(FLog::HangDetection, "WatcherThread Detected hang.");
|
||||
|
||||
Aya::TaskScheduler::singleton().printJobs();
|
||||
|
||||
LONG result;
|
||||
// Generate exception to get proper context in dump
|
||||
__try
|
||||
{
|
||||
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
|
||||
}
|
||||
__except (result = ProcessException(GetExceptionInformation(), true /*silent), EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
LaunchUploadProcess();
|
||||
}*/
|
||||
|
||||
// fuck up
|
||||
// random thread hangs every 5 minutes and it fucks everything up also theres a memory leak and shit
|
||||
// uhuahuhguhsudghsufhgUASHGHSDUGUSDUGHu
|
||||
|
||||
// _exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!destructing)
|
||||
{
|
||||
FASTLOG(FLog::Crash, "Processing non-hang");
|
||||
threadResult = ProcessException(exceptionInfo, false);
|
||||
quit = true;
|
||||
|
||||
LaunchUploadProcess();
|
||||
}
|
||||
}
|
||||
logEvent("WatcherThread Exiting.");
|
||||
}
|
||||
|
||||
void CrashReporter::EnableImmediateUpload(bool enabled)
|
||||
{
|
||||
immediateUploadEnabled = enabled;
|
||||
}
|
||||
|
||||
void CrashReporter::LaunchUploadProcess()
|
||||
{
|
||||
if (!immediateUploadEnabled)
|
||||
return;
|
||||
|
||||
// start new process to upload dmp
|
||||
char filename[MAX_PATH];
|
||||
DWORD size = GetModuleFileName(NULL, filename, MAX_PATH);
|
||||
if (size && !strstr(filename, "RCCService"))
|
||||
{
|
||||
FASTLOG(FLog::Crash, "Launching process to upload dmp.");
|
||||
STARTUPINFO si = {0};
|
||||
si.cb = sizeof(si);
|
||||
PROCESS_INFORMATION pi;
|
||||
char cmd[256];
|
||||
sprintf_s(cmd, 256, "\"%s\" -d", filename);
|
||||
::CreateProcess(NULL, cmd, NULL, NULL, false, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
|
||||
}
|
||||
}
|
||||
void CrashReporter::DisableHangReporting()
|
||||
{
|
||||
hangReportingEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
void CrashReporter::NotifyAlive()
|
||||
{
|
||||
FASTLOG(FLog::HangDetection, "Letting watcher thread know we're alive");
|
||||
hangReportingEnabled = true;
|
||||
isAlive = true;
|
||||
}
|
||||
77
engine/network/src/CrashReporter.hpp
Normal file
77
engine/network/src/CrashReporter.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef __CRASH_REPORTER_H
|
||||
#define __CRASH_REPORTER_H
|
||||
|
||||
#include <assert.h>
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "boost/thread.hpp"
|
||||
#include "CEvent.hpp"
|
||||
|
||||
|
||||
|
||||
/// Holds all the parameters to CrashReporter::Start
|
||||
struct CrashReportControls
|
||||
{
|
||||
// Used to generate the dump filename. Required with AOC_EMAIL_WITH_ATTACHMENT or AOC_WRITE_TO_DISK
|
||||
char appName[512];
|
||||
char appVersion[512];
|
||||
|
||||
// Used with AOC_WRITE_TO_DISK . Path to write to. Not the filename, just the path. Empty string means the current directory.
|
||||
char pathToMinidump[300];
|
||||
char crashExtention[64];
|
||||
|
||||
// How much memory to write. MiniDumpNormal is the least but doesn't seem to give correct globals. MiniDumpWithDataSegs gives more.
|
||||
int minidumpType;
|
||||
};
|
||||
|
||||
/// \brief On an unhandled exception, will save a minidump and email it.
|
||||
/// A minidump can be opened in visual studio to give the callstack and local variables at the time of the crash.
|
||||
/// It has the same amount of information as if you crashed while debugging in the relevant mode. So Debug tends to give
|
||||
/// accurate stacks and info while Release does not.
|
||||
///
|
||||
/// Minidumps are only accurate for the code as it was compiled at the date of the release. So you should label releases in source control
|
||||
/// and put that label number in the 'appVersion' field.
|
||||
|
||||
void fixExceptionsThroughKernel();
|
||||
|
||||
class CrashReporter
|
||||
{
|
||||
private:
|
||||
LONG threadResult;
|
||||
struct _EXCEPTION_POINTERS* exceptionInfo;
|
||||
Aya::CEvent reportCrashEvent;
|
||||
boost::scoped_ptr<boost::thread> watcherThread;
|
||||
bool hangReportingEnabled;
|
||||
bool isAlive;
|
||||
LONG deadlockCounter;
|
||||
bool destructing;
|
||||
bool immediateUploadEnabled;
|
||||
|
||||
void LaunchUploadProcess();
|
||||
|
||||
LONG ProcessExceptionHelper(struct _EXCEPTION_POINTERS* ExceptionInfo, bool writeFullDmp, bool noMsg, char* dumpFilepath);
|
||||
|
||||
protected:
|
||||
bool silentCrashReporting;
|
||||
virtual void logEvent(const char* msg) {};
|
||||
|
||||
public:
|
||||
static CrashReporter* singleton;
|
||||
CrashReportControls controls;
|
||||
CrashReporter();
|
||||
~CrashReporter();
|
||||
void Start();
|
||||
void WatcherThreadFunc();
|
||||
virtual LONG ProcessException(struct _EXCEPTION_POINTERS* ExceptionInfo, bool noMsg);
|
||||
LONG ProcessExceptionInThead(struct _EXCEPTION_POINTERS* ExceptionInfo);
|
||||
void TheadFunc(struct _EXCEPTION_POINTERS* ExceptionInfo);
|
||||
void DisableHangReporting();
|
||||
void EnableImmediateUpload(bool enabled);
|
||||
|
||||
// call every second or so from FG thread to signal responsive app.
|
||||
// must call at least once for hang reporting to be enabled.
|
||||
void NotifyAlive();
|
||||
|
||||
HRESULT GenerateDmpFileName(__out_ecount(cchdumpFilepath) char* dumpFilepath, int cchdumpFilepath, bool fastLog = false, bool fullDmp = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
64
engine/network/src/CrispProxy.hpp
Normal file
64
engine/network/src/CrispProxy.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/shared_ptr.hpp"
|
||||
#include "boost/thread.hpp"
|
||||
|
||||
namespace Crisp
|
||||
{
|
||||
namespace RMF3
|
||||
{
|
||||
typedef std::map<std::string, std::string> Data;
|
||||
}
|
||||
} // namespace Crisp
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// This very closely mirrors Crisp::RMF3::Response, except we expose the fields directly
|
||||
// and remove the async-specific bits.
|
||||
struct CrispResponse
|
||||
{
|
||||
const std::string id;
|
||||
const int result;
|
||||
const std::string originalContent;
|
||||
const std::string filteredContent;
|
||||
const int errorCode;
|
||||
const std::string errorDescription;
|
||||
|
||||
CrispResponse(const std::string& id_, int result_, const std::string& originalContent_, const std::string& filteredContent_, int errorCode_,
|
||||
const std::string& errorDescription_)
|
||||
: id(id_)
|
||||
, result(result_)
|
||||
, originalContent(originalContent_)
|
||||
, filteredContent(filteredContent_)
|
||||
, errorCode(errorCode_)
|
||||
, errorDescription(errorDescription_)
|
||||
{
|
||||
}
|
||||
|
||||
inline bool succeeded() const
|
||||
{
|
||||
return 0 == errorCode;
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<const CrispResponse> CrispResponsePtr;
|
||||
|
||||
// An interface of Crisp::RMF3 services. The intention is for subclasses
|
||||
// to implement the behavior so that not all projects need the crisprmf3.dll.
|
||||
class CrispProxy
|
||||
{
|
||||
public:
|
||||
typedef boost::function<void(CrispResponsePtr)> ResponseHandler;
|
||||
|
||||
virtual boost::thread checkContent(const std::string& id, const std::string& sender, const std::string& receiver, const std::string& content,
|
||||
const Crisp::RMF3::Data* pData, const std::string& policy, ResponseHandler& callback) const = 0;
|
||||
|
||||
virtual boost::thread sendEvent(const std::string& id, const std::string& sender, const std::string& receiver, const std::string& event,
|
||||
const Crisp::RMF3::Data* pData, ResponseHandler& callback) const = 0;
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
199
engine/network/src/DataBlockEncryptor.cpp
Normal file
199
engine/network/src/DataBlockEncryptor.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
// Roblox: Moved here from RakNet 3.x
|
||||
|
||||
/// \file
|
||||
///
|
||||
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
|
||||
///
|
||||
/// Usage of RakNet is subject to the appropriate license agreement.
|
||||
|
||||
|
||||
#include "DataBlockEncryptor.hpp"
|
||||
#include "RakNet/CheckSum.hpp"
|
||||
#include "RakNet/GetTime.hpp"
|
||||
#include "RakNet/Rand.hpp"
|
||||
#include "RakNet/RakAssert.hpp"
|
||||
#include <string.h>
|
||||
#include "Rijndael.hpp"
|
||||
// #include "Types.hpp"
|
||||
|
||||
DataBlockEncryptor::DataBlockEncryptor()
|
||||
{
|
||||
keySet = false;
|
||||
}
|
||||
|
||||
DataBlockEncryptor::~DataBlockEncryptor() {}
|
||||
|
||||
bool DataBlockEncryptor::IsKeySet(void) const
|
||||
{
|
||||
return keySet;
|
||||
}
|
||||
|
||||
void DataBlockEncryptor::SetKey(const unsigned char key[16])
|
||||
{
|
||||
keySet = true;
|
||||
// secretKeyAES128.set_key( key );
|
||||
makeKey(&keyEncrypt, DIR_ENCRYPT, 16, (char*)key);
|
||||
makeKey(&keyDecrypt, DIR_DECRYPT, 16, (char*)key);
|
||||
cipherInit(&cipherInst, MODE_ECB, 0); // ECB is not secure except that I chain manually farther down.
|
||||
}
|
||||
|
||||
void DataBlockEncryptor::UnsetKey(void)
|
||||
{
|
||||
keySet = false;
|
||||
}
|
||||
|
||||
void DataBlockEncryptor::Encrypt(
|
||||
unsigned char* input, unsigned int inputLength, unsigned char* output, unsigned int* outputLength, RakNet::RakNetRandom* rnr)
|
||||
{
|
||||
unsigned index, byteIndex, lastBlock;
|
||||
unsigned int checkSum;
|
||||
unsigned char paddingBytes;
|
||||
unsigned char encodedPad;
|
||||
unsigned char randomChar;
|
||||
CheckSum checkSumCalculator;
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
RakAssert(keySet);
|
||||
#endif
|
||||
|
||||
RakAssert(input && inputLength);
|
||||
|
||||
|
||||
// randomChar will randomize the data so the same data sent twice will not look the same
|
||||
randomChar = (unsigned char)rnr->RandomMT();
|
||||
|
||||
// 16-(((x-1) % 16)+1)
|
||||
|
||||
// # of padding bytes is 16 -(((input_length + extra_data -1) % 16)+1)
|
||||
paddingBytes = (unsigned char)(16 - (((inputLength + sizeof(randomChar) + sizeof(checkSum) + sizeof(encodedPad) - 1) % 16) + 1));
|
||||
|
||||
// Randomize the pad size variable
|
||||
encodedPad = (unsigned char)rnr->RandomMT();
|
||||
encodedPad <<= 4;
|
||||
encodedPad |= paddingBytes;
|
||||
|
||||
*outputLength = inputLength + sizeof(randomChar) + sizeof(checkSum) + sizeof(encodedPad) + paddingBytes;
|
||||
|
||||
// Write the data first, in case we are overwriting ourselves
|
||||
|
||||
if (input == output)
|
||||
memmove(output + sizeof(checkSum) + sizeof(randomChar) + sizeof(encodedPad) + paddingBytes, input, inputLength);
|
||||
else
|
||||
memcpy(output + sizeof(checkSum) + sizeof(randomChar) + sizeof(encodedPad) + paddingBytes, input, inputLength);
|
||||
|
||||
// Write the random char
|
||||
memcpy(output + sizeof(checkSum), (char*)&randomChar, sizeof(randomChar));
|
||||
|
||||
// Write the pad size variable
|
||||
memcpy(output + sizeof(checkSum) + sizeof(randomChar), (char*)&encodedPad, sizeof(encodedPad));
|
||||
|
||||
// Write the padding
|
||||
for (index = 0; index < paddingBytes; index++)
|
||||
*(output + sizeof(checkSum) + sizeof(randomChar) + sizeof(encodedPad) + index) = (unsigned char)rnr->RandomMT();
|
||||
|
||||
// Calculate the checksum on the data
|
||||
checkSumCalculator.Add(output + sizeof(checkSum), inputLength + sizeof(randomChar) + sizeof(encodedPad) + paddingBytes);
|
||||
|
||||
checkSum = checkSumCalculator.Get();
|
||||
|
||||
// Write checksum
|
||||
#ifdef HOST_ENDIAN_IS_BIG
|
||||
output[0] = checkSum & 0xFF;
|
||||
output[1] = (checkSum >> 8) & 0xFF;
|
||||
output[2] = (checkSum >> 16) & 0xFF;
|
||||
output[3] = (checkSum >> 24) & 0xFF;
|
||||
#else
|
||||
memcpy(output, (char*)&checkSum, sizeof(checkSum));
|
||||
#endif
|
||||
|
||||
// AES on the first block
|
||||
// secretKeyAES128.encrypt16( output );
|
||||
blockEncrypt(&cipherInst, &keyEncrypt, output, 16, output);
|
||||
|
||||
lastBlock = 0;
|
||||
|
||||
// Now do AES on every other block from back to front
|
||||
for (index = *outputLength - 16; index >= 16; index -= 16)
|
||||
{
|
||||
for (byteIndex = 0; byteIndex < 16; byteIndex++)
|
||||
output[index + byteIndex] ^= output[lastBlock + byteIndex];
|
||||
|
||||
// secretKeyAES128.encrypt16( output + index );
|
||||
blockEncrypt(&cipherInst, &keyEncrypt, output + index, 16, output + index);
|
||||
|
||||
lastBlock = index;
|
||||
}
|
||||
}
|
||||
|
||||
bool DataBlockEncryptor::Decrypt(unsigned char* input, unsigned int inputLength, unsigned char* output, unsigned int* outputLength)
|
||||
{
|
||||
unsigned index, byteIndex;
|
||||
unsigned int checkSum;
|
||||
unsigned char paddingBytes;
|
||||
unsigned char encodedPad;
|
||||
// NA: 2/26/2013 Remove warning for VS2012
|
||||
// unsigned char randomChar;
|
||||
CheckSum checkSumCalculator;
|
||||
#ifdef _DEBUG
|
||||
|
||||
RakAssert(keySet);
|
||||
#endif
|
||||
|
||||
if (input == 0 || inputLength < 16 || (inputLength % 16) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unchain in reverse order
|
||||
for (index = 16; index <= inputLength - 16; index += 16)
|
||||
{
|
||||
// secretKeyAES128.decrypt16( input + index );
|
||||
blockDecrypt(&cipherInst, &keyDecrypt, input + index, 16, output + index);
|
||||
|
||||
for (byteIndex = 0; byteIndex < 16; byteIndex++)
|
||||
{
|
||||
if (index + 16 == (unsigned)inputLength)
|
||||
output[index + byteIndex] ^= input[byteIndex];
|
||||
else
|
||||
output[index + byteIndex] ^= input[index + 16 + byteIndex];
|
||||
}
|
||||
|
||||
// lastBlock = index;
|
||||
};
|
||||
|
||||
// Decrypt the first block
|
||||
// secretKeyAES128.decrypt16( input );
|
||||
blockDecrypt(&cipherInst, &keyDecrypt, input, 16, output);
|
||||
|
||||
// Read checksum
|
||||
#ifdef HOST_ENDIAN_IS_BIG
|
||||
checkSum = (unsigned int)output[0] | (unsigned int)(output[1] << 8) | (unsigned int)(output[2] << 16) | (unsigned int)(output[3] << 24);
|
||||
#else
|
||||
memcpy((char*)&checkSum, output, sizeof(checkSum));
|
||||
#endif
|
||||
|
||||
// Read the pad size variable
|
||||
memcpy((char*)&encodedPad, output + sizeof(unsigned char) + sizeof(checkSum), sizeof(encodedPad));
|
||||
|
||||
// Ignore the high 4 bytes
|
||||
paddingBytes = encodedPad & 0x0F;
|
||||
|
||||
|
||||
// Get the data length
|
||||
*outputLength = inputLength - sizeof(unsigned char) - sizeof(checkSum) - sizeof(encodedPad) - paddingBytes;
|
||||
|
||||
// Calculate the checksum on the data.
|
||||
checkSumCalculator.Add(output + sizeof(checkSum), *outputLength + sizeof(unsigned char) + sizeof(encodedPad) + paddingBytes);
|
||||
|
||||
if (checkSum != checkSumCalculator.Get())
|
||||
return false;
|
||||
|
||||
// Read the data
|
||||
// if ( input == output )
|
||||
memmove(output, output + sizeof(unsigned char) + sizeof(checkSum) + sizeof(encodedPad) + paddingBytes, *outputLength);
|
||||
// else
|
||||
// memcpy( output, input + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes, *outputLength );
|
||||
|
||||
return true;
|
||||
}
|
||||
71
engine/network/src/DataBlockEncryptor.hpp
Normal file
71
engine/network/src/DataBlockEncryptor.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// Roblox: Moved here from RakNet 3.x
|
||||
|
||||
/// \file DataBlockEncryptor.h
|
||||
/// \internal
|
||||
/// \brief Encrypts and decrypts data blocks. Used as part of secure connections.
|
||||
///
|
||||
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
|
||||
///
|
||||
/// Usage of RakNet is subject to the appropriate license agreement.
|
||||
|
||||
|
||||
#ifndef __DATA_BLOCK_ENCRYPTOR_H
|
||||
#define __DATA_BLOCK_ENCRYPTOR_H
|
||||
|
||||
#include "Rijndael.hpp"
|
||||
#include "RakNet/RakMemoryOverride.hpp"
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class RakNetRandom;
|
||||
}
|
||||
|
||||
|
||||
/// Encrypts and decrypts data blocks.
|
||||
class DataBlockEncryptor
|
||||
{
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
DataBlockEncryptor();
|
||||
|
||||
// Destructor
|
||||
~DataBlockEncryptor();
|
||||
|
||||
/// \return true if SetKey has been called previously
|
||||
bool IsKeySet(void) const;
|
||||
|
||||
/// \brief Set the encryption key
|
||||
/// \param[in] key The new encryption key
|
||||
void SetKey(const unsigned char key[16]);
|
||||
|
||||
/// \brief Unset the encryption key
|
||||
void UnsetKey(void);
|
||||
|
||||
/// \brief Encryption adds 6 data bytes and then pads the number of bytes to be a multiple of 16.
|
||||
/// \details Output should be large enough to hold this.
|
||||
/// Output can be the same memory block as input
|
||||
/// \param[in] input the input buffer to encrypt
|
||||
/// \param[in] inputLength the size of the @em input buffer
|
||||
/// \param[in] output the output buffer to store encrypted data
|
||||
/// \param[in] outputLength the size of the output buffer
|
||||
void Encrypt(unsigned char* input, unsigned int inputLength, unsigned char* output, unsigned int* outputLength, RakNet::RakNetRandom* rnr);
|
||||
|
||||
/// \brief Decryption removes bytes, as few as 6.
|
||||
/// \details Output should be large enough to hold this.
|
||||
/// Output can be the same memory block as input
|
||||
/// \param[in] input the input buffer to decrypt
|
||||
/// \param[in] inputLength the size of the @em input buffer
|
||||
/// \param[in] output the output buffer to store decrypted data
|
||||
/// \param[in] outputLength the size of the @em output buffer
|
||||
/// \return False on bad checksum or input, true on success
|
||||
bool Decrypt(unsigned char* input, unsigned int inputLength, unsigned char* output, unsigned int* outputLength);
|
||||
|
||||
protected:
|
||||
keyInstance keyEncrypt;
|
||||
keyInstance keyDecrypt;
|
||||
cipherInstance cipherInst;
|
||||
bool keySet;
|
||||
};
|
||||
|
||||
#endif
|
||||
213
engine/network/src/Dictionary.cpp
Normal file
213
engine/network/src/Dictionary.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
|
||||
|
||||
#include "Dictionary.hpp"
|
||||
#include "RakNet/StringCompressor.hpp"
|
||||
#include "Streaming.hpp"
|
||||
|
||||
#include <boost/functional/hash/hash.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
using namespace Reflection;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
template<>
|
||||
bool SenderDictionary<std::string>::isDefaultValue(const std::string& value)
|
||||
{
|
||||
return value.empty();
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SenderDictionary<BinaryString>::isDefaultValue(const BinaryString& value)
|
||||
{
|
||||
return value.value().empty();
|
||||
}
|
||||
|
||||
void SenderDictionary<const Aya::Name*>::send(RakNet::BitStream& stream, const Aya::Name* value)
|
||||
{
|
||||
if (isDefaultValue(value->toString()))
|
||||
{
|
||||
// id==0 is reserved for the empty item
|
||||
unsigned char id = 0;
|
||||
stream.Write(id);
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<Dictionary::iterator, bool> pair = dictionary.insert(Dictionary::value_type(value, nextIndex));
|
||||
if (!pair.second)
|
||||
{
|
||||
// The item already exists in the dictionary. Simply send the current ID
|
||||
unsigned char id = pair.first->second;
|
||||
stream.Write(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the pre-existing entry from dictionary
|
||||
dictionary.erase(items[nextIndex]);
|
||||
|
||||
// Put the new entry into the item table
|
||||
items[nextIndex] = const_cast<Aya::Name*>(value);
|
||||
|
||||
// Send the id with 0x80 bit set to indicate this is a new item
|
||||
unsigned char id = nextIndex | 0x80;
|
||||
stream.Write(id); // TODO: We could get away with just sending a single bit. The receiver knows what the new index should be
|
||||
stream << value->toString();
|
||||
|
||||
// Advance nextIndex == [1..127]
|
||||
nextIndex %= (DICTIONARY_SIZE - 1);
|
||||
++nextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void ReceiverDictionary<std::string>::setDefault(std::string& value)
|
||||
{
|
||||
value.clear();
|
||||
}
|
||||
|
||||
template<>
|
||||
void ReceiverDictionary<BinaryString>::setDefault(BinaryString& value)
|
||||
{
|
||||
value = BinaryString();
|
||||
}
|
||||
|
||||
void ReceiverStringDictionary::setDefault(std::string& value)
|
||||
{
|
||||
value.clear();
|
||||
}
|
||||
|
||||
void ReceiverStringDictionary::learn(unsigned char id, const std::string& value)
|
||||
{
|
||||
dictionary[id] = value;
|
||||
if (protection)
|
||||
{
|
||||
if (!hashTable.get())
|
||||
{
|
||||
hashTable.reset(new std::size_t[DICTIONARY_SIZE]);
|
||||
}
|
||||
boost::hash<std::string> stringHash;
|
||||
hashTable.get()[id] = stringHash(std::string("a") + value + std::string("s"));
|
||||
}
|
||||
}
|
||||
bool ReceiverStringDictionary::get(unsigned char id, std::string& value)
|
||||
{
|
||||
value = dictionary[id];
|
||||
|
||||
if (protection)
|
||||
{
|
||||
AYAASSERT(hashTable.get());
|
||||
boost::hash<std::string> stringHash;
|
||||
if (stringHash(std::string("a") + value + std::string("s")) != hashTable.get()[id])
|
||||
{
|
||||
// Someone has been messing with our content... death to the infidels
|
||||
value.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// It's all good
|
||||
return true;
|
||||
}
|
||||
|
||||
void SharedStringDictionary::serializeString(const std::string& stringValue, RakNet::BitStream& bitStream)
|
||||
{
|
||||
SenderDictionary<std::string>::send(bitStream, stringValue);
|
||||
}
|
||||
|
||||
void SharedStringDictionary::serializeString(const ConstProperty& property, RakNet::BitStream& bitStream)
|
||||
{
|
||||
const PropertyDescriptor& desc = property.getDescriptor();
|
||||
std::string value = desc.getStringValue(property.getInstance());
|
||||
|
||||
serializeString(value, bitStream);
|
||||
}
|
||||
|
||||
void SharedStringDictionary::deserializeString(std::string& valueString, RakNet::BitStream& bitStream)
|
||||
{
|
||||
ReceiverDictionary<std::string>::receive(bitStream, valueString);
|
||||
}
|
||||
|
||||
void SharedStringDictionary::deserializeString(Property& property, RakNet::BitStream& bitStream)
|
||||
{
|
||||
std::string value;
|
||||
deserializeString(value, bitStream);
|
||||
|
||||
if (property.getInstance())
|
||||
{
|
||||
const PropertyDescriptor& desc = property.getDescriptor();
|
||||
desc.setStringValue(property.getInstance(), value);
|
||||
}
|
||||
}
|
||||
|
||||
SharedStringProtectedDictionary::SharedStringProtectedDictionary(bool protection)
|
||||
: ReceiverStringDictionary(protection)
|
||||
{
|
||||
}
|
||||
|
||||
void SharedStringProtectedDictionary::serializeString(const std::string& stringValue, RakNet::BitStream& bitStream)
|
||||
{
|
||||
SenderDictionary<std::string>::send(bitStream, stringValue);
|
||||
}
|
||||
|
||||
void SharedStringProtectedDictionary::serializeString(const ConstProperty& property, RakNet::BitStream& bitStream)
|
||||
{
|
||||
const PropertyDescriptor& desc = property.getDescriptor();
|
||||
std::string value = desc.getStringValue(property.getInstance());
|
||||
|
||||
serializeString(value, bitStream);
|
||||
}
|
||||
|
||||
bool SharedStringProtectedDictionary::deserializeString(std::string& valueString, RakNet::BitStream& bitStream)
|
||||
{
|
||||
return ReceiverStringDictionary::receive(bitStream, valueString);
|
||||
}
|
||||
|
||||
bool SharedStringProtectedDictionary::deserializeString(Property& property, RakNet::BitStream& bitStream)
|
||||
{
|
||||
std::string value;
|
||||
if (!deserializeString(value, bitStream))
|
||||
{
|
||||
// They were cheating
|
||||
return false;
|
||||
}
|
||||
|
||||
if (property.getInstance())
|
||||
{
|
||||
const PropertyDescriptor& desc = property.getDescriptor();
|
||||
desc.setStringValue(property.getInstance(), value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SharedBinaryStringDictionary::serializeString(const BinaryString& stringValue, RakNet::BitStream& bitStream)
|
||||
{
|
||||
SenderDictionary<BinaryString>::send(bitStream, stringValue);
|
||||
}
|
||||
|
||||
void SharedBinaryStringDictionary::serializeString(const ConstProperty& property, RakNet::BitStream& bitStream)
|
||||
{
|
||||
BinaryString value = property.getValue<BinaryString>();
|
||||
|
||||
serializeString(value, bitStream);
|
||||
}
|
||||
|
||||
void SharedBinaryStringDictionary::deserializeString(BinaryString& valueString, RakNet::BitStream& bitStream)
|
||||
{
|
||||
ReceiverDictionary<BinaryString>::receive(bitStream, valueString);
|
||||
}
|
||||
|
||||
void SharedBinaryStringDictionary::deserializeString(Property& property, RakNet::BitStream& bitStream)
|
||||
{
|
||||
BinaryString value;
|
||||
deserializeString(value, bitStream);
|
||||
|
||||
if (property.getInstance())
|
||||
property.setValue<BinaryString>(value);
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
395
engine/network/src/Dictionary.hpp
Normal file
395
engine/network/src/Dictionary.hpp
Normal file
@@ -0,0 +1,395 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "RakNet/BitStream.hpp"
|
||||
#include "boost/noncopyable.hpp"
|
||||
#include "Reflection/Property.hpp"
|
||||
#include "Utility/Guid.hpp"
|
||||
|
||||
#include "Utility/BinaryString.hpp"
|
||||
|
||||
#include "StreamingUtil.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
// Must be a power of 2
|
||||
#define DICTIONARY_SIZE 128
|
||||
|
||||
// A very simple dictionary of up to 127 items.
|
||||
// Sending the same item a 2nd time costs only 8 bits.
|
||||
// The algorithm is extremely simple - there's no fancy cache expiration.
|
||||
// When we run out of dictionary space we simply replace the oldest item
|
||||
// first. (It's a cyclic buffer)
|
||||
// To use, pair a SenderDictionary with a ReceiverDictionary.
|
||||
template<class T>
|
||||
class SenderDictionary
|
||||
{
|
||||
typedef std::map<T, unsigned char> Dictionary;
|
||||
Dictionary dictionary; // item --> id
|
||||
T items[DICTIONARY_SIZE]; // id --> item
|
||||
int nextIndex;
|
||||
static bool isDefaultValue(const T& value);
|
||||
|
||||
public:
|
||||
SenderDictionary()
|
||||
: nextIndex(1)
|
||||
{
|
||||
}
|
||||
bool canSend(const T& value)
|
||||
{
|
||||
typename Dictionary::const_iterator iter = dictionary.find(value);
|
||||
return (iter != dictionary.end());
|
||||
}
|
||||
bool trySend(RakNet::BitStream& stream, const T& value)
|
||||
{
|
||||
if (isDefaultValue(value))
|
||||
{
|
||||
// id==0 is reserved for the empty item
|
||||
unsigned char id = 0;
|
||||
stream.Write(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
typename Dictionary::const_iterator iter = dictionary.find(value);
|
||||
if (iter == dictionary.end())
|
||||
return false;
|
||||
|
||||
// The item already exists in the dictionary. Simply send the ID
|
||||
unsigned char id = iter->second;
|
||||
AYAASSERT((id & 0x80) == 0);
|
||||
AYAASSERT((id & 0x7F) < DICTIONARY_SIZE);
|
||||
|
||||
stream.Write(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void send(RakNet::BitStream& stream, const T& value)
|
||||
{
|
||||
if (isDefaultValue(value))
|
||||
{
|
||||
// id==0 is reserved for the empty item
|
||||
unsigned char id = 0;
|
||||
stream.Write(id);
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<typename Dictionary::iterator, bool> pair = dictionary.insert(typename Dictionary::value_type(value, nextIndex));
|
||||
if (!pair.second)
|
||||
{
|
||||
// The item already exists in the dictionary. Simply send the current ID
|
||||
unsigned char id = pair.first->second;
|
||||
stream.Write(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the pre-existing entry from dictionary
|
||||
dictionary.erase(items[nextIndex]);
|
||||
|
||||
// Put the new entry into the item table
|
||||
items[nextIndex] = value;
|
||||
|
||||
// Send the id with 0x80 bit set to indicate this is a new item
|
||||
unsigned char id = nextIndex | 0x80;
|
||||
stream.Write(id); // TODO: We could get away with just sending a single bit. The receiver knows what the new index should be
|
||||
stream << value;
|
||||
|
||||
// Advance nextIndex == [1..127]
|
||||
nextIndex %= (DICTIONARY_SIZE - 1);
|
||||
++nextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void sendEmptyItem(RakNet::BitStream& stream)
|
||||
{
|
||||
// id==0 is reserved for the empty item
|
||||
unsigned char id = 0;
|
||||
stream.Write(id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class SenderDictionary<const Aya::Name*>
|
||||
{
|
||||
typedef boost::unordered_map<const Aya::Name*, unsigned char> Dictionary;
|
||||
Dictionary dictionary; // item --> id
|
||||
Aya::Name* items[DICTIONARY_SIZE]; // id --> item
|
||||
int nextIndex;
|
||||
static bool isDefaultValue(const std::string& value)
|
||||
{
|
||||
return value.empty();
|
||||
}
|
||||
|
||||
public:
|
||||
SenderDictionary()
|
||||
: nextIndex(1)
|
||||
{
|
||||
}
|
||||
bool canSend(const Aya::Name* value)
|
||||
{
|
||||
Dictionary::const_iterator iter = dictionary.find(value);
|
||||
return (iter != dictionary.end());
|
||||
}
|
||||
bool trySend(RakNet::BitStream& stream, const Aya::Name* value)
|
||||
{
|
||||
if (isDefaultValue(value->toString()))
|
||||
{
|
||||
// id==0 is reserved for the empty item
|
||||
unsigned char id = 0;
|
||||
stream.Write(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
Dictionary::const_iterator iter = dictionary.find(value);
|
||||
if (iter == dictionary.end())
|
||||
return false;
|
||||
|
||||
// The item already exists in the dictionary. Simply send the ID
|
||||
unsigned char id = iter->second;
|
||||
AYAASSERT((id & 0x80) == 0);
|
||||
AYAASSERT((id & 0x7F) < DICTIONARY_SIZE);
|
||||
|
||||
stream.Write(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void send(RakNet::BitStream& stream, const Aya::Name* value);
|
||||
|
||||
void sendEmptyItem(RakNet::BitStream& stream)
|
||||
{
|
||||
// id==0 is reserved for the empty item
|
||||
unsigned char id = 0;
|
||||
stream.Write(id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class ReceiverDictionary
|
||||
{
|
||||
T dictionary[DICTIONARY_SIZE];
|
||||
static void setDefault(T& t);
|
||||
|
||||
public:
|
||||
/*void skip(RakNet::BitStream& stream)
|
||||
{
|
||||
unsigned char id;
|
||||
stream.Read(id);
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
}
|
||||
else if (id & 0x80)
|
||||
{
|
||||
T value;
|
||||
// The item is not in the dictionary
|
||||
stream >> value;
|
||||
}
|
||||
}*/
|
||||
// d9mz - this is a hack to get around a compiler bug in VS2005
|
||||
// (BUT WE'RE NOT USING VS2005 ARE WE)
|
||||
// error: '#pragma optimize' can only appear at file scope
|
||||
// #pragma optimize( "", off )
|
||||
void learn(unsigned char id, const T& value)
|
||||
{
|
||||
dictionary[id] = value;
|
||||
}
|
||||
bool get(unsigned char id, T& value)
|
||||
{
|
||||
value = dictionary[id];
|
||||
return true;
|
||||
}
|
||||
bool receive(RakNet::BitStream& stream, T& value)
|
||||
{
|
||||
unsigned char id;
|
||||
stream.Read(id);
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
setDefault(value);
|
||||
return true;
|
||||
}
|
||||
else if (id & 0x80)
|
||||
{
|
||||
// The item is not in the dictionary
|
||||
stream >> value;
|
||||
learn(id & 0x7F, value);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the item is in the dictionary
|
||||
return get(id, value);
|
||||
}
|
||||
}
|
||||
// d9mz - this is a hack to get around a compiler bug in VS2005
|
||||
// (BUT WE'RE NOT USING VS2005 ARE WE)
|
||||
// error: '#pragma optimize' can only appear at file scope
|
||||
// #pragma optimize( "", on )
|
||||
};
|
||||
|
||||
class ReceiverStringDictionary
|
||||
{
|
||||
std::string dictionary[DICTIONARY_SIZE];
|
||||
std::auto_ptr<std::size_t> hashTable;
|
||||
static void setDefault(std::string& t);
|
||||
|
||||
bool protection;
|
||||
|
||||
public:
|
||||
ReceiverStringDictionary(bool protection)
|
||||
: protection(protection)
|
||||
{
|
||||
}
|
||||
/*void skip(RakNet::BitStream& stream)
|
||||
{
|
||||
unsigned char id;
|
||||
stream.Read(id);
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
}
|
||||
else if (id & 0x80)
|
||||
{
|
||||
T value;
|
||||
// The item is not in the dictionary
|
||||
stream >> value;
|
||||
}
|
||||
}*/
|
||||
// d9mz - this is a hack to get around a compiler bug in VS2005
|
||||
// (BUT WE'RE NOT USING VS2005 ARE WE)
|
||||
// error: '#pragma optimize' can only appear at file scope
|
||||
// #pragma optimize( "", off )
|
||||
void learn(unsigned char id, const std::string& value);
|
||||
bool get(unsigned char id, std::string& value);
|
||||
template<class T>
|
||||
bool receive(RakNet::BitStream& stream, T& value)
|
||||
{
|
||||
unsigned char id;
|
||||
stream.Read(id);
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
setDefault(value);
|
||||
return true;
|
||||
}
|
||||
else if (id & 0x80)
|
||||
{
|
||||
// The item is not in the dictionary
|
||||
stream >> value;
|
||||
learn(id & 0x7F, value);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the item is in the dictionary
|
||||
return get(id, value);
|
||||
}
|
||||
}
|
||||
// d9mz - this is a hack to get around a compiler bug in VS2005
|
||||
// (BUT WE'RE NOT USING VS2005 ARE WE)
|
||||
// error: '#pragma optimize' can only appear at file scope
|
||||
// #pragma optimize( "", on )
|
||||
};
|
||||
|
||||
|
||||
template<class T>
|
||||
class SharedDictionary
|
||||
: public SenderDictionary<T>
|
||||
, public ReceiverDictionary<T>
|
||||
, boost::noncopyable
|
||||
{
|
||||
};
|
||||
|
||||
class SharedStringDictionary
|
||||
: public SenderDictionary<std::string>
|
||||
, public ReceiverDictionary<std::string>
|
||||
, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
void serializeString(const std::string& value, RakNet::BitStream& bitStream);
|
||||
void serializeString(const Reflection::ConstProperty& property, RakNet::BitStream& bitStream);
|
||||
void send(RakNet::BitStream& stream, const Aya::Name& value)
|
||||
{
|
||||
SenderDictionary<std::string>::send(stream, value.toString());
|
||||
}
|
||||
void send(RakNet::BitStream& stream, const char* value)
|
||||
{
|
||||
SenderDictionary<std::string>::send(stream, std::string(value));
|
||||
}
|
||||
bool trySend(RakNet::BitStream& stream, const Aya::Name& value)
|
||||
{
|
||||
return SenderDictionary<std::string>::trySend(stream, value.toString());
|
||||
}
|
||||
bool trySend(RakNet::BitStream& stream, const char* value)
|
||||
{
|
||||
return SenderDictionary<std::string>::trySend(stream, std::string(value));
|
||||
}
|
||||
void deserializeString(std::string& value, RakNet::BitStream& bitStream);
|
||||
void deserializeString(Reflection::Property& property, RakNet::BitStream& bitStream);
|
||||
void receive(RakNet::BitStream& stream, const Aya::Name*& value)
|
||||
{
|
||||
std::string s;
|
||||
ReceiverDictionary<std::string>::receive(stream, s);
|
||||
value = &Aya::Name::declare(s.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class SharedStringProtectedDictionary
|
||||
: public SenderDictionary<std::string>
|
||||
, public ReceiverStringDictionary
|
||||
, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
SharedStringProtectedDictionary(bool protection);
|
||||
void serializeString(const std::string& value, RakNet::BitStream& bitStream);
|
||||
void serializeString(const Reflection::ConstProperty& property, RakNet::BitStream& bitStream);
|
||||
void send(RakNet::BitStream& stream, const Aya::Name& value)
|
||||
{
|
||||
SenderDictionary<std::string>::send(stream, value.toString());
|
||||
}
|
||||
void send(RakNet::BitStream& stream, const char* value)
|
||||
{
|
||||
SenderDictionary<std::string>::send(stream, std::string(value));
|
||||
}
|
||||
bool trySend(RakNet::BitStream& stream, const Aya::Name& value)
|
||||
{
|
||||
return SenderDictionary<std::string>::trySend(stream, value.toString());
|
||||
}
|
||||
bool trySend(RakNet::BitStream& stream, const char* value)
|
||||
{
|
||||
return SenderDictionary<std::string>::trySend(stream, std::string(value));
|
||||
}
|
||||
bool deserializeString(std::string& value, RakNet::BitStream& bitStream);
|
||||
bool deserializeString(Reflection::Property& property, RakNet::BitStream& bitStream);
|
||||
void receive(RakNet::BitStream& stream, const Aya::Name*& value)
|
||||
{
|
||||
std::string s;
|
||||
ReceiverStringDictionary::receive(stream, s);
|
||||
// TODO: If there's a bug, then this could grow the Aya::Name database in
|
||||
// and unbounded fashion. Use lookup() instead
|
||||
value = &Aya::Name::declare(s.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
class SharedBinaryStringDictionary
|
||||
: public SenderDictionary<BinaryString>
|
||||
, public ReceiverDictionary<BinaryString>
|
||||
, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
void serializeString(const BinaryString& value, RakNet::BitStream& bitStream);
|
||||
void serializeString(const Reflection::ConstProperty& property, RakNet::BitStream& bitStream);
|
||||
|
||||
void deserializeString(BinaryString& value, RakNet::BitStream& bitStream);
|
||||
void deserializeString(Reflection::Property& property, RakNet::BitStream& bitStream);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
188
engine/network/src/DirectPhysicsReceiver.cpp
Normal file
188
engine/network/src/DirectPhysicsReceiver.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
|
||||
#include "DirectPhysicsReceiver.hpp"
|
||||
#include "Compressor.hpp"
|
||||
#include "RakNet/GetTime.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
|
||||
#include "Streaming.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <boost/type_traits.hpp>
|
||||
#include "NetworkProfiler.hpp"
|
||||
|
||||
DYNAMIC_FASTFLAG(PhysicsSenderUseOwnerTimestamp)
|
||||
DYNAMIC_FASTFLAG(CleanUpInterpolationTimestamps)
|
||||
SYNCHRONIZED_FASTFLAG(PhysicsPacketSendWorldStepTimestamp)
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
void DirectPhysicsReceiver::receivePacket(RakNet::BitStream& inBitstream, RakNet::Time timeStamp, ReplicatorStats::PhysicsReceiverStats* _stats)
|
||||
{
|
||||
RakNet::Time interpolationTimestamp;
|
||||
Aya::RemoteTime remoteSendTime;
|
||||
if (SFFlag::getPhysicsPacketSendWorldStepTimestamp())
|
||||
{
|
||||
inBitstream >> interpolationTimestamp;
|
||||
remoteSendTime = ((double)interpolationTimestamp / 1000.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteSendTime = ((double)timeStamp / 1000.0f);
|
||||
}
|
||||
|
||||
stats = _stats;
|
||||
|
||||
RakNet::Time now = RakNet::GetTime();
|
||||
|
||||
// AYAASSERT(now >= timeStamp); // Defect filed: DE6810
|
||||
if (!SFFlag::getPhysicsPacketSendWorldStepTimestamp() && DFFlag::PhysicsSenderUseOwnerTimestamp && timeStamp > now)
|
||||
{
|
||||
timeStamp = now;
|
||||
}
|
||||
|
||||
RakNet::Time deltaTime = SFFlag::getPhysicsPacketSendWorldStepTimestamp() ? now - interpolationTimestamp : now - timeStamp; //
|
||||
|
||||
// local Aya time when the packet was sent
|
||||
Time localTime = replicator->raknetTimeToRbxTime(timeStamp);
|
||||
|
||||
while (true)
|
||||
{
|
||||
shared_ptr<PartInstance> part;
|
||||
|
||||
if (replicator->isStreamingEnabled())
|
||||
{
|
||||
bool done;
|
||||
inBitstream >> done;
|
||||
if (done)
|
||||
break; // packet ended
|
||||
|
||||
bool cframeOnly;
|
||||
inBitstream >> cframeOnly;
|
||||
if (cframeOnly)
|
||||
{
|
||||
receiveMechanismCFrames(inBitstream, timeStamp, remoteSendTime);
|
||||
continue;
|
||||
}
|
||||
else if (!receiveRootPart(part, inBitstream))
|
||||
{
|
||||
continue; // mechanism ended
|
||||
}
|
||||
}
|
||||
else // non-streaming
|
||||
{
|
||||
if (!receiveRootPart(part, inBitstream))
|
||||
{
|
||||
break; // packet ended
|
||||
}
|
||||
BOOST_STATIC_ASSERT(sizeof(RakNet::Time) == sizeof(part->raknetTime));
|
||||
}
|
||||
|
||||
// read mechanism if non-streaming OR (streaming and not cframe only)
|
||||
if (part)
|
||||
{
|
||||
if (!iAmServer && localTime < part->getLastUpdateTime())
|
||||
{
|
||||
// old packet, discard
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->print(Aya::MESSAGE_INFO, "Discard old packet");
|
||||
}
|
||||
part.reset();
|
||||
}
|
||||
|
||||
if (part)
|
||||
{
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
RakNet::Time& partTime = part->raknetTime;
|
||||
if (partTime > (SFFlag::getPhysicsPacketSendWorldStepTimestamp() ? interpolationTimestamp : timeStamp))
|
||||
{
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->print(Aya::MESSAGE_INFO, "Physics-in old packet");
|
||||
}
|
||||
part.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SFFlag::getPhysicsPacketSendWorldStepTimestamp())
|
||||
{
|
||||
// RakNetTime will no longer hold some arbitrary send time, but instead
|
||||
// adjusted Physics Timestamp
|
||||
partTime = interpolationTimestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
partTime = timeStamp;
|
||||
}
|
||||
}
|
||||
#else
|
||||
RakNet::Time* partTime = &(part->raknetTime);
|
||||
if (*partTime > (SFFlag::getPhysicsPacketSendWorldStepTimestamp() ? interpolationTimestamp : timeStamp))
|
||||
{
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->print(Aya::MESSAGE_INFO, "Physics-in old packet");
|
||||
}
|
||||
part.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SFFlag::getPhysicsPacketSendWorldStepTimestamp())
|
||||
{
|
||||
// RakNetTime will no longer hold some arbitrary send time, but instead
|
||||
// adjusted Physics Timestamp
|
||||
*partTime = interpolationTimestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
*partTime = timeStamp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
RakNet::BitSize_t characterBits = inBitstream.GetReadOffset();
|
||||
int numNodesInHistory;
|
||||
receiveMechanism(inBitstream, part.get(), tempItem, remoteSendTime, numNodesInHistory);
|
||||
if (stats && part && "Torso" == part->getName())
|
||||
{
|
||||
stats->details.characterAnim.increment();
|
||||
stats->details.characterAnimSize.sample((inBitstream.GetReadOffset() - characterBits) / 8);
|
||||
}
|
||||
|
||||
if (part)
|
||||
{
|
||||
setPhysics(tempItem, remoteSendTime, deltaTime, numNodesInHistory);
|
||||
|
||||
if (!iAmServer)
|
||||
{
|
||||
if (DFFlag::CleanUpInterpolationTimestamps)
|
||||
{
|
||||
// Interpolation Delay has been re-purposed for use with relation to NetworkPing
|
||||
// Interpolation Delay depends greatly on NetworkPing and Frequency of Updates.
|
||||
part->setInterpolationDelay((double)deltaTime / 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
part->setInterpolationDelay((localTime - part->getLastUpdateTime()).seconds());
|
||||
}
|
||||
part->setLastUpdateTime(localTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
engine/network/src/DirectPhysicsReceiver.hpp
Normal file
26
engine/network/src/DirectPhysicsReceiver.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "PhysicsReceiver.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class DirectPhysicsReceiver : public PhysicsReceiver
|
||||
{
|
||||
private:
|
||||
MechanismItem tempItem;
|
||||
|
||||
public:
|
||||
DirectPhysicsReceiver(Replicator* replicator, bool isServer)
|
||||
: PhysicsReceiver(replicator, isServer)
|
||||
{
|
||||
}
|
||||
virtual void receivePacket(RakNet::BitStream& bitsream, RakNet::Time timeStamp, ReplicatorStats::PhysicsReceiverStats* stats);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
417
engine/network/src/ErrorCompPhysicsSender.cpp
Normal file
417
engine/network/src/ErrorCompPhysicsSender.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
|
||||
#if 1 // removing deprecated senders, set to 0 when FastFlag RemoveUnusedPhysicsSenders is set to true and removed (delete this file)
|
||||
#include "ErrorCompPhysicsSender.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Compressor.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
#include "MechanismItem.hpp"
|
||||
|
||||
#include "DataModel/ModelInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/PhysicsService.hpp"
|
||||
|
||||
|
||||
#include "RakNet/GetTime.hpp"
|
||||
#include "RakNet/RakPeerInterface.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "NetworkPacketCache.hpp"
|
||||
|
||||
#include "Streaming.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
|
||||
|
||||
ErrorCompPhysicsSender::ErrorCompPhysicsSender(Replicator& replicator)
|
||||
: PhysicsSender(replicator)
|
||||
, stepId(0)
|
||||
{
|
||||
if (replicator.isProtocolCompatible())
|
||||
physicsPacketCache = shared_from(ServiceProvider::find<PhysicsPacketCache>(&replicator));
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender::~ErrorCompPhysicsSender() {}
|
||||
|
||||
|
||||
|
||||
void ErrorCompPhysicsSender::step()
|
||||
{
|
||||
/*
|
||||
|
||||
First, update the error on all items that had been sent
|
||||
after the last step. These will have a stepId of 0.
|
||||
|
||||
Also, visit some of the oldest items in the list as well.
|
||||
|
||||
TODO: Rather than picking a constant "countdown", pick a number that
|
||||
is a fraction of the items sent last frame (This will
|
||||
aid in auto-throttling how much we send).
|
||||
|
||||
*/
|
||||
|
||||
if (!physicsService)
|
||||
{
|
||||
physicsService = shared_from(ServiceProvider::find<PhysicsService>(&replicator));
|
||||
|
||||
AYAASSERT(physicsService); // datamodel should always create this
|
||||
|
||||
// Get all current assemblies
|
||||
std::for_each(physicsService->begin(), physicsService->end(), boost::bind(&ErrorCompPhysicsSender::addNugget, this, _1));
|
||||
|
||||
// Listen for assembly changes
|
||||
addingAssemblyConnection = physicsService->assemblyAddingSignal.connect(boost::bind(&ErrorCompPhysicsSender::onAddingAssembly, this, _1));
|
||||
// removedAssemblyConnection = physicsService->assemblyRemovedSignal.connect(boost::bind(&ErrorCompPhysicsSender::onRemovedAssembly, this,
|
||||
// _1));
|
||||
}
|
||||
|
||||
std::for_each(newMovingAssemblies.begin(), newMovingAssemblies.end(), boost::bind(&ErrorCompPhysicsSender::addNugget2, this, _1));
|
||||
newMovingAssemblies.clear();
|
||||
|
||||
// Prepare the data needed to compute errors
|
||||
|
||||
// TODO: const
|
||||
const ModelInstance* characterModel = NULL;
|
||||
CoordinateFrame focus;
|
||||
if (const Player* player = replicator.findTargetPlayer())
|
||||
{
|
||||
if (player->hasCharacterHead(focus))
|
||||
{
|
||||
characterModel = player->getConstCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
stepId++; // can go for 100's of days at 30 fps until maxing out
|
||||
|
||||
{
|
||||
int numOldVisits = 0;
|
||||
|
||||
while (numOldVisits < 100)
|
||||
{
|
||||
NuggetList::iterator iter = nuggetUpdateList.begin();
|
||||
if (iter == nuggetUpdateList.end())
|
||||
break;
|
||||
|
||||
Nugget* nugget = &(**iter);
|
||||
|
||||
AYAASSERT(nugget->errorStepId <= stepId);
|
||||
|
||||
// remove nugget if part is not in physics service sender list, it's not moving.
|
||||
if (!nugget->part->PhysicsServiceHook::is_linked())
|
||||
{
|
||||
removeNugget(nugget->part);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nugget->errorStepId == stepId)
|
||||
{
|
||||
break; // we are done - first in the list is caught up
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nugget->errorStepId > 0)
|
||||
{ // don't include just-sent items in the count of old items
|
||||
++numOldVisits;
|
||||
}
|
||||
nugget->computeError(focus, characterModel, stepId);
|
||||
}
|
||||
|
||||
// resort nugget into send list
|
||||
nuggetSendList.erase(*iter);
|
||||
SortedNuggetList::iterator newPos = nuggetSendList.insert(*nugget);
|
||||
|
||||
// update update iterator
|
||||
*iter = newPos;
|
||||
|
||||
// move just updated item to end of update list
|
||||
nuggetUpdateList.splice(nuggetUpdateList.end(), nuggetUpdateList, iter);
|
||||
|
||||
// update nugget iterator inside update list
|
||||
nugget->updateListIter = --nuggetUpdateList.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ErrorCompPhysicsSender::sendPacket(int maxPackets, PacketPriority packetPriority, ReplicatorStats::PhysicsSenderStats* stats)
|
||||
{
|
||||
const unsigned int maxStreamSize(replicator.getPhysicsMtuSize());
|
||||
|
||||
boost::shared_ptr<RakNet::BitStream> bitStream;
|
||||
|
||||
int packetCount = 0;
|
||||
|
||||
SortedNuggetList::iterator iter = nuggetSendList.begin();
|
||||
|
||||
// Iterate by error until
|
||||
while (iter != nuggetSendList.end())
|
||||
{
|
||||
if (!bitStream)
|
||||
{
|
||||
if (packetCount >= maxPackets)
|
||||
break;
|
||||
|
||||
bitStream.reset(new RakNet::BitStream(maxStreamSize));
|
||||
|
||||
++packetCount;
|
||||
|
||||
*bitStream << (unsigned char)ID_TIMESTAMP;
|
||||
RakNet::Time now = RakNet::GetTime();
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
*bitStream << now;
|
||||
#else
|
||||
*bitStream << static_cast<unsigned long long>(now);
|
||||
#endif
|
||||
*bitStream << (unsigned char)ID_PHYSICS;
|
||||
}
|
||||
|
||||
Nugget& nugget = *iter;
|
||||
|
||||
// remove nugget if part is not in physics service sender list, it's not moving
|
||||
if (!nugget.part->PhysicsServiceHook::is_linked())
|
||||
{
|
||||
++iter;
|
||||
// remove will increment curBucket->iter
|
||||
removeNugget(nugget.part);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!sendPhysicsData(*bitStream, nugget))
|
||||
{
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nugget.part->getConstPartPrimitive()->getConstAssembly())
|
||||
{
|
||||
Nugget::onSent(nugget);
|
||||
|
||||
// move just sent item to front of update list
|
||||
nuggetUpdateList.splice(nuggetUpdateList.begin(), nuggetUpdateList, nugget.updateListIter);
|
||||
nugget.updateListIter = nuggetUpdateList.begin();
|
||||
|
||||
AYAASSERT((*iter).lastSent == nugget.part->getCoordinateFrame());
|
||||
AYAASSERT((*iter).biggestSize == Nugget::minSize());
|
||||
AYAASSERT((*iter).errorStepId == 0);
|
||||
++iter;
|
||||
}
|
||||
else
|
||||
{
|
||||
AYAASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (bitStream->GetNumberOfBytesUsed() > static_cast<int>(maxStreamSize))
|
||||
{
|
||||
// Packet "end" tag
|
||||
if (replicator.isStreamingEnabled())
|
||||
*bitStream << true; // done
|
||||
else
|
||||
replicator.serializeId(*bitStream, NULL);
|
||||
|
||||
// Send ID_PHYSICS
|
||||
replicator.rakPeer->Send(bitStream, packetPriority, UNRELIABLE, PHYSICS_CHANNEL, replicator.remotePlayerId, false);
|
||||
stats->physicsPacketsSent.sample();
|
||||
stats->physicsPacketsSentSmooth.sample();
|
||||
stats->physicsPacketsSentSize.sample(bitStream->GetNumberOfBytesUsed());
|
||||
|
||||
bitStream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (bitStream)
|
||||
{
|
||||
// Packet "end" tag
|
||||
if (replicator.isStreamingEnabled())
|
||||
*bitStream << true; // done
|
||||
else
|
||||
replicator.serializeId(*bitStream, NULL);
|
||||
|
||||
// Send ID_PHYSICS
|
||||
replicator.rakPeer->Send(bitStream, packetPriority, UNRELIABLE, PHYSICS_CHANNEL, replicator.remotePlayerId, false);
|
||||
stats->physicsPacketsSent.sample();
|
||||
stats->physicsPacketsSentSmooth.sample();
|
||||
stats->physicsPacketsSentSize.sample(bitStream->GetNumberOfBytesUsed());
|
||||
}
|
||||
|
||||
return packetCount;
|
||||
};
|
||||
|
||||
|
||||
void ErrorCompPhysicsSender::onAddingAssembly(shared_ptr<Instance> assembly)
|
||||
{
|
||||
shared_ptr<PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
newMovingAssemblies.push_back(part);
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender::addNugget(PartInstance& part)
|
||||
{
|
||||
addNugget2(shared_from(&part));
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender::addNugget2(shared_ptr<PartInstance> part)
|
||||
{
|
||||
// try add item to map
|
||||
std::pair<NuggetMap::iterator, bool> result = nuggetMap.insert(std::make_pair(part, Nugget(part)));
|
||||
|
||||
// add succeeded
|
||||
if (result.second)
|
||||
{
|
||||
// put new nugget to end of send list, it'll get updated next step()
|
||||
SortedNuggetList::iterator iter = nuggetSendList.insert(nuggetSendList.end(), result.first->second);
|
||||
nuggetUpdateList.push_front(iter);
|
||||
|
||||
iter->updateListIter = nuggetUpdateList.begin();
|
||||
iter->radius = part->getPartPrimitive()->getAssembly()->getLastComputedRadius();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender::removeNugget(shared_ptr<const PartInstance> part)
|
||||
{
|
||||
NuggetMap::iterator iter = nuggetMap.find(part);
|
||||
AYAASSERT(iter != nuggetMap.end());
|
||||
|
||||
nuggetSendList.erase(*iter->second.updateListIter);
|
||||
nuggetUpdateList.erase(iter->second.updateListIter);
|
||||
nuggetMap.erase(iter);
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender::onRemovedAssembly(shared_ptr<Instance> assembly)
|
||||
{
|
||||
shared_ptr<const PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
removeNugget(part);
|
||||
}
|
||||
|
||||
bool ErrorCompPhysicsSender::sendPhysicsData(RakNet::BitStream& bitStream, const Nugget& nugget)
|
||||
{
|
||||
sendDetailed = nugget.sendDetailed;
|
||||
CoordinateFrame cFrame = nugget.lastSent;
|
||||
float accumulatedError;
|
||||
return PhysicsSender::sendPhysicsData(bitStream, nugget.part.get(), sendDetailed, currentStepTimestamp, cFrame, accumulatedError, NULL);
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender::writeAssembly(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression)
|
||||
{
|
||||
if (physicsPacketCache)
|
||||
{
|
||||
// look for this packet in the cache
|
||||
if (!physicsPacketCache->fetchIfUpToDate(assembly, (unsigned char)sendDetailed, bitStream))
|
||||
{
|
||||
// cache miss, recreate bit stream and update cache
|
||||
unsigned int startBit = bitStream.GetWriteOffset();
|
||||
|
||||
PhysicsSender::writeAssembly(bitStream, assembly, compressionType);
|
||||
|
||||
bitStream.SetReadOffset(startBit);
|
||||
|
||||
if (!physicsPacketCache->update(assembly, (unsigned char)sendDetailed, bitStream, startBit, bitStream.GetNumberOfBitsUsed() - startBit))
|
||||
{
|
||||
// this item should be in the cache
|
||||
StandardOut::singleton()->print(MESSAGE_INFO, "cache update failed");
|
||||
AYAASSERT(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
PhysicsSender::writeAssembly(bitStream, assembly, compressionType);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
float ErrorCompPhysicsSender::Nugget::minDistance()
|
||||
{
|
||||
return 3.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender::Nugget::maxDistance()
|
||||
{
|
||||
return 1000.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender::Nugget::minSize()
|
||||
{
|
||||
return 2.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender::Nugget::maxSize()
|
||||
{
|
||||
return 50.0f;
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender::Nugget::Nugget(shared_ptr<PartInstance> part)
|
||||
: part(part) // indexes
|
||||
, error(0.0f)
|
||||
, errorStepId(-1)
|
||||
, lastSent(part->getCoordinateFrame())
|
||||
, biggestSize(minSize())
|
||||
, sendDetailed(true)
|
||||
, radius(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
// static
|
||||
void ErrorCompPhysicsSender::Nugget::onSent(Nugget& nugget)
|
||||
{
|
||||
// part - no change
|
||||
// error - will be computed
|
||||
nugget.errorStepId = 0;
|
||||
nugget.lastSent = nugget.part->getCoordinateFrame();
|
||||
nugget.biggestSize = minSize(); // resets
|
||||
}
|
||||
|
||||
|
||||
void ErrorCompPhysicsSender::Nugget::computeError(const CoordinateFrame& focus, const ModelInstance* focusModel, int stepId)
|
||||
{
|
||||
AYAASSERT(part->getConstPartPrimitive()->getConstAssembly());
|
||||
|
||||
if ((stepId % 20) == 0)
|
||||
{
|
||||
const Assembly* assembly = part->getConstPartPrimitive()->getConstAssembly();
|
||||
AYAASSERT(assembly);
|
||||
|
||||
radius = assembly->getLastComputedRadius();
|
||||
}
|
||||
|
||||
const CoordinateFrame& cf = part->getCoordinateFrame();
|
||||
|
||||
// 1. Biggest Size
|
||||
biggestSize = G3D::clamp(2 * radius, biggestSize, maxSize());
|
||||
|
||||
// current
|
||||
float currentDistance = minDistance(); // There is no center of interest. Location doesn't affect error
|
||||
if (focusModel)
|
||||
{
|
||||
currentDistance = Math::taxiCabMagnitude(cf.translation - focus.translation) - radius;
|
||||
currentDistance = G3D::clamp(currentDistance, minDistance(), maxDistance());
|
||||
}
|
||||
|
||||
// 2. Error
|
||||
// 3. Send Detailed
|
||||
if (part->isDescendantOf(focusModel))
|
||||
{ // the character!
|
||||
error = std::numeric_limits<double>::max();
|
||||
sendDetailed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float positionalError = Math::taxiCabMagnitude(cf.translation - lastSent.translation);
|
||||
const float rotationError = Math::sumDeltaAxis(cf.rotation, lastSent.rotation) * radius * 0.2f; // factor down - we are summing the axis
|
||||
error = biggestSize * biggestSize * (rotationError + positionalError) / currentDistance;
|
||||
sendDetailed = currentDistance < 20.0f;
|
||||
}
|
||||
|
||||
// 4. errorStepId
|
||||
errorStepId = stepId;
|
||||
}
|
||||
#endif // removing deprecated senders
|
||||
125
engine/network/src/ErrorCompPhysicsSender.hpp
Normal file
125
engine/network/src/ErrorCompPhysicsSender.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
#if 1 // removing deprecated senders, set to 0 when FastFlag RemoveUnusedPhysicsSenders is set to true and removed (delete this file)
|
||||
#pragma once
|
||||
|
||||
#include "PhysicsSender.hpp"
|
||||
#include "BoostAppend.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
|
||||
#include "signal.hpp"
|
||||
|
||||
|
||||
#include "boost/pool/pool_alloc.hpp"
|
||||
#include "boost/intrusive/set.hpp"
|
||||
using boost::multi_index_container;
|
||||
using namespace boost::multi_index;
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Instance;
|
||||
class PhysicsService;
|
||||
class ModelInstance;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator;
|
||||
class PhysicsPacketCache;
|
||||
|
||||
class ErrorCompPhysicsSender : public PhysicsSender
|
||||
{
|
||||
|
||||
class Nugget;
|
||||
|
||||
// custom containers
|
||||
typedef std::list<shared_ptr<PartInstance>, boost::fast_pool_allocator<shared_ptr<PartInstance>>> NewMovingAssemblies;
|
||||
NewMovingAssemblies newMovingAssemblies; // new moving assemblies for current step
|
||||
|
||||
typedef boost::intrusive::set_base_hook<boost::intrusive::tag<Nugget>, boost::intrusive::link_mode<boost::intrusive::normal_link>> NuggetHook;
|
||||
typedef boost::intrusive::multiset<Nugget, boost::intrusive::compare<std::greater<Nugget>>, boost::intrusive::base_hook<NuggetHook>>
|
||||
SortedNuggetList;
|
||||
|
||||
typedef std::list<SortedNuggetList::iterator> NuggetList;
|
||||
typedef boost::unordered_map<shared_ptr<const PartInstance>, Nugget> NuggetMap;
|
||||
|
||||
NuggetMap nuggetMap;
|
||||
NuggetList nuggetUpdateList;
|
||||
SortedNuggetList nuggetSendList;
|
||||
|
||||
class Nugget : public NuggetHook
|
||||
{
|
||||
public:
|
||||
shared_ptr<const PartInstance> part;
|
||||
double error;
|
||||
int errorStepId; // "stepId" of the last send (-1 means uninitialized. 0 means we just got sent and need updating)
|
||||
|
||||
Nugget(shared_ptr<PartInstance> part);
|
||||
|
||||
static void onSent(Nugget& nugget);
|
||||
|
||||
void computeError(const CoordinateFrame& focus, const ModelInstance* focusModel, int timestamp);
|
||||
|
||||
CoordinateFrame lastSent; // the CF last sent
|
||||
bool sendDetailed; // Should this send detailed (uncompressed) physics info
|
||||
float biggestSize; // May change for an articulated assembly....
|
||||
float radius; // cached assembly radius
|
||||
|
||||
static float minDistance();
|
||||
static float maxDistance();
|
||||
static float minSize();
|
||||
static float maxSize();
|
||||
|
||||
friend bool operator>(const Nugget& a, const Nugget& b)
|
||||
{
|
||||
return a.error > b.error;
|
||||
}
|
||||
|
||||
NuggetList::iterator updateListIter; // iterator into update list
|
||||
};
|
||||
|
||||
int stepId;
|
||||
shared_ptr<PhysicsService> physicsService;
|
||||
shared_ptr<PhysicsPacketCache> physicsPacketCache;
|
||||
|
||||
bool sendDetailed;
|
||||
|
||||
Aya::signals::scoped_connection addingAssemblyConnection;
|
||||
Aya::signals::scoped_connection removedAssemblyConnection;
|
||||
|
||||
public:
|
||||
ErrorCompPhysicsSender(Replicator& replicator);
|
||||
~ErrorCompPhysicsSender();
|
||||
|
||||
virtual void step();
|
||||
virtual int sendPacket(int maxPackets, PacketPriority packetPriority, ReplicatorStats::PhysicsSenderStats* stats);
|
||||
|
||||
protected:
|
||||
virtual void writeAssembly(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression = false);
|
||||
|
||||
private:
|
||||
void updateErrors();
|
||||
bool sendPhysicsData(RakNet::BitStream& bitStream, const Nugget& nugget);
|
||||
|
||||
void addNugget(PartInstance& part);
|
||||
void addNugget2(shared_ptr<PartInstance> part);
|
||||
|
||||
void onAddingAssembly(shared_ptr<Instance> assembly);
|
||||
void onRemovedAssembly(shared_ptr<Instance> assembly);
|
||||
void removeNugget(shared_ptr<const PartInstance> part);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
#endif // removing deprecated senders
|
||||
623
engine/network/src/ErrorCompPhysicsSender2.cpp
Normal file
623
engine/network/src/ErrorCompPhysicsSender2.cpp
Normal file
@@ -0,0 +1,623 @@
|
||||
|
||||
#if 1 // removing deprecated senders, set to 0 when FastFlag RemoveUnusedPhysicsSenders is set to true and removed (delete this file)
|
||||
|
||||
#include "ErrorCompPhysicsSender2.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Compressor.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
#include "MechanismItem.hpp"
|
||||
|
||||
#include "DataModel/ModelInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/PhysicsService.hpp"
|
||||
|
||||
|
||||
#include "RakNet/GetTime.hpp"
|
||||
#include "RakNet/RakPeerInterface.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "NetworkPacketCache.hpp"
|
||||
|
||||
#include "Streaming.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
#define NUM_GROUPS 4
|
||||
|
||||
ErrorCompPhysicsSender2::ErrorCompPhysicsSender2(Replicator& replicator)
|
||||
: PhysicsSender(replicator)
|
||||
, bucketIndex(0)
|
||||
, stepId(0)
|
||||
{
|
||||
|
||||
if (replicator.isProtocolCompatible())
|
||||
physicsPacketCache = shared_from(ServiceProvider::find<PhysicsPacketCache>(&replicator));
|
||||
|
||||
for (int i = 0; i < NUM_GROUPS; i++)
|
||||
{
|
||||
nuggetSendGroups.push_back(Bucket());
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender2::~ErrorCompPhysicsSender2() {}
|
||||
|
||||
|
||||
|
||||
void ErrorCompPhysicsSender2::step()
|
||||
{
|
||||
/*
|
||||
|
||||
First, update the error on all items that had been sent
|
||||
after the last step. These will have a stepId of 0.
|
||||
|
||||
Also, visit some of the oldest items in the list as well.
|
||||
|
||||
TODO: Rather than picking a constant "countdown", pick a number that
|
||||
is a fraction of the items sent last frame (This will
|
||||
aid in auto-throttling how much we send).
|
||||
|
||||
*/
|
||||
|
||||
if (!physicsService)
|
||||
{
|
||||
physicsService = shared_from(ServiceProvider::find<PhysicsService>(&replicator));
|
||||
|
||||
AYAASSERT(physicsService); // datamodel should always create this
|
||||
|
||||
// Get all current assemblies
|
||||
std::for_each(physicsService->begin(), physicsService->end(), boost::bind(&ErrorCompPhysicsSender2::addNugget, this, _1));
|
||||
|
||||
// Listen for assembly changes
|
||||
// Optimization: onAddingAssemly simply adds new item to a STL list, more expensive operations are deferred til later
|
||||
addingAssemblyConnection = physicsService->assemblyAddingSignal.connect(boost::bind(&ErrorCompPhysicsSender2::onAddingAssembly, this, _1));
|
||||
// removedAssemblyConnection = physicsService->assemblyRemovedSignal.connect(boost::bind(&ErrorCompPhysicsSender2::onRemovedAssembly, this,
|
||||
// _1));
|
||||
}
|
||||
|
||||
// Optimization: adding and removing nugget is expensive, by deferring it to here we allow it to run concurrently
|
||||
// process new moving assemblies
|
||||
std::for_each(newMovingAssemblies.begin(), newMovingAssemblies.end(), boost::bind(&ErrorCompPhysicsSender2::addNugget2, this, _1));
|
||||
newMovingAssemblies.clear();
|
||||
|
||||
// Prepare the data needed to compute errors
|
||||
const ModelInstance* characterModel = NULL;
|
||||
CoordinateFrame focus;
|
||||
if (const Player* player = replicator.findTargetPlayer())
|
||||
{
|
||||
if (player->hasCharacterHead(focus))
|
||||
{
|
||||
characterModel = player->getConstCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
stepId++; // can go for 100's of days at 30 fps until maxing out
|
||||
|
||||
// Compute errors
|
||||
{
|
||||
int numOldVisits = 0;
|
||||
|
||||
NuggetUpdateList::iterator iter = nuggetUpdateList.begin();
|
||||
while (numOldVisits <= 0)
|
||||
{
|
||||
if (iter == nuggetUpdateList.end())
|
||||
break;
|
||||
|
||||
Nugget* nugget = &(**iter);
|
||||
AYAASSERT(nugget->errorStepId <= stepId);
|
||||
|
||||
// remove nugget if part is not in physics service sender list, it's not moving.
|
||||
if (!nugget->part->PhysicsServiceHook::is_linked())
|
||||
{
|
||||
iter++;
|
||||
removeNugget(nugget->part);
|
||||
continue;
|
||||
}
|
||||
|
||||
int oldGroup = nugget->groupId;
|
||||
|
||||
if (nugget->errorStepId == stepId)
|
||||
{
|
||||
break; // we are done - first in the list is caught up
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nugget->errorStepId > 0)
|
||||
{ // don't include just-sent items in the count of old items
|
||||
++numOldVisits;
|
||||
}
|
||||
nugget->computeDeltaError(focus, characterModel, stepId);
|
||||
}
|
||||
|
||||
// add to group
|
||||
NuggetIterator sendIter =
|
||||
nuggetSendGroups[nugget->groupId].splice(nuggetSendGroups[nugget->groupId].iter, &nuggetSendGroups[oldGroup], *iter);
|
||||
|
||||
AYAASSERT(nuggetSendGroups[nugget->groupId].size() > 0
|
||||
? nuggetSendGroups[nugget->groupId].iter != nuggetSendGroups[nugget->groupId].nuggetList.end()
|
||||
: true);
|
||||
AYAASSERT(nuggetSendGroups[oldGroup].size() > 0 ? nuggetSendGroups[oldGroup].iter != nuggetSendGroups[oldGroup].nuggetList.end() : true);
|
||||
|
||||
*nugget->mapIter->second = sendIter;
|
||||
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::calculateSendCount()
|
||||
{
|
||||
bool last = true;
|
||||
int prev = nuggetSendGroups.size() - 1;
|
||||
for (int i = nuggetSendGroups.size() - 1; i >= 0; i--)
|
||||
{
|
||||
if (nuggetSendGroups[i].size() == 0)
|
||||
{
|
||||
nuggetSendGroups[i].targetSentCount = 0;
|
||||
nuggetSendGroups[i].sendCount = 0;
|
||||
continue;
|
||||
}
|
||||
// last non empty bucket always send 1
|
||||
if (last)
|
||||
{
|
||||
nuggetSendGroups[i].targetSentCount = 1;
|
||||
nuggetSendGroups[i].sendCount = 0;
|
||||
prev = i;
|
||||
last = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// how many to send from this bucket before sending 1 item in next bucket, multiply by actual send count of next bucket count to keep ratio at
|
||||
// 2:1
|
||||
float count = nuggetSendGroups[i].size() * 2.0f / nuggetSendGroups[prev].size() * nuggetSendGroups[prev].targetSentCount;
|
||||
|
||||
nuggetSendGroups[i].targetSentCount = G3D::iCeil(count);
|
||||
|
||||
prev = i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int ErrorCompPhysicsSender2::sendPacket(int maxPackets, PacketPriority packetPriority, ReplicatorStats::PhysicsSenderStats* stats)
|
||||
{
|
||||
const unsigned int maxStreamSize(replicator.getPhysicsMtuSize());
|
||||
|
||||
boost::shared_ptr<RakNet::BitStream> bitStream;
|
||||
|
||||
int packetCount = 0;
|
||||
|
||||
bool done = false;
|
||||
|
||||
calculateSendCount();
|
||||
|
||||
// flag is a set of bits where each one representing a bucket, 1 == has stuff to send
|
||||
int bucketFlag = (1 << nuggetSendGroups.size()) - 1;
|
||||
while (!done && bucketFlag)
|
||||
{
|
||||
// reset bucket iterator
|
||||
if (bucketIndex >= nuggetSendGroups.size())
|
||||
bucketIndex = 0;
|
||||
|
||||
Bucket* curBucket = &nuggetSendGroups[bucketIndex];
|
||||
|
||||
if (curBucket->nuggetList.size() == 0)
|
||||
{
|
||||
bucketFlag &= ~(1 << bucketIndex);
|
||||
bucketIndex++;
|
||||
continue;
|
||||
}
|
||||
else if (curBucket->iter->sendStepId == stepId)
|
||||
{
|
||||
bucketFlag &= ~(1 << bucketIndex);
|
||||
bucketIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
AYAASSERT(curBucket->iter != curBucket->nuggetList.end());
|
||||
|
||||
while (curBucket->sendCount < curBucket->targetSentCount)
|
||||
{
|
||||
if (!bitStream)
|
||||
{
|
||||
if (packetCount >= maxPackets)
|
||||
{
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bitStream.reset(new RakNet::BitStream(maxStreamSize));
|
||||
|
||||
++packetCount;
|
||||
|
||||
*bitStream << (unsigned char)ID_TIMESTAMP;
|
||||
|
||||
RakNet::Time timestamp = RakNet::GetTime();
|
||||
if (curBucket->iter->part->computeNetworkOwnerIsSomeoneElse())
|
||||
{
|
||||
// retrieve the original packet send time of the part
|
||||
AYAASSERT(timestamp >= curBucket->iter->part->raknetTime);
|
||||
if (curBucket->iter->part->raknetTime)
|
||||
timestamp = curBucket->iter->part->raknetTime;
|
||||
}
|
||||
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
*bitStream << timestamp;
|
||||
#else
|
||||
*bitStream << static_cast<unsigned long long>(timestamp);
|
||||
#endif
|
||||
*bitStream << (unsigned char)ID_PHYSICS;
|
||||
}
|
||||
|
||||
AYAASSERT(curBucket->iter != curBucket->nuggetList.end());
|
||||
|
||||
// remove nugget if part is not in physics service sender list, it's not moving
|
||||
if (!curBucket->iter->part->PhysicsServiceHook::is_linked())
|
||||
{
|
||||
removeNugget(curBucket->iter->part);
|
||||
|
||||
if (curBucket->size() == 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
AYAASSERT(curBucket->iter->sendStepId <= stepId);
|
||||
writeNugget(*bitStream, *curBucket->iter);
|
||||
|
||||
if (bitStream->GetNumberOfBytesUsed() > static_cast<int>(maxStreamSize))
|
||||
{
|
||||
// Packet "end" tag
|
||||
if (replicator.isStreamingEnabled())
|
||||
*bitStream << true; // done
|
||||
else
|
||||
replicator.serializeId(*bitStream, NULL);
|
||||
|
||||
// Send ID_PHYSICS
|
||||
replicator.rakPeer->Send(bitStream, packetPriority, UNRELIABLE, PHYSICS_CHANNEL, replicator.remotePlayerId, false);
|
||||
stats->physicsPacketsSent.sample();
|
||||
stats->physicsPacketsSentSmooth.sample();
|
||||
stats->physicsPacketsSentSize.sample(bitStream->GetNumberOfBytesUsed());
|
||||
|
||||
bitStream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
curBucket->sendCount++;
|
||||
curBucket->iter++;
|
||||
|
||||
// reset iterator for next time in loop
|
||||
if (curBucket->iter == curBucket->nuggetList.end())
|
||||
{
|
||||
curBucket->iter = curBucket->nuggetList.begin();
|
||||
break;
|
||||
}
|
||||
|
||||
AYAASSERT(curBucket->iter != curBucket->nuggetList.end());
|
||||
}
|
||||
|
||||
if (curBucket->sendCount >= curBucket->targetSentCount)
|
||||
curBucket->sendCount = 0;
|
||||
|
||||
bucketIndex++;
|
||||
}
|
||||
|
||||
if (bitStream)
|
||||
{
|
||||
// Packet "end" tag
|
||||
if (replicator.isStreamingEnabled())
|
||||
*bitStream << true; // done
|
||||
else
|
||||
replicator.serializeId(*bitStream, NULL);
|
||||
|
||||
// Send ID_PHYSICS
|
||||
replicator.rakPeer->Send(bitStream, packetPriority, UNRELIABLE, PHYSICS_CHANNEL, replicator.remotePlayerId, false);
|
||||
stats->physicsPacketsSent.sample();
|
||||
stats->physicsPacketsSentSmooth.sample();
|
||||
stats->physicsPacketsSentSize.sample(bitStream->GetNumberOfBytesUsed());
|
||||
}
|
||||
|
||||
return packetCount;
|
||||
};
|
||||
|
||||
bool ErrorCompPhysicsSender2::writeNugget(RakNet::BitStream& bitStream, Nugget& nugget)
|
||||
{
|
||||
if (!sendPhysicsData(bitStream, nugget))
|
||||
{
|
||||
nugget.sendStepId = stepId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nugget.part->getConstPartPrimitive()->getConstAssembly())
|
||||
{
|
||||
nugget.onSent(stepId);
|
||||
AYAASSERT(nugget.biggestSize == Nugget::minSize());
|
||||
AYAASSERT(nugget.errorStepId == 0);
|
||||
|
||||
// move just sent item to front of update list
|
||||
nuggetUpdateList.splice(nuggetUpdateList.begin(), nuggetUpdateList, nugget.mapIter->second);
|
||||
|
||||
// update iterators in map
|
||||
nugget.mapIter->second = nuggetUpdateList.begin();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
AYAASSERT(0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::onAddingAssembly(shared_ptr<Instance> assembly)
|
||||
{
|
||||
shared_ptr<PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
newMovingAssemblies.push_back(part);
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::addNugget(PartInstance& part)
|
||||
{
|
||||
addNugget2(shared_from(&part));
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::addNugget2(shared_ptr<PartInstance> part)
|
||||
{
|
||||
// try add item to map
|
||||
std::pair<NuggetMap::iterator, bool> result = nuggetMap.insert(std::make_pair(part, nuggetUpdateList.end()));
|
||||
|
||||
// add succeeded
|
||||
if (result.second)
|
||||
{
|
||||
int group = 0;
|
||||
NuggetIterator iter = nuggetSendGroups[group].push_back(part);
|
||||
iter->groupId = group;
|
||||
|
||||
// add to send iterator to front of update list, so the nugget will be updated in step()
|
||||
nuggetUpdateList.push_front(iter);
|
||||
|
||||
// set update iterator inside map
|
||||
result.first->second = nuggetUpdateList.begin();
|
||||
|
||||
// cache map iterator inside nugget
|
||||
iter->mapIter = result.first;
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::removeNugget(shared_ptr<const PartInstance> part)
|
||||
{
|
||||
NuggetMap::iterator iter = nuggetMap.find(part);
|
||||
AYAASSERT(iter != nuggetMap.end());
|
||||
|
||||
NuggetIterator nuggetIter = *iter->second;
|
||||
|
||||
int groupID = nuggetIter->groupId;
|
||||
|
||||
nuggetSendGroups[groupID].erase(nuggetIter);
|
||||
|
||||
nuggetUpdateList.erase(iter->second);
|
||||
|
||||
nuggetMap.erase(iter);
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::onRemovedAssembly(shared_ptr<Instance> assembly)
|
||||
{
|
||||
shared_ptr<const PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
NuggetMap::iterator iter = nuggetMap.find(part);
|
||||
AYAASSERT(iter != nuggetMap.end());
|
||||
|
||||
NuggetIterator nuggetIter = *iter->second;
|
||||
|
||||
int groupID = nuggetIter->groupId;
|
||||
|
||||
nuggetSendGroups[groupID].erase(nuggetIter);
|
||||
|
||||
nuggetUpdateList.erase(iter->second);
|
||||
|
||||
nuggetMap.erase(iter);
|
||||
}
|
||||
|
||||
bool ErrorCompPhysicsSender2::sendPhysicsData(RakNet::BitStream& bitStream, const Nugget& nugget)
|
||||
{
|
||||
sendDetailed = nugget.sendDetailed;
|
||||
CoordinateFrame cFrame = nugget.part->getCoordinateFrame();
|
||||
float accumulatedError;
|
||||
return PhysicsSender::sendPhysicsData(bitStream, nugget.part.get(), sendDetailed, currentStepTimestamp, cFrame, accumulatedError, NULL);
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::writeAssembly(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression)
|
||||
{
|
||||
if (physicsPacketCache)
|
||||
{
|
||||
// look for this packet in the cache
|
||||
if (!physicsPacketCache->fetchIfUpToDate(assembly, (unsigned char)sendDetailed, bitStream))
|
||||
{
|
||||
// cache miss, recreate bit stream and update cache
|
||||
RakNet::BitStream stream;
|
||||
|
||||
unsigned int startBit = bitStream.GetWriteOffset();
|
||||
|
||||
PhysicsSender::writeAssembly(bitStream, assembly, compressionType);
|
||||
|
||||
bitStream.SetReadOffset(startBit);
|
||||
|
||||
if (!physicsPacketCache->update(assembly, (unsigned char)sendDetailed, bitStream, startBit, bitStream.GetNumberOfBitsUsed() - startBit))
|
||||
{
|
||||
// this item should be in the cache
|
||||
StandardOut::singleton()->print(MESSAGE_INFO, "cache update failed");
|
||||
AYAASSERT(0);
|
||||
}
|
||||
|
||||
stream.SetReadOffset(0);
|
||||
bitStream.Write(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
PhysicsSender::writeAssembly(bitStream, assembly, compressionType);
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender2::NuggetIterator ErrorCompPhysicsSender2::Bucket::push_back(shared_ptr<PartInstance> part)
|
||||
{
|
||||
nuggetList.push_back(ErrorCompPhysicsSender2::Nugget(part));
|
||||
|
||||
// set iterator at start of list if 1st item
|
||||
if (nuggetList.size() == 1)
|
||||
iter = nuggetList.begin();
|
||||
|
||||
return --nuggetList.end();
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender2::NuggetIterator ErrorCompPhysicsSender2::Bucket::splice(const NuggetIterator where, Bucket* from, const NuggetIterator first)
|
||||
{
|
||||
if (this == from && where == first)
|
||||
return where;
|
||||
|
||||
// splicing within self does not cause iterators to become invalid
|
||||
if (this != from && from->iter == first)
|
||||
{
|
||||
from->iter++;
|
||||
|
||||
if (from->iter == from->nuggetList.end())
|
||||
from->iter = from->nuggetList.begin();
|
||||
}
|
||||
|
||||
if (nuggetList.size() > 0)
|
||||
{
|
||||
nuggetList.splice(where, from->nuggetList, first);
|
||||
NuggetIterator temp = where;
|
||||
return --temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
nuggetList.splice(nuggetList.begin(), from->nuggetList, first);
|
||||
iter = nuggetList.begin();
|
||||
return nuggetList.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::Bucket::erase(const NuggetIterator where)
|
||||
{
|
||||
if (iter == where)
|
||||
{
|
||||
iter = nuggetList.erase(where);
|
||||
if (iter == nuggetList.end())
|
||||
iter = nuggetList.begin();
|
||||
}
|
||||
else
|
||||
nuggetList.erase(where);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
float ErrorCompPhysicsSender2::Nugget::minDistance()
|
||||
{
|
||||
return 3.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender2::Nugget::maxDistance()
|
||||
{
|
||||
return 800.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender2::Nugget::minSize()
|
||||
{
|
||||
return 2.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender2::Nugget::maxSize()
|
||||
{
|
||||
return 25.0f;
|
||||
}
|
||||
float ErrorCompPhysicsSender2::Nugget::midArea()
|
||||
{
|
||||
return 312.5f;
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender2::Nugget::Nugget(shared_ptr<PartInstance> part)
|
||||
: part(part) // indexes
|
||||
, errorStepId(-1)
|
||||
, sendStepId(-1)
|
||||
, biggestSize(minSize())
|
||||
, sendDetailed(true)
|
||||
, groupId(NUM_GROUPS - 1)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorCompPhysicsSender2::Nugget::~Nugget() {}
|
||||
|
||||
// static
|
||||
void ErrorCompPhysicsSender2::Nugget::onSent(Nugget& nugget)
|
||||
{
|
||||
// part - no change
|
||||
nugget.errorStepId = 0;
|
||||
nugget.biggestSize = minSize(); // resets
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::Nugget::onSent(int stepId)
|
||||
{
|
||||
// part - no change
|
||||
errorStepId = 0;
|
||||
sendStepId = stepId;
|
||||
biggestSize = minSize(); // resets
|
||||
}
|
||||
|
||||
void ErrorCompPhysicsSender2::Nugget::computeDeltaError(const CoordinateFrame& focus, const ModelInstance* focusModel, int stepId)
|
||||
{
|
||||
const Assembly* assembly = part->getConstPartPrimitive()->getConstAssembly();
|
||||
AYAASSERT(assembly);
|
||||
|
||||
float radius = assembly->getLastComputedRadius();
|
||||
|
||||
// 1. Biggest Size
|
||||
biggestSize = G3D::clamp(2 * radius, biggestSize, maxSize());
|
||||
|
||||
// current
|
||||
float currentDistance = minDistance(); // There is no center of interest. Location doesn't affect error
|
||||
if (focusModel)
|
||||
{
|
||||
currentDistance = Math::taxiCabMagnitude(part->getCoordinateFrame().translation - focus.translation) - radius;
|
||||
currentDistance = G3D::clamp(currentDistance, minDistance(), maxDistance());
|
||||
}
|
||||
|
||||
float velocityError;
|
||||
float sizeError;
|
||||
|
||||
// 2. Error
|
||||
// 3. Send Detailed
|
||||
if (part->isDescendantOf(focusModel))
|
||||
{ // the character!
|
||||
groupId = 0;
|
||||
sendDetailed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// which group this object belongs to is initially set by the distance to player, then modified by the velocity and size
|
||||
float distance = currentDistance * NUM_GROUPS / maxDistance();
|
||||
|
||||
float lVel = G3D::clamp(part->getLinearVelocity().squaredMagnitude(), 0.0f, 10000.0f);
|
||||
float rVel = G3D::clamp(part->getRotationalVelocity().squaredMagnitude(), 0.0f, 10000.0f);
|
||||
|
||||
velocityError = -1 + ((lVel / 5000.0f * 0.80f) + (rVel / 5000.0f * 0.25f));
|
||||
|
||||
// -1 + (0 to 2), 0 == small object, 2 == big object
|
||||
sizeError = -1 + biggestSize * biggestSize / midArea();
|
||||
|
||||
groupId = G3D::iRound(distance - sizeError - velocityError);
|
||||
groupId = G3D::clamp((double)groupId, 0, NUM_GROUPS - 1);
|
||||
|
||||
sendDetailed = currentDistance < 20.0f;
|
||||
}
|
||||
|
||||
// 4. errorStepId
|
||||
errorStepId = stepId;
|
||||
|
||||
// const_cast<PartInstance*>(part.get())->setColor3(Color::colorFromError(groupId));
|
||||
}
|
||||
#endif // removing deprecated senders
|
||||
139
engine/network/src/ErrorCompPhysicsSender2.hpp
Normal file
139
engine/network/src/ErrorCompPhysicsSender2.hpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#if 1 // removing deprecated senders, set to 0 when FastFlag RemoveUnusedPhysicsSenders is set to true and removed (delete this file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PhysicsSender.hpp"
|
||||
#include "BoostAppend.hpp"
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "signal.hpp"
|
||||
|
||||
|
||||
#include "boost/pool/pool_alloc.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Instance;
|
||||
class PhysicsService;
|
||||
class ModelInstance;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator;
|
||||
class PhysicsPacketCache;
|
||||
|
||||
class ErrorCompPhysicsSender2 : public PhysicsSender
|
||||
{
|
||||
class Nugget;
|
||||
|
||||
typedef std::list<Nugget> NuggetList;
|
||||
typedef NuggetList::iterator NuggetIterator;
|
||||
typedef std::list<NuggetIterator> NuggetUpdateList;
|
||||
typedef boost::unordered_map<shared_ptr<const PartInstance>, NuggetUpdateList::iterator> NuggetMap;
|
||||
|
||||
class Nugget
|
||||
{
|
||||
public:
|
||||
shared_ptr<const PartInstance> part;
|
||||
int errorStepId; // "stepId" of the last error calculation (-1 means uninitialized. 0 means we just got sent and need updating)
|
||||
int sendStepId; // "stepId" of the last send
|
||||
int groupId; // which bucket this item belong to
|
||||
NuggetMap::iterator mapIter; // iterator into hash map, for fast update without having to do look up
|
||||
|
||||
Nugget(shared_ptr<PartInstance> part);
|
||||
~Nugget();
|
||||
|
||||
static void onSent(Nugget& nugget);
|
||||
void onSent(int stepId);
|
||||
|
||||
void computeDeltaError(const CoordinateFrame& focus, const ModelInstance* focusModel, int timestamp);
|
||||
|
||||
bool sendDetailed; // Should this send detailed (uncompressed) physics info
|
||||
float biggestSize; // May change for an articulated assembly....
|
||||
|
||||
static float minDistance();
|
||||
static float maxDistance();
|
||||
static float minSize();
|
||||
static float maxSize();
|
||||
static float midArea();
|
||||
};
|
||||
|
||||
struct Bucket
|
||||
{
|
||||
NuggetIterator iter;
|
||||
NuggetList nuggetList;
|
||||
|
||||
int targetSentCount;
|
||||
int sendCount;
|
||||
|
||||
Bucket()
|
||||
: targetSentCount(0)
|
||||
, sendCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
NuggetIterator push_back(shared_ptr<PartInstance> part);
|
||||
void erase(const NuggetIterator where);
|
||||
|
||||
// moves a node from one bucket to another.
|
||||
// if first == iter, iter gets moved to next node if not at end, otherwise it moves to beginning
|
||||
NuggetIterator splice(const NuggetIterator where, Bucket* from, const NuggetIterator first);
|
||||
|
||||
inline unsigned int size()
|
||||
{
|
||||
return nuggetList.size();
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<Bucket> NuggetSendGroups;
|
||||
|
||||
NuggetMap nuggetMap;
|
||||
NuggetUpdateList nuggetUpdateList;
|
||||
NuggetSendGroups nuggetSendGroups;
|
||||
unsigned int bucketIndex;
|
||||
|
||||
int stepId;
|
||||
shared_ptr<PhysicsService> physicsService;
|
||||
shared_ptr<PhysicsPacketCache> physicsPacketCache;
|
||||
|
||||
bool sendDetailed;
|
||||
|
||||
Aya::signals::scoped_connection addingAssemblyConnection;
|
||||
Aya::signals::scoped_connection removedAssemblyConnection;
|
||||
|
||||
typedef std::list<shared_ptr<PartInstance>, boost::fast_pool_allocator<shared_ptr<PartInstance>>> NewMovingAssemblies;
|
||||
NewMovingAssemblies newMovingAssemblies; // new moving assemblies for current step
|
||||
|
||||
public:
|
||||
ErrorCompPhysicsSender2(Replicator& replicator);
|
||||
~ErrorCompPhysicsSender2();
|
||||
|
||||
virtual void step();
|
||||
virtual int sendPacket(int maxPackets, PacketPriority packetPriority, ReplicatorStats::PhysicsSenderStats* stats);
|
||||
|
||||
protected:
|
||||
virtual void writeAssembly(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression = false);
|
||||
|
||||
private:
|
||||
void calculateSendCount();
|
||||
|
||||
bool sendPhysicsData(RakNet::BitStream& bitStream, const Nugget& nugget);
|
||||
|
||||
void addNugget(PartInstance& part);
|
||||
void addNugget2(shared_ptr<PartInstance> part);
|
||||
void removeNugget(shared_ptr<const PartInstance> part);
|
||||
|
||||
void onAddingAssembly(shared_ptr<Instance> assembly);
|
||||
void onRemovedAssembly(shared_ptr<Instance> assembly);
|
||||
|
||||
bool writeNugget(RakNet::BitStream& bitStream, Nugget& nugget);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
#endif // removing deprecated senders
|
||||
945
engine/network/src/GameConfigurer.cpp
Normal file
945
engine/network/src/GameConfigurer.cpp
Normal file
@@ -0,0 +1,945 @@
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
#include "Players.hpp"
|
||||
|
||||
#include "GameConfigurer.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/UserInputService.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/PhysicsSettings.hpp"
|
||||
|
||||
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
|
||||
#include "DataModel/ChangeHistory.hpp"
|
||||
|
||||
#include "DataModel/InsertService.hpp"
|
||||
|
||||
|
||||
#include "DataModel/SocialService.hpp"
|
||||
|
||||
#include "DataModel/GamePassService.hpp"
|
||||
|
||||
#include "DataModel/MarketplaceService.hpp"
|
||||
|
||||
#include "DataModel/TimerService.hpp"
|
||||
|
||||
#include "DataModel/Visit.hpp"
|
||||
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
|
||||
#include "DataModel/GuiService.hpp"
|
||||
|
||||
#include "DataModel/Value.hpp"
|
||||
|
||||
#include "DataModel/PlayerGui.hpp"
|
||||
|
||||
#include "DataModel/HttpService.hpp"
|
||||
|
||||
#include "DataModel/Folder.hpp"
|
||||
|
||||
#include "DataModel/ScreenGui.hpp"
|
||||
|
||||
#include "DataModel/ContentProvider.hpp"
|
||||
|
||||
#include "DataModel/MegaCluster.hpp"
|
||||
|
||||
#include "DataModel/TeleportService.hpp"
|
||||
|
||||
#include "DataModel/HttpRbxApiService.hpp"
|
||||
|
||||
#include "Utility/AyaService.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
|
||||
#include "Utility/RobloxServicesTools.hpp"
|
||||
#include "Script/script.hpp"
|
||||
#include "Script/ScriptContext.hpp"
|
||||
#include "Script/ModuleScript.hpp"
|
||||
#include "Script/CoreScript.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
#include "Xml/WebParser.hpp"
|
||||
#include "Utility/ScriptInformationProvider.hpp"
|
||||
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "ClientReplicator.hpp"
|
||||
#include "Marker.hpp"
|
||||
#include "GamePerfMonitor.hpp"
|
||||
#include "Players.hpp"
|
||||
|
||||
#include "Utility/rbxrandom.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "StringConv.hpp"
|
||||
|
||||
#include "Script/LuaVM.hpp"
|
||||
|
||||
#include "boost/tokenizer.hpp"
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "XStudioBuild.hpp"
|
||||
|
||||
FASTSTRINGVARIABLE(SocialServiceFriendUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=IsFriendsWith&playerid=%d&userid=%d")
|
||||
FASTSTRINGVARIABLE(SocialServiceBestFriendUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=IsBestFriendsWith&playerid=%d&userid=%d")
|
||||
FASTSTRINGVARIABLE(SocialServiceGroupUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=IsInGroup&playerid=%d&groupid=%d")
|
||||
FASTSTRINGVARIABLE(SocialServiceGroupRankUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=GetGroupRank&playerid=%d&groupid=%d")
|
||||
FASTSTRINGVARIABLE(SocialServiceGroupRoleUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=GetGroupRole&playerid=%d&groupid=%d")
|
||||
FASTSTRINGVARIABLE(GamePassServicePlayerHasPassUrl, "Game/GamePass/GamePassHandler.ashx?Action=HasPass&UserID=%d&PassID=%d")
|
||||
FASTSTRINGVARIABLE(MobileJoinRateFormatUrl, "Game/JoinRate.ashx?st=%d&i=%d&p=%d&c=%s&r=%s&d=%d&b=%d&platform=%s")
|
||||
FASTFLAG(UseBuildGenericGameUrl)
|
||||
|
||||
|
||||
FASTFLAGVARIABLE(ClientABTestingEnabled, true)
|
||||
FASTFLAG(LegacyMessageBox)
|
||||
|
||||
DYNAMIC_FASTFLAG(UseR15Character)
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
void GameConfigurer::parseArgs(const std::string& args)
|
||||
{
|
||||
parameters = Aya::make_shared<const Reflection::ValueTable>();
|
||||
WebParser::parseJSONTable(args, parameters);
|
||||
}
|
||||
|
||||
int GameConfigurer::getParamInt(const std::string& key)
|
||||
{
|
||||
Aya::Reflection::ValueTable::const_iterator iter = parameters->find(key);
|
||||
if (iter != parameters->end())
|
||||
return iter->second.get<int>();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string GameConfigurer::getParamString(const std::string& key)
|
||||
{
|
||||
Aya::Reflection::ValueTable::const_iterator iter = parameters->find(key);
|
||||
if (iter != parameters->end())
|
||||
return iter->second.get<std::string>();
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool GameConfigurer::getParamBool(const std::string& key)
|
||||
{
|
||||
Aya::Reflection::ValueTable::const_iterator iter = parameters->find(key);
|
||||
if (iter != parameters->end())
|
||||
return iter->second.get<bool>();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GameConfigurer::registerPlay(const std::string& key, int userId, int placeId)
|
||||
{
|
||||
if (!getParamBool("CookieStoreEnabled"))
|
||||
return;
|
||||
}
|
||||
|
||||
void GameConfigurer::setupUrls()
|
||||
{
|
||||
std::string baseUrl = getParamString("BaseUrl");
|
||||
dataModel->create<InsertService>();
|
||||
|
||||
if (FFlag::UseBuildGenericGameUrl)
|
||||
{
|
||||
SocialService* socialService = dataModel->create<SocialService>();
|
||||
socialService->setFriendUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceFriendUrl));
|
||||
socialService->setBestFriendUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceBestFriendUrl));
|
||||
socialService->setGroupUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceGroupUrl));
|
||||
socialService->setGroupRankUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceGroupRankUrl));
|
||||
socialService->setGroupRoleUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceGroupRoleUrl));
|
||||
dataModel->create<GamePassService>()->setPlayerHasPassUrl(BuildGenericGameUrl(baseUrl, FString::GamePassServicePlayerHasPassUrl));
|
||||
}
|
||||
else
|
||||
{
|
||||
SocialService* socialService = dataModel->create<SocialService>();
|
||||
socialService->setFriendUrl(baseUrl + FString::SocialServiceFriendUrl);
|
||||
socialService->setBestFriendUrl(baseUrl + FString::SocialServiceBestFriendUrl);
|
||||
socialService->setGroupUrl(baseUrl + FString::SocialServiceGroupUrl);
|
||||
socialService->setGroupRankUrl(baseUrl + FString::SocialServiceGroupRankUrl);
|
||||
socialService->setGroupRoleUrl(baseUrl + FString::SocialServiceGroupRoleUrl);
|
||||
dataModel->create<GamePassService>()->setPlayerHasPassUrl(baseUrl + FString::GamePassServicePlayerHasPassUrl);
|
||||
}
|
||||
}
|
||||
|
||||
PlayerConfigurer::PlayerConfigurer()
|
||||
: testing(true)
|
||||
, isTouchDevice(false)
|
||||
, connectResolved(false)
|
||||
, connectionFailed(false)
|
||||
, loadResolved(false)
|
||||
, joinResolved(false)
|
||||
, playResolved(true)
|
||||
, waitingForCharacter(false)
|
||||
, startTime(0)
|
||||
, playStartTime(0)
|
||||
, launchMode(-1)
|
||||
{
|
||||
}
|
||||
|
||||
PlayerConfigurer::~PlayerConfigurer()
|
||||
{
|
||||
for (auto& c : connections)
|
||||
c.disconnect();
|
||||
}
|
||||
|
||||
void PlayerConfigurer::ifSeleniumThenSetCookie(const std::string& key, const std::string& value) {}
|
||||
|
||||
void PlayerConfigurer::showErrorWindow(const std::string& message, const std::string& errorType, const std::string& errorCategory)
|
||||
{
|
||||
if (GuiService* gs = dataModel->create<GuiService>())
|
||||
{
|
||||
if (errorType != "Kick" || gs->getErrorMessage() == "")
|
||||
gs->setErrorMessage(message);
|
||||
}
|
||||
|
||||
dataModel->setUiMessage(message);
|
||||
}
|
||||
|
||||
static void ignoreResponse(std::string*, std::exception*) {}
|
||||
|
||||
void PlayerConfigurer::reportError(const std::string& error, const std::string& msg)
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_INFO, "***ERROR*** %s %s", error.c_str(), msg.c_str());
|
||||
if (!testing)
|
||||
{
|
||||
Aya::Visit* visit = dataModel->create<Aya::Visit>();
|
||||
visit->setUploadUrl("");
|
||||
}
|
||||
|
||||
Network::Client* client = dataModel->create<Network::Client>();
|
||||
client->disconnect();
|
||||
|
||||
if (TimerService* timer = dataModel->create<TimerService>())
|
||||
{
|
||||
std::string errorMsg = Aya::format("Error: %s", error.c_str());
|
||||
timer->delay(boost::bind(&PlayerConfigurer::showErrorWindow, this, errorMsg, msg, "Other"), 4.0);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConfigurer::reportCounter(const std::string& counterNamesCSV, bool blocking) {}
|
||||
|
||||
void PlayerConfigurer::requestCharacter(shared_ptr<Network::Replicator> replicator, shared_ptr<bool> isWaiting)
|
||||
{
|
||||
(*isWaiting) = false;
|
||||
|
||||
dataModel->setUiMessage("Requesting character");
|
||||
|
||||
if (!loadResolved)
|
||||
{
|
||||
loadResolved = true;
|
||||
double duration = G3D::System::time() - startTime;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
replicator->requestCharacter();
|
||||
dataModel->setUiMessage("Waiting for character");
|
||||
waitingForCharacter = true;
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
reportError(e.what(), "W4C");
|
||||
}
|
||||
}
|
||||
|
||||
void periodicallyZoomExtents(weak_ptr<DataModel> dmWeak, shared_ptr<bool> isWaiting)
|
||||
{
|
||||
shared_ptr<DataModel> dm = dmWeak.lock();
|
||||
|
||||
if (dm && *isWaiting)
|
||||
{
|
||||
dm->getWorkspace()->zoomToExtents();
|
||||
|
||||
dm->create<TimerService>()->delay(boost::bind(&periodicallyZoomExtents, dmWeak, isWaiting), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onGameClose()
|
||||
{
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onDisconnection(const std::string& peer, bool lostConnection)
|
||||
{
|
||||
if (peer.length() > 0)
|
||||
{
|
||||
if (lostConnection)
|
||||
{
|
||||
if (!connectionFailed)
|
||||
showErrorWindow("You have lost the connection to the game", "LostConnection", "LostConnection");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!connectionFailed)
|
||||
{
|
||||
showErrorWindow("This game has shut down", "Kick", "Kick");
|
||||
if (dataModel)
|
||||
{
|
||||
Network::Players* players = dataModel->find<Network::Players>();
|
||||
if (players)
|
||||
{
|
||||
Network::Player* p = players->getLocalPlayer();
|
||||
if (p)
|
||||
{
|
||||
Instance* ps = p->findFirstChildByName("PlayerScripts");
|
||||
if (ps)
|
||||
ps->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::string url = Aya::format("%s&disconnect=true", getParamString("PingUrl").c_str());
|
||||
#if defined(AYA_PLATFORM_DURANGO)
|
||||
HttpAsync::get(url);
|
||||
#else
|
||||
dataModel->httpGet(url, true);
|
||||
#endif
|
||||
}
|
||||
catch (Aya::base_exception&)
|
||||
{
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onConnectionAccepted(std::string url, shared_ptr<Instance> replicator)
|
||||
{
|
||||
connectResolved = true;
|
||||
|
||||
shared_ptr<bool> waitingForMarker(new bool(true));
|
||||
|
||||
try
|
||||
{
|
||||
if (!testing)
|
||||
{
|
||||
Aya::Visit* visit = dataModel->create<Aya::Visit>();
|
||||
visit->setPing(getParamString("PingUrl"), getParamInt("PingInterval"));
|
||||
}
|
||||
|
||||
if (!getParamBool("GenerateTeleportJoin"))
|
||||
dataModel->setUiMessageBrickCount();
|
||||
else
|
||||
dataModel->setUiMessage("Teleporting...");
|
||||
|
||||
boost::shared_ptr<Network::ClientReplicator> rep = Instance::fastSharedDynamicCast<Network::ClientReplicator>(replicator);
|
||||
|
||||
connections.push_back(rep->disconnectionSignal.connect(boost::bind(&PlayerConfigurer::onDisconnection, this, _1, _2)));
|
||||
connections.push_back(rep->receivedGlobalsSignal.connect(boost::bind(&PlayerConfigurer::onReceivedGlobals, this)));
|
||||
|
||||
connections.push_back(rep->gameLoadedSignal.connect(boost::bind(&PlayerConfigurer::onGameLoaded, this, waitingForMarker)));
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "onConnectionAccepted failed: %s", e.what());
|
||||
reportError(e.what(), "ConnectionAccepted");
|
||||
}
|
||||
|
||||
dataModel->create<TimerService>()->delay(boost::bind(&periodicallyZoomExtents, weak_from(dataModel), waitingForMarker), 0);
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onConnectionFailed(const std::string& remoteAddress, int errorCode, const std::string& errorMsg)
|
||||
{
|
||||
connectionFailed = true;
|
||||
std::string msg;
|
||||
if (FFlag::LegacyMessageBox)
|
||||
{
|
||||
msg = Aya::format("Failed to connect to the Game. (ID=%d)", errorCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = Aya::format("Failed to connect to the Game. (ID = %d: %s)", errorCode, errorMsg.c_str());
|
||||
}
|
||||
|
||||
std::string errorType = Aya::format("ID%d", errorCode);
|
||||
showErrorWindow(msg, errorType, "Other");
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onConnectionRejected()
|
||||
{
|
||||
showErrorWindow("This game is not available. Please try another", "WrongVersion", "WrongVersion");
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onReceivedGlobals() {}
|
||||
|
||||
void PlayerConfigurer::onGameLoaded(boost::shared_ptr<bool> isWaiting)
|
||||
{
|
||||
*isWaiting = false;
|
||||
|
||||
if (!loadResolved)
|
||||
{
|
||||
loadResolved = true;
|
||||
double duration = G3D::System::time() - startTime;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dataModel->setUiMessage("Waiting for character");
|
||||
waitingForCharacter = true;
|
||||
|
||||
// see if our virtualversion is within bounds for the place
|
||||
StarterPlayerService* sps = dataModel->find<StarterPlayerService>();
|
||||
GameBasicSettings::VirtualVersion placeMinVersion = static_cast<GameBasicSettings::VirtualVersion>(sps->getMinVirtualVersion());
|
||||
GameBasicSettings::VirtualVersion placeMaxVersion = static_cast<GameBasicSettings::VirtualVersion>(sps->getMaxVirtualVersion());
|
||||
GameBasicSettings::VirtualVersion clientVersion = Aya::GameBasicSettings::singleton().getVirtualVersion();
|
||||
|
||||
if (clientVersion < placeMinVersion)
|
||||
{
|
||||
clientVersion = placeMinVersion;
|
||||
Aya::GameBasicSettings::singleton().setVirtualVersionInternal(clientVersion);
|
||||
}
|
||||
else if (clientVersion > placeMaxVersion)
|
||||
{
|
||||
clientVersion = placeMaxVersion;
|
||||
Aya::GameBasicSettings::singleton().setVirtualVersionInternal(clientVersion);
|
||||
}
|
||||
|
||||
if (Network::Players::frontendProcessing(dataModel))
|
||||
{
|
||||
std::string starterScript = "StarterScript";
|
||||
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
||||
{
|
||||
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
reportError(e.what(), "W4C");
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onPlayerIdled(double time)
|
||||
{
|
||||
if (time > 1200)
|
||||
{
|
||||
showErrorWindow(Aya::format("You were disconnected for being idle %d minutes", (int)(time / 60)), "Idle", "Idle");
|
||||
|
||||
if (Network::Client* client = dataModel->find<Network::Client>())
|
||||
client->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConfigurer::setMessage(const std::string& msg)
|
||||
{
|
||||
if (!getParamBool("GenerateTeleportJoin"))
|
||||
dataModel->setUiMessage(msg);
|
||||
else
|
||||
dataModel->setUiMessage("Teleporting...");
|
||||
}
|
||||
|
||||
void PlayerConfigurer::configure(Aya::Security::Identities identity, DataModel* dm, const std::string& args, int lm)
|
||||
{
|
||||
startTime = G3D::System::time();
|
||||
|
||||
dataModel = dm;
|
||||
launchMode = lm;
|
||||
|
||||
// Client-side A/B testing setup
|
||||
// urls to try:
|
||||
// https://api.gametest1.robloxlabs.com/users/get-experiment-enrollments
|
||||
// https://api.gametest1.robloxlabs.com/users/get-studio-experiment-enrollments
|
||||
|
||||
std::string baseUrl = Aya::ContentProvider::getUnsecureApiBaseUrl(GetBaseURL());
|
||||
|
||||
// begin fetching now
|
||||
Aya::HttpFuture abTest1, abTest2;
|
||||
if (FFlag::ClientABTestingEnabled)
|
||||
{
|
||||
abTest1 = FetchABTestDataAsync(baseUrl + "users/get-experiment-enrollments");
|
||||
abTest2 = FetchABTestDataAsync(baseUrl + "users/get-studio-experiment-enrollments");
|
||||
}
|
||||
|
||||
Aya::Security::Impersonator impersonate(identity);
|
||||
|
||||
parseArgs(args);
|
||||
|
||||
// virtual version
|
||||
int virtualVersion = getParamInt("VirtualVersion");
|
||||
GameBasicSettings::VirtualVersion vv = static_cast<GameBasicSettings::VirtualVersion>(virtualVersion);
|
||||
dataModel->setInitialVersion(vv);
|
||||
|
||||
Aya::GameBasicSettings::singleton().setVirtualVersionInternal(vv);
|
||||
|
||||
Http::gameID = getParamString("GameId");
|
||||
|
||||
testing = false; // (getParamString("ClientTicket").length() == 0);
|
||||
|
||||
if (DFFlag::UseR15Character)
|
||||
dataModel->create<Network::Client>();
|
||||
|
||||
dataModel->setPlaceID(getParamInt("PlaceId"), getParamBool("IsRobloxPlace"));
|
||||
int universeId = getParamInt("UniverseId");
|
||||
dataModel->setUniverseId(universeId);
|
||||
dataModel->create<HttpService>();
|
||||
isTouchDevice = dm->create<UserInputService>()->getTouchEnabled();
|
||||
|
||||
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "! Joining game '%s' place %d at %s", getParamString("GameId").c_str(),
|
||||
getParamInt("PlaceId"), getParamString("MachineAddress").c_str());
|
||||
|
||||
connections.push_back(dataModel->closingSignal.connect(boost::bind(&PlayerConfigurer::onGameClose, this)));
|
||||
|
||||
dataModel->create<ChangeHistoryService>()->setEnabled(false);
|
||||
dataModel->create<ContentProvider>()->setThreadPool(16);
|
||||
|
||||
setupUrls();
|
||||
|
||||
DataModel::CreatorType creatorType;
|
||||
Reflection::EnumDesc<DataModel::CreatorType>::singleton().convertToValue(getParamString("CreatorTypeEnum").c_str(), creatorType);
|
||||
dataModel->setCreatorID(getParamInt("CreatorId"), creatorType);
|
||||
|
||||
Network::Players* players = dataModel->create<Network::Players>();
|
||||
Network::Players::ChatOption chatOption;
|
||||
Reflection::EnumDesc<Network::Players::ChatOption>::singleton().convertToValue(getParamString("ChatStyle").c_str(), chatOption);
|
||||
players->setChatOption(chatOption);
|
||||
|
||||
if (!DFFlag::UseR15Character)
|
||||
dataModel->create<Network::Client>();
|
||||
dataModel->create<Visit>();
|
||||
|
||||
ifSeleniumThenSetCookie("SeleniumTest1", "Started join script");
|
||||
|
||||
try
|
||||
{
|
||||
setMessage("Connecting to Server");
|
||||
|
||||
Network::Client* client = dm->create<Network::Client>();
|
||||
|
||||
connections.push_back(client->connectionAcceptedSignal.connect(boost::bind(&PlayerConfigurer::onConnectionAccepted, this, _1, _2)));
|
||||
connections.push_back(client->connectionRejectedSignal.connect(boost::bind(&PlayerConfigurer::onConnectionRejected, this)));
|
||||
connections.push_back(client->connectionFailedSignal.connect(boost::bind(&PlayerConfigurer::onConnectionFailed, this, _1, _2, _3)));
|
||||
|
||||
client->setTicket(getParamString("ClientTicket"));
|
||||
|
||||
ifSeleniumThenSetCookie("SeleniumTest2", "Successfully connected to server");
|
||||
|
||||
client->setGameSessionID(getParamString("SessionId"));
|
||||
|
||||
shared_ptr<Network::Player> player = Instance::fastSharedDynamicCast<Network::Player>(
|
||||
client->playerConnect(getParamInt("UserId"), getParamString("MachineAddress"), getParamInt("ServerPort"), getParamInt("ClientPort"), -1));
|
||||
|
||||
// prepare callback for when the Character appears
|
||||
playerChangedConnection = player->propertyChangedSignal.connect(boost::bind(&PlayerConfigurer::onPlayerChanged, this, _1));
|
||||
|
||||
registerPlay(getParamString("CookieStoreFirstTimePlayKey"), getParamInt("UserId"), getParamInt("PlaceId"));
|
||||
if (TimerService* timer = dataModel->create<TimerService>())
|
||||
{
|
||||
timer->delay(boost::bind(&PlayerConfigurer::registerPlay, this, getParamString("CookieStoreFiveMinutePlayKey"), getParamInt("UserId"),
|
||||
getParamInt("PlaceId")),
|
||||
60 * 5.0);
|
||||
}
|
||||
|
||||
player->setSuperSafeChat(getParamBool("SuperSafeChat"));
|
||||
player->setUnder13(getParamBool("IsUnknownOrUnder13"));
|
||||
Network::Player::MembershipType membershipType;
|
||||
Reflection::EnumDesc<Network::Player::MembershipType>::singleton().convertToValue(getParamString("MembershipType").c_str(), membershipType);
|
||||
player->setMembershipType(membershipType);
|
||||
player->setAccountAge(getParamInt("AccountAge"));
|
||||
|
||||
connections.push_back(player->idledSignal.connect(boost::bind(&PlayerConfigurer::onPlayerIdled, this, _1)));
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
player->setName(getParamString("UserName"));
|
||||
}
|
||||
catch (Aya::base_exception&)
|
||||
{
|
||||
// don't care, happens when called from studio cmd bar
|
||||
}
|
||||
|
||||
// I don't know why UserName needs to do this, so I'm copying this behavior for DisplayName -sorket
|
||||
try
|
||||
{
|
||||
player->setDisplayName(getParamString("DisplayName"));
|
||||
}
|
||||
catch (Aya::base_exception&)
|
||||
{
|
||||
// don't care, happens when called from studio cmd bar
|
||||
}
|
||||
|
||||
player->setCharacterAppearance(getParamString("CharacterAppearance"));
|
||||
player->setFollowUserId(getParamInt("FollowUserId"));
|
||||
|
||||
if (!testing)
|
||||
{
|
||||
Aya::Visit* visit = dataModel->create<Aya::Visit>();
|
||||
visit->setUploadUrl("");
|
||||
}
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
reportError(e.what(), "CreatePlayer");
|
||||
}
|
||||
|
||||
|
||||
if (!testing)
|
||||
{
|
||||
gamePerfMonitor.reset(
|
||||
new GamePerfMonitor(getParamString("BaseUrl"), getParamString("GameId"), getParamInt("PlaceId"), getParamInt("UserId")));
|
||||
gamePerfMonitor->start(dataModel);
|
||||
}
|
||||
|
||||
|
||||
if (FFlag::ClientABTestingEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadABTestFromString(abTest1.get());
|
||||
}
|
||||
catch (const std::exception& e1)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadABTestFromString(abTest2.get());
|
||||
}
|
||||
catch (const std::exception& e2)
|
||||
{
|
||||
FASTLOG2(FLog::Error, "Failed to load AB test data from both URLS: [%s] [%s]", e1.what(), e2.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConfigurer::onPlayerChanged(const Reflection::PropertyDescriptor* propertyDescriptor)
|
||||
{
|
||||
if (propertyDescriptor->name == "Character")
|
||||
{
|
||||
dataModel->clearUiMessage();
|
||||
waitingForCharacter = false;
|
||||
|
||||
playerChangedConnection.disconnect();
|
||||
|
||||
if (!joinResolved)
|
||||
{
|
||||
joinResolved = true;
|
||||
|
||||
MegaClusterInstance* megaCluster = Instance::fastDynamicCast<MegaClusterInstance>(dataModel->getWorkspace()->getTerrain());
|
||||
bool hasTerrain = megaCluster && megaCluster->isAllocated();
|
||||
|
||||
playStartTime = G3D::System::time();
|
||||
playResolved = false;
|
||||
}
|
||||
|
||||
if (gamePerfMonitor)
|
||||
{
|
||||
// delay collecting network stats by 2 mins after character is resolved
|
||||
if (TimerService* timer = dataModel->create<TimerService>())
|
||||
{
|
||||
timer->delay(boost::bind(&GamePerfMonitor::setPostDiagStats, gamePerfMonitor, true), 2 * 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isHidden(const boost::filesystem::path& p)
|
||||
{
|
||||
boost::filesystem::path name = p.filename();
|
||||
if (name != ".." && name != "." && name.c_str()[0] == '.')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool StudioConfigurer::findModulesAndLoad(
|
||||
const std::string& baseModulePath, const boost::filesystem::path& dir_path, boost::unordered_map<std::string, ProtectedString>& coreModules)
|
||||
{
|
||||
if (!boost::filesystem::exists(dir_path))
|
||||
return false;
|
||||
|
||||
boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
|
||||
for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr)
|
||||
{
|
||||
if (is_directory(itr->status()) && !isHidden(itr->path()))
|
||||
{
|
||||
if (findModulesAndLoad(baseModulePath, itr->path(), coreModules))
|
||||
return true;
|
||||
}
|
||||
|
||||
else if (is_regular_file(itr->status()))
|
||||
{
|
||||
boost::filesystem::path currentPath = itr->path();
|
||||
std::string filePath = currentPath.string();
|
||||
|
||||
if (filePath.find(".lua") != std::string::npos)
|
||||
{
|
||||
size_t pathLocation = filePath.find(baseModulePath);
|
||||
if (pathLocation != std::string::npos)
|
||||
{
|
||||
filePath = filePath.substr(pathLocation + baseModulePath.length() + 1);
|
||||
filePath = filePath.substr(0, filePath.length() - 4);
|
||||
}
|
||||
|
||||
// load module
|
||||
if (!filePath.empty())
|
||||
{
|
||||
ProtectedString source = Aya::CoreScript::fetchSource("/Modules/" + filePath).get();
|
||||
if (!source.empty())
|
||||
{
|
||||
coreModules[filePath] = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<Aya::Folder> moduleScriptFolder;
|
||||
|
||||
void StudioConfigurer::loadCoreModules()
|
||||
{
|
||||
Aya::CoreGuiService* coreGuiService = Aya::ServiceProvider::find<Aya::CoreGuiService>(dataModel);
|
||||
|
||||
if (!coreGuiService)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<Aya::ScreenGui> robloxScreenGui = coreGuiService->getRobloxScreenGui();
|
||||
if (!robloxScreenGui)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
moduleScriptFolder = Aya::Creatable<Instance>::create<Aya::Folder>();
|
||||
moduleScriptFolder->setName("Modules");
|
||||
moduleScriptFolder->setRobloxLocked(true);
|
||||
moduleScriptFolder->setParent(Aya::Instance::fastDynamicCast<Aya::Instance>(robloxScreenGui.get()));
|
||||
|
||||
boost::unordered_map<std::string, ProtectedString> coreModules;
|
||||
|
||||
if (LuaVM::canCompileScripts())
|
||||
{
|
||||
const std::string path = Aya::BaseScript::hasCoreScriptReplacements() ? Aya::BaseScript::adminScriptsPath + "/Modules"
|
||||
: ContentProvider::assetFolder() + "scripts/Modules";
|
||||
|
||||
boost::filesystem::path filePath(path);
|
||||
if (!boost::filesystem::exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
findModulesAndLoad(filePath.string(), filePath, coreModules);
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::unordered_map<std::string, std::string> byteCodeModules = LuaVM::getBytecodeCoreModules();
|
||||
for (boost::unordered_map<std::string, std::string>::iterator iter = byteCodeModules.begin(); iter != byteCodeModules.end(); ++iter)
|
||||
{
|
||||
coreModules[Aya::rot13((*iter).first)] = ProtectedString::fromBytecode((*iter).second);
|
||||
}
|
||||
}
|
||||
|
||||
for (boost::unordered_map<std::string, ProtectedString>::iterator iter = coreModules.begin(); iter != coreModules.end(); ++iter)
|
||||
{
|
||||
shared_ptr<ModuleScript> moduleScript = Aya::Creatable<Instance>::create<ModuleScript>();
|
||||
|
||||
std::string name = (*iter).first;
|
||||
shared_ptr<Aya::Folder> lastFolder = moduleScriptFolder;
|
||||
|
||||
boost::filesystem::path namePath(name);
|
||||
if (namePath.has_parent_path())
|
||||
{
|
||||
const std::string delimiter = "\\";
|
||||
|
||||
while (name.find("/") != std::string::npos)
|
||||
{
|
||||
name.replace(name.find("/"), 1, delimiter);
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
int level = 0;
|
||||
std::string token;
|
||||
while ((pos = name.find(delimiter)) != std::string::npos)
|
||||
{
|
||||
token = name.substr(0, pos);
|
||||
name.erase(0, pos + delimiter.length());
|
||||
|
||||
if (Instance* findFolder = moduleScriptFolder->findFirstChildByNameRecursive(token))
|
||||
{
|
||||
if (Aya::Folder* folderInstance = Instance::fastDynamicCast<Aya::Folder>(findFolder))
|
||||
{
|
||||
lastFolder = shared_from(folderInstance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shared_ptr<Aya::Folder> newFolder = Aya::Creatable<Instance>::create<Aya::Folder>();
|
||||
newFolder->setName(token);
|
||||
newFolder->setRobloxLocked(true);
|
||||
if (level == 0)
|
||||
{
|
||||
newFolder->setParent(moduleScriptFolder.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
newFolder->setParent(lastFolder.get());
|
||||
}
|
||||
lastFolder = newFolder;
|
||||
}
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
moduleScript->setName(name);
|
||||
moduleScript->setSource((*iter).second);
|
||||
moduleScript->setRobloxLocked(true);
|
||||
|
||||
moduleScript->setParent(lastFolder.get());
|
||||
}
|
||||
}
|
||||
|
||||
void StudioConfigurer::configure(Aya::Security::Identities identity, DataModel* dm, const std::string& args, int launchMode)
|
||||
{
|
||||
dataModel = dm;
|
||||
parseArgs(args);
|
||||
|
||||
std::string baseUrl = getParamString("BaseUrl");
|
||||
|
||||
dataModel->create<InsertService>();
|
||||
|
||||
setupUrls();
|
||||
|
||||
if (Network::Players::frontendProcessing(dataModel))
|
||||
{
|
||||
loadCoreModules();
|
||||
|
||||
// Really ugly hack since getting the datamodel is a pain in the fucking ass
|
||||
Aya::GameBasicSettings::singleton().virtualVersionChangedSignal.connect(
|
||||
[&](const GameBasicSettings::VirtualVersion version)
|
||||
{
|
||||
if (version == GameBasicSettings::VERSION_2016 && !moduleScriptFolder->getChildren2())
|
||||
{
|
||||
boost::unordered_map<std::string, ProtectedString> coreModules;
|
||||
|
||||
if (LuaVM::canCompileScripts())
|
||||
{
|
||||
const std::string path = Aya::BaseScript::hasCoreScriptReplacements() ? Aya::BaseScript::adminScriptsPath + "/Modules"
|
||||
: ContentProvider::assetFolder() + "scripts/Modules";
|
||||
|
||||
boost::filesystem::path filePath(path);
|
||||
if (!boost::filesystem::exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
findModulesAndLoad(filePath.string(), filePath, coreModules);
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::unordered_map<std::string, std::string> byteCodeModules = LuaVM::getBytecodeCoreModules();
|
||||
for (boost::unordered_map<std::string, std::string>::iterator iter = byteCodeModules.begin(); iter != byteCodeModules.end();
|
||||
++iter)
|
||||
{
|
||||
coreModules[Aya::rot13((*iter).first)] = ProtectedString::fromBytecode((*iter).second);
|
||||
}
|
||||
}
|
||||
|
||||
for (boost::unordered_map<std::string, ProtectedString>::iterator iter = coreModules.begin(); iter != coreModules.end(); ++iter)
|
||||
{
|
||||
shared_ptr<ModuleScript> moduleScript = Aya::Creatable<Instance>::create<ModuleScript>();
|
||||
|
||||
std::string name = (*iter).first;
|
||||
shared_ptr<Aya::Folder> lastFolder = moduleScriptFolder;
|
||||
|
||||
boost::filesystem::path namePath(name);
|
||||
if (namePath.has_parent_path())
|
||||
{
|
||||
const std::string delimiter = "\\";
|
||||
|
||||
while (name.find("/") != std::string::npos)
|
||||
{
|
||||
name.replace(name.find("/"), 1, delimiter);
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
int level = 0;
|
||||
std::string token;
|
||||
while ((pos = name.find(delimiter)) != std::string::npos)
|
||||
{
|
||||
token = name.substr(0, pos);
|
||||
name.erase(0, pos + delimiter.length());
|
||||
|
||||
if (Instance* findFolder = moduleScriptFolder->findFirstChildByNameRecursive(token))
|
||||
{
|
||||
if (Aya::Folder* folderInstance = Instance::fastDynamicCast<Aya::Folder>(findFolder))
|
||||
{
|
||||
lastFolder = shared_from(folderInstance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shared_ptr<Aya::Folder> newFolder = Aya::Creatable<Instance>::create<Aya::Folder>();
|
||||
newFolder->setName(token);
|
||||
newFolder->setRobloxLocked(true);
|
||||
if (level == 0)
|
||||
{
|
||||
newFolder->setParent(moduleScriptFolder.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
newFolder->setParent(lastFolder.get());
|
||||
}
|
||||
lastFolder = newFolder;
|
||||
}
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
moduleScript->setName(name);
|
||||
moduleScript->setSource((*iter).second);
|
||||
moduleScript->setRobloxLocked(true);
|
||||
Aya::StandardOut::singleton()->printf(MESSAGE_INFO, "loading %s", name.c_str());
|
||||
moduleScript->setParent(lastFolder.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(AYA_PLATFORM_DURANGO)
|
||||
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
||||
{
|
||||
if (starterScript.empty())
|
||||
starterScript = "StarterScript";
|
||||
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(AYA_STUDIO) && ENABLE_XBOX_STUDIO_BUILD
|
||||
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
||||
{
|
||||
starterScript = "XStarterScript";
|
||||
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// this will be called in case of old play solo
|
||||
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
||||
{
|
||||
AyaService* ayaService = ServiceProvider::create<AyaService>(dataModel);
|
||||
|
||||
{
|
||||
starterScript = "StarterScript";
|
||||
|
||||
if (Network::Players::backendProcessing(dataModel))
|
||||
starterScript = "ServerCoreScripts/" + starterScript;
|
||||
|
||||
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
||||
}
|
||||
}
|
||||
}
|
||||
121
engine/network/src/GameConfigurer.hpp
Normal file
121
engine/network/src/GameConfigurer.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include "Reflection/Type.hpp"
|
||||
#include "Reflection/Property.hpp"
|
||||
#include "signal.hpp"
|
||||
|
||||
#include "Security/SecurityContext.hpp"
|
||||
|
||||
|
||||
namespace boost
|
||||
{
|
||||
namespace filesystem
|
||||
{
|
||||
class path;
|
||||
}
|
||||
} // namespace boost
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class DataModel;
|
||||
class Instance;
|
||||
class GamePerfMonitor;
|
||||
class ProtectedString;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
class Replicator;
|
||||
}
|
||||
|
||||
class GameConfigurer
|
||||
{
|
||||
public:
|
||||
GameConfigurer() {};
|
||||
virtual ~GameConfigurer() {}
|
||||
|
||||
protected:
|
||||
DataModel* dataModel;
|
||||
boost::shared_ptr<const Reflection::ValueTable> parameters;
|
||||
|
||||
void parseArgs(const std::string& args);
|
||||
int getParamInt(const std::string& key);
|
||||
std::string getParamString(const std::string& key);
|
||||
bool getParamBool(const std::string& key);
|
||||
void registerPlay(const std::string& key, int userId, int placeId);
|
||||
|
||||
void setupUrls();
|
||||
|
||||
public:
|
||||
virtual void configure(Aya::Security::Identities identity, DataModel* dm, const std::string& args, int launchMode = -1) = 0;
|
||||
};
|
||||
|
||||
class PlayerConfigurer : public GameConfigurer
|
||||
{
|
||||
bool testing;
|
||||
bool isTouchDevice;
|
||||
bool connectResolved;
|
||||
bool connectionFailed;
|
||||
bool loadResolved;
|
||||
bool joinResolved;
|
||||
bool playResolved;
|
||||
G3D::RealTime startTime;
|
||||
G3D::RealTime playStartTime;
|
||||
bool waitingForCharacter;
|
||||
int launchMode;
|
||||
|
||||
boost::shared_ptr<GamePerfMonitor> gamePerfMonitor;
|
||||
|
||||
Aya::signals::scoped_connection playerChangedConnection;
|
||||
|
||||
std::vector<Aya::signals::connection> connections;
|
||||
|
||||
void ifSeleniumThenSetCookie(const std::string& key, const std::string& value);
|
||||
void showErrorWindow(const std::string& message, const std::string& errorType, const std::string& errorCategory);
|
||||
|
||||
void reportError(const std::string& error, const std::string& msg);
|
||||
void reportCounter(const std::string& counterNamesCSV, bool blocking);
|
||||
void reportStats(const std::string& category, float value);
|
||||
void reportDuration(const std::string& category, const std::string& result, double duration, bool blocking);
|
||||
|
||||
void requestCharacter(boost::shared_ptr<Network::Replicator> replicator, boost::shared_ptr<bool> isWaiting);
|
||||
|
||||
void onGameClose();
|
||||
void onDisconnection(const std::string& peer, bool lostConnection);
|
||||
void onConnectionAccepted(std::string url, boost::shared_ptr<Instance> replicator);
|
||||
void onConnectionFailed(const std::string& remoteAddress, int errorCode, const std::string& errorMsg);
|
||||
void onConnectionRejected();
|
||||
void onReceivedGlobals();
|
||||
void onGameLoaded(boost::shared_ptr<bool> isWaiting);
|
||||
void onPlayerIdled(double time);
|
||||
void onPlayerChanged(const Reflection::PropertyDescriptor* propertyDescriptor);
|
||||
|
||||
void setMessage(const std::string& msg);
|
||||
|
||||
public:
|
||||
PlayerConfigurer();
|
||||
~PlayerConfigurer();
|
||||
|
||||
/*override*/ void configure(Aya::Security::Identities identity, DataModel* dm, const std::string& args, int launchMode = -1);
|
||||
};
|
||||
|
||||
class StudioConfigurer : public GameConfigurer
|
||||
{
|
||||
private:
|
||||
bool findModulesAndLoad(
|
||||
const std::string& baseModulePath, const boost::filesystem::path& dir_path, boost::unordered_map<std::string, ProtectedString>& coreModules);
|
||||
void loadCoreModules();
|
||||
|
||||
public:
|
||||
StudioConfigurer() {}
|
||||
~StudioConfigurer() {}
|
||||
|
||||
std::string starterScript;
|
||||
/*override*/ void configure(Aya::Security::Identities identity, DataModel* dm, const std::string& args, int launchMode = -1);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
81
engine/network/src/GamePerfMonitor.cpp
Normal file
81
engine/network/src/GamePerfMonitor.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "GamePerfMonitor.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/Stats.hpp"
|
||||
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
|
||||
#include "DataModel/TimerService.hpp"
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "Utility/RunStateOwner.hpp"
|
||||
|
||||
|
||||
#include "Players.hpp"
|
||||
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
|
||||
FASTINTVARIABLE(GamePerfMonitorPercentage, 2)
|
||||
FASTINTVARIABLE(GamePerfMonitorReportTimer, 10) // minutes
|
||||
|
||||
GamePerfMonitor::~GamePerfMonitor() {}
|
||||
|
||||
void GamePerfMonitor::collectAndPostStats(boost::weak_ptr<DataModel> dataModel)
|
||||
{
|
||||
boost::shared_ptr<DataModel> sharedDM = dataModel.lock();
|
||||
if (!sharedDM)
|
||||
return;
|
||||
|
||||
if (sharedDM->isClosed())
|
||||
return;
|
||||
|
||||
postDiagStats(sharedDM);
|
||||
|
||||
if (TimerService* timer = sharedDM->create<TimerService>())
|
||||
{
|
||||
timer->delay(boost::bind(&GamePerfMonitor::collectAndPostStats, this, dataModel), FInt::GamePerfMonitorReportTimer * 60);
|
||||
}
|
||||
}
|
||||
|
||||
void GamePerfMonitor::postDiagStats(boost::shared_ptr<DataModel> dataModel)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void GamePerfMonitor::onGameClose(boost::weak_ptr<DataModel> dataModel)
|
||||
{
|
||||
boost::shared_ptr<DataModel> sharedDM = dataModel.lock();
|
||||
if (!sharedDM)
|
||||
return;
|
||||
}
|
||||
|
||||
void GamePerfMonitor::start(DataModel* dataModel)
|
||||
{
|
||||
if (!dataModel)
|
||||
return;
|
||||
|
||||
Stats::StatsService* statsService = dataModel->create<Stats::StatsService>();
|
||||
if (statsService)
|
||||
frmStats = shared_from(statsService->findFirstChildByName("FrameRateManager"));
|
||||
|
||||
bool shouldPost = (abs(userId) % 100) < FInt::GamePerfMonitorPercentage;
|
||||
dataModel->setJobsExtendedStatsWindow(30);
|
||||
|
||||
if (shouldPost)
|
||||
{
|
||||
if (TimerService* timer = dataModel->create<TimerService>())
|
||||
{
|
||||
timer->delay(boost::bind(&GamePerfMonitor::collectAndPostStats, this, weak_from(dataModel)), 60);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldPost)
|
||||
{
|
||||
dataModel->closingSignal.connect(boost::bind(&GamePerfMonitor::onGameClose, this, weak_from(dataModel)));
|
||||
}
|
||||
}
|
||||
51
engine/network/src/GamePerfMonitor.hpp
Normal file
51
engine/network/src/GamePerfMonitor.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class DataModel;
|
||||
class Instance;
|
||||
|
||||
class GamePerfMonitor
|
||||
{
|
||||
std::string baseUrl;
|
||||
std::string jobId;
|
||||
int placeId;
|
||||
int userId;
|
||||
bool shouldPostDiagStats;
|
||||
|
||||
boost::shared_ptr<Instance> frmStats; // keep a shared pointer here so we can still use it after RenderView shuts down
|
||||
|
||||
void collectAndPostStats(boost::weak_ptr<DataModel> dataModel);
|
||||
void postDiagStats(boost::shared_ptr<DataModel> dataModel);
|
||||
|
||||
void onGameClose(boost::weak_ptr<DataModel> dataModel);
|
||||
|
||||
|
||||
public:
|
||||
GamePerfMonitor(const std::string& baseUrl, const std::string& jobId, int placeId, int userId)
|
||||
: baseUrl(baseUrl)
|
||||
, jobId(jobId)
|
||||
, placeId(placeId)
|
||||
, userId(userId)
|
||||
, shouldPostDiagStats(false) {};
|
||||
~GamePerfMonitor();
|
||||
|
||||
void start(DataModel* dataModel);
|
||||
|
||||
void setPostDiagStats(bool shouldPost)
|
||||
{
|
||||
shouldPostDiagStats = shouldPost;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
10
engine/network/src/GuidRegistryService.cpp
Normal file
10
engine/network/src/GuidRegistryService.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "GuidRegistryService.hpp"
|
||||
|
||||
const char* const Aya::Network::sGuidRegistryService = "GuidRegistryService";
|
||||
|
||||
Aya::Network::GuidRegistryService::GuidRegistryService(void)
|
||||
: registry(Registry::create())
|
||||
{
|
||||
}
|
||||
|
||||
Aya::Network::GuidRegistryService::~GuidRegistryService(void) {}
|
||||
23
engine/network/src/GuidRegistryService.hpp
Normal file
23
engine/network/src/GuidRegistryService.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Service.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
extern const char* const sGuidRegistryService;
|
||||
class GuidRegistryService
|
||||
: public DescribedNonCreatable<GuidRegistryService, Instance, sGuidRegistryService>
|
||||
, public Service
|
||||
{
|
||||
public:
|
||||
boost::intrusive_ptr<GuidItem<Instance>::Registry> const registry;
|
||||
GuidRegistryService(void);
|
||||
~GuidRegistryService(void);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
336
engine/network/src/InterpolatingPhysicsReceiver.cpp
Normal file
336
engine/network/src/InterpolatingPhysicsReceiver.cpp
Normal file
@@ -0,0 +1,336 @@
|
||||
|
||||
#if 1 // remove this file when fast flag RemoveInterpolationReciever is accepted and removed
|
||||
|
||||
#include "InterpolatingPhysicsReceiver.hpp"
|
||||
#include "Compressor.hpp"
|
||||
#include "RakNet/GetTime.hpp"
|
||||
#include "RakNet/RakPeerInterface.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
|
||||
#include "Utility/ObscureValue.hpp"
|
||||
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
FASTFLAG(RemoveInterpolationReciever)
|
||||
|
||||
class InterpolatingPhysicsReceiver::Job : public ReplicatorJob
|
||||
{
|
||||
weak_ptr<InterpolatingPhysicsReceiver> receiver;
|
||||
ObscureValue<double> receiveRate;
|
||||
|
||||
public:
|
||||
Job(shared_ptr<InterpolatingPhysicsReceiver> receiver)
|
||||
: ReplicatorJob("Net InterpolatePhysics", *(receiver->replicator), DataModelJob::PhysicsIn)
|
||||
, receiver(receiver)
|
||||
, receiveRate(replicator->settings().getReceiveRate())
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, receiveRate);
|
||||
}
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
return computeStandardError(stats, receiveRate);
|
||||
}
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (shared_ptr<InterpolatingPhysicsReceiver> safeReceiver = receiver.lock())
|
||||
{
|
||||
safeReceiver->step(RakNet::GetTimeMS());
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
static double lerpValue = 0.05;
|
||||
|
||||
InterpolatingPhysicsReceiver::InterpolatingPhysicsReceiver(Replicator* replicator, bool isServer)
|
||||
: PhysicsReceiver(replicator, isServer)
|
||||
, outOfOrderMechanisms(lerpValue)
|
||||
{
|
||||
if (FFlag::RemoveInterpolationReciever)
|
||||
{
|
||||
AYAASSERT(false); // nobody should be creating this
|
||||
}
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::start(shared_ptr<PhysicsReceiver> physicsReceiver)
|
||||
{
|
||||
shared_ptr<InterpolatingPhysicsReceiver> interpolatingPhysicsReceiver =
|
||||
boost::dynamic_pointer_cast<InterpolatingPhysicsReceiver>(physicsReceiver);
|
||||
if (interpolatingPhysicsReceiver.get() != this)
|
||||
AYACRASH();
|
||||
|
||||
tryToCreateJob(interpolatingPhysicsReceiver);
|
||||
if (!job)
|
||||
serviceProviderConnection = replicator->ancestryChangedSignal.connect(
|
||||
boost::bind(&InterpolatingPhysicsReceiver::onAncestryChanged, this, interpolatingPhysicsReceiver));
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::tryToCreateJob(shared_ptr<InterpolatingPhysicsReceiver> interpolatingPhysicsReceiver)
|
||||
{
|
||||
AYAASSERT(interpolatingPhysicsReceiver.get() == this);
|
||||
AYAASSERT(!job);
|
||||
|
||||
// We can't create the job until we're in the DataModel
|
||||
if (!DataModel::get(replicator))
|
||||
return;
|
||||
|
||||
job.reset(new Job(interpolatingPhysicsReceiver));
|
||||
TaskScheduler::singleton().add(job);
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::onAncestryChanged(shared_ptr<InterpolatingPhysicsReceiver> interpolatingPhysicsReceiver)
|
||||
{
|
||||
tryToCreateJob(interpolatingPhysicsReceiver);
|
||||
if (job)
|
||||
// We've succeeded in creating the job
|
||||
serviceProviderConnection.disconnect();
|
||||
}
|
||||
|
||||
InterpolatingPhysicsReceiver::~InterpolatingPhysicsReceiver()
|
||||
{
|
||||
TaskScheduler::singleton().remove(job);
|
||||
}
|
||||
|
||||
|
||||
void InterpolatingPhysicsReceiver::Nugget::receive(
|
||||
RakNet::BitStream& bitstream, RakNet::Time timeStamp, const ModelInstance* noLagModel, InterpolatingPhysicsReceiver* receiver)
|
||||
{
|
||||
if (history->count > 0)
|
||||
{
|
||||
// Check for out-of-order data
|
||||
if (timeStamp < history->data[history->last].networkTime)
|
||||
{
|
||||
MechanismItem dummy;
|
||||
int numNodesInHistory;
|
||||
receiver->receiveMechanism(bitstream, part.get(), dummy, timeStamp, numNodesInHistory);
|
||||
receiver->outOfOrderMechanisms.sample(1);
|
||||
return;
|
||||
}
|
||||
else
|
||||
receiver->outOfOrderMechanisms.sample(0);
|
||||
}
|
||||
|
||||
history->last = (history->last + 1 + bufferSize) % bufferSize;
|
||||
if (history->count < bufferSize)
|
||||
history->count++;
|
||||
|
||||
MechanismItem& item = history->data[history->last];
|
||||
|
||||
int numNodesInHistory;
|
||||
receiver->receiveMechanism(bitstream, part.get(), item, timeStamp, numNodesInHistory);
|
||||
item.networkTime = timeStamp;
|
||||
|
||||
// Now compute the lag
|
||||
if (noLagModel && part && part->isDescendantOf(noLagModel))
|
||||
{
|
||||
lag = 0;
|
||||
}
|
||||
else if (history->count >= 2)
|
||||
{
|
||||
const int index2 = (history->last - 1 + bufferSize) % bufferSize;
|
||||
double newLag = (double)(item.networkTime - history->data[index2].networkTime);
|
||||
|
||||
// Add a lag buffer
|
||||
const double physicsReceiveDelayFactor = 1.2;
|
||||
newLag *= physicsReceiveDelayFactor;
|
||||
|
||||
// Use an asymetric log average that favors large values.
|
||||
// Small values will be large in number, so they will win out
|
||||
// over time.
|
||||
#if 1
|
||||
lag = (lag * lag + newLag * newLag) / (lag + newLag);
|
||||
#else
|
||||
if (newLag > lag)
|
||||
lag = 0.8 * newLag + 0.2 * lag;
|
||||
else
|
||||
lag = 0.2 * newLag + 0.8 * lag;
|
||||
#endif
|
||||
}
|
||||
receiver->sampleLag(lag);
|
||||
}
|
||||
|
||||
|
||||
void InterpolatingPhysicsReceiver::setLerpedPhysics(const MechanismItem& itemBefore, const MechanismItem& itemAfter, float lerpAlpha)
|
||||
{
|
||||
// For efficiency we don't bother lerping near maxima
|
||||
if (lerpAlpha <= 0.001)
|
||||
setPhysics(itemBefore);
|
||||
else if (lerpAlpha >= 0.99)
|
||||
setPhysics(itemAfter);
|
||||
else if (!MechanismItem::consistent(&itemBefore, &itemAfter))
|
||||
setPhysics(itemAfter);
|
||||
else
|
||||
{
|
||||
MechanismItem::lerp(&itemBefore, &itemAfter, &reusableMechanismItem, lerpAlpha);
|
||||
setPhysics(reusableMechanismItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool InterpolatingPhysicsReceiver::Nugget::step(RakNet::Time time, InterpolatingPhysicsReceiver* receiver) const
|
||||
{
|
||||
if (part->getDragging())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!part->getPartPrimitive()->getAssembly())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!receiver->okDistributedReceivePart(part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (history->count == 0)
|
||||
return true;
|
||||
|
||||
if (history->count == 1)
|
||||
{
|
||||
receiver->setPhysics(history->data[history->last]);
|
||||
return true;
|
||||
}
|
||||
|
||||
const double laggedTime = time - lag;
|
||||
|
||||
const MechanismItem* itemAfter = 0;
|
||||
int i;
|
||||
for (i = 0; i < history->count; ++i)
|
||||
{
|
||||
const int index = (history->last - i + bufferSize) % bufferSize;
|
||||
|
||||
const MechanismItem& item(history->data[index]);
|
||||
|
||||
if (item.networkTime > laggedTime)
|
||||
{
|
||||
itemAfter = &item;
|
||||
}
|
||||
else if (itemAfter == 0)
|
||||
{
|
||||
itemAfter = &item;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
float lerpAlpha = (float)(laggedTime - item.networkTime) / (float)(itemAfter->networkTime - item.networkTime);
|
||||
// TODO: Allow for forward-looking lerpAlpha up to a point
|
||||
receiver->setLerpedPhysics(item, *itemAfter, lerpAlpha);
|
||||
receiver->sampleBufferSeek(i + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
receiver->sampleBufferSeek(i);
|
||||
receiver->setPhysics(*itemAfter);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::step(RakNet::Time time)
|
||||
{
|
||||
lagStats.clearBufferAccumulator();
|
||||
Nuggets::index<Nugget::part_tag>::type& index = nuggets.get<Nugget::part_tag>();
|
||||
Nuggets::index_iterator<Nugget::part_tag>::type iter = index.begin();
|
||||
Nuggets::index_iterator<Nugget::part_tag>::type end = index.end();
|
||||
while (iter != end)
|
||||
{
|
||||
// TODO: Erase if no longer relevant
|
||||
if (!(*iter).step(time, this))
|
||||
iter = index.erase(iter);
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
lagStats.sampleBufferAccumulator();
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::receivePacket(RakNet::BitStream& inBitstream, RakNet::Time timeStamp, ReplicatorStats::PhysicsReceiverStats* stats)
|
||||
{
|
||||
lagStats.clearLagAccumulator();
|
||||
Nuggets::index<Nugget::part_tag>::type& index = nuggets.get<Nugget::part_tag>();
|
||||
|
||||
const ModelInstance* noLagModel = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
shared_ptr<PartInstance> part;
|
||||
|
||||
if (!receiveRootPart(part, inBitstream))
|
||||
{
|
||||
break; // no instance - done
|
||||
}
|
||||
|
||||
Nuggets::index_iterator<Nugget::part_tag>::type it = index.find(part);
|
||||
|
||||
if (it != index.end())
|
||||
{
|
||||
index.modify(it, boost::bind(&Nugget::receive, _1, boost::ref(inBitstream), timeStamp, noLagModel, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
Nugget nugget(part);
|
||||
nugget.receive(inBitstream, timeStamp, noLagModel, this);
|
||||
if (part)
|
||||
index.insert(nugget);
|
||||
}
|
||||
}
|
||||
lagStats.sampleLagAccumulator();
|
||||
}
|
||||
|
||||
|
||||
InterpolatingPhysicsReceiver::LagStats::LagStats()
|
||||
: averageBufferSeek(lerpValue)
|
||||
, averageLag(lerpValue)
|
||||
, maxBufferSeek(0)
|
||||
{
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::LagStats::clearBufferAccumulator()
|
||||
{
|
||||
bufferSeekAccumulator = AccumulatorSet();
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::LagStats::sampleBufferAccumulator()
|
||||
{
|
||||
if (boost::accumulators::count(bufferSeekAccumulator) > 0)
|
||||
averageBufferSeek.sample(boost::accumulators::mean(bufferSeekAccumulator));
|
||||
}
|
||||
void InterpolatingPhysicsReceiver::LagStats::clearLagAccumulator()
|
||||
{
|
||||
lagAccumulator = AccumulatorSet();
|
||||
}
|
||||
|
||||
void InterpolatingPhysicsReceiver::LagStats::sampleLagAccumulator()
|
||||
{
|
||||
if (boost::accumulators::count(lagAccumulator) > 0)
|
||||
averageLag.sample(boost::accumulators::mean(lagAccumulator));
|
||||
}
|
||||
|
||||
#endif // remove this file when fast flag RemoveInterpolationReciever is accepted and removed
|
||||
187
engine/network/src/InterpolatingPhysicsReceiver.hpp
Normal file
187
engine/network/src/InterpolatingPhysicsReceiver.hpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#if 1 // remove this file when fast flag RemoveInterpolationReciever is accepted and removed
|
||||
|
||||
#pragma once
|
||||
#include "PhysicsReceiver.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
#include "BoostAppend.hpp"
|
||||
#include "RunningAverage.hpp"
|
||||
|
||||
#include "Nil.hpp"
|
||||
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
|
||||
#include <boost/accumulators/accumulators.hpp>
|
||||
#include <boost/accumulators/statistics/stats.hpp>
|
||||
#include <boost/accumulators/statistics/mean.hpp>
|
||||
|
||||
|
||||
using boost::multi_index_container;
|
||||
using namespace boost::multi_index;
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class ModelInstance;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
/// Keeps a history of prior data and interpolates position data by introducing lag
|
||||
class InterpolatingPhysicsReceiver : public PhysicsReceiver
|
||||
{
|
||||
class Nugget
|
||||
{
|
||||
public:
|
||||
struct part_tag
|
||||
{
|
||||
};
|
||||
struct lastUpdate_tag
|
||||
{
|
||||
};
|
||||
|
||||
// Index fields
|
||||
shared_ptr<PartInstance> part;
|
||||
RakNet::Time lastUpdate;
|
||||
|
||||
// Non-indexed fields
|
||||
double lag;
|
||||
|
||||
static const int bufferSize = 40;
|
||||
struct History
|
||||
{
|
||||
// TODO: Use boost::circular_buffer
|
||||
int last;
|
||||
int count;
|
||||
MechanismItem data[bufferSize];
|
||||
History()
|
||||
: last(0)
|
||||
, count(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
shared_ptr<History> history;
|
||||
|
||||
Nugget(const shared_ptr<PartInstance>& part)
|
||||
: part(part)
|
||||
, lag(0)
|
||||
, history(new History())
|
||||
{
|
||||
}
|
||||
|
||||
void receive(RakNet::BitStream& bitsream, RakNet::Time timeStamp, const ModelInstance* noLagModel, InterpolatingPhysicsReceiver* receiver);
|
||||
bool step(RakNet::Time time, InterpolatingPhysicsReceiver* receiver) const; // returns false if the item should be removed from the list
|
||||
};
|
||||
|
||||
typedef multi_index_container<Nugget,
|
||||
indexed_by<hashed_unique<tag<Nugget::part_tag>, BOOST_MULTI_INDEX_MEMBER(Nugget, shared_ptr<PartInstance>, part)>,
|
||||
ordered_non_unique<tag<Nugget::lastUpdate_tag>, BOOST_MULTI_INDEX_MEMBER(Nugget, RakNet::Time, lastUpdate)>>>
|
||||
Nuggets;
|
||||
Nuggets nuggets;
|
||||
|
||||
class Job;
|
||||
shared_ptr<Job> job;
|
||||
Aya::signals::scoped_connection serviceProviderConnection;
|
||||
|
||||
class LagStats
|
||||
{
|
||||
private:
|
||||
typedef boost::accumulators::accumulator_set<double, boost::accumulators::stats<boost::accumulators::tag::mean>> AccumulatorSet;
|
||||
AccumulatorSet bufferSeekAccumulator;
|
||||
AccumulatorSet lagAccumulator;
|
||||
static void gccWorkaround()
|
||||
{
|
||||
// These statements eliminate "defined but not used" warnings
|
||||
/*
|
||||
boost::accumulators::extract::quantile;
|
||||
boost::accumulators::extract::sum;
|
||||
boost::accumulators::extract::sum_of_weights;
|
||||
boost::accumulators::extract::sum_of_variates;
|
||||
boost::accumulators::extract::mean;
|
||||
boost::accumulators::extract::mean_of_weights;
|
||||
boost::accumulators::extract::tail_mean;
|
||||
*/
|
||||
}
|
||||
|
||||
RunningAverage<double> averageBufferSeek;
|
||||
RunningAverage<double> averageLag;
|
||||
|
||||
public:
|
||||
unsigned int maxBufferSeek;
|
||||
LagStats();
|
||||
|
||||
void clearBufferAccumulator();
|
||||
void accumulateBufferSeek(unsigned int bufferSeek)
|
||||
{
|
||||
if (bufferSeek > maxBufferSeek)
|
||||
maxBufferSeek = bufferSeek;
|
||||
bufferSeekAccumulator(bufferSeek);
|
||||
}
|
||||
void sampleBufferAccumulator();
|
||||
|
||||
void clearLagAccumulator();
|
||||
void accumulateLag(double lag)
|
||||
{
|
||||
lagAccumulator(lag);
|
||||
}
|
||||
void sampleLagAccumulator();
|
||||
|
||||
unsigned int getMaxBufferSeek() const
|
||||
{
|
||||
return maxBufferSeek;
|
||||
}
|
||||
double getAverageBufferSeek() const
|
||||
{
|
||||
return averageBufferSeek.value();
|
||||
}
|
||||
double getAverageLag() const
|
||||
{
|
||||
return averageLag.value();
|
||||
}
|
||||
};
|
||||
LagStats lagStats;
|
||||
|
||||
MechanismItem reusableMechanismItem;
|
||||
|
||||
public:
|
||||
RunningAverage<double> outOfOrderMechanisms;
|
||||
InterpolatingPhysicsReceiver(Replicator* replicator, bool isServer);
|
||||
~InterpolatingPhysicsReceiver();
|
||||
/*override*/ void start(shared_ptr<PhysicsReceiver> physicsReceiver);
|
||||
void setLerpedPhysics(const MechanismItem& itemBefore, const MechanismItem& itemAfter, float lerpAlpha);
|
||||
virtual void receivePacket(RakNet::BitStream& bitsream, RakNet::Time timeStamp, ReplicatorStats::PhysicsReceiverStats* stats);
|
||||
void sampleBufferSeek(unsigned int bufferSeek)
|
||||
{
|
||||
lagStats.accumulateBufferSeek(bufferSeek);
|
||||
}
|
||||
void sampleLag(double lag)
|
||||
{
|
||||
lagStats.accumulateLag(lag);
|
||||
}
|
||||
unsigned int getMaxBufferSeek() const
|
||||
{
|
||||
return lagStats.maxBufferSeek;
|
||||
}
|
||||
double getAverageBufferSeek() const
|
||||
{
|
||||
return lagStats.getAverageBufferSeek();
|
||||
}
|
||||
double getAverageLag() const
|
||||
{
|
||||
return lagStats.getAverageLag();
|
||||
}
|
||||
|
||||
void step(RakNet::Time time);
|
||||
|
||||
private:
|
||||
void onAncestryChanged(shared_ptr<InterpolatingPhysicsReceiver> interpolatingPhysicsReceiver);
|
||||
void tryToCreateJob(shared_ptr<InterpolatingPhysicsReceiver> interpolatingPhysicsReceiver);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
#endif // remove this file when fast flag RemoveInterpolationReciever is accepted and removed
|
||||
163
engine/network/src/Item.cpp
Normal file
163
engine/network/src/Item.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Streaming.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
void Item::writeItemType(RakNet::BitStream& stream, Item::ItemType value)
|
||||
{
|
||||
// using a 5b item id.
|
||||
BOOST_STATIC_ASSERT(ItemTypeMaxValue < 19);
|
||||
|
||||
if (value >= 1 && value <= 3)
|
||||
{
|
||||
// Compact form
|
||||
stream.WriteBits((const unsigned char*)&value, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
const char zero = 0;
|
||||
stream.WriteBits((const unsigned char*)&zero, 2);
|
||||
// Long form
|
||||
stream.WriteBits((const unsigned char*)&value, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void Item::readItemType(RakNet::BitStream& stream, Item::ItemType& value)
|
||||
{
|
||||
unsigned int t;
|
||||
readFastN<2>(stream, t);
|
||||
value = static_cast<ItemType>(t);
|
||||
|
||||
if (value != 0)
|
||||
{
|
||||
// Compact form
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int t;
|
||||
readFastN<5>(stream, t);
|
||||
value = static_cast<ItemType>(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool ItemQueue::validate() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ItemQueue::preValidate(int i)
|
||||
{
|
||||
AYAASSERT(inCode == 0);
|
||||
inCode = i;
|
||||
AYAASSERT(validate());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ItemQueue::postValidate(int i)
|
||||
{
|
||||
AYAASSERT(validate());
|
||||
AYAASSERT(inCode == i);
|
||||
inCode = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
ItemQueue::ItemQueue()
|
||||
{
|
||||
inCode = 0;
|
||||
}
|
||||
|
||||
ItemQueue::~ItemQueue()
|
||||
{
|
||||
AYAASSERT(inCode == 0);
|
||||
}
|
||||
|
||||
bool ItemQueue::empty() const
|
||||
{
|
||||
return itemList.empty();
|
||||
}
|
||||
|
||||
size_t ItemQueue::size() const
|
||||
{
|
||||
return itemList.size();
|
||||
}
|
||||
|
||||
Aya::Time::Interval ItemQueue::head_wait() const
|
||||
{
|
||||
if (itemList.empty())
|
||||
return Aya::Time::Interval::zero();
|
||||
|
||||
return Aya::Time::now<Aya::Time::Fast>() - itemList.front().timestamp;
|
||||
}
|
||||
|
||||
Aya::Time ItemQueue::head_time() const
|
||||
{
|
||||
if (itemList.empty())
|
||||
return Aya::Time();
|
||||
|
||||
return itemList.front().timestamp;
|
||||
}
|
||||
|
||||
void ItemQueue::deleteAll()
|
||||
{
|
||||
Item* item;
|
||||
while (pop_if_present(item))
|
||||
delete item;
|
||||
}
|
||||
|
||||
void ItemQueue::clear()
|
||||
{
|
||||
itemList.clear();
|
||||
}
|
||||
|
||||
bool ItemQueue::pop_if_present(Item*& item)
|
||||
{
|
||||
AYAASSERT(preValidate(3));
|
||||
|
||||
if (itemList.empty())
|
||||
{
|
||||
AYAASSERT(postValidate(3));
|
||||
return false;
|
||||
}
|
||||
|
||||
item = &itemList.front();
|
||||
itemList.pop_front();
|
||||
|
||||
AYAASSERT(postValidate(3));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void ItemQueue::push_back(Item* item)
|
||||
{
|
||||
item->timestamp = Aya::Time::now<Aya::Time::Fast>();
|
||||
AYAASSERT(preValidate(4));
|
||||
itemList.push_back(*item);
|
||||
AYAASSERT(postValidate(4));
|
||||
}
|
||||
|
||||
|
||||
void ItemQueue::push_front(Item* item)
|
||||
{
|
||||
item->timestamp = Aya::Time::now<Aya::Time::Fast>();
|
||||
AYAASSERT(preValidate(5));
|
||||
itemList.push_front(*item);
|
||||
AYAASSERT(postValidate(5));
|
||||
}
|
||||
|
||||
void ItemQueue::push_front_preserve_timestamp(Item* item)
|
||||
{
|
||||
AYAASSERT(preValidate(5));
|
||||
itemList.push_front(*item);
|
||||
AYAASSERT(postValidate(5));
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
157
engine/network/src/Item.hpp
Normal file
157
engine/network/src/Item.hpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#pragma once
|
||||
|
||||
#include "Declarations.hpp"
|
||||
|
||||
#include "boost.hpp"
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "time.hpp"
|
||||
|
||||
#include "Memory.hpp"
|
||||
|
||||
#include "boost/intrusive/list.hpp"
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator;
|
||||
|
||||
struct ItemTag;
|
||||
typedef boost::intrusive::list_base_hook<boost::intrusive::tag<ItemTag>> ItemHook;
|
||||
|
||||
class AyaBaseClass Item
|
||||
: boost::noncopyable
|
||||
, public ItemHook
|
||||
{
|
||||
public:
|
||||
enum ItemType
|
||||
{
|
||||
ItemTypeEnd = 0,
|
||||
ItemTypeDelete = 1,
|
||||
ItemTypeNew = 2,
|
||||
ItemTypeChangeProperty = 3,
|
||||
ItemTypeMarker = 4,
|
||||
ItemTypePing = 5,
|
||||
ItemTypePingBack = 6,
|
||||
ItemTypeEventInvocation = 7,
|
||||
ItemTypeRequestCharacter = 8,
|
||||
ItemTypeRocky = 9, // Cheat reporting
|
||||
ItemTypePropAcknowledgement = 10,
|
||||
ItemTypeJoinData = 11,
|
||||
ItemTypeUpdateClientQuota = 12,
|
||||
ItemTypeStreamData = 13,
|
||||
ItemTypeRegionRemoval = 14,
|
||||
ItemTypeInstanceRemoval = 15,
|
||||
ItemTypeTag = 16,
|
||||
ItemTypeStats = 17,
|
||||
ItemTypeHash = 18,
|
||||
ItemTypeMaxValue = 18,
|
||||
};
|
||||
|
||||
protected:
|
||||
Replicator& replicator;
|
||||
Item(Replicator& replicator)
|
||||
: replicator(replicator)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
Aya::Time timestamp;
|
||||
|
||||
virtual ~Item() {}
|
||||
virtual bool write(RakNet::BitStream& bitStream) = 0;
|
||||
static void writeItemType(RakNet::BitStream& stream, Item::ItemType value);
|
||||
static void readItemType(RakNet::BitStream& stream, Item::ItemType& value);
|
||||
};
|
||||
|
||||
// Memory pool enabled Item
|
||||
class PooledItem
|
||||
: public Item
|
||||
, public AutoMemPool::Object
|
||||
{
|
||||
public:
|
||||
PooledItem(Replicator& replicator)
|
||||
: Item(replicator)
|
||||
{
|
||||
}
|
||||
virtual ~PooledItem() {}
|
||||
};
|
||||
|
||||
// Debugs and checks for re-entrant calls. Right now NOT thread safe. Just here for safety watch.
|
||||
|
||||
class AyaBaseClass ItemQueue : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
typedef boost::intrusive::list<Item, boost::intrusive::base_hook<ItemHook>> ItemList;
|
||||
|
||||
private:
|
||||
ItemList itemList;
|
||||
|
||||
volatile int inCode;
|
||||
|
||||
bool validate() const;
|
||||
|
||||
bool preValidate(int i);
|
||||
|
||||
bool postValidate(int i);
|
||||
|
||||
public:
|
||||
ItemQueue();
|
||||
|
||||
~ItemQueue();
|
||||
|
||||
bool empty() const;
|
||||
size_t size() const;
|
||||
Aya::Time::Interval head_wait() const;
|
||||
Aya::Time head_time() const;
|
||||
|
||||
void deleteAll();
|
||||
|
||||
void clear();
|
||||
|
||||
bool pop_if_present(Item*& item);
|
||||
|
||||
void push_back(Item* item);
|
||||
|
||||
void push_front(Item* item);
|
||||
|
||||
void push_front_preserve_timestamp(Item* item);
|
||||
|
||||
ItemList::iterator begin()
|
||||
{
|
||||
return itemList.begin();
|
||||
}
|
||||
ItemList::iterator end()
|
||||
{
|
||||
return itemList.end();
|
||||
}
|
||||
};
|
||||
|
||||
class AyaBaseClass DeserializedItem
|
||||
{
|
||||
protected:
|
||||
Item::ItemType type;
|
||||
|
||||
public:
|
||||
DeserializedItem()
|
||||
: type(Item::ItemTypeEnd) {};
|
||||
virtual ~DeserializedItem() {};
|
||||
|
||||
Item::ItemType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
virtual void process(Replicator& replicator) = 0;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
31
engine/network/src/Marker.cpp
Normal file
31
engine/network/src/Marker.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "./Marker.hpp"
|
||||
#include "atomic.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
const char* const sMarker = "NetworkMarker";
|
||||
|
||||
Reflection::EventDesc<Marker, void()> event_Returned(&Marker::receivedSignal, "Received");
|
||||
|
||||
static Aya::atomic<int> ctr;
|
||||
|
||||
Marker::Marker()
|
||||
: returned(false)
|
||||
, id(++ctr)
|
||||
{
|
||||
// Markers should never be put under another object
|
||||
this->lockParent();
|
||||
}
|
||||
|
||||
void Marker::fireReturned()
|
||||
{
|
||||
// TODO: Memoize the result and handle new connections using a Future system
|
||||
receivedSignal();
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
25
engine/network/src/Marker.hpp
Normal file
25
engine/network/src/Marker.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
extern const char* const sMarker;
|
||||
|
||||
class Marker : public Aya::DescribedNonCreatable<Marker, Instance, sMarker>
|
||||
{
|
||||
bool returned; // == the marker has made the round-trip
|
||||
public:
|
||||
Marker();
|
||||
const long id;
|
||||
Aya::signal<void()> receivedSignal;
|
||||
void fireReturned();
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
87
engine/network/src/MechanismItem.cpp
Normal file
87
engine/network/src/MechanismItem.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
|
||||
#include "MechanismItem.hpp"
|
||||
#include "Utility/PV.hpp"
|
||||
|
||||
#include "Utility/Math.hpp"
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
MechanismItem::~MechanismItem()
|
||||
{
|
||||
for (int i = 0; i < buffer.size(); ++i)
|
||||
delete buffer[i];
|
||||
}
|
||||
|
||||
void MechanismItem::reset(int numElements)
|
||||
{
|
||||
networkHumanoidState = 0;
|
||||
hasVelocity = false;
|
||||
networkTime = 0;
|
||||
currentElements = numElements;
|
||||
while (buffer.size() < numElements)
|
||||
buffer.append(new AssemblyItem());
|
||||
}
|
||||
|
||||
AssemblyItem& MechanismItem::appendAssembly()
|
||||
{
|
||||
AYAASSERT(buffer.size() >= currentElements);
|
||||
|
||||
if (buffer.size() == currentElements)
|
||||
buffer.append(new AssemblyItem());
|
||||
else
|
||||
{
|
||||
AYAASSERT(buffer[currentElements] != NULL);
|
||||
buffer[currentElements]->reset();
|
||||
}
|
||||
|
||||
AssemblyItem& answer = *buffer[currentElements];
|
||||
|
||||
currentElements++;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
/*
|
||||
For now - no lerp on mechanisms (>1 assembly)
|
||||
*/
|
||||
|
||||
bool MechanismItem::consistent(const MechanismItem* before, const MechanismItem* after)
|
||||
{
|
||||
AYAASSERT(before && after);
|
||||
AYAASSERT(before->networkTime < after->networkTime);
|
||||
|
||||
return ((before->hasVelocity == after->hasVelocity) && (before->numAssemblies() == 1) && (after->numAssemblies() == 1) &&
|
||||
(before->getAssemblyItem(0).motorAngles.size() == after->getAssemblyItem(0).motorAngles.size()));
|
||||
}
|
||||
|
||||
|
||||
void MechanismItem::lerp(const MechanismItem* before, const MechanismItem* after, MechanismItem* out, float lerpAlpha)
|
||||
{
|
||||
AYAASSERT(consistent(before, after));
|
||||
AYAASSERT(before->numAssemblies() == 1);
|
||||
|
||||
out->reset(before->numAssemblies());
|
||||
|
||||
out->networkTime = 0; // shouldn't be used
|
||||
out->networkHumanoidState = after->networkHumanoidState;
|
||||
out->hasVelocity = after->hasVelocity;
|
||||
|
||||
AssemblyItem& beforeA = before->getAssemblyItem(0);
|
||||
AssemblyItem& afterA = after->getAssemblyItem(0);
|
||||
AssemblyItem& outA = out->getAssemblyItem(0);
|
||||
|
||||
outA.rootPart = afterA.rootPart;
|
||||
|
||||
outA.pv = beforeA.pv.lerp(afterA.pv, lerpAlpha);
|
||||
|
||||
outA.motorAngles.resize(beforeA.motorAngles.size()); // should prevent reallocs
|
||||
for (int i = 0; i < beforeA.motorAngles.size(); ++i)
|
||||
{
|
||||
outA.motorAngles[i] = CompactCFrame(beforeA.motorAngles[i].translation.lerp(afterA.motorAngles[i].translation, lerpAlpha),
|
||||
beforeA.motorAngles[i].getAxisAngle().lerp(afterA.motorAngles[i].getAxisAngle(), lerpAlpha));
|
||||
}
|
||||
}
|
||||
79
engine/network/src/MechanismItem.hpp
Normal file
79
engine/network/src/MechanismItem.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "Utility/PV.hpp"
|
||||
|
||||
#include "Memory.hpp"
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "Array.hpp"
|
||||
#include "CompactCFrame.hpp"
|
||||
#include "RakNet/RakNetTime.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Primitive;
|
||||
|
||||
class AssemblyItem : public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
shared_ptr<PartInstance> rootPart;
|
||||
Aya::PV pv;
|
||||
G3D::Array<CompactCFrame> motorAngles;
|
||||
|
||||
void reset()
|
||||
{
|
||||
rootPart.reset();
|
||||
motorAngles.fastClear();
|
||||
}
|
||||
|
||||
AssemblyItem()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class MechanismItem : boost::noncopyable
|
||||
{
|
||||
private:
|
||||
// TODO: Replace with intrusive list of AssemblyItem
|
||||
G3D::Array<AssemblyItem*> buffer;
|
||||
|
||||
int currentElements;
|
||||
|
||||
public:
|
||||
RakNet::Time networkTime;
|
||||
unsigned char networkHumanoidState;
|
||||
bool hasVelocity;
|
||||
|
||||
void reset(int numElements = 0);
|
||||
|
||||
MechanismItem()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
~MechanismItem();
|
||||
|
||||
AssemblyItem& appendAssembly();
|
||||
|
||||
int numAssemblies() const
|
||||
{
|
||||
return currentElements;
|
||||
}
|
||||
|
||||
AssemblyItem& getAssemblyItem(int i) const
|
||||
{
|
||||
AYAASSERT(i < currentElements);
|
||||
return *buffer[i];
|
||||
}
|
||||
|
||||
static bool consistent(const MechanismItem* before, const MechanismItem* after);
|
||||
|
||||
static void lerp(const MechanismItem* before, const MechanismItem* after, MechanismItem* out, float lerpAlpha);
|
||||
};
|
||||
} // namespace Aya
|
||||
50
engine/network/src/MovementHistoryJob.cpp
Normal file
50
engine/network/src/MovementHistoryJob.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
#include "MovementHistoryJob.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "Util.hpp"
|
||||
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
MovementHistoryJob::MovementHistoryJob(shared_ptr<DataModel> dataModel)
|
||||
: DataModelJob("Movement History Job", DataModelJob::Write, false, dataModel, Time::Interval(0))
|
||||
, dataModel(dataModel)
|
||||
, movementHistoryRate(20)
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessIncoming;
|
||||
}
|
||||
|
||||
Time::Interval MovementHistoryJob::sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, movementHistoryRate);
|
||||
}
|
||||
|
||||
TaskScheduler::Job::Error MovementHistoryJob::error(const Stats& stats)
|
||||
{
|
||||
return TaskScheduler::Job::computeStandardErrorCyclicExecutiveSleeping(stats, movementHistoryRate);
|
||||
}
|
||||
|
||||
|
||||
TaskScheduler::StepResult MovementHistoryJob::stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
bool deprecated = true;
|
||||
AYAASSERT(!deprecated);
|
||||
|
||||
if (shared_ptr<DataModel> safeDataModel = dataModel.lock())
|
||||
{
|
||||
DataModel::scoped_write_request request(safeDataModel.get());
|
||||
|
||||
safeDataModel->getWorkspace()->updateHistory();
|
||||
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
34
engine/network/src/MovementHistoryJob.hpp
Normal file
34
engine/network/src/MovementHistoryJob.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "DataModel/DataModelJob.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class PartInstance;
|
||||
class DataModel;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Server;
|
||||
class ServerReplicator;
|
||||
|
||||
class MovementHistoryJob : public DataModelJob
|
||||
{
|
||||
private:
|
||||
boost::weak_ptr<DataModel> dataModel;
|
||||
float movementHistoryRate;
|
||||
|
||||
// Job overrides
|
||||
/*override*/ virtual Error error(const Stats& stats);
|
||||
/*override*/ Time::Interval sleepTime(const Stats& stats);
|
||||
/*override*/ virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats);
|
||||
|
||||
public:
|
||||
MovementHistoryJob(shared_ptr<DataModel> dataModel);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
144
engine/network/src/NetworkClusterPacketCache.cpp
Normal file
144
engine/network/src/NetworkClusterPacketCache.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "NetworkClusterPacketCache.hpp"
|
||||
|
||||
#include "DataModel/MegaCluster.hpp"
|
||||
|
||||
|
||||
#include "Voxel/Cell.hpp"
|
||||
|
||||
#include "Voxel2/Grid.hpp"
|
||||
|
||||
|
||||
// raknet
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
LOGGROUP(TerrainCellListener)
|
||||
|
||||
const char* const Aya::Network::sClusterPacketCacheBase = "ClusterPacketCacheBase";
|
||||
const char* const Aya::Network::sClusterPacketCache = "ClusterPacketCache";
|
||||
const char* const Aya::Network::sOneQuarterClusterPacketCache = "OneQuarterClusterPacketCacheBase";
|
||||
|
||||
template<class Key>
|
||||
ClusterPacketCacheBase<Key>::ClusterPacketCacheBase()
|
||||
{
|
||||
}
|
||||
|
||||
template<class Key>
|
||||
unsigned int ClusterPacketCacheBase<Key>::getCachedBitStreamBytesUsed(const Key& regionId)
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
CachedBitStream& cachedStream = streamCache[regionId];
|
||||
|
||||
if (!cachedStream.dirty)
|
||||
{
|
||||
return cachedStream.bitStream->GetNumberOfBytesUsed();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Key>
|
||||
bool ClusterPacketCacheBase<Key>::fetchIfUpToDate(const Key& regionId, RakNet::BitStream& outBitStream)
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
CachedBitStream& cachedStream = streamCache[regionId];
|
||||
|
||||
if (!cachedStream.dirty)
|
||||
{
|
||||
outBitStream.WriteBits(cachedStream.bitStream->GetData(), cachedStream.bitStream->GetNumberOfBitsUsed(), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy all data from bitStream to cache
|
||||
template<class Key>
|
||||
bool ClusterPacketCacheBase<Key>::update(const Key& regionId, RakNet::BitStream& bitStream, unsigned int numBits)
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
CachedBitStream& cachedStream = streamCache[regionId];
|
||||
|
||||
if (!cachedStream.bitStream)
|
||||
{
|
||||
cachedStream.bitStream.reset(new RakNet::BitStream());
|
||||
}
|
||||
|
||||
cachedStream.bitStream->Reset();
|
||||
cachedStream.bitStream->Write(bitStream, numBits);
|
||||
cachedStream.dirty = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Key>
|
||||
void ClusterPacketCacheBase<Key>::setupListener(MegaClusterInstance* megaClusterInstance)
|
||||
{
|
||||
if (!clusterInstance)
|
||||
{
|
||||
if (megaClusterInstance->isSmooth())
|
||||
megaClusterInstance->getSmoothGrid()->connectListener(this);
|
||||
else
|
||||
megaClusterInstance->getVoxelGrid()->connectListener(this);
|
||||
|
||||
clusterInstance = shared_from(megaClusterInstance);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Key>
|
||||
void ClusterPacketCacheBase<Key>::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
||||
{
|
||||
if (oldProvider)
|
||||
{
|
||||
streamCache.clear();
|
||||
|
||||
if (clusterInstance)
|
||||
{
|
||||
if (clusterInstance->isSmooth())
|
||||
clusterInstance->getSmoothGrid()->disconnectListener(this);
|
||||
else
|
||||
clusterInstance->getVoxelGrid()->disconnectListener(this);
|
||||
|
||||
clusterInstance.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// d9mz - error: explicit instantiation of 'Aya::Network::ClusterPacketCacheBase' must occur in namespace 'Network'
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
// Explicit template instantiation
|
||||
template class ClusterPacketCacheBase<StreamRegion::Id>;
|
||||
template class ClusterPacketCacheBase<SpatialRegion::Id>;
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
void ClusterPacketCache::terrainCellChanged(const Voxel::CellChangeInfo& cell)
|
||||
{
|
||||
streamCache[SpatialRegion::regionContainingVoxel(cell.position)].dirty = true;
|
||||
}
|
||||
|
||||
void ClusterPacketCache::onTerrainRegionChanged(const Voxel2::Region& region)
|
||||
{
|
||||
AYAASSERT(false);
|
||||
}
|
||||
|
||||
void OneQuarterClusterPacketCache::terrainCellChanged(const Voxel::CellChangeInfo& cell)
|
||||
{
|
||||
streamCache[StreamRegion::regionContainingVoxel(cell.position)].dirty = true;
|
||||
}
|
||||
|
||||
void OneQuarterClusterPacketCache::onTerrainRegionChanged(const Voxel2::Region& region)
|
||||
{
|
||||
std::vector<Vector3int32> ids = region.getChunkIds(StreamRegion::_PrivateConstants::kRegionSizeInVoxelsAsBitShift);
|
||||
|
||||
for (auto i : ids)
|
||||
streamCache[StreamRegion::Id(i)].dirty = true;
|
||||
}
|
||||
109
engine/network/src/NetworkClusterPacketCache.hpp
Normal file
109
engine/network/src/NetworkClusterPacketCache.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Service.hpp"
|
||||
|
||||
#include "signal.hpp"
|
||||
|
||||
#include "Utility/StreamRegion.hpp"
|
||||
|
||||
|
||||
#include "Voxel/CellChangeListener.hpp"
|
||||
|
||||
#include "Voxel2/GridListener.hpp"
|
||||
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class MegaClusterInstance;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
// This class stores the final raknet bit stream of each cluster chunk.
|
||||
// Cache operations are guarded by a shared mutex that uses readers-writer lock,
|
||||
// this allows multiple send jobs to fetch from cache concurrently, but only one can update at any given time
|
||||
extern const char* const sClusterPacketCacheBase;
|
||||
|
||||
template<class Key>
|
||||
class ClusterPacketCacheBase
|
||||
: public Voxel::CellChangeListener
|
||||
, public Voxel2::GridListener
|
||||
{
|
||||
protected:
|
||||
struct CachedBitStream
|
||||
{
|
||||
bool dirty;
|
||||
boost::shared_ptr<RakNet::BitStream> bitStream;
|
||||
|
||||
CachedBitStream()
|
||||
: dirty(true)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<Key, CachedBitStream> StreamCacheList;
|
||||
StreamCacheList streamCache;
|
||||
|
||||
boost::shared_mutex sharedMutex;
|
||||
boost::shared_ptr<MegaClusterInstance> clusterInstance;
|
||||
|
||||
public:
|
||||
ClusterPacketCacheBase();
|
||||
virtual ~ClusterPacketCacheBase() {};
|
||||
|
||||
// Search for cached bitstream by index. This function uses a shared locked to allow multiple reads at same time.
|
||||
// Returns true if fetched successfully.
|
||||
bool fetchIfUpToDate(const Key& regionId, RakNet::BitStream& outBitStream);
|
||||
|
||||
// Copy all data from bitStream to cache. This function upgrades the shard mutex into an unique lock that blocks all other
|
||||
// read and write operations.
|
||||
bool update(const Key& regionId, RakNet::BitStream& bitStream, unsigned int numBits);
|
||||
|
||||
unsigned int getCachedBitStreamBytesUsed(const Key& regionId);
|
||||
|
||||
void setupListener(MegaClusterInstance* megaClusterInstance);
|
||||
|
||||
protected:
|
||||
void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
};
|
||||
|
||||
extern const char* const sClusterPacketCache;
|
||||
class ClusterPacketCache
|
||||
: public ClusterPacketCacheBase<SpatialRegion::Id>
|
||||
, public DescribedNonCreatable<ClusterPacketCache, Instance, sClusterPacketCache>
|
||||
, public Service
|
||||
{
|
||||
public:
|
||||
ClusterPacketCache()
|
||||
{
|
||||
setName(sClusterPacketCache);
|
||||
};
|
||||
~ClusterPacketCache() {};
|
||||
|
||||
void terrainCellChanged(const Voxel::CellChangeInfo& cell) override;
|
||||
void onTerrainRegionChanged(const Voxel2::Region& region) override;
|
||||
};
|
||||
|
||||
extern const char* const sOneQuarterClusterPacketCache;
|
||||
class OneQuarterClusterPacketCache
|
||||
: public ClusterPacketCacheBase<StreamRegion::Id>
|
||||
, public DescribedNonCreatable<OneQuarterClusterPacketCache, Instance, sOneQuarterClusterPacketCache>
|
||||
, public Service
|
||||
{
|
||||
public:
|
||||
OneQuarterClusterPacketCache()
|
||||
{
|
||||
setName(sOneQuarterClusterPacketCache);
|
||||
};
|
||||
~OneQuarterClusterPacketCache() {};
|
||||
|
||||
void terrainCellChanged(const Voxel::CellChangeInfo& cell) override;
|
||||
void onTerrainRegionChanged(const Voxel2::Region& region) override;
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
425
engine/network/src/NetworkFilter.cpp
Normal file
425
engine/network/src/NetworkFilter.cpp
Normal file
@@ -0,0 +1,425 @@
|
||||
#include "NetworkFilter.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "Humanoid/Humanoid.hpp"
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "DataModel/PlayerGui.hpp"
|
||||
|
||||
#include "DataModel/Hopper.hpp"
|
||||
|
||||
#include "DataModel/Teams.hpp"
|
||||
|
||||
#include "DataModel/Accoutrement.hpp"
|
||||
|
||||
#include "DataModel/SkateboardPlatform.hpp"
|
||||
|
||||
#include "DataModel/Seat.hpp"
|
||||
|
||||
#include "DataModel/StarterPlayerService.hpp"
|
||||
|
||||
#include "Players.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Reflection
|
||||
{
|
||||
template<>
|
||||
EnumDesc<Network::FilterResult>::EnumDesc()
|
||||
: EnumDescriptor("FilterResult")
|
||||
{
|
||||
addPair(Network::Reject, "Rejected");
|
||||
addPair(Network::Accept, "Accepted");
|
||||
}
|
||||
|
||||
template<>
|
||||
Network::FilterResult& Variant::convert<Network::FilterResult>(void)
|
||||
{
|
||||
return genericConvert<Network::FilterResult>();
|
||||
}
|
||||
|
||||
} // namespace Reflection
|
||||
|
||||
template<>
|
||||
bool StringConverter<Network::FilterResult>::convertToValue(const std::string& text, Network::FilterResult& value)
|
||||
{
|
||||
return Reflection::EnumDesc<Network::FilterResult>::singleton().convertToValue(text.c_str(), value);
|
||||
}
|
||||
} // namespace Aya
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Reflection;
|
||||
using namespace Network;
|
||||
|
||||
|
||||
NetworkFilter::NetworkFilter(Replicator* replicator)
|
||||
: replicator(replicator)
|
||||
{
|
||||
}
|
||||
|
||||
NetworkFilter::~NetworkFilter(void) {}
|
||||
|
||||
template<class T, FilterResult R>
|
||||
static bool filterInside(Instance* parent, FilterResult& result)
|
||||
{
|
||||
if (!parent)
|
||||
return false;
|
||||
|
||||
if (Instance::fastDynamicCast<T>(parent))
|
||||
{
|
||||
result = R;
|
||||
return true;
|
||||
}
|
||||
|
||||
return filterInside<T, R>(parent->getParent(), result);
|
||||
}
|
||||
|
||||
|
||||
bool Aya::Network::NetworkFilter::filterChangedProperty(Instance* instance, const Reflection::PropertyDescriptor& desc, FilterResult& result)
|
||||
{
|
||||
// if desc==Instance::propParent, then filterParent will be called, so those rules apply, too
|
||||
|
||||
// Can't change items in these services:
|
||||
if (filterInside<StarterGuiService, Reject>(instance, result))
|
||||
return true;
|
||||
if (filterInside<StarterPackService, Reject>(instance, result))
|
||||
return true;
|
||||
if (filterInside<StarterPlayerService, Reject>(instance, result))
|
||||
return true;
|
||||
if (filterInside<Teams, Reject>(instance, result))
|
||||
return true;
|
||||
|
||||
// Allow/Reject some physics properties
|
||||
if (desc == PartInstance::prop_NetworkIsSleeping)
|
||||
{
|
||||
result = Accept;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// DE3665
|
||||
if (desc == PartInstance::prop_NetworkOwner)
|
||||
{
|
||||
result = Reject;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (desc == Player::prop_SimulationRadius)
|
||||
{
|
||||
if (replicator->getPlayer().get() == instance)
|
||||
result = Accept;
|
||||
else
|
||||
result = Reject;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Humanoid* humanoid = Instance::fastDynamicCast<Humanoid>(instance))
|
||||
{
|
||||
Network::Player* player = Network::Players::getPlayerFromCharacter(Humanoid::getCharacterFromHumanoid(humanoid));
|
||||
|
||||
if (player == replicator->getPlayer().get())
|
||||
if (humanoid->isLegalForClientToChange(desc))
|
||||
{
|
||||
result = Accept;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Aya::Network::NetworkFilter::filterNew(Instance* instance, Instance* parent, FilterResult& result)
|
||||
{
|
||||
if (filterParent(instance, parent, result))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Aya::Network::NetworkFilter::filterDelete(Instance* instance, FilterResult& result)
|
||||
{
|
||||
if (filterParent(instance, NULL, result))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Aya::Network::NetworkFilter::filterParent(Instance* instance, Instance* parent, FilterResult& result)
|
||||
{
|
||||
if (Player* player = Instance::fastDynamicCast<Player>(instance))
|
||||
{
|
||||
if (!player->getParent())
|
||||
{
|
||||
// This Player is just being inserted into the world, so it is presumably the local player.
|
||||
// Let it go if it is going to the right place:
|
||||
result = Instance::fastDynamicCast<Players>(parent) ? Accept : Reject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't move to these services:
|
||||
if (filterInside<StarterGuiService, Reject>(parent, result))
|
||||
return true;
|
||||
if (filterInside<StarterPackService, Reject>(parent, result))
|
||||
return true;
|
||||
if (filterInside<StarterPlayerService, Reject>(parent, result))
|
||||
return true;
|
||||
if (filterInside<Teams, Reject>(parent, result))
|
||||
return true;
|
||||
|
||||
// Can't move when in these services:
|
||||
if (filterInside<StarterGuiService, Reject>(instance, result))
|
||||
return true;
|
||||
if (filterInside<StarterPackService, Reject>(instance, result))
|
||||
return true;
|
||||
if (filterInside<StarterPlayerService, Reject>(instance, result))
|
||||
return true;
|
||||
if (filterInside<Teams, Reject>(instance, result))
|
||||
return true;
|
||||
|
||||
// Can't set if parent is locked
|
||||
if (instance && instance->getIsParentLocked())
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_WARNING, "trying to set locked parent!");
|
||||
result = Reject;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Aya::Network::NetworkFilter::filterEvent(Instance* instance, const Reflection::EventDescriptor& desc, FilterResult& result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::unordered_set<std::string> StrictNetworkFilter::propertyWhiteList;
|
||||
boost::unordered_set<std::string> StrictNetworkFilter::eventWhiteList;
|
||||
|
||||
StrictNetworkFilter::StrictNetworkFilter(Replicator* replicator)
|
||||
: replicator(replicator)
|
||||
, instanceBeingRemovedFromLocalPlayer(NULL)
|
||||
{
|
||||
// these lists should only contain scriptable properties or events, all non-scriptable (REPLICATE_ONLY) items are not filtered
|
||||
|
||||
if (propertyWhiteList.size() == 0)
|
||||
{
|
||||
std::string props[] = {"Jump", "Sit", "Throttle", "Steer", "SimulationRadius"};
|
||||
propertyWhiteList.insert(props, props + sizeof(props) / sizeof(std::string));
|
||||
}
|
||||
|
||||
if (eventWhiteList.size() == 0)
|
||||
{
|
||||
std::string events[] = {"OnServerEvent", "PromptProductPurchaseFinished", "PromptPurchaseFinished", "PromptPurchaseRequested",
|
||||
"ClientPurchaseSuccess", "ServerPurchaseVerification", "Activated", "Deactivated", "SimulationRadiusChanged", "LuaDialogCallbackSignal",
|
||||
"ServerAdVerification", "ClientAdVerificationResults"};
|
||||
eventWhiteList.insert(events, events + sizeof(events) / sizeof(std::string));
|
||||
}
|
||||
}
|
||||
|
||||
void StrictNetworkFilter::onChildRemoved(Instance* removed, const Instance* oldParent)
|
||||
{
|
||||
instanceBeingRemovedFromLocalPlayer = NULL;
|
||||
|
||||
if (Player* player = replicator->findTargetPlayer())
|
||||
{
|
||||
if (ModelInstance* playerModel = player->getCharacter())
|
||||
{
|
||||
if (oldParent == playerModel || oldParent->isDescendantOf(playerModel))
|
||||
{
|
||||
instanceBeingRemovedFromLocalPlayer = removed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((oldParent == player) || (oldParent->isDescendantOf(player)))
|
||||
{
|
||||
instanceBeingRemovedFromLocalPlayer = removed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResult StrictNetworkFilter::filterChangedProperty(Instance* instance, const Reflection::PropertyDescriptor& desc)
|
||||
{
|
||||
if (desc == Instance::propParent)
|
||||
return Accept; // should be handled by filterParent
|
||||
|
||||
if (desc.isScriptable() || desc.canXmlWrite() || desc.canXmlRead())
|
||||
{
|
||||
if (propertyWhiteList.find(desc.name.toString()) != propertyWhiteList.end())
|
||||
return Accept;
|
||||
|
||||
return Reject;
|
||||
}
|
||||
|
||||
// accept all non-scriptable properties
|
||||
return Accept;
|
||||
}
|
||||
|
||||
bool isOrUnderAncestor(const Instance* instance, const Instance* newParent, const Instance* ancestor)
|
||||
{
|
||||
const Instance* oldParent = instance->getParent();
|
||||
|
||||
// originating parent must be player or player character
|
||||
if (oldParent != newParent)
|
||||
{
|
||||
// only server code should enter here
|
||||
AYAASSERT(Players::backendProcessing(instance));
|
||||
|
||||
if (!oldParent)
|
||||
return false;
|
||||
|
||||
// originating parent must be player or player character
|
||||
if (oldParent == ancestor || oldParent->isDescendantOf(ancestor))
|
||||
return true;
|
||||
}
|
||||
else if (newParent == ancestor || newParent->isDescendantOf(ancestor))
|
||||
{
|
||||
// only client should enter here
|
||||
AYAASSERT(Players::frontendProcessing(instance));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FilterResult StrictNetworkFilter::filterParent(Instance* instance, Instance* newParent)
|
||||
{
|
||||
FilterResult result = Reject;
|
||||
|
||||
Aya::Weld* weld = NULL;
|
||||
weld = Instance::fastDynamicCast<Aya::Weld>(instance);
|
||||
|
||||
if (instance == instanceBeingRemovedFromLocalPlayer)
|
||||
{
|
||||
// allow parents to be changed from local player
|
||||
result = Accept;
|
||||
}
|
||||
else if (newParent && (instance->fastDynamicCast<Aya::Tool>() || weld || instance->fastDynamicCast<Aya::Accoutrement>() ||
|
||||
instance->fastDynamicCast<Aya::Seat>() ||
|
||||
instance->fastDynamicCast<Aya::SkateboardPlatform>())) // Only accept reparenting of tools, hats, and seats
|
||||
{
|
||||
if (Player* player = replicator->findTargetPlayer())
|
||||
{
|
||||
if (ModelInstance* playerModel = player->getCharacter())
|
||||
{
|
||||
if (isOrUnderAncestor(instance, newParent, playerModel))
|
||||
{
|
||||
result = Accept;
|
||||
|
||||
if (weld)
|
||||
{
|
||||
if (weld->getName() != "RightGrip" && (weld->getPart0() && weld->getPart0()->isDescendantOf(playerModel)))
|
||||
result = Reject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result == Reject)
|
||||
{
|
||||
if (isOrUnderAncestor(instance, newParent, player))
|
||||
result = Accept;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this should be set in onChildRemoved, which should happen right before filterParent, so reset after ever use
|
||||
instanceBeingRemovedFromLocalPlayer = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FilterResult StrictNetworkFilter::filterEvent(Instance* instance, const Reflection::EventDescriptor& desc)
|
||||
{
|
||||
if (desc.isScriptable())
|
||||
{
|
||||
// accept only white listed scriptable events
|
||||
if (eventWhiteList.find(desc.name.toString()) != eventWhiteList.end())
|
||||
return Accept;
|
||||
|
||||
return Reject;
|
||||
}
|
||||
|
||||
// accept non-scriptable events
|
||||
return Accept;
|
||||
}
|
||||
|
||||
FilterResult StrictNetworkFilter::filterNew(const Instance* instance, const Instance* parent)
|
||||
{
|
||||
// filter all new instances except local player
|
||||
if (const Player* player = Instance::fastDynamicCast<const Player>(instance))
|
||||
{
|
||||
if (const Players* players = Instance::fastDynamicCast<const Players>(instance->getParent()))
|
||||
{
|
||||
if (players->getConstLocalPlayer() == player) // Client
|
||||
{
|
||||
return Accept;
|
||||
}
|
||||
}
|
||||
else // Server
|
||||
{
|
||||
// This Player is just being inserted into the world, so it is presumably the local player.
|
||||
// Let it go if it is going to the right place:
|
||||
return Instance::fastDynamicCast<Players>(parent) ? Accept : Reject;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: create StarterGear on server
|
||||
if (Instance::fastDynamicCast<StarterGear>(instance))
|
||||
return Accept;
|
||||
else if (Instance::fastDynamicCast<Weld>(instance)) // allow new tool weld under the player character model
|
||||
{
|
||||
if (Player* player = replicator->findTargetPlayer())
|
||||
{
|
||||
if (ModelInstance* playerModel = player->getCharacter())
|
||||
{
|
||||
if (instance->getParent()) // Client, parent should already be set
|
||||
{
|
||||
AYAASSERT(Players::frontendProcessing(instance));
|
||||
return isOrUnderAncestor(instance, parent, playerModel) ? Accept : Reject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent)
|
||||
{
|
||||
// Server, parent haven't been set yet
|
||||
AYAASSERT(Players::backendProcessing(parent));
|
||||
|
||||
if (parent->isDescendantOf(playerModel))
|
||||
return Accept;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Reject;
|
||||
}
|
||||
|
||||
FilterResult StrictNetworkFilter::filterDelete(const Instance* instance)
|
||||
{
|
||||
if (Player* player = replicator->findTargetPlayer())
|
||||
{
|
||||
if (ModelInstance* playerModel = player->getCharacter())
|
||||
{
|
||||
if (instance->isDescendantOf(playerModel))
|
||||
return Accept;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (instance->isDescendantOf(player))
|
||||
return Accept;
|
||||
}
|
||||
}
|
||||
|
||||
return Reject;
|
||||
}
|
||||
|
||||
FilterResult StrictNetworkFilter::filterTerrainCellChange()
|
||||
{
|
||||
return Reject;
|
||||
}
|
||||
58
engine/network/src/NetworkFilter.hpp
Normal file
58
engine/network/src/NetworkFilter.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "Reflection/Property.hpp"
|
||||
#include "Reflection/Event.hpp"
|
||||
#include "API.hpp"
|
||||
|
||||
#include <boost/unordered_set.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class Instance;
|
||||
namespace Network
|
||||
{
|
||||
class Replicator;
|
||||
|
||||
// Allow only white listed data
|
||||
class StrictNetworkFilter
|
||||
{
|
||||
Replicator* replicator;
|
||||
Instance* instanceBeingRemovedFromLocalPlayer;
|
||||
|
||||
static boost::unordered_set<std::string> propertyWhiteList;
|
||||
static boost::unordered_set<std::string> eventWhiteList;
|
||||
|
||||
public:
|
||||
StrictNetworkFilter(Replicator* replicator);
|
||||
~StrictNetworkFilter() {}
|
||||
|
||||
FilterResult filterChangedProperty(Instance* instance, const Reflection::PropertyDescriptor& desc);
|
||||
FilterResult filterParent(Instance* instance, Instance* newParent);
|
||||
FilterResult filterEvent(Instance* instance, const Reflection::EventDescriptor& desc);
|
||||
FilterResult filterNew(const Instance* instance, const Instance* parent);
|
||||
FilterResult filterDelete(const Instance* instance);
|
||||
FilterResult filterTerrainCellChange();
|
||||
|
||||
void onChildRemoved(Instance* removed, const Instance* oldParent);
|
||||
};
|
||||
|
||||
// Algorithms for filtering common incoming data.
|
||||
// Whitelists certain physics packets and certain humanoind packets.
|
||||
// Blacklists certain data.
|
||||
// Used as a first-pass filtering of known safe data.
|
||||
class NetworkFilter
|
||||
{
|
||||
Replicator* replicator;
|
||||
|
||||
public:
|
||||
NetworkFilter(Replicator* replicator);
|
||||
~NetworkFilter(void);
|
||||
|
||||
bool filterChangedProperty(Instance* instance, const Reflection::PropertyDescriptor& desc, FilterResult& result);
|
||||
bool filterParent(Instance* instance, Instance* parent, FilterResult& result);
|
||||
bool filterNew(Instance* instance, Instance* parent, FilterResult& result);
|
||||
bool filterDelete(Instance* instance, FilterResult& result);
|
||||
bool filterEvent(Instance* instance, const Reflection::EventDescriptor& desc, FilterResult& result);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
82
engine/network/src/NetworkOwner.hpp
Normal file
82
engine/network/src/NetworkOwner.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Utility/SystemAddress.hpp"
|
||||
|
||||
#include "Utility/Color.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class NetworkOwner
|
||||
{
|
||||
public:
|
||||
static const Aya::SystemAddress Server()
|
||||
{
|
||||
static Aya::SystemAddress s(1, 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
// created on server, have not be properly assigned via network owner job
|
||||
static const Aya::SystemAddress ServerUnassigned()
|
||||
{
|
||||
static Aya::SystemAddress s(1, 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
// default
|
||||
static const Aya::SystemAddress Unassigned()
|
||||
{
|
||||
static Aya::SystemAddress s;
|
||||
AYAASSERT(s == SystemAddress());
|
||||
AYAASSERT(s != NetworkOwner::Server());
|
||||
AYAASSERT(s != NetworkOwner::ServerUnassigned());
|
||||
AYAASSERT(s != NetworkOwner::AssignedOther());
|
||||
return s;
|
||||
}
|
||||
|
||||
// generic value used on client indicating assigned to other clients or server (i.e. not self)
|
||||
static const Aya::SystemAddress AssignedOther()
|
||||
{
|
||||
static Aya::SystemAddress s(0, 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool isClient(const Aya::SystemAddress& address)
|
||||
{
|
||||
return ((address != Server()) && (address != Unassigned()) && (address != ServerUnassigned()));
|
||||
}
|
||||
|
||||
static bool isServer(const Aya::SystemAddress& address)
|
||||
{
|
||||
return address == Server() || address == ServerUnassigned();
|
||||
}
|
||||
|
||||
static Color3 colorFromAddress(const Aya::SystemAddress& systemAddress)
|
||||
{
|
||||
if (systemAddress == Server())
|
||||
{
|
||||
return Color3::white();
|
||||
}
|
||||
else if (systemAddress == Unassigned() || systemAddress == ServerUnassigned())
|
||||
{
|
||||
return Color3::black();
|
||||
}
|
||||
else if (systemAddress == AssignedOther())
|
||||
return Color3::gray();
|
||||
else
|
||||
{
|
||||
unsigned int address = systemAddress.getAddress();
|
||||
unsigned int port = systemAddress.getPort();
|
||||
address += port;
|
||||
return Aya::Color::colorFromInt(address);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
323
engine/network/src/NetworkOwnerJob.cpp
Normal file
323
engine/network/src/NetworkOwnerJob.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
|
||||
#include "NetworkOwnerJob.hpp"
|
||||
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "Players.hpp"
|
||||
#include "Server.hpp"
|
||||
#include "ServerReplicator.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "World/Assembly.hpp"
|
||||
#include "World/Mechanism.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/World.hpp"
|
||||
#include "World/SpatialFilter.hpp"
|
||||
#include "World/DistributedPhysics.hpp"
|
||||
#include "NetworkOwner.hpp"
|
||||
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
DYNAMIC_FASTFLAG(CyclicExecutiveForServerTweaks)
|
||||
|
||||
|
||||
#define DEC_SIM_RADIUS_FPS 15.0f
|
||||
#define INC_SIM_RADIUS_FPS 17.5f
|
||||
|
||||
NetworkOwnerJob::NetworkOwnerJob(shared_ptr<DataModel> dataModel)
|
||||
: DataModelJob("Distributed Physics Ownership", DataModelJob::Write, false, dataModel, Time::Interval(0))
|
||||
, dataModel(dataModel)
|
||||
, networkOwnerRate(NetworkSettings::singleton().networkOwnerRate)
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
}
|
||||
|
||||
Time::Interval NetworkOwnerJob::sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, networkOwnerRate);
|
||||
}
|
||||
|
||||
TaskScheduler::Job::Error NetworkOwnerJob::error(const Stats& stats)
|
||||
{
|
||||
if (DFFlag::CyclicExecutiveForServerTweaks)
|
||||
{
|
||||
return TaskScheduler::Job::computeStandardErrorCyclicExecutiveSleeping(stats, networkOwnerRate);
|
||||
}
|
||||
return TaskScheduler::Job::computeStandardError(stats, networkOwnerRate);
|
||||
}
|
||||
|
||||
|
||||
TaskScheduler::StepResult NetworkOwnerJob::stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (shared_ptr<DataModel> safeDataModel = dataModel.lock())
|
||||
{
|
||||
DataModel::scoped_write_request request(safeDataModel.get());
|
||||
AYAASSERT(Server::serverIsPresent(safeDataModel.get()));
|
||||
|
||||
if (Server* server = ServiceProvider::find<Server>(safeDataModel.get()))
|
||||
{
|
||||
updatePlayerLocations(server);
|
||||
|
||||
SpatialFilter* spatialFilter =
|
||||
safeDataModel->getWorkspace()->getWorld()->getSpatialFilter(); // go through all parts/primitives in the spatial filter server side
|
||||
for (size_t phase = 0; phase < Assembly::NUM_PHASES; phase++)
|
||||
{
|
||||
const SpatialFilter::AssemblySet& assemblies = spatialFilter->getAssemblies(static_cast<Assembly::FilterPhase>(phase));
|
||||
SpatialFilter::AssemblySet::const_iterator it;
|
||||
for (it = assemblies.begin(); it != assemblies.end(); ++it)
|
||||
{
|
||||
Assembly* assembly = *it;
|
||||
AYAASSERT(Mechanism::isMovingAssemblyRoot(assembly));
|
||||
PartInstance* part = PartInstance::fromPrimitive(assembly->getAssemblyPrimitive());
|
||||
updateNetworkOwner(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void NetworkOwnerJob::updatePlayerLocations(Server* server)
|
||||
{
|
||||
clientMap.clear();
|
||||
|
||||
for (size_t i = 0; i < server->numChildren(); ++i)
|
||||
{
|
||||
if (ServerReplicator* proxy = Instance::fastDynamicCast<ServerReplicator>(server->getChild(i)))
|
||||
{
|
||||
Region2::WeightedPoint weightedPoint;
|
||||
if (const PartInstance* head = proxy->readPlayerSimulationRegion(weightedPoint))
|
||||
{
|
||||
const Primitive* headPrim = head->getConstPartPrimitive();
|
||||
const Mechanism* headMechanism = headPrim->getConstMechanism();
|
||||
LEGACY_ASSERT(Mechanism::isMovingAssemblyRoot(headPrim->getConstAssembly()));
|
||||
|
||||
Aya::SystemAddress rbxAddress = RakNetToRbxAddress(proxy->remotePlayerId);
|
||||
ClientMapPair pair(rbxAddress, ClientLocation(weightedPoint, proxy, headMechanism));
|
||||
clientMap.insert(pair);
|
||||
|
||||
// reset part count
|
||||
proxy->numPartsOwned = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - untangle, make more efficient
|
||||
|
||||
void NetworkOwnerJob::updateNetworkOwner(PartInstance* part)
|
||||
{
|
||||
AYAASSERT(part->getPartPrimitive());
|
||||
AYAASSERT(Assembly::isAssemblyRootPrimitive(part->getPartPrimitive()));
|
||||
AYAASSERT(part->getPartPrimitive()->getAssembly()->getAssemblyPrimitive() == part->getPartPrimitive());
|
||||
|
||||
Aya::SystemAddress owner = part->getNetworkOwner();
|
||||
|
||||
if (part->getNetworkOwnershipRule() == NetworkOwnership_Manual)
|
||||
{
|
||||
ClientMapIt clientLoc = clientMap.find(owner);
|
||||
if ((clientLoc == clientMap.end() && !Network::Players::findPlayerWithAddress(owner, part)) && owner != NetworkOwner::Server())
|
||||
{
|
||||
resetNetworkOwner(part, NetworkOwner::Server());
|
||||
part->setNetworkOwnershipAuto();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((owner == NetworkOwner::Unassigned()) || (owner == NetworkOwner::ServerUnassigned()))
|
||||
{
|
||||
|
||||
Aya::SystemAddress newOwner = NetworkOwner::Server();
|
||||
|
||||
// check to see if this part belong to a player character
|
||||
if (ModelInstance* m = Instance::fastDynamicCast<ModelInstance>(part->getParent()))
|
||||
{
|
||||
if (Players::getPlayerFromCharacter(m))
|
||||
{
|
||||
ClientMapConstIt closestClient = findClosestClientToPart(part);
|
||||
|
||||
if (closestClient != clientMap.end())
|
||||
{
|
||||
newOwner = closestClient->first;
|
||||
|
||||
closestClient->second.clientProxy->numPartsOwned++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resetNetworkOwner(part, newOwner);
|
||||
return;
|
||||
}
|
||||
|
||||
ClientMapConstIt currentClient = clientMap.find(owner); // will be clientMap.end() for server
|
||||
ClientMapConstIt closestClient;
|
||||
|
||||
bool currentOk = false;
|
||||
bool closestOk = false;
|
||||
|
||||
// projectile special handling to prevent the ownership change from one client to another
|
||||
if (!currentClient->second.overloaded)
|
||||
{
|
||||
if (part->isProjectile() && currentClient != clientMap.end())
|
||||
{
|
||||
currentClient->second.clientProxy->numPartsOwned++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
closestClient = findClosestClientToPart(part);
|
||||
currentOk = ((currentClient != clientMap.end()) && clientCanSimulate(part, currentClient));
|
||||
closestOk = ((closestClient != currentClient) && (closestClient != clientMap.end()) && part->networkOwnerTimeUp() &&
|
||||
clientCanSimulate(part, closestClient));
|
||||
|
||||
if (currentOk)
|
||||
{
|
||||
if (closestOk && switchOwners(part, currentClient, closestClient))
|
||||
{
|
||||
resetNetworkOwner(part, closestClient->first);
|
||||
closestClient->second.clientProxy->numPartsOwned++;
|
||||
}
|
||||
else
|
||||
currentClient->second.clientProxy->numPartsOwned++;
|
||||
return;
|
||||
}
|
||||
// !currentOk
|
||||
if (closestOk)
|
||||
{ // no timer needed if current is not ok
|
||||
resetNetworkOwner(part, closestClient->first);
|
||||
closestClient->second.clientProxy->numPartsOwned++;
|
||||
return;
|
||||
}
|
||||
|
||||
resetNetworkOwner(part, NetworkOwner::Server());
|
||||
}
|
||||
|
||||
void NetworkOwnerJob::resetNetworkOwner(PartInstance* part, const Aya::SystemAddress value)
|
||||
{
|
||||
if (part->getNetworkOwner() != value)
|
||||
{
|
||||
part->setNetworkOwnerAndNotify(value);
|
||||
|
||||
part->raknetTime = 0; // clear the last update timestamp
|
||||
// 3 seconds is too slow for iPad clients to push parts to server when stressed
|
||||
// float delayTime = (value == NetworkOwner::Server()) ? 0.1f : 3.0f; // i.e. - a server part only waits 0.1 second before it can
|
||||
// switch. A client part waits 3 seconds.
|
||||
float delayTime = 0.1f;
|
||||
part->resetNetworkOwnerTime(delayTime);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkOwnerJob::ClientMapConstIt NetworkOwnerJob::findClosestClientToPart(PartInstance* part)
|
||||
{
|
||||
return findClosestClient(part->getCoordinateFrame().translation.xz());
|
||||
}
|
||||
|
||||
NetworkOwnerJob::ClientMapConstIt NetworkOwnerJob::findClosestClient(const Vector2& testLocation)
|
||||
{
|
||||
ClientMapConstIt answer = clientMap.end();
|
||||
float bestError = 1e20f;
|
||||
|
||||
for (ClientMapConstIt it = clientMap.begin(); it != clientMap.end(); ++it)
|
||||
{
|
||||
float temp = Region2::getRelativeError(testLocation, it->second.clientPoint);
|
||||
if (temp < bestError)
|
||||
{
|
||||
bestError = temp;
|
||||
answer = it;
|
||||
|
||||
// can't get closer then 0
|
||||
if (temp == 0.0f)
|
||||
return answer;
|
||||
}
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
/*
|
||||
For a client to be able to simulate a part/assembly, it must be
|
||||
|
||||
Mechanisms that DO NOT include the client's character: Within the client's simulation region
|
||||
|
||||
Mechanisms that DO include the client's character:
|
||||
Standing on top of something simulated by someone else? ... never simulate
|
||||
NOT standing on something simulated by others: Always simulate
|
||||
*/
|
||||
|
||||
|
||||
bool NetworkOwnerJob::clientCanSimulate(PartInstance* part, ClientMapConstIt testLocation)
|
||||
{
|
||||
AYAASSERT(testLocation != clientMap.end());
|
||||
|
||||
// return false;
|
||||
|
||||
if (isClientCharacterMechanism(part, testLocation))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 partLoc = part->getCoordinateFrame().translation.xz();
|
||||
return Region2::pointInRange(partLoc, testLocation->second.clientPoint, DistributedPhysics::SERVER_SLOP());
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkOwnerJob::isClientCharacterMechanism(PartInstance* part, ClientMapConstIt testLocation)
|
||||
{
|
||||
const Mechanism* characterMechanism = testLocation->second.characterMechanism;
|
||||
|
||||
const Primitive* dPhysPrim = part->getConstPartPrimitive();
|
||||
const Mechanism* dPhysMechanism = dPhysPrim->getConstMechanism();
|
||||
|
||||
AYAASSERT(Mechanism::isMovingAssemblyRoot(dPhysPrim->getConstAssembly()));
|
||||
|
||||
if (characterMechanism == dPhysMechanism)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkOwnerJob::switchOwners(PartInstance* part, ClientMapConstIt currentOwner, ClientMapConstIt possibleNewOwner)
|
||||
{
|
||||
AYAASSERT(currentOwner != clientMap.end());
|
||||
AYAASSERT(possibleNewOwner != clientMap.end());
|
||||
AYAASSERT(part->getNetworkOwner() == currentOwner->first);
|
||||
AYAASSERT(part->getNetworkOwner() != possibleNewOwner->first);
|
||||
|
||||
if (isClientCharacterMechanism(part, currentOwner))
|
||||
{ // no switch allowed if current mechanism is current owner mechanism
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Region2::closerToOtherPoint(part->getCoordinateFrame().translation.xz(), currentOwner->second.clientPoint,
|
||||
possibleNewOwner->second.clientPoint, DistributedPhysics::SERVER_SLOP());
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOwnerJob::invalidateProjectileOwnership(Aya::SystemAddress addr)
|
||||
{
|
||||
ClientMapIt clientLoc = clientMap.find(addr);
|
||||
if (clientLoc != clientMap.end())
|
||||
{
|
||||
clientLoc->second.overloaded = true;
|
||||
}
|
||||
}
|
||||
78
engine/network/src/NetworkOwnerJob.hpp
Normal file
78
engine/network/src/NetworkOwnerJob.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "DataModel/DataModelJob.hpp"
|
||||
|
||||
#include "Utility/SystemAddress.hpp"
|
||||
|
||||
#include "Utility/G3DCore.hpp"
|
||||
|
||||
#include "Utility/Region2.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class PartInstance;
|
||||
class Mechanism;
|
||||
class DataModel;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Server;
|
||||
class ServerReplicator;
|
||||
|
||||
class NetworkOwnerJob : public DataModelJob
|
||||
{
|
||||
private:
|
||||
boost::weak_ptr<DataModel> dataModel;
|
||||
float networkOwnerRate;
|
||||
|
||||
class ClientLocation
|
||||
{
|
||||
public:
|
||||
Region2::WeightedPoint clientPoint;
|
||||
ServerReplicator* clientProxy;
|
||||
const Mechanism* characterMechanism;
|
||||
bool overloaded;
|
||||
|
||||
ClientLocation(const Region2::WeightedPoint& clientPoint, ServerReplicator* clientProxy, const Mechanism* characterMechanism)
|
||||
: clientPoint(clientPoint)
|
||||
, clientProxy(clientProxy)
|
||||
, characterMechanism(characterMechanism)
|
||||
, overloaded(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<Aya::SystemAddress, ClientLocation> ClientMap;
|
||||
typedef std::pair<Aya::SystemAddress, ClientLocation> ClientMapPair;
|
||||
typedef ClientMap::const_iterator ClientMapConstIt;
|
||||
typedef ClientMap::iterator ClientMapIt;
|
||||
|
||||
ClientMap clientMap;
|
||||
|
||||
void updatePlayerLocations(Server* server);
|
||||
void updateNetworkOwner(PartInstance* part);
|
||||
ClientMapConstIt findClosestClientToPart(PartInstance* part);
|
||||
ClientMapConstIt findClosestClient(const Vector2& testLocation);
|
||||
|
||||
bool clientCanSimulate(PartInstance* part, ClientMapConstIt testLocation);
|
||||
bool isClientCharacterMechanism(PartInstance* part, ClientMapConstIt testLocation);
|
||||
bool switchOwners(PartInstance* part, ClientMapConstIt currentOwner, ClientMapConstIt possibleNewOwner);
|
||||
static void resetNetworkOwner(PartInstance* part, const Aya::SystemAddress value);
|
||||
|
||||
// Job overrides
|
||||
/*override*/ virtual Error error(const Stats& stats);
|
||||
/*override*/ Time::Interval sleepTime(const Stats& stats);
|
||||
/*override*/ virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats);
|
||||
|
||||
|
||||
public:
|
||||
NetworkOwnerJob(shared_ptr<DataModel> dataModel);
|
||||
void invalidateProjectileOwnership(Aya::SystemAddress addr);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
337
engine/network/src/NetworkPacketCache.cpp
Normal file
337
engine/network/src/NetworkPacketCache.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
|
||||
#include "NetworkPacketCache.hpp"
|
||||
#include "FastLog.hpp"
|
||||
#include "DataModel/PhysicsService.hpp"
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
|
||||
|
||||
// raknet
|
||||
#include "RakNet/BitStream.hpp"
|
||||
#include "NetworkOwner.hpp"
|
||||
|
||||
LOGGROUP(NetworkCache)
|
||||
DYNAMIC_FASTFLAGVARIABLE(DebugLogStaleInstanceCacheEntry, false)
|
||||
|
||||
|
||||
const char* const Aya::Network::sPhysicsPacketCache = "PhysicsPacketCache";
|
||||
const char* const Aya::Network::sInstancePacketCache = "InstancePacketCache";
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
PhysicsPacketCache::PhysicsPacketCache()
|
||||
{
|
||||
setName(sPhysicsPacketCache);
|
||||
|
||||
FASTLOG(FLog::NetworkCache, "PhysicsPacketCache Start");
|
||||
}
|
||||
|
||||
PhysicsPacketCache::~PhysicsPacketCache() {}
|
||||
|
||||
void PhysicsPacketCache::insert(const Assembly* key)
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
std::pair<StreamCacheMap::iterator, bool> res = streamCache.insert(std::make_pair(key, InnerMap()));
|
||||
AYAASSERT(res.second);
|
||||
|
||||
// create cache entry for child assemblies
|
||||
key->visitConstDescendentAssemblies(boost::bind(&PhysicsPacketCache::insertChildAssembly, this, _1));
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::insertChildAssembly(const Assembly* assembly)
|
||||
{
|
||||
AYAASSERT(assembly);
|
||||
|
||||
std::pair<StreamCacheMap::iterator, bool> res = streamCache.insert(std::make_pair(assembly, InnerMap()));
|
||||
AYAASSERT(res.second);
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::remove(const Assembly* key)
|
||||
{
|
||||
AYAASSERT(key);
|
||||
|
||||
boost::unique_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
// remove child assemblies
|
||||
key->visitConstDescendentAssemblies(boost::bind(&PhysicsPacketCache::removeChildAssembly, this, _1));
|
||||
|
||||
streamCache.erase(key);
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::removeChildAssembly(const Assembly* assembly)
|
||||
{
|
||||
AYAASSERT(assembly);
|
||||
|
||||
streamCache.erase(assembly);
|
||||
}
|
||||
|
||||
bool PhysicsPacketCache::fetchIfUpToDate(const Assembly* key, unsigned char index, RakNet::BitStream& outBitStream)
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
StreamCacheMap::iterator iter = streamCache.find(key);
|
||||
|
||||
if (iter != streamCache.end())
|
||||
{
|
||||
InnerMap& innerMap = iter->second;
|
||||
|
||||
// entry has not been created
|
||||
InnerMap::iterator innerIter = innerMap.find(index);
|
||||
if (innerIter == innerMap.end())
|
||||
return false;
|
||||
|
||||
CachedBitStream* cachedItem = innerIter->second.get();
|
||||
|
||||
AYAASSERT(key->getConstAssemblyPrimitive()->getWorld());
|
||||
|
||||
int id = cachedItem->lastStepId;
|
||||
if ((id > 0) && (id == key->getConstAssemblyPrimitive()->getWorld()->getWorldStepId()))
|
||||
{
|
||||
AYAASSERT(cachedItem->bitStream);
|
||||
|
||||
outBitStream.WriteBits(cachedItem->bitStream->GetData(), cachedItem->dataBitStartOffset, (RakNet::BitSize_t)cachedItem->totalNumBits);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PhysicsPacketCache::update(
|
||||
const Assembly* key, unsigned char index, RakNet::BitStream& bitStream, unsigned int startReadBitPos, unsigned int numBits)
|
||||
{
|
||||
boost::upgrade_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
StreamCacheMap::iterator iter = streamCache.find(key);
|
||||
|
||||
if (iter != streamCache.end())
|
||||
{
|
||||
boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
|
||||
|
||||
InnerMap& innerMap = iter->second;
|
||||
std::pair<InnerMap::iterator, bool> i = innerMap.insert(std::make_pair(index, boost::shared_ptr<CachedBitStream>()));
|
||||
if (i.second)
|
||||
{
|
||||
// create new entry if index is not found
|
||||
i.first->second.reset(new CachedBitStream());
|
||||
}
|
||||
|
||||
InnerMap::iterator innerIter = i.first;
|
||||
AYAASSERT(innerIter != innerMap.end());
|
||||
|
||||
CachedBitStream* cachedItem = innerIter->second.get();
|
||||
|
||||
// calculate how many bytes to write
|
||||
int numBytes = ((numBits + 7) >> 3);
|
||||
if ((startReadBitPos & 7) != 0)
|
||||
numBytes++;
|
||||
|
||||
if (!cachedItem->bitStream)
|
||||
cachedItem->bitStream.reset(new RakNet::BitStream(numBytes));
|
||||
|
||||
cachedItem->bitStream->Reset();
|
||||
|
||||
cachedItem->totalNumBits = numBits;
|
||||
int startByte = ((startReadBitPos + 7) >> 3);
|
||||
|
||||
// start at beginning of the byte if read position is between 2 bytes
|
||||
if ((startReadBitPos & 7) != 0)
|
||||
startByte--;
|
||||
|
||||
// write byte aligned for speed and record starting bit so later we know where to start reading
|
||||
cachedItem->bitStream->Write((const char*)bitStream.GetData() + startByte, numBytes);
|
||||
cachedItem->dataBitStartOffset = startReadBitPos - (startByte << 3);
|
||||
|
||||
AYAASSERT(key->getConstAssemblyPrimitive()->getWorld());
|
||||
cachedItem->lastStepId = key->getConstAssemblyPrimitive()->getWorld()->getWorldStepId();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
||||
{
|
||||
if (oldProvider)
|
||||
{
|
||||
addingAssemblyConnection.disconnect();
|
||||
removedAssemblyConnection.disconnect();
|
||||
|
||||
streamCache.clear();
|
||||
}
|
||||
|
||||
if (newProvider)
|
||||
{
|
||||
boost::shared_ptr<PhysicsService> physicsService = shared_from(ServiceProvider::find<PhysicsService>(newProvider));
|
||||
AYAASSERT(physicsService);
|
||||
|
||||
// Get all current assemblies
|
||||
std::for_each(physicsService->begin(), physicsService->end(), boost::bind(&PhysicsPacketCache::addPart, this, _1));
|
||||
|
||||
// Listen for assembly changes
|
||||
addingAssemblyConnection = physicsService->assemblyAddingSignal.connect(boost::bind(&PhysicsPacketCache::onAddingAssembly, this, _1));
|
||||
removedAssemblyConnection = physicsService->assemblyRemovedSignal.connect(boost::bind(&PhysicsPacketCache::onRemovedAssembly, this, _1));
|
||||
}
|
||||
|
||||
Super::onServiceProvider(oldProvider, newProvider);
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::onAddingAssembly(shared_ptr<Instance> assembly)
|
||||
{
|
||||
shared_ptr<PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
const Assembly* partAssembly = part->getConstPartPrimitive()->getConstAssembly();
|
||||
|
||||
insert(partAssembly);
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::onRemovedAssembly(shared_ptr<Instance> assembly)
|
||||
{
|
||||
shared_ptr<PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
const Assembly* partAssembly = part->getConstPartPrimitive()->getConstAssembly();
|
||||
|
||||
remove(partAssembly);
|
||||
}
|
||||
|
||||
void PhysicsPacketCache::addPart(PartInstance& part)
|
||||
{
|
||||
const Assembly* partAssembly = part.getConstPartPrimitive()->getConstAssembly();
|
||||
insert(partAssembly);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
InstancePacketCache::InstancePacketCache()
|
||||
{
|
||||
setName(sInstancePacketCache);
|
||||
}
|
||||
|
||||
InstancePacketCache::~InstancePacketCache() {}
|
||||
|
||||
void InstancePacketCache::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
||||
{
|
||||
if (oldProvider)
|
||||
{
|
||||
for (StreamCacheMap::iterator i = streamCache.begin(); i != streamCache.end(); i++)
|
||||
{
|
||||
i->second->ancestorChangedConnection.disconnect();
|
||||
i->second->propChangedConnection.disconnect();
|
||||
}
|
||||
|
||||
streamCache.clear();
|
||||
}
|
||||
|
||||
Super::onServiceProvider(oldProvider, newProvider);
|
||||
}
|
||||
|
||||
void InstancePacketCache::onAncestorChanged(shared_ptr<Instance> instance, shared_ptr<Instance> newParent)
|
||||
{
|
||||
if (!newParent)
|
||||
{
|
||||
// remove from cache
|
||||
remove(instance.get());
|
||||
|
||||
// StandardOut::singleton()->printf(MESSAGE_WARNING, "Cache Removing %s", instance->getName().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void InstancePacketCache::insert(const Instance* key)
|
||||
{
|
||||
boost::shared_ptr<CachedBitStream> newCacheItem(new CachedBitStream(key->getGuid().readableString()));
|
||||
|
||||
std::pair<StreamCacheMap::iterator, bool> result = streamCache.insert(std::make_pair(key, newCacheItem));
|
||||
|
||||
// new item, add listener for prop change
|
||||
if (result.second)
|
||||
{
|
||||
Instance* instance = const_cast<Instance*>(key);
|
||||
result.first->second->propChangedConnection =
|
||||
instance->propertyChangedSignal.connect(boost::bind(&InstancePacketCache::CachedBitStream::onPropertyChanged, result.first->second, _1));
|
||||
result.first->second->ancestorChangedConnection =
|
||||
instance->ancestryChangedSignal.connect(boost::bind(&InstancePacketCache::onAncestorChanged, this, _1, _2));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(key->getGuid().readableString() == result.first->second->guidString))
|
||||
{
|
||||
result.first->second->dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstancePacketCache::remove(const Instance* key)
|
||||
{
|
||||
StreamCacheMap::iterator iter = streamCache.find(key);
|
||||
|
||||
if (iter != streamCache.end())
|
||||
{
|
||||
iter->second->ancestorChangedConnection.disconnect();
|
||||
iter->second->propChangedConnection.disconnect();
|
||||
streamCache.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
bool InstancePacketCache::fetchIfUpToDate(const Instance* key, RakNet::BitStream& outBitStream, bool isJoinData)
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
StreamCacheMap::iterator iter = streamCache.find(key);
|
||||
|
||||
if (iter != streamCache.end())
|
||||
{
|
||||
CachedBitStream* cachedItem = iter->second.get();
|
||||
|
||||
if (!(key->getGuid().readableString() == cachedItem->guidString))
|
||||
{
|
||||
cachedItem->dirty = true;
|
||||
}
|
||||
|
||||
if (!cachedItem->bitStream[(int)isJoinData] || cachedItem->dirty)
|
||||
return false;
|
||||
|
||||
outBitStream.WriteBits(
|
||||
cachedItem->bitStream[(int)isJoinData]->GetData(), cachedItem->bitStream[(int)isJoinData]->GetNumberOfBitsUsed(), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// StandardOut::singleton()->printf(MESSAGE_INFO, "Instance cache miss, part: %s, parent: %s",
|
||||
// key->getName().c_str(),
|
||||
// key->getParent() ? key->getParent()->getName().c_str() : ""
|
||||
// );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InstancePacketCache::update(const Instance* key, RakNet::BitStream& bitStream, unsigned int numBits, bool isJoinData)
|
||||
{
|
||||
boost::upgrade_lock<boost::shared_mutex> lock(sharedMutex);
|
||||
|
||||
StreamCacheMap::iterator iter = streamCache.find(key);
|
||||
|
||||
if (iter != streamCache.end())
|
||||
{
|
||||
boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
|
||||
|
||||
boost::shared_ptr<CachedBitStream> cachedItem = iter->second;
|
||||
|
||||
if (!cachedItem->bitStream[(int)isJoinData])
|
||||
cachedItem->bitStream[(int)isJoinData].reset(new RakNet::BitStream());
|
||||
|
||||
cachedItem->bitStream[(int)isJoinData]->Reset();
|
||||
cachedItem->bitStream[(int)isJoinData]->Write(bitStream, numBits);
|
||||
cachedItem->dirty = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
143
engine/network/src/NetworkPacketCache.hpp
Normal file
143
engine/network/src/NetworkPacketCache.hpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "Tree/Service.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "signal.hpp"
|
||||
|
||||
#include <boost/unordered/unordered_map.hpp>
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
extern const char* const sPhysicsPacketCache;
|
||||
class PhysicsPacketCache
|
||||
: public DescribedNonCreatable<PhysicsPacketCache, Instance, sPhysicsPacketCache>
|
||||
, public Service
|
||||
{
|
||||
typedef DescribedNonCreatable<PhysicsPacketCache, Instance, sPhysicsPacketCache> Super;
|
||||
|
||||
public:
|
||||
class CachedBitStream
|
||||
{
|
||||
public:
|
||||
int lastStepId;
|
||||
int dataBitStartOffset;
|
||||
int totalNumBits;
|
||||
boost::shared_ptr<RakNet::BitStream> bitStream;
|
||||
|
||||
CachedBitStream()
|
||||
: lastStepId(0)
|
||||
, dataBitStartOffset(0)
|
||||
, totalNumBits(0)
|
||||
{
|
||||
}
|
||||
~CachedBitStream() {}
|
||||
};
|
||||
|
||||
private:
|
||||
typedef boost::unordered_map<unsigned char, boost::shared_ptr<CachedBitStream>> InnerMap;
|
||||
typedef boost::unordered_map<const Assembly*, InnerMap> StreamCacheMap;
|
||||
StreamCacheMap streamCache;
|
||||
boost::shared_mutex sharedMutex;
|
||||
|
||||
Aya::signals::scoped_connection addingAssemblyConnection;
|
||||
Aya::signals::scoped_connection removedAssemblyConnection;
|
||||
|
||||
public:
|
||||
PhysicsPacketCache();
|
||||
~PhysicsPacketCache();
|
||||
|
||||
bool fetchIfUpToDate(const Assembly* key, unsigned char index, RakNet::BitStream& outBitStream);
|
||||
|
||||
// copy data from bitStream starting at its read position to numBits
|
||||
bool update(const Assembly* key, unsigned char index, RakNet::BitStream& bitStream, unsigned int startReadBitPos, unsigned int numBits);
|
||||
|
||||
protected:
|
||||
virtual void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
|
||||
private:
|
||||
void insert(const Assembly* key);
|
||||
void insertChildAssembly(const Assembly* assembly);
|
||||
void remove(const Assembly* key);
|
||||
void removeChildAssembly(const Assembly* assembly);
|
||||
void onAddingAssembly(shared_ptr<Instance> assembly);
|
||||
void onRemovedAssembly(shared_ptr<Instance> assembly);
|
||||
void addPart(PartInstance& part);
|
||||
|
||||
boost::unique_lock<boost::shared_mutex> debugLock(boost::upgrade_lock<boost::shared_mutex>* upgradeLock = NULL);
|
||||
};
|
||||
|
||||
|
||||
extern const char* const sInstancePacketCache;
|
||||
class InstancePacketCache
|
||||
: public DescribedNonCreatable<InstancePacketCache, Instance, sInstancePacketCache>
|
||||
, public Service
|
||||
{
|
||||
typedef DescribedNonCreatable<InstancePacketCache, Instance, sInstancePacketCache> Super;
|
||||
typedef std::list<Aya::signals::connection> ConnectionList;
|
||||
|
||||
class CachedBitStream
|
||||
{
|
||||
public:
|
||||
bool dirty;
|
||||
|
||||
// regular new instance data containing non dictionary properties, and join time data containing all properties (except parent)
|
||||
boost::shared_ptr<RakNet::BitStream> bitStream[2];
|
||||
|
||||
// debug
|
||||
const std::string guidString;
|
||||
|
||||
Aya::signals::scoped_connection propChangedConnection;
|
||||
Aya::signals::scoped_connection ancestorChangedConnection;
|
||||
|
||||
CachedBitStream(const std::string& guid)
|
||||
: dirty(true)
|
||||
, guidString(guid)
|
||||
{
|
||||
}
|
||||
~CachedBitStream() {}
|
||||
|
||||
void onPropertyChanged(const Aya::Reflection::PropertyDescriptor* desc)
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<const Instance*, boost::shared_ptr<CachedBitStream>> StreamCacheMap;
|
||||
StreamCacheMap streamCache;
|
||||
boost::shared_mutex sharedMutex;
|
||||
|
||||
ConnectionList connections;
|
||||
|
||||
void onAncestorChanged(shared_ptr<Instance> instance, shared_ptr<Instance> newParent);
|
||||
|
||||
public:
|
||||
InstancePacketCache();
|
||||
~InstancePacketCache();
|
||||
|
||||
// not thread safe
|
||||
void insert(const Instance* key);
|
||||
|
||||
// not thread safe
|
||||
void remove(const Instance* key);
|
||||
|
||||
bool fetchIfUpToDate(const Instance* key, RakNet::BitStream& outBitStream, bool isJoinData);
|
||||
|
||||
// copy data from bitStream starting at its read position to numBits
|
||||
bool update(const Instance* key, RakNet::BitStream& bitStream, unsigned int numBits, bool isJoinData);
|
||||
|
||||
protected:
|
||||
virtual void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
250
engine/network/src/NetworkProfiler.cpp
Normal file
250
engine/network/src/NetworkProfiler.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#include "NetworkProfiler.hpp"
|
||||
|
||||
#ifdef NETWORK_PROFILER
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "RakNetTypes.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
const char* const columnNames = "tag, offset, bitSize, layer1, layer2, layer3, layer4, layer5, layer6, layer7, layer8, layer9";
|
||||
|
||||
NetworkProfiler::NetworkProfiler(void)
|
||||
: deepestLayer(0)
|
||||
, networkSettings(&NetworkSettings::singleton())
|
||||
, loggerPlugin(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
NetworkProfiler::~NetworkProfiler(void)
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
bool NetworkProfiler::CanProfile()
|
||||
{
|
||||
if (networkSettings->profiling)
|
||||
{
|
||||
Connect();
|
||||
if (networkSettings->profilerTimedSeconds > 0.f)
|
||||
{
|
||||
if (profilerTimer.delta().seconds() > networkSettings->profilerTimedSeconds)
|
||||
{
|
||||
// timeout, stop profiling
|
||||
networkSettings->profiling = false;
|
||||
Disconnect();
|
||||
return false; // note, early return
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::Connect()
|
||||
{
|
||||
if (!connected)
|
||||
{
|
||||
loggerPlugin = new RakNet::SQLiteClientLoggerPlugin();
|
||||
packetizedTCP.AttachPlugin(loggerPlugin);
|
||||
packetizedTCP.Start(0, 0);
|
||||
RakNet::SystemAddress serverAddress =
|
||||
packetizedTCP.Connect(networkSettings->profilerServerIp.c_str(), networkSettings->profilerServerPort, true);
|
||||
if (serverAddress == RakNet::UNASSIGNED_SYSTEM_ADDRESS)
|
||||
{
|
||||
StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "Failed to connect to profiler log server.");
|
||||
packetizedTCP.Stop();
|
||||
packetizedTCP.DetachPlugin(loggerPlugin);
|
||||
delete loggerPlugin;
|
||||
networkSettings->profiling = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
loggerPlugin->SetServerParameters(serverAddress, "roblox.db3");
|
||||
connected = true;
|
||||
if (networkSettings->profilerTimedSeconds > 0.f)
|
||||
{
|
||||
profilerTimer.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::Disconnect()
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
loggerPlugin->ClearResultHandlers();
|
||||
packetizedTCP.Stop();
|
||||
packetizedTCP.DetachPlugin(loggerPlugin);
|
||||
delete loggerPlugin;
|
||||
dataBlobStack.clear();
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkProfiler* NetworkProfiler::singleton()
|
||||
{
|
||||
static NetworkProfiler networkProfiler;
|
||||
return &networkProfiler;
|
||||
}
|
||||
|
||||
void NetworkProfiler::logPacket(const std::string& type, const RakNet::Packet* packet)
|
||||
{
|
||||
if (CanProfile())
|
||||
{
|
||||
const char* profilerTag = networkSettings->profilerTag.c_str();
|
||||
rakSqlLog("general", "tag, packetType, packetBitSize", (profilerTag, type.c_str(), packet->bitSize));
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::startCpuProfiling(int tag)
|
||||
{
|
||||
if (networkSettings->profilecpu)
|
||||
{
|
||||
cpuProfilingStats[tag].newSample();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::stepCpuProfiling(int tag)
|
||||
{
|
||||
if (networkSettings->profilecpu)
|
||||
{
|
||||
cpuProfilingStats[tag].step();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::outputCpuProfiling()
|
||||
{
|
||||
for (int tag = 0; tag < PROFILER_TAG_COUNT; tag++)
|
||||
{
|
||||
if (cpuProfilingStats[tag].getNumSample() > 0)
|
||||
{
|
||||
// output the stats
|
||||
StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "[%d] profiling results (%d samples):", tag, cpuProfilingStats[tag].getNumSample());
|
||||
float lastStepDelta = 0.0f;
|
||||
for (int i = 0; cpuProfilingStats[tag].stepDelta[i] > 0.f; i++)
|
||||
{
|
||||
float deltaBetweenSteps;
|
||||
float stepDelta = cpuProfilingStats[tag].stepDelta[i];
|
||||
deltaBetweenSteps = stepDelta - lastStepDelta;
|
||||
lastStepDelta = stepDelta;
|
||||
StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "Step %d: %f (delta %f, total %f)", i + 1, stepDelta, deltaBetweenSteps,
|
||||
deltaBetweenSteps * cpuProfilingStats[tag].getNumSample());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::startProfiling(const std::string& dataBlobName, const RakNet::BitStream* bitStream)
|
||||
{
|
||||
if (CanProfile())
|
||||
{
|
||||
dataBlobStack.push_back(DataBlobInfo(dataBlobName, bitStream->GetReadOffset()));
|
||||
if (dataBlobStack.size() > deepestLayer)
|
||||
{
|
||||
deepestLayer = dataBlobStack.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkProfiler::endProfiling(const std::string& dataBlobName, const RakNet::BitStream* bitStream)
|
||||
{
|
||||
if (CanProfile())
|
||||
{
|
||||
DataBlobInfo dataBlobInfo = dataBlobStack.back();
|
||||
AYAASSERT(dataBlobName == dataBlobInfo.name); // Make sure startProfiling and endProfiling are always in pairs
|
||||
AYAASSERT(dataBlobStack.size() > 0);
|
||||
const char* profilerTag = networkSettings->profilerTag.c_str();
|
||||
if (dataBlobStack.size() == deepestLayer) // only log the leaf
|
||||
{
|
||||
if (!NetworkSettings::singleton().profilerOneColumnPerStack)
|
||||
{
|
||||
std::string layers = "";
|
||||
for (std::vector<DataBlobInfo>::iterator iter = dataBlobStack.begin(); iter != dataBlobStack.end(); iter++)
|
||||
{
|
||||
layers += ((*iter).name + ".");
|
||||
}
|
||||
rakSqlLog("report", "tag, offset, bitsize, layers",
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset, layers.c_str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (dataBlobStack.size())
|
||||
{
|
||||
case 1:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), "", "", "", "", "", "", "", ""));
|
||||
break;
|
||||
case 2:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), "", "", "", "", "", "", ""));
|
||||
break;
|
||||
case 3:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(), "", "", "", "", "",
|
||||
""));
|
||||
break;
|
||||
case 4:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(),
|
||||
dataBlobStack.at(3).name.c_str(), "", "", "", "", ""));
|
||||
break;
|
||||
case 5:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(),
|
||||
dataBlobStack.at(3).name.c_str(), dataBlobStack.at(4).name.c_str(), "", "", "", ""));
|
||||
break;
|
||||
case 6:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(),
|
||||
dataBlobStack.at(3).name.c_str(), dataBlobStack.at(4).name.c_str(), dataBlobStack.at(5).name.c_str(), "", "", ""));
|
||||
break;
|
||||
case 7:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(),
|
||||
dataBlobStack.at(3).name.c_str(), dataBlobStack.at(4).name.c_str(), dataBlobStack.at(5).name.c_str(),
|
||||
dataBlobStack.at(6).name.c_str(), "", ""));
|
||||
break;
|
||||
case 8:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(),
|
||||
dataBlobStack.at(3).name.c_str(), dataBlobStack.at(4).name.c_str(), dataBlobStack.at(5).name.c_str(),
|
||||
dataBlobStack.at(6).name.c_str(), dataBlobStack.at(7).name.c_str(), ""));
|
||||
break;
|
||||
case 9:
|
||||
rakSqlLog("details", columnNames,
|
||||
(profilerTag, dataBlobInfo.bitStreamOffset, bitStream->GetReadOffset() - dataBlobInfo.bitStreamOffset,
|
||||
dataBlobStack.at(0).name.c_str(), dataBlobStack.at(1).name.c_str(), dataBlobStack.at(2).name.c_str(),
|
||||
dataBlobStack.at(3).name.c_str(), dataBlobStack.at(4).name.c_str(), dataBlobStack.at(5).name.c_str(),
|
||||
dataBlobStack.at(6).name.c_str(), dataBlobStack.at(7).name.c_str(), dataBlobStack.at(8).name.c_str()));
|
||||
break;
|
||||
default:
|
||||
AYAASSERT(false && "Overflowing the network profiler data layer stack (9). Maybe add more?");
|
||||
}
|
||||
}
|
||||
}
|
||||
deepestLayer = dataBlobStack.size(); // this will prevent the non-leaf node to be printed
|
||||
dataBlobStack.pop_back();
|
||||
}
|
||||
}
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
#endif
|
||||
137
engine/network/src/NetworkProfiler.hpp
Normal file
137
engine/network/src/NetworkProfiler.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include "RakNet/SQLite3Plugin/SQLite3ClientPlugin.hpp"
|
||||
#include "RakNet/SQLite3Plugin/SQLiteClientLoggerPlugin.hpp"
|
||||
#include "RakNet/PacketizedTCP.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#ifdef NETWORK_PROFILER
|
||||
#define NETPROFILE_LOG(typeStr, packetPtr) Aya::Network::NetworkProfiler::singleton()->logPacket(typeStr, packetPtr)
|
||||
#define NETPROFILE_START(dataBlobNameStr, bitStreamPtr) Aya::Network::NetworkProfiler::singleton()->startProfiling(dataBlobNameStr, bitStreamPtr)
|
||||
#define NETPROFILE_END(dataBlobNameStr, bitStreamPtr) Aya::Network::NetworkProfiler::singleton()->endProfiling(dataBlobNameStr, bitStreamPtr)
|
||||
#define CPUPROFILER_START(tag) Aya::Network::NetworkProfiler::singleton()->startCpuProfiling(tag);
|
||||
#define CPUPROFILER_STEP(tag) Aya::Network::NetworkProfiler::singleton()->stepCpuProfiling(tag);
|
||||
#define CPUPROFILER_OUTPUT() Aya::Network::NetworkProfiler::singleton()->outputCpuProfiling();
|
||||
#else
|
||||
#define NETPROFILE_LOG(typeStr, packetPtr)
|
||||
#define NETPROFILE_START(dataBlobNameStr, bitStreamPtr)
|
||||
#define NETPROFILE_END(dataBlobNameStr, bitStreamPtr)
|
||||
#define CPUPROFILER_START(tag)
|
||||
#define CPUPROFILER_STEP(tag)
|
||||
#define CPUPROFILER_OUTPUT()
|
||||
#endif
|
||||
|
||||
#ifdef NETWORK_PROFILER
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class NetworkProfiler
|
||||
{
|
||||
public:
|
||||
class DataBlobInfo
|
||||
{
|
||||
public:
|
||||
DataBlobInfo(const std::string& _name, RakNet::BitSize_t _bitStreamOffset)
|
||||
{
|
||||
name = _name;
|
||||
bitStreamOffset = _bitStreamOffset;
|
||||
}
|
||||
std::string name;
|
||||
RakNet::BitSize_t bitStreamOffset;
|
||||
};
|
||||
|
||||
enum ProfilerTags
|
||||
{
|
||||
PROFILER_streamOutPart,
|
||||
PROFILER_jointRemoval,
|
||||
PROFILER_gcStep,
|
||||
PROFILER_TAG_3,
|
||||
PROFILER_TAG_4,
|
||||
PROFILER_TAG_5,
|
||||
PROFILER_TAG_6,
|
||||
PROFILER_TAG_7,
|
||||
PROFILER_TAG_8,
|
||||
PROFILER_TAG_9,
|
||||
PROFILER_TAG_COUNT
|
||||
};
|
||||
|
||||
class CpuProfilingStat
|
||||
{
|
||||
int currentStep;
|
||||
int numSample;
|
||||
Aya::Timer<Aya::Time::Precise> timer;
|
||||
|
||||
public:
|
||||
double stepDelta[256]; // maximum 256 steps
|
||||
CpuProfilingStat()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
inline void newSample()
|
||||
{
|
||||
timer.reset();
|
||||
numSample++;
|
||||
currentStep = 0;
|
||||
}
|
||||
inline void step()
|
||||
{
|
||||
AYAASSERT(numSample > 0);
|
||||
AYAASSERT(currentStep < 256);
|
||||
stepDelta[currentStep] = (stepDelta[currentStep] * (numSample - 1) + timer.delta().seconds()) / numSample;
|
||||
currentStep++;
|
||||
}
|
||||
int getNumSample()
|
||||
{
|
||||
return numSample;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
for (int i = 0; i < numSample; i++)
|
||||
{
|
||||
stepDelta[i] = 0.0f;
|
||||
}
|
||||
currentStep = 0;
|
||||
numSample = 0;
|
||||
}
|
||||
};
|
||||
|
||||
CpuProfilingStat cpuProfilingStats[PROFILER_TAG_COUNT];
|
||||
|
||||
static NetworkProfiler* singleton();
|
||||
void logPacket(const std::string& type, const RakNet::Packet* packet);
|
||||
|
||||
void startProfiling(const std::string& dataBlobName, const RakNet::BitStream* bitStream);
|
||||
void endProfiling(const std::string& dataBlobName, const RakNet::BitStream* bitStream);
|
||||
|
||||
void startCpuProfiling(int);
|
||||
void stepCpuProfiling(int);
|
||||
void outputCpuProfiling();
|
||||
|
||||
virtual ~NetworkProfiler(void);
|
||||
|
||||
private:
|
||||
// members
|
||||
RakNet::PacketizedTCP packetizedTCP;
|
||||
RakNet::SQLiteClientLoggerPlugin* loggerPlugin;
|
||||
const RakNet::BitStream* currentBitStream;
|
||||
std::vector<DataBlobInfo> dataBlobStack; // use vector to make use of its iterator
|
||||
std::size_t deepestLayer;
|
||||
bool connected;
|
||||
Aya::Timer<Aya::Time::Fast> profilerTimer;
|
||||
NetworkSettings* networkSettings;
|
||||
|
||||
// functions
|
||||
NetworkProfiler(void);
|
||||
void Connect();
|
||||
void Disconnect();
|
||||
bool CanProfile();
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
#endif
|
||||
423
engine/network/src/NetworkSettings.cpp
Normal file
423
engine/network/src/NetworkSettings.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "RakNet/ReliabilityLayer.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
|
||||
#include "NetworkProfiler.hpp"
|
||||
#include "Utility/MemoryStats.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
const char* const sNetworkSettings = "NetworkSettings";
|
||||
|
||||
namespace Reflection
|
||||
{
|
||||
template<>
|
||||
EnumDesc<NetworkSettings::PhysicsSendMethod>::EnumDesc()
|
||||
: EnumDescriptor("PhysicsSendMethod")
|
||||
{
|
||||
addPair(NetworkSettings::ErrorComputation, "ErrorComputation");
|
||||
addPair(NetworkSettings::ErrorComputation2, "ErrorComputation2");
|
||||
addPair(NetworkSettings::RoundRobin, "RoundRobin");
|
||||
addPair(NetworkSettings::TopNErrors, "TopNErrors");
|
||||
}
|
||||
|
||||
template<>
|
||||
EnumDesc<NetworkSettings::PhysicsReceiveMethod>::EnumDesc()
|
||||
: EnumDescriptor("PhysicsReceiveMethod")
|
||||
{
|
||||
addPair(NetworkSettings::Direct, "Direct");
|
||||
addPair(NetworkSettings::Interpolation, "Interpolation");
|
||||
}
|
||||
|
||||
template<>
|
||||
EnumDesc<PacketReliability>::EnumDesc()
|
||||
: EnumDescriptor("PacketReliability")
|
||||
{
|
||||
addPair(UNRELIABLE, "UNRELIABLE");
|
||||
addPair(UNRELIABLE_SEQUENCED, "UNRELIABLE_SEQUENCED");
|
||||
addPair(RELIABLE, "RELIABLE");
|
||||
addPair(RELIABLE_ORDERED, "RELIABLE_ORDERED");
|
||||
addPair(RELIABLE_SEQUENCED, "RELIABLE_SEQUENCED");
|
||||
}
|
||||
|
||||
template<>
|
||||
EnumDesc<PacketPriority>::EnumDesc()
|
||||
: EnumDescriptor("PacketPriority")
|
||||
{
|
||||
addPair(IMMEDIATE_PRIORITY, "IMMEDIATE_PRIORITY");
|
||||
addPair(HIGH_PRIORITY, "HIGH_PRIORITY");
|
||||
addPair(MEDIUM_PRIORITY, "MEDIUM_PRIORITY");
|
||||
addPair(LOW_PRIORITY, "LOW_PRIORITY");
|
||||
}
|
||||
|
||||
} // namespace Reflection
|
||||
|
||||
|
||||
static Reflection::BoundProp<int> prop_PreferredClientPort("PreferredClientPort", "Network", &NetworkSettings::preferredClientPort);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_DataSendRate(
|
||||
"DataSendRate", "Network", &NetworkSettings::getDataSendRate, &NetworkSettings::setDataSendRate);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_DataGCRate(
|
||||
"DataGCRate", "Network", &NetworkSettings::getDataGCRate, &NetworkSettings::setDataGCRate);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_PhysicsSendRate(
|
||||
"PhysicsSendRate", "Network", &NetworkSettings::getPhysicsSendRate, &NetworkSettings::setPhysicsSendRate);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_ClientPhysicsSendRate(
|
||||
"ClientPhysicsSendRate", "Network", &NetworkSettings::getClientPhysicsSendRate, &NetworkSettings::setClientPhysicsSendRate);
|
||||
static Reflection::BoundProp<float> prop_SendRate("NetworkOwnerRate", "Network", &NetworkSettings::networkOwnerRate);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_TouchSendRate(
|
||||
"TouchSendRate", "Network", &NetworkSettings::getTouchSendRate, &NetworkSettings::setTouchSendRate);
|
||||
static Reflection::BoundProp<bool> prop_isThrottledByOutgoingBandwidthLimit(
|
||||
"IsThrottledByOutgoingBandwidthLimit", "Network", &NetworkSettings::isThrottledByOutgoingBandwidthLimit);
|
||||
static Reflection::BoundProp<bool> prop_IsThrottledByCongestionControl(
|
||||
"IsThrottledByCongestionControl", "Network", &NetworkSettings::isThrottledByCongestionControl);
|
||||
static Reflection::PropDescriptor<NetworkSettings, double> prop_ReceiveRate(
|
||||
"ReceiveRate", "Network", &NetworkSettings::getReceiveRate, &NetworkSettings::setReceiveRate);
|
||||
static Reflection::BoundProp<int> prop_WaitingForCharacterLogRate("WaitingForCharacterLogRate", "Network",
|
||||
&NetworkSettings::deprecatedWaitingForCharacterLogRate,
|
||||
Reflection::PropertyDescriptor::Attributes::deprecated(Reflection::PropertyDescriptor::HIDDEN_SCRIPTING));
|
||||
static Reflection::PropDescriptor<NetworkSettings, std::string> prop_ReportStatUrl("ReportStatURL", "Network", &NetworkSettings::getReportStatURL,
|
||||
&NetworkSettings::setReportStatURL, Reflection::PropertyDescriptor::Attributes::deprecated(Reflection::PropertyDescriptor::HIDDEN_SCRIPTING));
|
||||
static Reflection::BoundProp<bool> prop_UsePhysicsPacketCache("UsePhysicsPacketCache", "Network", &NetworkSettings::usePhysicsPacketCache);
|
||||
static Reflection::BoundProp<bool> prop_UseInstancePacketCache("UseInstancePacketCache", "Network", &NetworkSettings::useInstancePacketCache);
|
||||
static Reflection::BoundProp<bool> propIsQueueErrorComputed("IsQueueErrorComputed", "Network", &NetworkSettings::isQueueErrorComputed);
|
||||
|
||||
static Reflection::BoundProp<bool> prop_TrackDataTypes("TrackDataTypes", "Diagnostics", &NetworkSettings::trackDataTypes);
|
||||
static Reflection::BoundProp<bool> prop_TrackPhysicsDetails("TrackPhysicsDetails", "Diagnostics", &NetworkSettings::trackPhysicsDetails);
|
||||
static Reflection::BoundProp<bool> prop_PrintSplitMessages("PrintSplitMessage", "Diagnostics", &NetworkSettings::printSplitMessages);
|
||||
static Reflection::BoundProp<bool> prop_PrintTouches("PrintTouches", "Diagnostics", &NetworkSettings::printTouches);
|
||||
static Reflection::BoundProp<bool> prop_PrintInstances("PrintInstances", "Diagnostics", &NetworkSettings::printInstances);
|
||||
static Reflection::BoundProp<bool> prop_PrintStreamInstanceQuota(
|
||||
"PrintStreamInstanceQuota", "Diagnostics", &NetworkSettings::printStreamInstanceQuota);
|
||||
static Reflection::BoundProp<bool> prop_PrintProperties("PrintProperties", "Diagnostics", &NetworkSettings::printProperties);
|
||||
static Reflection::BoundProp<bool> prop_PrintPhysicsErrors("PrintPhysicsErrors", "Diagnostics", &NetworkSettings::printPhysicsErrors);
|
||||
static Reflection::BoundProp<bool> prop_PrintDataFilters("PrintFilters", "Diagnostics", &NetworkSettings::printDataFilters);
|
||||
static Reflection::BoundProp<bool> prop_ArePhysicsRejectionsReported(
|
||||
"ArePhysicsRejectionsReported", "Diagnostics", &NetworkSettings::printPhysicsFilters);
|
||||
static Reflection::BoundProp<bool> propPrintEvents("PrintEvents", "Diagnostics", &NetworkSettings::printEvents);
|
||||
static Reflection::BoundProp<bool> propPrintBits("PrintBits", "Diagnostics", &NetworkSettings::printBits);
|
||||
static Reflection::BoundProp<double> prop_incommingReplicationLag(
|
||||
"IncommingReplicationLag", "Diagnostics", &NetworkSettings::incommingReplicationLag);
|
||||
|
||||
static Reflection::EnumPropDescriptor<NetworkSettings, NetworkSettings::PhysicsSendMethod> prop_PhysicsSend(
|
||||
"PhysicsSend", "Physics", &NetworkSettings::getPhysicsSendMethod, &NetworkSettings::setPhysicsSendMethod);
|
||||
static Reflection::EnumPropDescriptor<NetworkSettings, NetworkSettings::PhysicsReceiveMethod> prop_PhysicsReceive(
|
||||
"PhysicsReceive", "Physics", &NetworkSettings::dummyGetPhysicsReceiveMethod, &NetworkSettings::dummySetPhysicsReceiveMethod);
|
||||
static Reflection::EnumPropDescriptor<NetworkSettings, PacketPriority> prop_PhysicsSendPriority("PhysicsSendPriority", "Physics",
|
||||
&NetworkSettings::getPhysicsSendPriority, &NetworkSettings::setPhysicsSendPriority, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING);
|
||||
|
||||
Reflection::BoundProp<bool> NetworkSettings::prop_DistributedPhysics(
|
||||
"ExperimentalPhysicsEnabled", "Physics", &NetworkSettings::distributedPhysicsEnabled);
|
||||
|
||||
static Reflection::PropDescriptor<NetworkSettings, int> prop_PhysicsMtuAdjust(
|
||||
"PhysicsMtuAdjust", "Physics", &NetworkSettings::getPhysicsMtuAdjust, &NetworkSettings::setPhysicsMtuAdjust);
|
||||
static Reflection::PropDescriptor<NetworkSettings, int> prop_ReplicationMtuAdjust(
|
||||
"DataMtuAdjust", "Data", &NetworkSettings::getReplicationMtuAdjust, &NetworkSettings::setReplicationMtuAdjust);
|
||||
static Reflection::BoundProp<int> propCanSendPacketBufferLimit("CanSendPacketBufferLimit", "Data", &NetworkSettings::canSendPacketBufferLimit);
|
||||
static Reflection::BoundProp<int> propSendPacketBufferLimit("SendPacketBufferLimit", "Data", &NetworkSettings::sendPacketBufferLimit);
|
||||
static Reflection::BoundProp<int> propMaxDataModelSendBuffer("MaxDataModelSendBuffer", "Data", &NetworkSettings::canSendPacketBufferLimit,
|
||||
Reflection::PropertyDescriptor::Attributes::deprecated(propCanSendPacketBufferLimit));
|
||||
static Reflection::EnumPropDescriptor<NetworkSettings, PacketPriority> prop_DataSendPriority("DataSendPriority", "Data",
|
||||
&NetworkSettings::getDataSendPriority, &NetworkSettings::setDataSendPriority, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING);
|
||||
|
||||
#ifdef NETWORK_PROFILER
|
||||
static Reflection::BoundProp<bool> prop_Profiling("Profiling", "Profiler", &NetworkSettings::profiling, Reflection::PropertyDescriptor::UI);
|
||||
static Reflection::BoundProp<bool> prop_ProfilingCpu("ProfileCpu", "Profiler", &NetworkSettings::profilecpu);
|
||||
static Reflection::BoundProp<bool> prop_ProfilerOneColumnPerStack(
|
||||
"ProfilerOneColumnPerStack", "Profiler", &NetworkSettings::profilerOneColumnPerStack, Reflection::PropertyDescriptor::UI);
|
||||
static Reflection::BoundProp<std::string> prop_ProfilerServerIp("ProfilerServerIp", "Profiler", &NetworkSettings::profilerServerIp);
|
||||
static Reflection::BoundProp<int> prop_ProfilerServerPort("ProfilerServerPort", "Profiler", &NetworkSettings::profilerServerPort);
|
||||
static Reflection::BoundProp<std::string> prop_ProfilerTag(
|
||||
"ProfilerTag", "Profiler", &NetworkSettings::profilerTag, Reflection::PropertyDescriptor::UI);
|
||||
static Reflection::BoundProp<double> prop_ProfilerTimedSeconds("ProfilerTimedSeconds", "Profiler", &NetworkSettings::profilerTimedSeconds);
|
||||
static Reflection::BoundFuncDesc<NetworkSettings, void()> func_printProfilingResult(
|
||||
&NetworkSettings::printProfilingResult, "PrintProfilingResult", Security::None);
|
||||
#endif
|
||||
|
||||
#ifdef NETWORK_PROFILER
|
||||
static Reflection::BoundProp<bool> prop_EnableHeavyCompression(
|
||||
"EnableHeavyCompression", "Optimization", &NetworkSettings::enableHeavyCompression, Reflection::PropertyDescriptor::UI);
|
||||
#else
|
||||
static Reflection::BoundProp<bool> prop_EnableHeavyCompression(
|
||||
"EnableHeavyCompression", "Optimization", &NetworkSettings::enableHeavyCompression, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING);
|
||||
#endif
|
||||
|
||||
static Reflection::PropDescriptor<NetworkSettings, int> prop_extraMemoryUsed("ExtraMemoryUsed", category_Data,
|
||||
&NetworkSettings::getExtraMemoryUsedInMB, &NetworkSettings::setExtraMemoryUsedInMB, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING,
|
||||
Security::Plugin);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_freeMemoryMBytes("FreeMemoryMBytes", category_Data,
|
||||
&NetworkSettings::getFreeMemoryMBytes, NULL, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING, Security::Plugin);
|
||||
static Reflection::PropDescriptor<NetworkSettings, float> prop_freeMemoryPoolMBytes("FreeMemoryPoolMBytes", category_Data,
|
||||
&NetworkSettings::getFreeMemoryPoolMBytes, NULL, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING, Security::Plugin);
|
||||
static Reflection::PropDescriptor<NetworkSettings, bool> prop_RenderRegions(
|
||||
"RenderStreamedRegions", category_Appearance, &NetworkSettings::getRenderStreamedRegions, &NetworkSettings::setRenderStreamedRegions);
|
||||
static Reflection::PropDescriptor<NetworkSettings, bool> prop_ShowPartMovementPath(
|
||||
"ShowPartMovementWayPoint", category_Appearance, &NetworkSettings::getShowPartMovementPath, &NetworkSettings::setShowPartMovementPath);
|
||||
static Reflection::PropDescriptor<NetworkSettings, int> prop_TotalNumMovementWayPoint(
|
||||
"TotalNumMovementWayPoint", category_Appearance, &NetworkSettings::getTotalNumMovementWayPoint, &NetworkSettings::setTotalNumMovementWayPoint);
|
||||
#ifdef AYA_TEST_BUILD
|
||||
static Reflection::PropDescriptor<NetworkSettings, int> prop_TaskSchedulerFindJobFPS(
|
||||
"TaskSchedulerFindJobFPS", category_Appearance, &NetworkSettings::getTaskSchedulerFindJobFPS, &NetworkSettings::setTaskSchedulerFindJobFPS);
|
||||
static Reflection::PropDescriptor<NetworkSettings, bool> prop_TaskSchedulerUpdateJobPriorityOnWake("TaskSchedulerUpdateJobPriorityOnWake",
|
||||
category_Appearance, &NetworkSettings::getTaskSchedulerUpdateJobPriorityOnWake, &NetworkSettings::setTaskSchedulerUpdateJobPriorityOnWake);
|
||||
#endif
|
||||
static Reflection::PropDescriptor<NetworkSettings, bool> prop_ShowActiveAnimationAsset(
|
||||
"ShowActiveAnimationAsset", category_Appearance, &NetworkSettings::getShowActiveAnimationAsset, &NetworkSettings::setShowActiveAnimationAsset);
|
||||
REFLECTION_END();
|
||||
|
||||
NetworkSettings::NetworkSettings(void)
|
||||
: printInstances(false)
|
||||
, printStreamInstanceQuota(false)
|
||||
, printTouches(false)
|
||||
, distributedPhysicsEnabled(true)
|
||||
, printPhysicsErrors(false)
|
||||
, printPhysicsFilters(false)
|
||||
, printProperties(false)
|
||||
, printEvents(false)
|
||||
, printDataFilters(false)
|
||||
, printBits(false)
|
||||
, incommingReplicationLag(0.0)
|
||||
, isQueueErrorComputed(true)
|
||||
, usePhysicsPacketCache(false)
|
||||
, useInstancePacketCache(false)
|
||||
, canSendPacketBufferLimit(1)
|
||||
, sendPacketBufferLimit(-1) // default is to not check it
|
||||
, physicsMtuAdjust(-200)
|
||||
, preferredClientPort(0)
|
||||
, replicationMtuAdjust(-200)
|
||||
, dataSendRate(30.0f)
|
||||
, physicsSendRate(20.0f)
|
||||
, clientPhysicsSendRate(20.0f)
|
||||
, dataGCRate(20.0f)
|
||||
, networkOwnerRate(10.0f)
|
||||
, touchSendRate(10.0f)
|
||||
, isThrottledByOutgoingBandwidthLimit(false)
|
||||
, isThrottledByCongestionControl(false)
|
||||
, receiveRate(60.0f)
|
||||
, physicsSendMethod(
|
||||
TopNErrors) // default to TopNErrors as unit tests will be using the default value, if we are going to use other types of senders please
|
||||
// make coordinating changes in PhysicsReceiver as it is expecting packets from either RoundRobin sender or TopNError sender
|
||||
, physicsSendPriority(HIGH_PRIORITY)
|
||||
, dataSendPriority(MEDIUM_PRIORITY)
|
||||
, physicsReceiveMethod(Interpolation)
|
||||
, printSplitMessages(false)
|
||||
, trackDataTypes(false)
|
||||
, trackPhysicsDetails(false)
|
||||
, profiling(false)
|
||||
, profilecpu(false)
|
||||
, profilerOneColumnPerStack(false)
|
||||
, profilerTag("")
|
||||
, profilerServerIp("127.0.0.1")
|
||||
, profilerServerPort(38123)
|
||||
, profilerTimedSeconds(0.f)
|
||||
, enableHeavyCompression(true)
|
||||
, extraMemoryUsed(0)
|
||||
, totalNumMovementWayPoint(1000)
|
||||
{
|
||||
setName("Network");
|
||||
}
|
||||
|
||||
template<typename A>
|
||||
static A clamp(const A& min, const A& value, const A& max)
|
||||
{
|
||||
return std::max(min, std::min(value, max));
|
||||
}
|
||||
|
||||
void NetworkSettings::setPhysicsMtuAdjust(int value)
|
||||
{
|
||||
value = clamp(-1000, value, 0);
|
||||
if (value != physicsMtuAdjust)
|
||||
{
|
||||
physicsMtuAdjust = value;
|
||||
raisePropertyChanged(prop_PhysicsMtuAdjust);
|
||||
}
|
||||
}
|
||||
void NetworkSettings::setReplicationMtuAdjust(int value)
|
||||
{
|
||||
value = clamp(-1000, value, 0);
|
||||
if (value != replicationMtuAdjust)
|
||||
{
|
||||
replicationMtuAdjust = value;
|
||||
raisePropertyChanged(prop_ReplicationMtuAdjust);
|
||||
}
|
||||
}
|
||||
void NetworkSettings::setDataSendRate(float value)
|
||||
{
|
||||
value = clamp(5.0f, value, 120.0f);
|
||||
if (value != dataSendRate)
|
||||
{
|
||||
dataSendRate = value;
|
||||
raisePropertyChanged(prop_DataSendRate);
|
||||
}
|
||||
}
|
||||
void NetworkSettings::setDataGCRate(float value)
|
||||
{
|
||||
value = clamp(2.0f, value, 60.0f);
|
||||
if (value != dataGCRate)
|
||||
{
|
||||
dataGCRate = value;
|
||||
raisePropertyChanged(prop_DataGCRate);
|
||||
}
|
||||
}
|
||||
void NetworkSettings::setPhysicsSendRate(float value)
|
||||
{
|
||||
value = clamp(0.1f, value, 120.0f);
|
||||
if (value != physicsSendRate)
|
||||
{
|
||||
physicsSendRate = value;
|
||||
raisePropertyChanged(prop_PhysicsSendRate);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::setClientPhysicsSendRate(float value)
|
||||
{
|
||||
value = clamp(0.1f, value, 120.0f);
|
||||
if (value != clientPhysicsSendRate)
|
||||
{
|
||||
clientPhysicsSendRate = value;
|
||||
raisePropertyChanged(prop_ClientPhysicsSendRate);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::setTouchSendRate(float value)
|
||||
{
|
||||
value = clamp(1.0f, value, 120.0f);
|
||||
if (value != touchSendRate)
|
||||
{
|
||||
touchSendRate = value;
|
||||
raisePropertyChanged(prop_TouchSendRate);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::setReceiveRate(double value)
|
||||
{
|
||||
value = clamp(5.0, value, 120.0);
|
||||
if (value != receiveRate)
|
||||
{
|
||||
receiveRate = value;
|
||||
raisePropertyChanged(prop_ReceiveRate);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::setPhysicsSendMethod(const PhysicsSendMethod& value)
|
||||
{
|
||||
if (value != physicsSendMethod)
|
||||
{
|
||||
physicsSendMethod = value;
|
||||
raiseChanged(prop_PhysicsSend);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::setPhysicsSendPriority(const PacketPriority& value)
|
||||
{
|
||||
if (value != physicsSendPriority)
|
||||
{
|
||||
physicsSendPriority = value;
|
||||
raiseChanged(prop_PhysicsSendPriority);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::setDataSendPriority(const PacketPriority& value)
|
||||
{
|
||||
if (value != dataSendPriority)
|
||||
{
|
||||
dataSendPriority = value;
|
||||
raiseChanged(prop_DataSendPriority);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::dummySetPhysicsReceiveMethod(const PhysicsReceiveMethod& value)
|
||||
{
|
||||
if (value != physicsReceiveMethod)
|
||||
{
|
||||
physicsReceiveMethod = value;
|
||||
raiseChanged(prop_PhysicsReceive);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSettings::heavyCompressionEnabled()
|
||||
{
|
||||
return enableHeavyCompression;
|
||||
}
|
||||
|
||||
void NetworkSettings::setExtraMemoryUsedInMB(int value)
|
||||
{
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
|
||||
if (value != extraMemoryUsed)
|
||||
{
|
||||
extraMemoryUsed = value;
|
||||
raiseChanged(prop_extraMemoryUsed);
|
||||
}
|
||||
}
|
||||
|
||||
float NetworkSettings::getFreeMemoryMBytes() const
|
||||
{
|
||||
return MemoryStats::freeMemoryBytes() / 1024.0f / 1024.0f;
|
||||
}
|
||||
|
||||
float NetworkSettings::getFreeMemoryPoolMBytes() const
|
||||
{
|
||||
return MemoryStats::slowGetMemoryPoolAvailability() / 1024.0f / 1024.0f;
|
||||
}
|
||||
|
||||
void NetworkSettings::setRenderStreamedRegions(bool value)
|
||||
{
|
||||
if (value != Workspace::showStreamedRegions)
|
||||
{
|
||||
Workspace::showStreamedRegions = value;
|
||||
raiseChanged(prop_RenderRegions);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSettings::getRenderStreamedRegions() const
|
||||
{
|
||||
return Workspace::showStreamedRegions;
|
||||
}
|
||||
|
||||
void NetworkSettings::setShowPartMovementPath(bool value)
|
||||
{
|
||||
if (value != Workspace::showPartMovementPath)
|
||||
{
|
||||
Workspace::showPartMovementPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSettings::getShowPartMovementPath() const
|
||||
{
|
||||
return Workspace::showPartMovementPath;
|
||||
}
|
||||
|
||||
void NetworkSettings::setTotalNumMovementWayPoint(int value)
|
||||
{
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 1000000)
|
||||
value = 1000000;
|
||||
|
||||
if (value != totalNumMovementWayPoint)
|
||||
{
|
||||
totalNumMovementWayPoint = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSettings::getShowActiveAnimationAsset() const
|
||||
{
|
||||
return Workspace::showActiveAnimationAsset;
|
||||
}
|
||||
|
||||
void NetworkSettings::setShowActiveAnimationAsset(bool value)
|
||||
{
|
||||
if (value != Workspace::showActiveAnimationAsset)
|
||||
{
|
||||
Workspace::showActiveAnimationAsset = value;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::printProfilingResult()
|
||||
{
|
||||
CPUPROFILER_OUTPUT();
|
||||
}
|
||||
} // namespace Aya
|
||||
226
engine/network/src/NetworkSettings.hpp
Normal file
226
engine/network/src/NetworkSettings.hpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#pragma once
|
||||
|
||||
#include "DataModel/GlobalSettings.hpp"
|
||||
|
||||
#include "RakNet/PacketPriority.hpp"
|
||||
#include "TaskScheduler.hpp"
|
||||
|
||||
#include "Utility/ObscureValue.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
extern const char* const sNetworkSettings;
|
||||
class NetworkSettings : public GlobalAdvancedSettingsItem<NetworkSettings, sNetworkSettings>
|
||||
{
|
||||
public:
|
||||
NetworkSettings();
|
||||
|
||||
bool printSplitMessages;
|
||||
bool printPhysicsErrors;
|
||||
bool printInstances;
|
||||
bool printStreamInstanceQuota;
|
||||
bool printTouches;
|
||||
bool printProperties;
|
||||
bool printEvents;
|
||||
bool printPhysicsFilters;
|
||||
bool printDataFilters;
|
||||
bool printBits;
|
||||
bool trackDataTypes;
|
||||
bool trackPhysicsDetails;
|
||||
double incommingReplicationLag;
|
||||
typedef enum
|
||||
{
|
||||
ErrorComputation,
|
||||
ErrorComputation2,
|
||||
RoundRobin,
|
||||
TopNErrors
|
||||
} PhysicsSendMethod;
|
||||
typedef enum
|
||||
{
|
||||
Direct,
|
||||
Interpolation
|
||||
} PhysicsReceiveMethod;
|
||||
|
||||
bool isQueueErrorComputed;
|
||||
|
||||
bool usePhysicsPacketCache;
|
||||
bool useInstancePacketCache;
|
||||
|
||||
bool profiling;
|
||||
bool profilecpu;
|
||||
bool profilerOneColumnPerStack;
|
||||
std::string profilerServerIp;
|
||||
int profilerServerPort;
|
||||
std::string profilerTag;
|
||||
double profilerTimedSeconds; // 0 or negative for un-timed profiling
|
||||
bool enableHeavyCompression;
|
||||
bool heavyCompressionEnabled();
|
||||
|
||||
private:
|
||||
PhysicsSendMethod physicsSendMethod;
|
||||
PhysicsReceiveMethod physicsReceiveMethod;
|
||||
|
||||
PacketPriority physicsSendPriority;
|
||||
PacketPriority dataSendPriority;
|
||||
|
||||
public:
|
||||
PhysicsSendMethod getPhysicsSendMethod() const
|
||||
{
|
||||
return physicsSendMethod;
|
||||
}
|
||||
void setPhysicsSendMethod(const PhysicsSendMethod& value);
|
||||
|
||||
PacketPriority getPhysicsSendPriority() const
|
||||
{
|
||||
return physicsSendPriority;
|
||||
}
|
||||
void setPhysicsSendPriority(const PacketPriority& value);
|
||||
|
||||
PacketPriority getDataSendPriority() const
|
||||
{
|
||||
return dataSendPriority;
|
||||
}
|
||||
void setDataSendPriority(const PacketPriority& value);
|
||||
|
||||
PhysicsReceiveMethod dummyGetPhysicsReceiveMethod() const
|
||||
{
|
||||
return physicsReceiveMethod;
|
||||
}
|
||||
void dummySetPhysicsReceiveMethod(const PhysicsReceiveMethod& value);
|
||||
|
||||
const std::string getReportStatURL() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
void setReportStatURL(const std::string& value) {}
|
||||
|
||||
static Reflection::BoundProp<bool> prop_DistributedPhysics;
|
||||
bool distributedPhysicsEnabled;
|
||||
|
||||
int preferredClientPort;
|
||||
|
||||
int getPhysicsMtuAdjust() const
|
||||
{
|
||||
return physicsMtuAdjust;
|
||||
}
|
||||
void setPhysicsMtuAdjust(int value);
|
||||
|
||||
int getReplicationMtuAdjust() const
|
||||
{
|
||||
return replicationMtuAdjust;
|
||||
}
|
||||
void setReplicationMtuAdjust(int value);
|
||||
|
||||
// Data-out throttle
|
||||
float getDataSendRate() const
|
||||
{
|
||||
return dataSendRate;
|
||||
}
|
||||
void setDataSendRate(float value);
|
||||
|
||||
float getDataGCRate() const
|
||||
{
|
||||
return dataGCRate;
|
||||
}
|
||||
void setDataGCRate(float value);
|
||||
|
||||
float getPhysicsSendRate() const
|
||||
{
|
||||
return physicsSendRate;
|
||||
}
|
||||
void setPhysicsSendRate(float value);
|
||||
|
||||
float getClientPhysicsSendRate() const
|
||||
{
|
||||
return clientPhysicsSendRate;
|
||||
}
|
||||
void setClientPhysicsSendRate(float value);
|
||||
|
||||
// Streaming debug
|
||||
void setExtraMemoryUsedInMB(int value);
|
||||
int getExtraMemoryUsedInMB() const
|
||||
{
|
||||
return extraMemoryUsed;
|
||||
}
|
||||
float getFreeMemoryMBytes() const;
|
||||
float getFreeMemoryPoolMBytes() const;
|
||||
|
||||
bool getRenderStreamedRegions() const;
|
||||
void setRenderStreamedRegions(bool value);
|
||||
|
||||
bool getShowPartMovementPath() const;
|
||||
void setShowPartMovementPath(bool value);
|
||||
int getTotalNumMovementWayPoint() const
|
||||
{
|
||||
return totalNumMovementWayPoint;
|
||||
}
|
||||
void setTotalNumMovementWayPoint(int value);
|
||||
#ifdef AYA_TEST_BUILD
|
||||
int getTaskSchedulerFindJobFPS() const
|
||||
{
|
||||
return TaskScheduler::findJobFPS;
|
||||
}
|
||||
void setTaskSchedulerFindJobFPS(int value)
|
||||
{
|
||||
TaskScheduler::findJobFPS = value;
|
||||
}
|
||||
bool getTaskSchedulerUpdateJobPriorityOnWake() const
|
||||
{
|
||||
return TaskScheduler::updateJobPriorityOnWake;
|
||||
}
|
||||
void setTaskSchedulerUpdateJobPriorityOnWake(bool value)
|
||||
{
|
||||
TaskScheduler::updateJobPriorityOnWake = value;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool getShowActiveAnimationAsset() const;
|
||||
void setShowActiveAnimationAsset(bool value);
|
||||
|
||||
void printProfilingResult();
|
||||
|
||||
float touchSendRate;
|
||||
|
||||
int canSendPacketBufferLimit;
|
||||
int sendPacketBufferLimit;
|
||||
float networkOwnerRate;
|
||||
bool isThrottledByOutgoingBandwidthLimit;
|
||||
bool isThrottledByCongestionControl;
|
||||
|
||||
// Data-in throttle
|
||||
double getReceiveRate() const
|
||||
{
|
||||
return receiveRate;
|
||||
}
|
||||
void setReceiveRate(double value);
|
||||
|
||||
float getTouchSendRate() const
|
||||
{
|
||||
return touchSendRate;
|
||||
}
|
||||
void setTouchSendRate(float value);
|
||||
|
||||
int deprecatedWaitingForCharacterLogRate;
|
||||
|
||||
protected:
|
||||
int physicsMtuAdjust;
|
||||
int replicationMtuAdjust;
|
||||
|
||||
// Data-out throttle
|
||||
ObscureValue<float> dataSendRate;
|
||||
ObscureValue<float> physicsSendRate;
|
||||
ObscureValue<float> clientPhysicsSendRate;
|
||||
float dataGCRate;
|
||||
|
||||
// Data-in throttle
|
||||
ObscureValue<double> receiveRate;
|
||||
|
||||
// streaming debug
|
||||
int extraMemoryUsed;
|
||||
|
||||
// movement debug
|
||||
int totalNumMovementWayPoint;
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
81
engine/network/src/PacketIds.hpp
Normal file
81
engine/network/src/PacketIds.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RakNet/MessageIdentifiers.hpp"
|
||||
#include "RakNet/PacketPriority.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
enum
|
||||
{
|
||||
ID_SET_GLOBALS = ID_USER_PACKET_ENUM, // Sent by Server to Client
|
||||
|
||||
ID_TEACH_DESCRIPTOR_DICTIONARIES,
|
||||
|
||||
ID_DATA,
|
||||
ID_REQUEST_MARKER,
|
||||
|
||||
ID_PHYSICS,
|
||||
ID_PHYSICS_TOUCHES,
|
||||
|
||||
ID_CHAT_ALL,
|
||||
ID_CHAT_TEAM,
|
||||
ID_REPORT_ABUSE,
|
||||
|
||||
ID_SUBMIT_TICKET,
|
||||
|
||||
ID_CHAT_GAME,
|
||||
ID_CHAT_PLAYER,
|
||||
ID_CLUSTER,
|
||||
|
||||
ID_PROTOCAL_MISMATCH,
|
||||
|
||||
ID_SPAWN_NAME,
|
||||
|
||||
ID_PROTOCOL_SYNC,
|
||||
ID_SCHEMA_SYNC,
|
||||
|
||||
ID_PLACEID_VERIFICATION,
|
||||
|
||||
ID_DICTIONARY_FORMAT,
|
||||
|
||||
ID_HASH_MISMATCH,
|
||||
ID_SECURITYKEY_MISMATCH,
|
||||
|
||||
ID_REQUEST_STATS,
|
||||
|
||||
#ifdef ENABLE_VOICE_CHAT
|
||||
ID_OPUS_DATA
|
||||
#endif
|
||||
};
|
||||
|
||||
// Order of packets on a connection:
|
||||
// Client --> Server ID_NEW_INCOMING_CONNECTION
|
||||
// If server accepts connection, the following happen:
|
||||
// Client --> Server ID_SUBMIT_TICKET (contains client info like network protocol version)
|
||||
// Server --> Client ID_SET_GLOBALS (contains all the top replication containers, stuff like workspace, lighting, etc.)
|
||||
// Client --> Server ID_INSTANCE_NEW
|
||||
// Client --> Server ID_INSTANCE_NEW
|
||||
// ...
|
||||
// Client --> Server ID_REQUEST_CHARACTER
|
||||
|
||||
const PacketPriority PHYSICS_GENERAL_PRIORITY = MEDIUM_PRIORITY;
|
||||
const int PHYSICS_CHANNEL = 0;
|
||||
|
||||
const PacketPriority DATAMODEL_PRIORITY = MEDIUM_PRIORITY;
|
||||
const PacketReliability DATAMODEL_RELIABILITY = RELIABLE_ORDERED;
|
||||
const int DATA_CHANNEL = 0;
|
||||
|
||||
const PacketPriority CHAT_PRIORITY = HIGH_PRIORITY;
|
||||
const PacketReliability CHAT_RELIABILITY = RELIABLE;
|
||||
const int CHAT_CHANNEL = 2;
|
||||
|
||||
const PacketPriority VOICE_CHAT_PRIORITY = HIGH_PRIORITY;
|
||||
const PacketReliability VOICE_CHAT_RELIABILITY = UNRELIABLE_SEQUENCED;
|
||||
const int VOICE_CHAT_CHANNEL = 3;
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
260
engine/network/src/Peer.cpp
Normal file
260
engine/network/src/Peer.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include "Peer.hpp"
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "RakNet/GetTime.hpp"
|
||||
#include "RakNet/RakPeer.hpp"
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "DataModel/Stats.hpp"
|
||||
|
||||
#include "DataModel/DataModelJob.hpp"
|
||||
|
||||
#include "DataModel/PhysicsService.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "Utility/ObscureValue.hpp"
|
||||
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
|
||||
#include "DataBlockEncryptor.hpp"
|
||||
|
||||
const char* const Aya::Network::sPeer = "NetworkPeer";
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
|
||||
static Reflection::BoundFuncDesc<Peer, void(int)> func_SetOutgoingKBPSLimit(
|
||||
&Peer::setOutgoingKBPSLimit, "SetOutgoingKBPSLimit", "limit", Security::Plugin);
|
||||
REFLECTION_END();
|
||||
|
||||
class ProfiledRakPeer : public RakNet::RakPeer
|
||||
{
|
||||
private:
|
||||
Peer& peer; // time spent per step in the Raknet thread
|
||||
typedef RakNet::RakPeer Super;
|
||||
|
||||
public:
|
||||
ProfiledRakPeer(Peer& peer)
|
||||
: peer(peer)
|
||||
{
|
||||
}
|
||||
virtual bool RunUpdateCycle(RakNet::TimeUS timeNS, RakNet::Time timeMS)
|
||||
{
|
||||
Timer<Time::Benchmark> timer;
|
||||
RakNet::BitStream updateBitStream(MAXIMUM_MTU_SIZE);
|
||||
bool result = Super::RunUpdateCycle(timeNS, timeMS, updateBitStream);
|
||||
peer.rakDutyCycle.sample(timer.delta());
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
static double lerp = 0.05;
|
||||
unsigned char Peer::aesKey[16];
|
||||
|
||||
Peer::Peer()
|
||||
: rakDutyCycle(lerp)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
aesKey[i] = 0xFE ^ 7 * i;
|
||||
|
||||
protocolVersion = NETWORK_PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
Peer::~Peer() {}
|
||||
|
||||
void Peer::onCreateRakPeer()
|
||||
{
|
||||
RakNet::RakNetGUID guid = rakPeer->rawPeer()->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS);
|
||||
unsigned int seed = (unsigned int)((guid.g >> 32) ^ guid.g);
|
||||
rnr.SeedMT(seed);
|
||||
|
||||
rakPeer->rawPeer()->SetOccasionalPing(true);
|
||||
}
|
||||
|
||||
void Peer::encryptDataPart(RakNet::BitStream& bitStream)
|
||||
{
|
||||
DataBlockEncryptor encryptor;
|
||||
encryptor.SetKey(Peer::aesKey);
|
||||
|
||||
unsigned int length = (unsigned int)bitStream.GetNumberOfBytesUsed() - 1;
|
||||
|
||||
unsigned int bytesRequired = length + 6;
|
||||
bytesRequired = ((bytesRequired + 15) / 16) * 16;
|
||||
|
||||
int deltaBits = 8 * bytesRequired - (bitStream.GetNumberOfBitsAllocated() - 8);
|
||||
if (deltaBits > 0)
|
||||
bitStream.AddBitsAndReallocate(deltaBits);
|
||||
|
||||
encryptor.Encrypt((unsigned char*)bitStream.GetData() + 1, length, (unsigned char*)bitStream.GetData() + 1, &length, &rnr);
|
||||
bitStream.SetWriteOffset((1 + length) * 8);
|
||||
}
|
||||
|
||||
|
||||
void Peer::decryptDataPart(RakNet::BitStream& inBitstream)
|
||||
{
|
||||
DataBlockEncryptor encryptor;
|
||||
encryptor.SetKey(Peer::aesKey);
|
||||
|
||||
unsigned int length = (unsigned int)inBitstream.GetNumberOfBytesUsed() - 1;
|
||||
|
||||
bool success = encryptor.Decrypt((unsigned char*)inBitstream.GetData() + 1, length, (unsigned char*)inBitstream.GetData() + 1, &length);
|
||||
if (!success)
|
||||
throw std::runtime_error("Data error");
|
||||
}
|
||||
|
||||
bool Peer::askAddChild(const Instance* instance) const
|
||||
{
|
||||
return Instance::fastDynamicCast<Replicator>(instance) != NULL;
|
||||
}
|
||||
|
||||
class PeerStatsItem : public Stats::Item
|
||||
{
|
||||
Peer* peer;
|
||||
Stats::Item* rakRate;
|
||||
Stats::Item* rakActivity;
|
||||
Stats::Item* physicsSenders;
|
||||
Stats::Item* bufferHealth;
|
||||
|
||||
public:
|
||||
PeerStatsItem(Peer* peer)
|
||||
: peer(peer)
|
||||
{
|
||||
Stats::Item* item =
|
||||
createChildItem("Packets Thread"); // TODO: Rename this when possible. Unfortunately, Game service requires this to be here
|
||||
rakRate = item->createChildItem("Rate");
|
||||
rakActivity = item->createChildItem("Activity");
|
||||
physicsSenders = item->createChildItem("Physics Senders"); // TODO: More this out of "Packets Thread" when we refactor Game Service
|
||||
bufferHealth = item->createChildItem("Send Buffer Health");
|
||||
}
|
||||
|
||||
/* override */ void update()
|
||||
{
|
||||
rakActivity->formatPercent(peer->rakDutyCycle.dutyCycle());
|
||||
double rate = peer->rakDutyCycle.rate();
|
||||
rakRate->formatValue(rate, "%.2g/s", rate);
|
||||
|
||||
PhysicsService* physicsService = ServiceProvider::find<PhysicsService>(this);
|
||||
physicsSenders->formatValue(physicsService ? physicsService->numSenders() : 0);
|
||||
|
||||
double health = peer->rakPeer->GetBufferHealth();
|
||||
bufferHealth->formatValue(health, "%.4g", health);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class PacketReceiveJob : public DataModelJob
|
||||
{
|
||||
weak_ptr<DataModel> dataModel;
|
||||
ObscureValue<double> receiveRate;
|
||||
|
||||
public:
|
||||
weak_ptr<ConcurrentRakPeer> rakPeer;
|
||||
PacketReceiveJob(shared_ptr<ConcurrentRakPeer> rakPeer, DataModel* dataModel)
|
||||
: DataModelJob("Net PacketReceive", DataModelJob::DataIn, false, shared_from(dataModel), Time::Interval(0))
|
||||
, rakPeer(rakPeer)
|
||||
, dataModel(shared_from(dataModel))
|
||||
, receiveRate(NetworkSettings::singleton().getReceiveRate())
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ReceiveIncoming;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, receiveRate);
|
||||
}
|
||||
|
||||
virtual Job::Error error(const Stats& stats)
|
||||
{
|
||||
return computeStandardError(stats, receiveRate);
|
||||
}
|
||||
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (shared_ptr<DataModel> safeDataModel = dataModel.lock())
|
||||
{
|
||||
FASTLOG1(FLog::DataModelJobs, "Packet receive start, data model: %p", safeDataModel.get());
|
||||
DataModel::scoped_write_request request(safeDataModel.get());
|
||||
|
||||
if (shared_ptr<ConcurrentRakPeer> safeRakPeer = rakPeer.lock())
|
||||
while (RakNet::Packet* packet = safeRakPeer->rawPeer()->Receive())
|
||||
safeRakPeer->DeallocatePacket(packet);
|
||||
|
||||
FASTLOG1(FLog::DataModelJobs, "Packet receive finish, data model: %p", safeDataModel.get());
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void Peer::setOutgoingKBPSLimit(int limit)
|
||||
{
|
||||
if (limit <= 0)
|
||||
rakPeer->rawPeer()->SetPerConnectionOutgoingBandwidthLimit(0);
|
||||
else
|
||||
rakPeer->rawPeer()->SetPerConnectionOutgoingBandwidthLimit(1000 * G3D::iClamp(limit, 10, 10000));
|
||||
}
|
||||
|
||||
void Peer::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
||||
{
|
||||
Aya::Stats::StatsService* stats = ServiceProvider::find<Aya::Stats::StatsService>(oldProvider);
|
||||
if (stats)
|
||||
{
|
||||
shared_ptr<Stats::Item> network = shared_from_polymorphic_downcast<Stats::Item>(stats->findFirstChildByName("Network"));
|
||||
if (network)
|
||||
network->setParent(NULL);
|
||||
}
|
||||
|
||||
if (receiveJob)
|
||||
{
|
||||
receiveJob->rakPeer.reset(); // Make sure it doesn't try to run from now on. (The concurrency is such that it isn't running now)
|
||||
TaskScheduler::singleton().remove(receiveJob);
|
||||
receiveJob.reset();
|
||||
}
|
||||
|
||||
// Ensure that all Replicators are removed, because they
|
||||
// point to rakPeer, which is about to be deleted
|
||||
this->removeAllChildren();
|
||||
|
||||
if (rakPeer)
|
||||
{
|
||||
rakPeer->rawPeer()->DetachPlugin(this);
|
||||
rakPeer.reset();
|
||||
}
|
||||
|
||||
Super::onServiceProvider(oldProvider, newProvider);
|
||||
|
||||
if (newProvider)
|
||||
{
|
||||
rakPeer.reset(new ConcurrentRakPeer(new ProfiledRakPeer(*this), boost::polymorphic_downcast<DataModel*>(newProvider)));
|
||||
rakPeer->rawPeer()->AttachPlugin(this);
|
||||
// rakPeer->rawPeer()->SetMTUSize(1400);
|
||||
onCreateRakPeer();
|
||||
|
||||
receiveJob = shared_ptr<PacketReceiveJob>(new PacketReceiveJob(rakPeer, boost::polymorphic_downcast<DataModel*>(newProvider)));
|
||||
TaskScheduler::singleton().add(receiveJob);
|
||||
}
|
||||
|
||||
stats = ServiceProvider::find<Aya::Stats::StatsService>(newProvider);
|
||||
if (stats)
|
||||
{
|
||||
AYAASSERT(!shared_from_polymorphic_downcast<Stats::Item>(stats->findFirstChildByName("Network")));
|
||||
shared_ptr<Stats::Item> network = Creatable<Instance>::create<PeerStatsItem>(this);
|
||||
network->setName("Network");
|
||||
network->setParent(stats);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
68
engine/network/src/Peer.hpp
Normal file
68
engine/network/src/Peer.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "PacketIds.hpp"
|
||||
#include "Streaming.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "Item.hpp"
|
||||
#include "API.hpp"
|
||||
|
||||
#include "RakNet/PluginInterface2.hpp"
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
#include "Utility/RunStateOwner.hpp"
|
||||
|
||||
#include "Utility/Region2.hpp"
|
||||
|
||||
#include "DataModel/DataModelJob.hpp"
|
||||
|
||||
#include "queue"
|
||||
#include "RakNet/Rand.hpp"
|
||||
|
||||
class RakPeerInterface;
|
||||
class PacketLogger;
|
||||
class RakPeer;
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class ConcurrentRakPeer;
|
||||
class Replicator;
|
||||
class PacketReceiveJob;
|
||||
|
||||
// Client and Server descend from this class
|
||||
extern const char* const sPeer;
|
||||
class Peer
|
||||
: public Reflection::Described<Peer, sPeer, Instance>
|
||||
, public RakNet::PluginInterface2
|
||||
{
|
||||
private:
|
||||
typedef Reflection::Described<Peer, sPeer, Instance> Super;
|
||||
shared_ptr<PacketReceiveJob> receiveJob;
|
||||
static unsigned char aesKey[16];
|
||||
RakNet::RakNetRandom rnr;
|
||||
|
||||
public:
|
||||
RunningAverageDutyCycle<> rakDutyCycle;
|
||||
boost::shared_ptr<ConcurrentRakPeer> rakPeer;
|
||||
void setOutgoingKBPSLimit(int limit);
|
||||
|
||||
void encryptDataPart(RakNet::BitStream& bitStream);
|
||||
static void decryptDataPart(RakNet::BitStream& bitStream);
|
||||
|
||||
protected:
|
||||
int protocolVersion; // default network protocol, shared between client and server
|
||||
|
||||
Peer();
|
||||
~Peer();
|
||||
// Called after rakPeer is created
|
||||
virtual void onCreateRakPeer();
|
||||
bool askAddChild(const Instance* instance) const;
|
||||
/*override*/ void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
299
engine/network/src/PersistentDataStore.cpp
Normal file
299
engine/network/src/PersistentDataStore.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "PersistentDataStore.hpp"
|
||||
#include "DataModel/Value.hpp"
|
||||
|
||||
#include "Script/script.hpp"
|
||||
#include "Xml/XmlSerializer.hpp"
|
||||
#include "Xml/WebSerializer.hpp"
|
||||
#include "DataModel/Message.hpp"
|
||||
|
||||
#include "DataModel/Animation.hpp"
|
||||
|
||||
#include "DataModel/TextLabel.hpp"
|
||||
|
||||
#include "DataModel/TextButton.hpp"
|
||||
|
||||
#include "DataModel/TextBox.hpp"
|
||||
|
||||
#include "Players.hpp"
|
||||
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
static inline void sendDataPersistenceStats(int placeId)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << placeId;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
static int computeLimit(const Reflection::Variant& value);
|
||||
|
||||
|
||||
static void computeInstanceLimit(shared_ptr<Instance> instance, int* result)
|
||||
{
|
||||
(*result) += instance->getPersistentDataCost();
|
||||
}
|
||||
|
||||
static void computeValueMapLimit(const std::pair<std::string, Reflection::Variant>& pair, int* result)
|
||||
{
|
||||
(*result) += computeLimit(pair.second);
|
||||
}
|
||||
static void computeValueCollectionLimit(const Reflection::Variant& value, int* result)
|
||||
{
|
||||
(*result) += computeLimit(value);
|
||||
}
|
||||
static int computeLimit(const Reflection::Variant& value)
|
||||
{
|
||||
if (value.isType<std::string>())
|
||||
{
|
||||
return Instance::computeStringCost(value.get<std::string>());
|
||||
}
|
||||
if (value.isType<shared_ptr<Instance>>())
|
||||
{
|
||||
int result = 0;
|
||||
computeInstanceLimit(value.get<shared_ptr<Instance>>(), &result);
|
||||
return result;
|
||||
}
|
||||
if (value.isType<shared_ptr<const Aya::Reflection::ValueMap>>())
|
||||
{
|
||||
int sum = 1;
|
||||
shared_ptr<const Aya::Reflection::ValueMap> valueMap = value.get<shared_ptr<const Aya::Reflection::ValueMap>>();
|
||||
std::for_each(valueMap->begin(), valueMap->end(), boost::bind(&computeValueMapLimit, _1, &sum));
|
||||
return sum;
|
||||
}
|
||||
if (value.isType<shared_ptr<const Reflection::ValueArray>>())
|
||||
{
|
||||
int sum = 1;
|
||||
shared_ptr<const Reflection::ValueArray> valueCollection = value.get<shared_ptr<const Reflection::ValueArray>>();
|
||||
std::for_each(valueCollection->begin(), valueCollection->end(), boost::bind(&computeValueCollectionLimit, _1, &sum));
|
||||
return sum;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
PersistentDataStore::PersistentDataStore(const Reflection::ValueMap* input, const Players* players, int complexityLimit)
|
||||
: complexity(0)
|
||||
, players(players)
|
||||
, complexityLimit(complexityLimit)
|
||||
, leaderboardDirty(false)
|
||||
{
|
||||
if (input)
|
||||
{
|
||||
valueMap = (*input);
|
||||
leaderboardDirty = true;
|
||||
std::for_each(valueMap.begin(), valueMap.end(), boost::bind(&computeValueMapLimit, _1, &complexity));
|
||||
|
||||
if (valueMap.size() > 0)
|
||||
{
|
||||
static boost::once_flag flag = BOOST_ONCE_INIT;
|
||||
const DataModel* dm = DataModel::get(players);
|
||||
boost::call_once(boost::bind(&sendDataPersistenceStats, dm ? dm->getPlaceID() : 0), flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool PersistentDataStore::serializeValueMap(std::string& output, const Reflection::ValueMap& valueMap)
|
||||
{
|
||||
boost::scoped_ptr<XmlElement> root(WebSerializer::writeTable(valueMap));
|
||||
if (root)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
TextXmlWriter machine(stream);
|
||||
machine.serialize(root.get());
|
||||
stream.flush();
|
||||
|
||||
output = stream.str();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PersistentDataStore::saveLeaderboard(std::string& output)
|
||||
{
|
||||
leaderboardDirty = false;
|
||||
Reflection::ValueMap leaderboardValueMap;
|
||||
if (players)
|
||||
{
|
||||
for (boost::unordered_set<std::string>::const_iterator iter = players->beginLeaderboardKey(), end = players->endLeaderboardKey(); iter != end;
|
||||
++iter)
|
||||
{
|
||||
leaderboardValueMap[*iter] = getNumber(*iter);
|
||||
}
|
||||
}
|
||||
return serializeValueMap(output, leaderboardValueMap);
|
||||
}
|
||||
|
||||
bool PersistentDataStore::save(std::string& output)
|
||||
{
|
||||
return serializeValueMap(output, valueMap);
|
||||
}
|
||||
void PersistentDataStore::setComplexityLimit(int value)
|
||||
{
|
||||
complexityLimit = value;
|
||||
}
|
||||
void PersistentDataStore::removeKey(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter != valueMap.end())
|
||||
complexity -= computeLimit(iter->second);
|
||||
valueMap.erase(key);
|
||||
}
|
||||
bool PersistentDataStore::enforceComplexity(const std::string& key)
|
||||
{
|
||||
int addedComplexity = computeLimit(valueMap[key]);
|
||||
|
||||
if (addedComplexity + complexity <= complexityLimit)
|
||||
{
|
||||
complexity += addedComplexity;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueMap.erase(key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PersistentDataStore::isNumber(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter != valueMap.end() && !iter->second.isType<double>())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
double PersistentDataStore::getNumber(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter == valueMap.end() || !iter->second.isType<double>())
|
||||
return 0.0f;
|
||||
return iter->second.get<double>();
|
||||
}
|
||||
bool PersistentDataStore::setNumber(const std::string& key, double value)
|
||||
{
|
||||
if (isNumber(key) && getNumber(key) == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (players->hasLeaderboardKey(key))
|
||||
leaderboardDirty = true;
|
||||
|
||||
removeKey(key);
|
||||
|
||||
if (value == 0)
|
||||
return true;
|
||||
|
||||
valueMap[key] = value;
|
||||
|
||||
return enforceComplexity(key);
|
||||
}
|
||||
std::string PersistentDataStore::getString(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter == valueMap.end() || !iter->second.isType<std::string>())
|
||||
return "";
|
||||
return iter->second.get<std::string>();
|
||||
}
|
||||
bool PersistentDataStore::setString(const std::string& key, const std::string& value)
|
||||
{
|
||||
removeKey(key);
|
||||
|
||||
if (value == "")
|
||||
return true;
|
||||
|
||||
valueMap[key] = value;
|
||||
|
||||
return enforceComplexity(key);
|
||||
}
|
||||
bool PersistentDataStore::getBoolean(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter == valueMap.end() || !iter->second.isType<bool>())
|
||||
return false;
|
||||
return iter->second.get<bool>();
|
||||
}
|
||||
bool PersistentDataStore::setBoolean(const std::string& key, bool value)
|
||||
{
|
||||
removeKey(key);
|
||||
|
||||
if (value == false)
|
||||
return true;
|
||||
|
||||
valueMap[key] = value;
|
||||
|
||||
return enforceComplexity(key);
|
||||
}
|
||||
shared_ptr<Instance> PersistentDataStore::getInstance(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter == valueMap.end() || !iter->second.isType<shared_ptr<Instance>>())
|
||||
return shared_ptr<Instance>();
|
||||
|
||||
return iter->second.get<shared_ptr<Instance>>()->clone(SerializationCreator);
|
||||
}
|
||||
|
||||
bool PersistentDataStore::setInstance(const std::string& key, shared_ptr<Instance> value)
|
||||
{
|
||||
removeKey(key);
|
||||
|
||||
if (!value)
|
||||
return true;
|
||||
|
||||
shared_ptr<Instance> clonedInstance = value->clone(SerializationCreator);
|
||||
if (clonedInstance)
|
||||
valueMap[key] = clonedInstance;
|
||||
|
||||
return enforceComplexity(key);
|
||||
}
|
||||
|
||||
shared_ptr<const Reflection::ValueArray> PersistentDataStore::getList(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter == valueMap.end() || !iter->second.isType<shared_ptr<const Reflection::ValueArray>>())
|
||||
return shared_ptr<const Reflection::ValueArray>();
|
||||
return iter->second.get<shared_ptr<const Reflection::ValueArray>>();
|
||||
}
|
||||
|
||||
bool PersistentDataStore::setList(const std::string& key, shared_ptr<const Reflection::ValueArray> value)
|
||||
{
|
||||
removeKey(key);
|
||||
|
||||
if (!value || value->size() == 0)
|
||||
return true;
|
||||
|
||||
valueMap[key] = value;
|
||||
|
||||
return enforceComplexity(key);
|
||||
}
|
||||
shared_ptr<const Aya::Reflection::ValueMap> PersistentDataStore::getTable(const std::string& key)
|
||||
{
|
||||
Reflection::ValueMap::iterator iter = valueMap.find(key);
|
||||
if (iter == valueMap.end() || !iter->second.isType<shared_ptr<const Aya::Reflection::ValueMap>>())
|
||||
return shared_ptr<const Aya::Reflection::ValueMap>();
|
||||
return iter->second.get<shared_ptr<const Aya::Reflection::ValueMap>>();
|
||||
}
|
||||
|
||||
|
||||
bool PersistentDataStore::setTable(const std::string& key, shared_ptr<const Aya::Reflection::ValueMap> value)
|
||||
{
|
||||
removeKey(key);
|
||||
|
||||
if (!value || value->empty())
|
||||
return true;
|
||||
|
||||
valueMap[key] = value;
|
||||
|
||||
return enforceComplexity(key);
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
72
engine/network/src/PersistentDataStore.hpp
Normal file
72
engine/network/src/PersistentDataStore.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
class Players;
|
||||
|
||||
class PersistentDataStore : boost::noncopyable
|
||||
{
|
||||
private:
|
||||
Reflection::ValueMap valueMap;
|
||||
const Players* players;
|
||||
|
||||
bool leaderboardDirty;
|
||||
int complexity;
|
||||
int complexityLimit;
|
||||
|
||||
void removeKey(const std::string& key);
|
||||
bool enforceComplexity(const std::string& key);
|
||||
|
||||
static bool serializeValueMap(std::string& output, const Reflection::ValueMap& valueMap);
|
||||
|
||||
bool isNumber(const std::string& key);
|
||||
|
||||
public:
|
||||
PersistentDataStore(const Reflection::ValueMap* input, const Players* players, int complexityLimit);
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return valueMap.empty();
|
||||
}
|
||||
bool isLeaderboardDirty() const
|
||||
{
|
||||
return leaderboardDirty;
|
||||
}
|
||||
|
||||
bool save(std::string& output);
|
||||
bool saveLeaderboard(std::string& output);
|
||||
|
||||
int getComplexity() const
|
||||
{
|
||||
return complexity;
|
||||
}
|
||||
int getComplexityLimit() const
|
||||
{
|
||||
return complexityLimit;
|
||||
}
|
||||
void setComplexityLimit(int value);
|
||||
|
||||
float getLeaderboard(const std::string& key);
|
||||
bool setLeaderboard(const std::string& key, float value);
|
||||
|
||||
double getNumber(const std::string& key);
|
||||
bool setNumber(const std::string& key, double value);
|
||||
std::string getString(const std::string& key);
|
||||
bool setString(const std::string& key, const std::string& value);
|
||||
bool getBoolean(const std::string& key);
|
||||
bool setBoolean(const std::string& key, bool value);
|
||||
shared_ptr<Instance> getInstance(const std::string& key);
|
||||
bool setInstance(const std::string&, shared_ptr<Instance> value);
|
||||
|
||||
shared_ptr<const Reflection::ValueArray> getList(const std::string& key);
|
||||
bool setList(const std::string&, shared_ptr<const Reflection::ValueArray> value);
|
||||
shared_ptr<const Aya::Reflection::ValueMap> getTable(const std::string& key);
|
||||
bool setTable(const std::string&, shared_ptr<const Aya::Reflection::ValueMap> value);
|
||||
};
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
890
engine/network/src/PhysicsReceiver.cpp
Normal file
890
engine/network/src/PhysicsReceiver.cpp
Normal file
@@ -0,0 +1,890 @@
|
||||
|
||||
|
||||
#include "PhysicsReceiver.hpp"
|
||||
|
||||
#include "Compressor.hpp"
|
||||
#include "RakNet/RakPeerInterface.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
|
||||
#include "Streaming.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/PhysicsService.hpp"
|
||||
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
#include "World/Joint.hpp"
|
||||
#include "World/Mechanism.hpp"
|
||||
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
|
||||
#include "PhysicsSender.hpp"
|
||||
#include "NetworkProfiler.hpp"
|
||||
|
||||
#include "DrawAdorn.hpp"
|
||||
#include "Base/Adorn.hpp"
|
||||
#include <stack>
|
||||
#include "Utility/MovementHistory.hpp"
|
||||
|
||||
#include "Utility/VarInt.hpp"
|
||||
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
DYNAMIC_FASTFLAG(HumanoidFloorPVUpdateSignal)
|
||||
DYNAMIC_FASTINTVARIABLE(DebugMovementPathNumTotalWayPoint, 1000)
|
||||
DYNAMIC_FASTFLAG(SimpleHermiteSplineInterpolate)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
namespace PathBasedMovementDebug
|
||||
{
|
||||
NodeDebugInfo FirstNode = {Color3::blue(), 13, false};
|
||||
NodeDebugInfo CompressedBaselineCFrame = {Color3::orange(), 12, true};
|
||||
NodeDebugInfo PathNode = {Color3::yellow(), 6, true};
|
||||
NodeDebugInfo XPacketCompressedPathNode = {Color3::green(), 6, true};
|
||||
NodeDebugInfo InterpolateCFrameFromNode = {Color3::white(), 10, true};
|
||||
NodeDebugInfo InterpolateCFrame = {Color3::white(), 11, true};
|
||||
NodeDebugInfo VelocityNode = {Color3::red(), 5, false};
|
||||
NodeDebugInfo OldNode = {Color3::red(), 6, true};
|
||||
NodeDebugInfo CFrame = {Color3::white(), 5, true}; // regular cframe not from path based movement
|
||||
} // namespace PathBasedMovementDebug
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
|
||||
void DeserializedTouchItem::process(Replicator& replicator)
|
||||
{
|
||||
if (replicator.physicsReceiver)
|
||||
{
|
||||
for (auto& tp : touchPairs)
|
||||
{
|
||||
replicator.physicsReceiver->processTouchPair(tp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsReceiver::PhysicsReceiver(Replicator* replicator, bool isServer)
|
||||
: replicator(replicator)
|
||||
, iAmServer(isServer)
|
||||
, stats(NULL)
|
||||
, movementWaypointList(DFInt::DebugMovementPathNumTotalWayPoint)
|
||||
{
|
||||
setTime(Time::nowFast());
|
||||
}
|
||||
|
||||
void PhysicsReceiver::setTime(Time now_)
|
||||
{
|
||||
now = now_;
|
||||
}
|
||||
|
||||
/*
|
||||
Format for a mechanism:
|
||||
|
||||
>> MechanismAttributes
|
||||
>> PrimaryAssembly
|
||||
>> done
|
||||
while (!done)
|
||||
{
|
||||
>> ChildAssembly
|
||||
>> done
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
void logItem(MechanismItem& item, RakNet::Time timeStamp)
|
||||
{
|
||||
float y = item.getAssemblyItem(0).pv.position.translation.y;
|
||||
|
||||
G3D::Log::common()->printf("%u\t", timeStamp);
|
||||
G3D::Log::common()->printf("%f", y);
|
||||
G3D::Log::common()->println("");
|
||||
}
|
||||
*/
|
||||
|
||||
void PhysicsReceiver::receiveMechanismCFrames(RakNet::BitStream& bitStream, RakNet::Time timeStamp, const Aya::RemoteTime& sendTime)
|
||||
{
|
||||
NETPROFILE_START("receiveMechanismCFrames", &bitStream);
|
||||
int bitStart = bitStream.GetReadOffset();
|
||||
shared_ptr<PartInstance> part;
|
||||
while (receivePart(part, bitStream))
|
||||
{
|
||||
if (part)
|
||||
{
|
||||
if (part->raknetTime > timeStamp)
|
||||
{
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->print(Aya::MESSAGE_INFO, "Physics-in old packet");
|
||||
}
|
||||
part.reset();
|
||||
}
|
||||
}
|
||||
CoordinateFrame cf;
|
||||
Velocity vel;
|
||||
readCoordinateFrame(bitStream, cf);
|
||||
if (DFFlag::SimpleHermiteSplineInterpolate)
|
||||
{
|
||||
readVelocity(bitStream, vel);
|
||||
}
|
||||
|
||||
if (part)
|
||||
{
|
||||
part->setPhysics(cf);
|
||||
part->addInterpolationSample(cf, vel, sendTime, now, 0);
|
||||
}
|
||||
}
|
||||
if (stats)
|
||||
{
|
||||
stats->details.mechanismCFrame.increment();
|
||||
stats->details.mechanismCFrameSize.sample((bitStream.GetReadOffset() - bitStart) / 8);
|
||||
}
|
||||
NETPROFILE_END("receiveMechanismCFrames", &bitStream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::receiveMechanism(
|
||||
RakNet::BitStream& bitStream, PartInstance* rootPart, MechanismItem& item, RemoteTime remoteSendTime, int& numNodesInHistory)
|
||||
{
|
||||
if (NetworkSettings::singleton().getTotalNumMovementWayPoint() == 123456) // magic number to clear all after each packet
|
||||
{
|
||||
movementWaypointList.clear();
|
||||
}
|
||||
|
||||
NETPROFILE_START("receiveMechanism", &bitStream);
|
||||
int bitStart = bitStream.GetReadOffset();
|
||||
item.reset();
|
||||
|
||||
readMechanismAttributes(bitStream, item);
|
||||
readMovementHistory(bitStream, remoteSendTime, rootPart, item, numNodesInHistory);
|
||||
if (iAmServer) // client always sends assembly only
|
||||
{
|
||||
readAssembly(bitStream, rootPart, item, false);
|
||||
}
|
||||
bool done;
|
||||
|
||||
bitStream >> done;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
shared_ptr<PartInstance> part;
|
||||
bool ok = receivePart(part, bitStream);
|
||||
AYAASSERT(ok);
|
||||
|
||||
readAssembly(bitStream, part.get(), item, false);
|
||||
|
||||
bitStream >> done;
|
||||
}
|
||||
|
||||
if (stats)
|
||||
{
|
||||
stats->details.mechanism.increment();
|
||||
stats->details.mechanismSize.sample((bitStream.GetReadOffset() - bitStart) / 8);
|
||||
}
|
||||
NETPROFILE_END("receiveMechanism", &bitStream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readMovementHistory(
|
||||
RakNet::BitStream& bitStream, RemoteTime remoteSendTime, PartInstance* rootPart, MechanismItem& mechanismItem, int& numNodesInHistory)
|
||||
{
|
||||
numNodesInHistory = 0;
|
||||
|
||||
if (iAmServer)
|
||||
{
|
||||
// client always sends assembly only
|
||||
return;
|
||||
}
|
||||
NETPROFILE_START("readMovementHistory", &bitStream);
|
||||
bool hasMovement;
|
||||
unsigned int numNode;
|
||||
uint8_t precisionLevel, timeInterval2Ms;
|
||||
int8_t x, y, z;
|
||||
float timeInterval, timeToEnd;
|
||||
bitStream >> hasMovement;
|
||||
if (hasMovement)
|
||||
{
|
||||
CoordinateFrame cf;
|
||||
bool crossPacketCompression;
|
||||
bitStream >> crossPacketCompression;
|
||||
readAssembly(bitStream, rootPart, mechanismItem, crossPacketCompression);
|
||||
if (rootPart)
|
||||
{
|
||||
if (crossPacketCompression)
|
||||
{
|
||||
cf = rootPart->getLastCFrame(rootPart->getCoordinateFrame());
|
||||
cf.rotation = mechanismItem.getAssemblyItem(mechanismItem.numAssemblies() - 1).pv.position.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
cf = mechanismItem.getAssemblyItem(mechanismItem.numAssemblies() - 1).pv.position;
|
||||
}
|
||||
rootPart->setLastCFrame(cf);
|
||||
}
|
||||
|
||||
// bitStream >> numNode;
|
||||
VarInt<>::decode<RakNet::BitStream>(bitStream, &numNode);
|
||||
|
||||
if (numNode > 0)
|
||||
{
|
||||
nodeStack.clear(); // Moved to header in order to avoid reallocation overhead
|
||||
|
||||
Vector3 nodeTrans = cf.translation;
|
||||
AssemblyItem& assemblyItem = mechanismItem.getAssemblyItem(mechanismItem.numAssemblies() - 1);
|
||||
timeToEnd = 0.f;
|
||||
for (uint8_t i = 0; i < (uint8_t)numNode; i++)
|
||||
{
|
||||
bitStream >> precisionLevel;
|
||||
bitStream >> x;
|
||||
bitStream >> y;
|
||||
bitStream >> z;
|
||||
bitStream >> timeInterval2Ms;
|
||||
|
||||
// reconstruct the time
|
||||
timeInterval = MovementHistory::getSecFrom2Ms(timeInterval2Ms);
|
||||
// reconstruct the cframe
|
||||
Vector3 delta;
|
||||
delta.x = MovementHistory::decompress(x, precisionLevel);
|
||||
delta.y = MovementHistory::decompress(y, precisionLevel);
|
||||
delta.z = MovementHistory::decompress(z, precisionLevel);
|
||||
nodeTrans = nodeTrans - delta;
|
||||
CoordinateFrame nodeCFrame(cf.rotation, nodeTrans);
|
||||
|
||||
bool isBaselineNode = false;
|
||||
if (crossPacketCompression)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
// this node is the current CFrame
|
||||
assemblyItem.pv.position.translation = nodeCFrame.translation;
|
||||
if (rootPart)
|
||||
{
|
||||
AYAASSERT(numNode > 1); // we should have at least two nodes: a baseline node and a path node
|
||||
rootPart->setLastCFrame(nodeCFrame);
|
||||
isBaselineNode = true;
|
||||
// addVectorAdorn(cf.translation, nodeCFrame.translation, Color3::purple());
|
||||
}
|
||||
}
|
||||
else if (i == 1)
|
||||
{
|
||||
// Calculate the linear velocity based on last delta
|
||||
assemblyItem.pv.velocity.linear = delta / timeInterval;
|
||||
if (NetworkSettings::singleton().getTotalNumMovementWayPoint() % 7 == 0) // magic number to print velocity
|
||||
{
|
||||
addWayPointAdorn(nodeCFrame.translation + assemblyItem.pv.velocity.linear, PathBasedMovementDebug::VelocityNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBaselineNode)
|
||||
{
|
||||
TimedCF timedCf;
|
||||
timedCf.cf = nodeCFrame;
|
||||
timeToEnd += timeInterval;
|
||||
timedCf.timeToEnd = timeToEnd;
|
||||
nodeStack.push_back(timedCf);
|
||||
}
|
||||
else
|
||||
{
|
||||
addWayPointAdorn(nodeCFrame.translation, PathBasedMovementDebug::CompressedBaselineCFrame);
|
||||
}
|
||||
}
|
||||
if (rootPart)
|
||||
{
|
||||
// StandardOut::singleton()->printf(MESSAGE_INFO, "-------------- %d", crossPacketCompression);
|
||||
int numNodesAhead = nodeStack.size();
|
||||
numNodesInHistory = numNodesAhead;
|
||||
|
||||
while (nodeStack.size() > 0)
|
||||
{
|
||||
TimedCF timedCf = nodeStack.back();
|
||||
RemoteTime nodeTime = remoteSendTime - Time::Interval(timedCf.timeToEnd);
|
||||
if (replicator->remoteRaknetTimeToLocalRbxTime(nodeTime) >= rootPart->getLastUpdateTime()) // only process the new nodes
|
||||
{
|
||||
if (DFFlag::SimpleHermiteSplineInterpolate)
|
||||
rootPart->addInterpolationSample(timedCf.cf, mechanismItem.getAssemblyItem(mechanismItem.numAssemblies() - 1).pv.velocity,
|
||||
nodeTime, replicator->remoteRaknetTimeToLocalRbxTime(remoteSendTime), timedCf.timeToEnd, numNodesAhead);
|
||||
else
|
||||
rootPart->addInterpolationSample(timedCf.cf, Velocity(), nodeTime,
|
||||
replicator->remoteRaknetTimeToLocalRbxTime(remoteSendTime), timedCf.timeToEnd, numNodesAhead);
|
||||
|
||||
addWayPointAdorn(timedCf.cf.translation,
|
||||
crossPacketCompression ? PathBasedMovementDebug::XPacketCompressedPathNode : PathBasedMovementDebug::PathNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->print(Aya::MESSAGE_INFO, "Discard old node *****");
|
||||
|
||||
addWayPointAdorn(timedCf.cf.translation, PathBasedMovementDebug::OldNode);
|
||||
}
|
||||
}
|
||||
|
||||
nodeStack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AssemblyItem& assemblyItem = mechanismItem.appendAssembly();
|
||||
assemblyItem.rootPart = shared_from<PartInstance>(rootPart);
|
||||
if (rootPart)
|
||||
{
|
||||
assemblyItem.pv.position = rootPart->getCoordinateFrame();
|
||||
assemblyItem.pv.velocity = rootPart->getVelocity();
|
||||
}
|
||||
readMotorAngles(bitStream, assemblyItem);
|
||||
}
|
||||
NETPROFILE_END("readMovementHistory", &bitStream);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PhysicsReceiver::readMechanismAttributes(RakNet::BitStream& bitStream, MechanismItem& historyItem)
|
||||
{
|
||||
NETPROFILE_START("readMechanismAttributes", &bitStream);
|
||||
historyItem.hasVelocity = replicator->settings().distributedPhysicsEnabled;
|
||||
|
||||
bool hasState;
|
||||
bitStream >> hasState;
|
||||
if (hasState)
|
||||
{
|
||||
bitStream >> historyItem.networkHumanoidState;
|
||||
}
|
||||
else
|
||||
{
|
||||
historyItem.networkHumanoidState = 0;
|
||||
}
|
||||
NETPROFILE_END("readMechanismAttributes", &bitStream);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PhysicsReceiver::readAssembly(RakNet::BitStream& bitstream, PartInstance* rootPart, MechanismItem& mechanismItem, bool crossPacketCompression)
|
||||
{
|
||||
NETPROFILE_START("readAssembly", &bitstream);
|
||||
AssemblyItem& assemblyItem = mechanismItem.appendAssembly();
|
||||
assemblyItem.rootPart = shared_from<PartInstance>(rootPart);
|
||||
|
||||
readPV(bitstream, assemblyItem, crossPacketCompression);
|
||||
if (crossPacketCompression && rootPart)
|
||||
{
|
||||
// reuse the last translation (for now)
|
||||
assemblyItem.pv.position.translation = rootPart->getCoordinateFrame().translation;
|
||||
}
|
||||
|
||||
readMotorAngles(bitstream, assemblyItem);
|
||||
|
||||
if (rootPart)
|
||||
{
|
||||
addWayPointAdorn(assemblyItem.pv.position.translation, PathBasedMovementDebug::FirstNode);
|
||||
}
|
||||
|
||||
if (rootPart)
|
||||
{
|
||||
AYAASSERT(rootPart->getPartPrimitive()->getAssembly());
|
||||
|
||||
Primitive* primitive = rootPart->getPartPrimitive();
|
||||
Joint* toParent = aya_static_cast<Joint*>(primitive->getEdgeToParent());
|
||||
|
||||
// This joint could be null if the primitive still exists, but is no longer a child joint to parent
|
||||
|
||||
if (toParent)
|
||||
{
|
||||
toParent->setPhysics(); // read on joint angles, etc.
|
||||
}
|
||||
}
|
||||
NETPROFILE_END("readAssembly", &bitstream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::deserializeTouches(RakNet::BitStream& bitstream, const RakNet::SystemAddress& from, std::vector<TouchPair>& touchPairs)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
TouchPair tp;
|
||||
if (!deserializeTouch(bitstream, from, tp))
|
||||
return;
|
||||
|
||||
if (tp.p1 && tp.p2)
|
||||
touchPairs.push_back(tp);
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsReceiver::deserializeTouch(RakNet::BitStream& bitstream, const RakNet::SystemAddress& from, TouchPair& touchPair)
|
||||
{
|
||||
shared_ptr<PartInstance> part1;
|
||||
if (!receivePart(part1, bitstream))
|
||||
return false; // this marks the end of the packet
|
||||
|
||||
shared_ptr<PartInstance> part2;
|
||||
bool ok = receivePart(part2, bitstream);
|
||||
AYAASSERT(ok);
|
||||
|
||||
bool touched;
|
||||
bitstream >> touched;
|
||||
|
||||
if (!part1)
|
||||
return true;
|
||||
if (!part2)
|
||||
return true;
|
||||
|
||||
if (replicator->settings().printTouches)
|
||||
{
|
||||
if (touched)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: Touch:%s->%s << %s", part1->getName().c_str(),
|
||||
part2->getName().c_str(), RakNetAddressToString(replicator->remotePlayerId).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: Untouch:%s->%s << %s", part1->getName().c_str(),
|
||||
part2->getName().c_str(), RakNetAddressToString(replicator->remotePlayerId).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
touchPair = TouchPair(part1, part2, touched ? TouchPair::Touch : TouchPair::Untouch, RakNetToRbxAddress(from));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readTouches(RakNet::BitStream& bitstream, const RakNet::SystemAddress& from)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
TouchPair tp;
|
||||
if (!deserializeTouch(bitstream, from, tp))
|
||||
return;
|
||||
|
||||
if (tp.p1 && tp.p2)
|
||||
processTouchPair(tp);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsReceiver::processTouchPair(const TouchPair& tp)
|
||||
{
|
||||
if (tp.type == TouchPair::Touch)
|
||||
tp.p1->reportTouch(tp.p2);
|
||||
else
|
||||
tp.p1->reportUntouch(tp.p2);
|
||||
|
||||
// Send pair to other Replicators
|
||||
if (!physicsService)
|
||||
physicsService = shared_from(ServiceProvider::find<PhysicsService>(replicator));
|
||||
|
||||
physicsService->onTouchStep(tp);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readPV(RakNet::BitStream& bitStream, AssemblyItem& item, bool crossPacketCompression)
|
||||
{
|
||||
NETPROFILE_START("readPV", &bitStream);
|
||||
if (crossPacketCompression)
|
||||
{
|
||||
// readRotation only
|
||||
Compressor::readRotation(bitStream, item.pv.position.rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
readCoordinateFrame(bitStream, item.pv.position);
|
||||
readVelocity(bitStream, item.pv.velocity);
|
||||
|
||||
if (NetworkSettings::singleton().getTotalNumMovementWayPoint() % 7 == 0) // magic number to print velocity
|
||||
{
|
||||
addWayPointAdorn(item.pv.position.translation + item.pv.velocity.linear, PathBasedMovementDebug::VelocityNode);
|
||||
}
|
||||
}
|
||||
NETPROFILE_END("readPV", &bitStream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readCoordinateFrame(RakNet::BitStream& bitStream, CoordinateFrame& cFrame)
|
||||
{
|
||||
NETPROFILE_START("readCoordinateFrame", &bitStream);
|
||||
|
||||
if (stats == NULL)
|
||||
{
|
||||
Compressor::readTranslation(bitStream, cFrame.translation);
|
||||
Compressor::readRotation(bitStream, cFrame.rotation);
|
||||
NETPROFILE_END("readCoordinateFrame", &bitStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
NETPROFILE_START("readTranslation", &bitStream);
|
||||
int bitStart = bitStream.GetReadOffset();
|
||||
Compressor::readTranslation(bitStream, cFrame.translation);
|
||||
if (stats)
|
||||
{
|
||||
stats->details.translation.increment();
|
||||
stats->details.translationSize.sample((bitStream.GetReadOffset() - bitStart) / 8);
|
||||
}
|
||||
NETPROFILE_END("readTranslation", &bitStream);
|
||||
NETPROFILE_START("readRotation", &bitStream);
|
||||
bitStart = bitStream.GetReadOffset();
|
||||
Compressor::readRotation(bitStream, cFrame.rotation);
|
||||
if (stats)
|
||||
{
|
||||
stats->details.rotation.increment();
|
||||
stats->details.rotationSize.sample((bitStream.GetReadOffset() - bitStart) / 8);
|
||||
}
|
||||
NETPROFILE_END("readRotation", &bitStream);
|
||||
NETPROFILE_END("readCoordinateFrame", &bitStream);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readVelocity(RakNet::BitStream& bitStream, Velocity& velocity)
|
||||
{
|
||||
NETPROFILE_START("readVelocity", &bitStream);
|
||||
int bitStart = bitStream.GetReadOffset();
|
||||
if (replicator->settings().distributedPhysicsEnabled)
|
||||
{
|
||||
readVectorFast(bitStream, velocity.linear.x, velocity.linear.y, velocity.linear.z);
|
||||
readVectorFast(bitStream, velocity.rotational.x, velocity.rotational.y, velocity.rotational.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = Velocity::zero();
|
||||
}
|
||||
if (stats)
|
||||
{
|
||||
stats->details.velocity.increment();
|
||||
stats->details.velocitySize.sample((bitStream.GetReadOffset() - bitStart) / 8);
|
||||
}
|
||||
NETPROFILE_END("readVelocity", &bitStream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readMotorAngles(RakNet::BitStream& bitStream, AssemblyItem& item)
|
||||
{
|
||||
NETPROFILE_START("readMotorAngles", &bitStream);
|
||||
unsigned char compactNum;
|
||||
bitStream >> compactNum;
|
||||
|
||||
if (compactNum > 50 && replicator->settings().printPhysicsErrors)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_WARNING, "Physics-in has %d motors", compactNum);
|
||||
|
||||
const int numMotors = compactNum;
|
||||
item.motorAngles.resize(numMotors); // i.e. - fast clear, no allocation
|
||||
|
||||
for (int i = 0; i < numMotors; ++i)
|
||||
{
|
||||
readCompactCFrame(bitStream, item.motorAngles[i]);
|
||||
}
|
||||
NETPROFILE_END("readMotorAngles", &bitStream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::readCompactCFrame(RakNet::BitStream& bitStream, CompactCFrame& cFrame)
|
||||
{
|
||||
NETPROFILE_START("readCompactCFrame", &bitStream);
|
||||
bool isSimpleZAngle = bitStream.ReadBit();
|
||||
|
||||
if (isSimpleZAngle)
|
||||
{
|
||||
unsigned char byteAngle;
|
||||
bitStream >> byteAngle;
|
||||
cFrame = CompactCFrame(Vector3::zero(), Vector3::unitZ(), Math::rotationFromByte(byteAngle));
|
||||
AYAASSERT(!Math::isNanInfVector3(cFrame.getAxis()));
|
||||
AYAASSERT(!Math::isNanInf(cFrame.getAngle()));
|
||||
AYAASSERT(!Math::isNanInfVector3(cFrame.translation));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasTranslation = bitStream.ReadBit();
|
||||
bool hasRotation = bitStream.ReadBit();
|
||||
if (hasTranslation)
|
||||
{
|
||||
Compressor::readTranslation(bitStream, cFrame.translation);
|
||||
}
|
||||
else
|
||||
{
|
||||
cFrame.translation = Vector3::zero();
|
||||
}
|
||||
|
||||
if (hasRotation)
|
||||
{
|
||||
Vector3 axis;
|
||||
readVectorFast(bitStream, axis.x, axis.y, axis.z);
|
||||
unsigned char byteAngle;
|
||||
bitStream >> byteAngle;
|
||||
float angle = Math::rotationFromByte(byteAngle);
|
||||
cFrame.setAxisAngle(axis, angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
cFrame.setAxisAngle(Aya::Vector3::unitX(), 0);
|
||||
}
|
||||
|
||||
AYAASSERT(!Math::isNanInfVector3(cFrame.getAxis()));
|
||||
AYAASSERT(!Math::isNanInf(cFrame.getAngle()));
|
||||
AYAASSERT(!Math::isNanInfVector3(cFrame.translation));
|
||||
}
|
||||
NETPROFILE_END("readCompactCFrame", &bitStream);
|
||||
}
|
||||
|
||||
void PhysicsReceiver::setPhysics(
|
||||
const MechanismItem& item, const Aya::RemoteTime& remoteSendTime, const RakNet::TimeMS lagInMs, int numNodesInHistory)
|
||||
{
|
||||
bool hasVelocity = item.hasVelocity;
|
||||
Time::Interval lag(static_cast<double>(lagInMs) / 1000.0f);
|
||||
|
||||
for (int i = 0; i < item.numAssemblies(); ++i)
|
||||
{
|
||||
AssemblyItem& assemblyItem = item.getAssemblyItem(i);
|
||||
|
||||
// assemblyItem.primitive will be NULL if the Part was destroyed while the physics packet was outstanding
|
||||
|
||||
if (PartInstance* part = assemblyItem.rootPart.get())
|
||||
{
|
||||
if (this->replicator->filterPhysics(part) == Reject)
|
||||
{
|
||||
if (replicator->settings().printPhysicsFilters)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "filterPhysics %s", part->getName().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
Primitive* primitive = part->getPartPrimitive();
|
||||
|
||||
// Show david this assert and then destroy - validating the case where we received this history item in the past, and the primitive is no
|
||||
// longer in world
|
||||
AYAASSERT(primitive->getWorld());
|
||||
|
||||
if (!Assembly::isAssemblyRootPrimitive(primitive))
|
||||
{
|
||||
if (replicator->settings().printPhysicsFilters)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "!isAssemblyRootPrimitive %s", part->getName().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
Assembly* a = primitive->getAssembly();
|
||||
if (a->computeIsGrounded())
|
||||
{
|
||||
if (replicator->settings().printPhysicsFilters)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "computeIsGrounded %s", part->getName().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
a->setPhysics(assemblyItem.motorAngles, assemblyItem.pv); // motor angles
|
||||
a->setNetworkHumanoidState((i == 0) ? item.networkHumanoidState : 0);
|
||||
|
||||
if (hasVelocity)
|
||||
{
|
||||
if (iAmServer)
|
||||
{
|
||||
part->addInterpolationSample(assemblyItem.pv.position, assemblyItem.pv.velocity, remoteSendTime, now, 0.f, numNodesInHistory);
|
||||
part->setPhysics(assemblyItem.pv);
|
||||
|
||||
Time localTime = replicator->remoteRaknetTimeToLocalRbxTime(remoteSendTime);
|
||||
part->addMovementNode(assemblyItem.pv.position, assemblyItem.pv.velocity, localTime); // force update the movement node
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLagCompenstated = false;
|
||||
|
||||
if (isLagCompenstated)
|
||||
{
|
||||
// Extrapolate with velocity
|
||||
if (assemblyItem.pv.velocity.rotational.squaredLength() >= 0.01)
|
||||
{
|
||||
Quaternion qOrientation(assemblyItem.pv.position.rotation);
|
||||
qOrientation.normalize();
|
||||
Quaternion qDot(Quaternion(assemblyItem.pv.velocity.rotational) * qOrientation * 0.5f);
|
||||
qOrientation += qDot * lag.seconds();
|
||||
qOrientation.normalize();
|
||||
qOrientation.toRotationMatrix(assemblyItem.pv.position.rotation);
|
||||
}
|
||||
if (assemblyItem.pv.velocity.linear.squaredLength() >= 1)
|
||||
assemblyItem.pv.position.translation += assemblyItem.pv.velocity.linear * lag.seconds();
|
||||
|
||||
part->addInterpolationSample(
|
||||
assemblyItem.pv.position, assemblyItem.pv.velocity, remoteSendTime + lag, now, 0.f, numNodesInHistory);
|
||||
}
|
||||
else
|
||||
{
|
||||
part->addInterpolationSample(assemblyItem.pv.position, assemblyItem.pv.velocity, remoteSendTime, now, 0.f, numNodesInHistory);
|
||||
}
|
||||
|
||||
CoordinateFrame oldPosition;
|
||||
Aya::Velocity previousLinearVelocity;
|
||||
Aya::Time lastUpdateTime;
|
||||
|
||||
if (DFFlag::HumanoidFloorPVUpdateSignal)
|
||||
{
|
||||
oldPosition = part->getCoordinateFrame();
|
||||
previousLinearVelocity = part->getVelocity();
|
||||
lastUpdateTime = part->getLastUpdateTime();
|
||||
}
|
||||
part->setPhysics(assemblyItem.pv);
|
||||
|
||||
if (DFFlag::HumanoidFloorPVUpdateSignal)
|
||||
{
|
||||
float deltaTime = (replicator->remoteRaknetTimeToLocalRbxTime(remoteSendTime) - lastUpdateTime).seconds();
|
||||
// shared_ptr<PartInstance> partPointer = shared_from(part);
|
||||
if (part->onDemandRead())
|
||||
part->onDemandWrite()->onPositionUpdatedByNetworkSignal(
|
||||
boost::ref(part), boost::ref(oldPosition), boost::ref(previousLinearVelocity), boost::ref(deltaTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
part->addInterpolationSample(assemblyItem.pv.position, assemblyItem.pv.velocity, remoteSendTime, now, 0.f, numNodesInHistory);
|
||||
CoordinateFrame oldPosition;
|
||||
Aya::Velocity previousLinearVelocity;
|
||||
Aya::Time lastUpdateTime;
|
||||
if (DFFlag::HumanoidFloorPVUpdateSignal)
|
||||
{
|
||||
oldPosition = part->getCoordinateFrame();
|
||||
previousLinearVelocity = part->getVelocity();
|
||||
lastUpdateTime = part->getLastUpdateTime();
|
||||
}
|
||||
part->setPhysics(assemblyItem.pv.position);
|
||||
|
||||
if (DFFlag::HumanoidFloorPVUpdateSignal)
|
||||
{
|
||||
float deltaTime = (replicator->remoteRaknetTimeToLocalRbxTime(remoteSendTime) - lastUpdateTime).seconds();
|
||||
// shared_ptr<PartInstance> partPointer = shared_from(part);
|
||||
if (part->onDemandRead())
|
||||
part->onDemandWrite()->onPositionUpdatedByNetworkSignal(
|
||||
boost::ref(part), boost::ref(oldPosition), boost::ref(previousLinearVelocity), boost::ref(deltaTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PhysicsReceiver::addWayPointAdorn(const Vector3& p, const PathBasedMovementDebug::NodeDebugInfo& info, const std::string& debugText)
|
||||
{
|
||||
if (Workspace::showPartMovementPath && info.show)
|
||||
{
|
||||
if (movementWaypointList.capacity() != NetworkSettings::singleton().getTotalNumMovementWayPoint())
|
||||
{
|
||||
movementWaypointList.rset_capacity(NetworkSettings::singleton().getTotalNumMovementWayPoint());
|
||||
}
|
||||
if (movementWaypointList.capacity() > 0)
|
||||
{
|
||||
movementWaypointList.push_back(MovementWaypointAdorn(p, info.color, info.size, debugText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsReceiver::addVectorAdorn(const Vector3& start, const Vector3& end, const Aya::Color4& c)
|
||||
{
|
||||
if (Workspace::showPartMovementPath)
|
||||
{
|
||||
if (movementVectorList.capacity() != NetworkSettings::singleton().getTotalNumMovementWayPoint())
|
||||
{
|
||||
movementVectorList.rset_capacity(NetworkSettings::singleton().getTotalNumMovementWayPoint());
|
||||
}
|
||||
if (movementVectorList.capacity() > 0)
|
||||
{
|
||||
movementVectorList.push_back(MovementVectorAdorn(start, end, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PhysicsReceiver::okDistributedReceivePart(const shared_ptr<PartInstance>& part)
|
||||
{
|
||||
return (!replicator->settings().distributedPhysicsEnabled || replicator->checkDistributedReceive(part.get()));
|
||||
}
|
||||
|
||||
|
||||
bool PhysicsReceiver::receiveRootPart(shared_ptr<PartInstance>& part, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
NETPROFILE_START("receiveRootPart", &inBitstream);
|
||||
bool answer = receivePart(part, inBitstream);
|
||||
|
||||
if (part)
|
||||
{
|
||||
if (part->getPartPrimitive()->getAssembly()->computeIsGrounded())
|
||||
{
|
||||
part.reset();
|
||||
}
|
||||
else if (!okDistributedReceivePart(part))
|
||||
{
|
||||
part.reset();
|
||||
}
|
||||
}
|
||||
NETPROFILE_END("receiveRootPart", &inBitstream);
|
||||
return answer;
|
||||
}
|
||||
|
||||
|
||||
bool PhysicsReceiver::receivePart(shared_ptr<PartInstance>& part, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
shared_ptr<Instance> instance;
|
||||
Aya::Guid::Data id;
|
||||
if (replicator->deserializeInstanceRef(inBitstream, instance, id))
|
||||
{
|
||||
if (instance == NULL)
|
||||
{
|
||||
return false; // packet end tag
|
||||
}
|
||||
part = Instance::fastSharedDynamicCast<PartInstance>(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_WARNING, "Physics-in of unidentified %s", id.readableString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (part)
|
||||
{
|
||||
if (!PartInstance::nonNullInWorkspace(part))
|
||||
{
|
||||
if (replicator->settings().printPhysicsErrors)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "Physics-in of part not in workspace %s", id.readableString().c_str());
|
||||
}
|
||||
part.reset();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void PhysicsReceiver::renderPartMovementPath(Adorn* adorn)
|
||||
{
|
||||
|
||||
for (boost::circular_buffer<MovementWaypointAdorn>::iterator iter = movementWaypointList.begin(); iter != movementWaypointList.end(); iter++)
|
||||
{
|
||||
MovementWaypointAdorn wp = *iter;
|
||||
DrawAdorn::star(adorn, wp.position, wp.size, wp.color, wp.color, wp.color);
|
||||
|
||||
if (wp.text.length() > 0)
|
||||
{
|
||||
Vector3 aboveStar = wp.position;
|
||||
aboveStar.y += 5.0f;
|
||||
|
||||
const Camera& camera = *adorn->getCamera();
|
||||
Vector3 screenLoc = camera.project(aboveStar);
|
||||
if (screenLoc.z == std::numeric_limits<float>::infinity())
|
||||
continue;
|
||||
|
||||
adorn->drawFont2D(wp.text, screenLoc.xy(), 10.0f, false, Color4(Color3::white(), 1.0f), Color4(Color3::black(), 1.0f),
|
||||
Text::FONT_ARIALBOLD, Text::XALIGN_CENTER, Text::YALIGN_BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
for (boost::circular_buffer<MovementVectorAdorn>::iterator iter = movementVectorList.begin(); iter != movementVectorList.end(); iter++)
|
||||
{
|
||||
MovementVectorAdorn wp = *iter;
|
||||
adorn->line3d(wp.startPos, wp.endPos, wp.color);
|
||||
}
|
||||
}
|
||||
144
engine/network/src/PhysicsReceiver.hpp
Normal file
144
engine/network/src/PhysicsReceiver.hpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "MechanismItem.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
#include "Declarations.hpp"
|
||||
|
||||
#include "boost.hpp"
|
||||
#include <vector>
|
||||
|
||||
#include "RakNet/RakNetTypes.hpp"
|
||||
#include "RakNet/GetTime.hpp"
|
||||
|
||||
#include "Base/IAdornable.hpp"
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class PartInstance;
|
||||
class CompactCFrame;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
namespace PathBasedMovementDebug
|
||||
{
|
||||
struct NodeDebugInfo
|
||||
{
|
||||
Color3 color;
|
||||
float size;
|
||||
bool show;
|
||||
};
|
||||
} // namespace PathBasedMovementDebug
|
||||
|
||||
class DeserializedTouchItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
std::vector<TouchPair> touchPairs;
|
||||
|
||||
DeserializedTouchItem() {}
|
||||
~DeserializedTouchItem() {}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
class Replicator;
|
||||
|
||||
class AyaBaseClass PhysicsReceiver : boost::noncopyable
|
||||
{
|
||||
shared_ptr<PhysicsService> physicsService;
|
||||
|
||||
void readMovementHistory(
|
||||
RakNet::BitStream& bitStream, RemoteTime remoteSendTime, PartInstance* rootPart, MechanismItem& mechanismItem, int& numNodesInHistory);
|
||||
void readMechanismAttributes(RakNet::BitStream& bitStream, MechanismItem& item);
|
||||
|
||||
void readAssembly(RakNet::BitStream& bitstream, PartInstance* rootPart, MechanismItem& mechanismItem, bool crossPacketCompression);
|
||||
void readPV(RakNet::BitStream& bitStream, AssemblyItem& item, bool crossPacketCompression);
|
||||
void readCoordinateFrame(RakNet::BitStream& bitStream, CoordinateFrame& cFrame);
|
||||
void readVelocity(RakNet::BitStream& bitStream, Velocity& velocity);
|
||||
void readMotorAngles(RakNet::BitStream& bitStream, AssemblyItem& item);
|
||||
void readCompactCFrame(RakNet::BitStream& bitStream, CompactCFrame& cFrame);
|
||||
|
||||
bool receivePart(shared_ptr<PartInstance>& part, RakNet::BitStream& inBitstream);
|
||||
|
||||
protected:
|
||||
struct MovementWaypointAdorn
|
||||
{
|
||||
Vector3 position;
|
||||
Aya::Color4 color;
|
||||
float size;
|
||||
std::string text;
|
||||
MovementWaypointAdorn(const Vector3& p, const Aya::Color4& c, float s, const std::string& debugText)
|
||||
{
|
||||
position = p;
|
||||
color = c;
|
||||
size = s;
|
||||
text = debugText;
|
||||
}
|
||||
};
|
||||
|
||||
struct MovementVectorAdorn
|
||||
{
|
||||
Vector3 startPos;
|
||||
Vector3 endPos;
|
||||
Aya::Color4 color;
|
||||
MovementVectorAdorn(const Vector3& start, const Vector3& end, const Aya::Color4& c)
|
||||
{
|
||||
startPos = start;
|
||||
endPos = end;
|
||||
color = c;
|
||||
}
|
||||
};
|
||||
|
||||
const bool iAmServer;
|
||||
|
||||
Replicator* const replicator;
|
||||
ReplicatorStats::PhysicsReceiverStats* stats;
|
||||
Time now; // or reasonably close to it.
|
||||
|
||||
struct TimedCF
|
||||
{
|
||||
CoordinateFrame cf;
|
||||
float timeToEnd;
|
||||
};
|
||||
std::vector<TimedCF> nodeStack;
|
||||
|
||||
boost::circular_buffer<MovementWaypointAdorn> movementWaypointList;
|
||||
boost::circular_buffer<MovementVectorAdorn> movementVectorList;
|
||||
void addWayPointAdorn(const Vector3& p, const PathBasedMovementDebug::NodeDebugInfo& info, const std::string& debugText = "");
|
||||
void addVectorAdorn(const Vector3& start, const Vector3& end, const Aya::Color4& c);
|
||||
|
||||
bool okDistributedReceivePart(const shared_ptr<PartInstance>& part);
|
||||
bool receiveRootPart(shared_ptr<PartInstance>& part, RakNet::BitStream& inBitstream);
|
||||
|
||||
public:
|
||||
PhysicsReceiver(Replicator* replicator, bool isServer);
|
||||
|
||||
virtual void start(shared_ptr<PhysicsReceiver> physicsReceiver) {}
|
||||
|
||||
virtual ~PhysicsReceiver() {}
|
||||
|
||||
void setTime(Time now_);
|
||||
void receiveMechanism(
|
||||
RakNet::BitStream& bitStream, PartInstance* rootPart, MechanismItem& item, RemoteTime remoteSendTime, int& numNodesInHistory);
|
||||
void receiveMechanismCFrames(RakNet::BitStream& bitStream, RakNet::Time timeStamp, const Aya::RemoteTime& remoteSendTime);
|
||||
void setPhysics(const MechanismItem& item, const RemoteTime& remoteSendTime = RemoteTime(), const RakNet::TimeMS = 0, int numNodesInHistory = 0);
|
||||
|
||||
virtual void receivePacket(RakNet::BitStream& bitsream, RakNet::Time timeStamp, ReplicatorStats::PhysicsReceiverStats* stats) = 0;
|
||||
|
||||
void deserializeTouches(RakNet::BitStream& bitstream, const RakNet::SystemAddress& from, std::vector<TouchPair>& touchPairs);
|
||||
bool deserializeTouch(RakNet::BitStream& bitstream, const RakNet::SystemAddress& from, TouchPair& touchPair);
|
||||
void processTouchPair(const TouchPair& tp);
|
||||
void readTouches(RakNet::BitStream& bitstream, const RakNet::SystemAddress& from);
|
||||
|
||||
void renderPartMovementPath(Adorn* adorn);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
635
engine/network/src/PhysicsSender.cpp
Normal file
635
engine/network/src/PhysicsSender.cpp
Normal file
@@ -0,0 +1,635 @@
|
||||
|
||||
|
||||
#include "PhysicsSender.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Compressor.hpp"
|
||||
#include "RakNet/RakNetStatistics.hpp"
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#include "Utility/Profiling.hpp"
|
||||
|
||||
#include "Utility/ObscureValue.hpp"
|
||||
|
||||
|
||||
#include "Kernel/Constants.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
|
||||
#include "World/World.hpp"
|
||||
#include "World/Primitive.hpp"
|
||||
#include "World/Assembly.hpp"
|
||||
#include "World/Mechanism.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "RoundRobinPhysicsSender.hpp"
|
||||
#include "DataModel/PhysicsService.hpp"
|
||||
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
FASTINTVARIABLE(NumPhysicsTouchPacketsPerStep, 1)
|
||||
DYNAMIC_FASTINTVARIABLE(NumPhysicsPacketsPerStep, 1)
|
||||
DYNAMIC_FASTINTVARIABLE(PhysicsSenderRate, 15)
|
||||
DYNAMIC_FASTFLAG(CleanUpInterpolationTimestamps)
|
||||
|
||||
class PhysicsSender::Job : public ReplicatorJob
|
||||
{
|
||||
weak_ptr<PhysicsSender> physicsSender;
|
||||
ObscureValue<float> physicsSendRate;
|
||||
PacketPriority packetPriority;
|
||||
unsigned int skipSteps;
|
||||
|
||||
public:
|
||||
Job(shared_ptr<PhysicsSender> physicsSender)
|
||||
: ReplicatorJob("Replicator SendPhysics", physicsSender->replicator, DataModelJob::PhysicsOut)
|
||||
, physicsSender(physicsSender)
|
||||
, physicsSendRate(replicator->settings().getPhysicsSendRate())
|
||||
, packetPriority(replicator->settings().getPhysicsSendPriority())
|
||||
, skipSteps(0)
|
||||
{
|
||||
if (dynamic_cast<RoundRobinPhysicsSender*>(physicsSender.get()))
|
||||
{
|
||||
physicsSendRate = replicator->settings().getClientPhysicsSendRate();
|
||||
}
|
||||
|
||||
// TODO: set as default in NetworkSettings
|
||||
physicsSendRate = (float)DFInt::PhysicsSenderRate;
|
||||
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessOutgoing;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, physicsSendRate);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && DFFlag::CleanUpInterpolationTimestamps)
|
||||
{
|
||||
return 1.0f; // ALWAYS RUN
|
||||
}
|
||||
return computeStandardErrorCyclicExecutiveSleeping(stats, physicsSendRate);
|
||||
}
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (DFFlag::CleanUpInterpolationTimestamps)
|
||||
{
|
||||
int stepsTillRun = std::max(Constants::uiStepsPerSec() / DFInt::PhysicsSenderRate, 1);
|
||||
if (skipSteps != (stepsTillRun - 1))
|
||||
{
|
||||
skipSteps++;
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
else
|
||||
{
|
||||
skipSteps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (shared_ptr<PhysicsSender> safePhysicsSender = physicsSender.lock())
|
||||
{
|
||||
safePhysicsSender->step();
|
||||
ReplicatorStats::PhysicsSenderStats& physStats = safePhysicsSender->replicator.physicsSenderStats();
|
||||
|
||||
const int limit = replicator->settings().sendPacketBufferLimit;
|
||||
if (limit == -1)
|
||||
{
|
||||
safePhysicsSender->sendPacket(safePhysicsSender->sendPacketsPerStep, packetPriority, &physStats);
|
||||
physStats.physicsSentThrottle.sample(0.0); // not throttled
|
||||
}
|
||||
else if (replicator)
|
||||
{
|
||||
int bufferCount = replicator->getBufferCountAvailable(limit, packetPriority);
|
||||
safePhysicsSender->sendPacket(bufferCount, packetPriority, &physStats);
|
||||
physStats.physicsSentThrottle.sample(1.0 - bufferCount / limit);
|
||||
}
|
||||
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class PhysicsSender::TouchJob : public ReplicatorJob
|
||||
{
|
||||
weak_ptr<PhysicsSender> physicsSender;
|
||||
float sendRate;
|
||||
PacketPriority packetPriority;
|
||||
|
||||
public:
|
||||
TouchJob(shared_ptr<PhysicsSender> physicsSender)
|
||||
: ReplicatorJob("Replicator SendTouches", physicsSender->replicator, DataModelJob::PhysicsOut)
|
||||
, physicsSender(physicsSender)
|
||||
, sendRate(replicator->settings().touchSendRate)
|
||||
, packetPriority(replicator->settings().getPhysicsSendPriority())
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessOutgoing;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, sendRate);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
if (canSendPacket(replicator, packetPriority))
|
||||
if (shared_ptr<PhysicsSender> safePhysicsSender = physicsSender.lock())
|
||||
if (safePhysicsSender->pendingTouchCount() > 0)
|
||||
return computeStandardErrorCyclicExecutiveSleeping(stats, sendRate);
|
||||
Error error;
|
||||
return error;
|
||||
}
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (shared_ptr<PhysicsSender> safePhysicsSender = physicsSender.lock())
|
||||
{
|
||||
safePhysicsSender->sendTouches(packetPriority);
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
size_t PhysicsSender::pendingTouchCount()
|
||||
{
|
||||
return physicsService->pendingTouchCount();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void PhysicsSender::writeTouches(RakNet::BitStream& bitStream, unsigned int maxBitStreamSize, T& touchPairList)
|
||||
{
|
||||
Aya::SystemAddress addr = RakNetToRbxAddress(replicator.remotePlayerId);
|
||||
typename T::iterator iter;
|
||||
for (iter = touchPairList.begin(); iter != touchPairList.end(); ++iter)
|
||||
{
|
||||
if (iter->originator == addr)
|
||||
continue;
|
||||
|
||||
const bool touched = iter->type == TouchPair::Touch;
|
||||
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
|
||||
replicator.serializeId(bitStream, iter->p1.get());
|
||||
replicator.serializeId(bitStream, iter->p2.get());
|
||||
bitStream << touched;
|
||||
|
||||
if (replicator.settings().printTouches)
|
||||
{
|
||||
if (touched)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: Touch:%s->%s >> %s, bytes: %d",
|
||||
iter->p1->getName().c_str(), iter->p2->getName().c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str(),
|
||||
bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: Untouch:%s->%s >> %s, bytes: %d",
|
||||
iter->p1->getName().c_str(), iter->p2->getName().c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str(),
|
||||
bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
}
|
||||
|
||||
if (bitStream.GetNumberOfBytesUsed() > maxBitStreamSize)
|
||||
break;
|
||||
}
|
||||
|
||||
if (iter == touchPairList.end())
|
||||
touchPairList.clear();
|
||||
else
|
||||
touchPairList.erase(touchPairList.begin(), iter);
|
||||
}
|
||||
|
||||
|
||||
void PhysicsSender::sendTouches(PacketPriority packetPriority)
|
||||
{
|
||||
const unsigned int maxStreamSize(replicator.getPhysicsMtuSize());
|
||||
int numPackets = FInt::NumPhysicsTouchPacketsPerStep;
|
||||
|
||||
// append new touches to send list
|
||||
int newTouchId = physicsService->getTouchesId();
|
||||
if (touchPairsId < newTouchId)
|
||||
{
|
||||
physicsService->getTouches(touchPairs);
|
||||
touchPairsId = newTouchId;
|
||||
}
|
||||
|
||||
ReplicatorStats::PhysicsSenderStats& physStats = replicator.physicsSenderStats();
|
||||
physStats.waitingTouches.sample(touchPairs.size());
|
||||
|
||||
if (touchPairs.empty())
|
||||
return;
|
||||
|
||||
while (!touchPairs.empty() && (numPackets > 0))
|
||||
{
|
||||
boost::shared_ptr<RakNet::BitStream> bitStream(new RakNet::BitStream(maxStreamSize));
|
||||
|
||||
*bitStream << (unsigned char)ID_PHYSICS_TOUCHES;
|
||||
|
||||
writeTouches(*bitStream, maxStreamSize, touchPairs);
|
||||
|
||||
// Packet "end" tag
|
||||
replicator.serializeId(*bitStream, NULL);
|
||||
|
||||
// Send ID_PHYSICS_TOUCHES
|
||||
replicator.rakPeer->Send(bitStream, packetPriority, RELIABLE_ORDERED, PHYSICS_CHANNEL, replicator.remotePlayerId, false);
|
||||
|
||||
physStats.touchPacketsSent.sample();
|
||||
physStats.touchPacketsSentSize.sample(bitStream->GetNumberOfBytesUsed());
|
||||
numPackets--;
|
||||
|
||||
if (touchPairs.empty())
|
||||
physicsService->onTouchesSent();
|
||||
}
|
||||
};
|
||||
|
||||
PhysicsSender::PhysicsSender(Replicator& replicator)
|
||||
: replicator(replicator)
|
||||
, sendPacketsPerStep(DFInt::NumPhysicsPacketsPerStep)
|
||||
, senderStats(NULL)
|
||||
, touchPairsId(0)
|
||||
{
|
||||
|
||||
physicsService = shared_from(ServiceProvider::find<PhysicsService>(&replicator));
|
||||
};
|
||||
|
||||
void PhysicsSender::start(shared_ptr<PhysicsSender> physicsSender)
|
||||
{
|
||||
// Someone else is holding a shared_ptr to us at the moment
|
||||
physicsSender->job = shared_ptr<Job>(new Job(physicsSender));
|
||||
TaskScheduler::singleton().add(physicsSender->job);
|
||||
|
||||
physicsSender->touchJob = shared_ptr<TouchJob>(new TouchJob(physicsSender));
|
||||
TaskScheduler::singleton().add(physicsSender->touchJob);
|
||||
}
|
||||
|
||||
PhysicsSender::~PhysicsSender()
|
||||
{
|
||||
TaskScheduler::singleton().remove(job);
|
||||
job.reset();
|
||||
TaskScheduler::singleton().remove(touchJob);
|
||||
touchJob.reset();
|
||||
}
|
||||
|
||||
/*
|
||||
Format for a mechanism:
|
||||
|
||||
>> MechanismAttributes
|
||||
>> PrimaryAssembly
|
||||
>> done
|
||||
while (!done)
|
||||
{
|
||||
>> ChildPart
|
||||
>> ChildAssembly
|
||||
>> done
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Compressor::CompressionType PhysicsSender::getCompressionType(const Assembly* assembly, bool detailed)
|
||||
{
|
||||
if (replicator.settings().distributedPhysicsEnabled && Mechanism::isComplexMovingMechanism(assembly))
|
||||
{
|
||||
return Compressor::UNCOMPRESSED;
|
||||
}
|
||||
else if (detailed)
|
||||
{
|
||||
return Compressor::RAKNET_COMPRESSED;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Compressor::HEAVILY_COMPRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSender::sendChildPrimitiveCoordinateFrame(
|
||||
Primitive* prim, RakNet::BitStream* bitStream, Replicator* replicator, Compressor::CompressionType compressionType)
|
||||
{
|
||||
AYAASSERT(replicator->isStreamingEnabled());
|
||||
|
||||
if (PartInstance* part = PartInstance::fromPrimitive(prim))
|
||||
{
|
||||
if (replicator->isReplicationContainer(part))
|
||||
{
|
||||
// only update the client if the part is replicated
|
||||
RakNet::BitStream& bitStreamRef = *bitStream;
|
||||
if (replicator->trySerializeId(bitStreamRef, part))
|
||||
writeCoordinateFrame(bitStreamRef, prim->getCoordinateFrame(), compressionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSender::sendMechanismCFrames(RakNet::BitStream& bitStream, const PartInstance* part, bool detailed)
|
||||
{
|
||||
AYAASSERT(replicator.isStreamingEnabled());
|
||||
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
|
||||
const Primitive* primitive = part->getConstPartPrimitive();
|
||||
const Mechanism* mechanism = Mechanism::getConstPrimitiveMechanism(primitive);
|
||||
const Assembly* assembly = primitive->getConstAssembly();
|
||||
|
||||
AYAASSERT(mechanism);
|
||||
|
||||
Compressor::CompressionType compressionType = getCompressionType(assembly, detailed);
|
||||
|
||||
if (replicator.isReplicationContainer(part))
|
||||
{
|
||||
if (replicator.trySerializeId(bitStream, part))
|
||||
writeCoordinateFrame(bitStream, part->getCoordinateFrame(), compressionType);
|
||||
}
|
||||
|
||||
// send each part's cframe if they are in streamed region
|
||||
const_cast<Mechanism*>(mechanism)->visitPrimitives(
|
||||
boost::bind(&PhysicsSender::sendChildPrimitiveCoordinateFrame, this, _1, &bitStream, &replicator, compressionType));
|
||||
|
||||
replicator.serializeId(bitStream, NULL); // token: done
|
||||
|
||||
if (senderStats)
|
||||
{
|
||||
senderStats->details.mechanismCFrame.increment();
|
||||
senderStats->details.mechanismCFrameSize.sample(bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSender::sendMechanism(RakNet::BitStream& bitStream, const PartInstance* part, bool detailed, const Time& lastSendTime,
|
||||
CoordinateFrame& lastSendCFrame, float& accumulatedError, const PartInstance* playerHead)
|
||||
{
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
|
||||
const Assembly* assembly = part->getConstPartPrimitive()->getConstAssembly();
|
||||
|
||||
AYAASSERT(assembly);
|
||||
// AYAASSERT(Mechanism::isMovingAssemblyRoot(assembly));
|
||||
|
||||
Compressor::CompressionType compressionType = getCompressionType(assembly, detailed);
|
||||
|
||||
writeMechanismAttributes(bitStream, assembly);
|
||||
|
||||
bool hasMovement = true;
|
||||
if (!writeMovementHistory(bitStream, part->getMovementHistory(), assembly, lastSendTime, lastSendCFrame, hasMovement, compressionType,
|
||||
accumulatedError, playerHead))
|
||||
{
|
||||
writeAssembly(bitStream, assembly, compressionType);
|
||||
lastSendCFrame = part->getCoordinateFrame();
|
||||
}
|
||||
|
||||
if (hasMovement)
|
||||
{
|
||||
assembly->visitConstDescendentAssemblies(boost::bind(&PhysicsSender::sendChildAssembly, this, &bitStream, _1, compressionType));
|
||||
}
|
||||
else if (compressionType == Compressor::UNCOMPRESSED)
|
||||
{
|
||||
assembly->visitConstDescendentAssemblies(boost::bind(&PhysicsSender::addChildRotationError, this, _1, boost::ref(accumulatedError)));
|
||||
}
|
||||
|
||||
bitStream << true; // token - done with child assemblies
|
||||
|
||||
if (senderStats)
|
||||
{
|
||||
senderStats->details.mechanism.increment();
|
||||
senderStats->details.mechanismSize.sample(bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool PhysicsSender::writeMovementHistory(RakNet::BitStream& bitStream, const MovementHistory& history, const Assembly* assembly,
|
||||
const Time& lastSendTime, CoordinateFrame& lastSendCFrame, bool& hasMovement, Compressor::CompressionType compressionType,
|
||||
float& accumulatedError, const PartInstance* playerHead)
|
||||
{
|
||||
hasMovement = true;
|
||||
return false; // client will not use path based part movement
|
||||
}
|
||||
|
||||
void PhysicsSender::writeMechanismAttributes(RakNet::BitStream& bitStream, const Assembly* assembly)
|
||||
{
|
||||
bool hasState = (assembly->getNetworkHumanoidState() != 0);
|
||||
bitStream << hasState;
|
||||
if (hasState)
|
||||
{
|
||||
bitStream << assembly->getNetworkHumanoidState();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PhysicsSender::sendChildAssembly(RakNet::BitStream* bitStream, const Assembly* assembly, Compressor::CompressionType compressionType)
|
||||
{
|
||||
RakNet::BitStream& bitStreamRef = *bitStream;
|
||||
const PartInstance* part = PartInstance::fromConstAssembly(assembly);
|
||||
AYAASSERT(part);
|
||||
|
||||
// send only if we can serialize the part id
|
||||
if (replicator.canSerializeId(part))
|
||||
{
|
||||
bitStreamRef << false; // token - not done with child assemblies
|
||||
replicator.serializeId(bitStreamRef, part);
|
||||
writeAssembly(bitStreamRef, assembly, compressionType);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSender::addChildRotationError(const Assembly* assembly, float& accumulatedError)
|
||||
{
|
||||
const PartInstance* part = PartInstance::fromConstAssembly(assembly);
|
||||
AYAASSERT(part);
|
||||
accumulatedError += part->getRotationalVelocity().magnitude();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PhysicsSender::writeAssembly(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression)
|
||||
{
|
||||
writePV(bitStream, assembly, compressionType, crossPacketCompression);
|
||||
writeMotorAngles(bitStream, assembly, compressionType);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PhysicsSender::writePV(RakNet::BitStream& bitStream, const Assembly* a, Compressor::CompressionType compressionType, bool crossPacketCompression)
|
||||
{
|
||||
const PV& pv = a->getConstAssemblyPrimitive()->getPV();
|
||||
writeCoordinateFrame(bitStream, pv.position, compressionType);
|
||||
writeVelocity(bitStream, pv.velocity);
|
||||
}
|
||||
|
||||
void PhysicsSender::writeCoordinateFrame(RakNet::BitStream& bitStream, const CoordinateFrame& cFrame, Compressor::CompressionType compressionType)
|
||||
{
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
Compressor::writeTranslation(bitStream, cFrame.translation, compressionType);
|
||||
if (senderStats)
|
||||
{
|
||||
senderStats->details.translation.increment();
|
||||
senderStats->details.translationSize.sample(bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
Compressor::writeRotation(bitStream, cFrame.rotation, compressionType);
|
||||
if (senderStats)
|
||||
{
|
||||
senderStats->details.rotation.increment();
|
||||
senderStats->details.rotationSize.sample(bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSender::writeVelocity(RakNet::BitStream& bitStream, const Velocity& velocity)
|
||||
{
|
||||
if (replicator.settings().distributedPhysicsEnabled)
|
||||
{
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
|
||||
bitStream.WriteVector(velocity.linear.x, velocity.linear.y, velocity.linear.z);
|
||||
bitStream.WriteVector(velocity.rotational.x, velocity.rotational.y, velocity.rotational.z);
|
||||
|
||||
if (senderStats)
|
||||
{
|
||||
senderStats->details.velocity.increment();
|
||||
senderStats->details.velocitySize.sample(bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PhysicsSender::writeMotorAngles(RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType)
|
||||
{
|
||||
tempMotorAngles.resize(0);
|
||||
assembly->getPhysics(tempMotorAngles);
|
||||
|
||||
AYAASSERT(tempMotorAngles.size() < 255);
|
||||
|
||||
unsigned char compactNum = static_cast<unsigned char>(tempMotorAngles.size());
|
||||
bitStream << compactNum;
|
||||
|
||||
for (int i = 0; i < tempMotorAngles.size(); ++i)
|
||||
{
|
||||
writeCompactCFrame(bitStream, tempMotorAngles[i], compressionType);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSender::writeCompactCFrame(RakNet::BitStream& bitStream, const CompactCFrame& cFrame, Compressor::CompressionType compressionType)
|
||||
{
|
||||
bool hasTranslation = !cFrame.translation.isZero();
|
||||
bool isSimpleZAngle = G3D::fuzzyEq(fabs(cFrame.getAxis().z), 1.0f);
|
||||
bool hasRotation = !G3D::fuzzyEq(cFrame.getAngle(), 0.0f);
|
||||
|
||||
// common simple surface normal motor
|
||||
if (!hasTranslation && hasRotation && isSimpleZAngle)
|
||||
{
|
||||
bitStream.Write1();
|
||||
unsigned char byteAngle = Math::rotationToByte(cFrame.getAxis().z * cFrame.getAngle());
|
||||
bitStream << byteAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitStream.Write0();
|
||||
// two bits, has trans, and hasrot
|
||||
bitStream << hasTranslation;
|
||||
bitStream << hasRotation;
|
||||
|
||||
if (hasTranslation)
|
||||
{
|
||||
Compressor::writeTranslation(bitStream, cFrame.translation, compressionType);
|
||||
}
|
||||
if (hasRotation)
|
||||
{
|
||||
Vector3 axis = cFrame.getAxis();
|
||||
bitStream.WriteVector(axis.x, axis.y,
|
||||
axis.z); // would be nice to use WriteNormVector, but it's broken. (gives out NANs in certain situations, it needs fixing)
|
||||
|
||||
unsigned char byteAngle = Math::rotationToByte(cFrame.getAngle()); // todo: losing one bit here, angle is always positive.
|
||||
bitStream << byteAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool PhysicsSender::canSend(const PartInstance* part, const Assembly* assembly, RakNet::BitStream& bitStream)
|
||||
{
|
||||
AYAASSERT(!assembly || !part || (assembly->getConstAssemblyPrimitive() == part->getConstPartPrimitive()));
|
||||
|
||||
if (assembly && part)
|
||||
{
|
||||
if (!replicator.settings().distributedPhysicsEnabled || replicator.checkDistributedSend(part))
|
||||
{
|
||||
if (replicator.isStreamingEnabled())
|
||||
return true;
|
||||
else if (!replicator.isSerializePending(part))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PhysicsSender::sendPhysicsData(RakNet::BitStream& bitStream, const PartInstance* part, bool detailed, const Time& lastSendTime,
|
||||
CoordinateFrame& lastSendCFrame, float& accumulatedError, const PartInstance* playerHead)
|
||||
{
|
||||
AYAASSERT(part);
|
||||
if (part && Assembly::isAssemblyRootPrimitive(part->getConstPartPrimitive()))
|
||||
{
|
||||
senderStats = NULL;
|
||||
if (replicator.settings().trackPhysicsDetails)
|
||||
{
|
||||
senderStats = &(replicator.replicatorStats.physicsSenderStats);
|
||||
}
|
||||
|
||||
const Assembly* assembly = part->getConstPartPrimitive()->getConstAssembly();
|
||||
if (canSend(part, assembly, bitStream))
|
||||
{
|
||||
if (replicator.isStreamingEnabled())
|
||||
{
|
||||
bitStream << false; // token: not done with packet
|
||||
|
||||
if (!replicator.isInStreamedRegions(part->getConstPartPrimitive()->getExtentsWorld()))
|
||||
{
|
||||
bitStream << true; // token: CFrame only
|
||||
sendMechanismCFrames(bitStream, part, detailed);
|
||||
lastSendCFrame = part->getCoordinateFrame();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitStream << false; // token: (not) CFrame only
|
||||
if (replicator.trySerializeId(bitStream, part))
|
||||
{
|
||||
sendMechanism(bitStream, part, detailed, lastSendTime, lastSendCFrame, accumulatedError, playerHead);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
replicator.serializeId(bitStream, NULL); // token: done with this mechanism
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (replicator.trySerializeId(bitStream, part))
|
||||
{
|
||||
sendMechanism(bitStream, part, detailed, lastSendTime, lastSendCFrame, accumulatedError, playerHead);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
117
engine/network/src/PhysicsSender.hpp
Normal file
117
engine/network/src/PhysicsSender.hpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include "Compressor.hpp"
|
||||
#include "Declarations.hpp"
|
||||
|
||||
#include "Utility/G3DCore.hpp"
|
||||
|
||||
#include "Utility/PV.hpp"
|
||||
|
||||
#include "boost.hpp"
|
||||
#include "signal.hpp"
|
||||
|
||||
#include "time.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include "CompactCFrame.hpp"
|
||||
#include <boost/unordered_set.hpp>
|
||||
#include "RakNet/PacketPriority.hpp"
|
||||
#include "RunningAverage.hpp"
|
||||
|
||||
#include "ReplicatorStats.hpp"
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Assembly;
|
||||
class Primitive;
|
||||
struct TouchPair;
|
||||
class PartInstance;
|
||||
class MovementHistory;
|
||||
class PhysicsService;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator;
|
||||
|
||||
class AyaBaseClass PhysicsSender : boost::noncopyable
|
||||
{
|
||||
private:
|
||||
class Job; // The Job that sends physics
|
||||
shared_ptr<Job> job;
|
||||
class TouchJob; // The Job that sends Touches
|
||||
shared_ptr<TouchJob> touchJob;
|
||||
|
||||
std::list<TouchPair> touchPairs;
|
||||
int touchPairsId;
|
||||
|
||||
private:
|
||||
G3D::Array<CompactCFrame> tempMotorAngles;
|
||||
|
||||
void writeMechanismAttributes(RakNet::BitStream& bitStream, const Assembly* assembly);
|
||||
|
||||
void sendChildAssembly(RakNet::BitStream* bitStream, const Assembly* assembly, Compressor::CompressionType compressionType);
|
||||
void sendChildPrimitiveCoordinateFrame(
|
||||
Primitive* prim, RakNet::BitStream* bitStream, Replicator* replicator, Compressor::CompressionType compressionType);
|
||||
|
||||
void writeCompactCFrame(RakNet::BitStream& bitStream, const CompactCFrame& cFrame, Compressor::CompressionType compressionType);
|
||||
void writeCoordinateFrame(RakNet::BitStream& bitStream, const CoordinateFrame& cFrame, Compressor::CompressionType compressionType);
|
||||
|
||||
Compressor::CompressionType getCompressionType(const Assembly* assembly, bool detailed);
|
||||
|
||||
void addChildRotationError(const Assembly* assembly, float& accumulatedError);
|
||||
|
||||
protected:
|
||||
Replicator& replicator;
|
||||
ReplicatorStats::PhysicsSenderStats* senderStats;
|
||||
|
||||
shared_ptr<PhysicsService> physicsService;
|
||||
|
||||
RunningAverage<int> itemsPerPacket;
|
||||
int sendPacketsPerStep;
|
||||
|
||||
Time currentStepTimestamp;
|
||||
|
||||
bool canSend(const PartInstance* part, const Assembly* assembly, RakNet::BitStream& bitStream);
|
||||
bool sendPhysicsData(RakNet::BitStream& bitStream, const PartInstance* part, bool detailed, const Time& lastSendTime,
|
||||
CoordinateFrame& lastSendCFrame, float& accumulatedError, const PartInstance* playerHead);
|
||||
|
||||
void sendMechanism(RakNet::BitStream& bitStream, const PartInstance* part, bool detailed, const Time& lastSendTime,
|
||||
CoordinateFrame& lastSendCFrame, float& accumulatedError, const PartInstance* playerHead);
|
||||
void sendMechanismCFrames(RakNet::BitStream& bitStream, const PartInstance* part, bool detailed);
|
||||
void writeVelocity(RakNet::BitStream& bitStream, const Velocity& velocity);
|
||||
void writeMotorAngles(RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType);
|
||||
|
||||
virtual void writePV(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression);
|
||||
|
||||
virtual void writeAssembly(
|
||||
RakNet::BitStream& bitStream, const Assembly* assembly, Compressor::CompressionType compressionType, bool crossPacketCompression = false);
|
||||
virtual bool writeMovementHistory(RakNet::BitStream& bitStream, const MovementHistory& history, const Assembly* assembly,
|
||||
const Time& lastSendTime, CoordinateFrame& lastSendCFrame, bool& hasMovement, Compressor::CompressionType compressionType,
|
||||
float& accumulatedError, const PartInstance* playerHead);
|
||||
|
||||
public:
|
||||
PhysicsSender(Replicator& replicator);
|
||||
static void start(shared_ptr<PhysicsSender> physicsSender);
|
||||
virtual ~PhysicsSender();
|
||||
|
||||
virtual void step() = 0;
|
||||
void sendTouches(PacketPriority packetPriority);
|
||||
|
||||
template<typename T>
|
||||
void writeTouches(RakNet::BitStream& bitstream, unsigned int maxBitStreamSize, T& touchPairList);
|
||||
|
||||
virtual int sendPacket(int maxPackets, PacketPriority packetPriority, ReplicatorStats::PhysicsSenderStats* stats) = 0;
|
||||
|
||||
size_t pendingTouchCount();
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
3241
engine/network/src/Player.cpp
Normal file
3241
engine/network/src/Player.cpp
Normal file
File diff suppressed because it is too large
Load Diff
671
engine/network/src/Player.hpp
Normal file
671
engine/network/src/Player.hpp
Normal file
@@ -0,0 +1,671 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "time.hpp"
|
||||
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
#include "DataModel/Team.hpp"
|
||||
|
||||
#include "DataModel/FriendService.hpp"
|
||||
|
||||
#include "DataModel/Camera.hpp"
|
||||
|
||||
#include "DataModel/StarterPlayerService.hpp"
|
||||
|
||||
#include "Utility/BrickColor.hpp"
|
||||
|
||||
#include "Utility/G3DCore.hpp"
|
||||
|
||||
#include "Utility/HeapValue.hpp"
|
||||
|
||||
#include "Utility/Rect.hpp"
|
||||
|
||||
#include "Utility/RunningAverageState.hpp"
|
||||
|
||||
|
||||
// for player mouse
|
||||
#include "DataModel/Mouse.hpp"
|
||||
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include "DataModel/TeleportService.hpp"
|
||||
|
||||
|
||||
LOGGROUP(Network)
|
||||
LOGGROUP(Player)
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
struct SystemAddress;
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class Visit;
|
||||
class ModelInstance;
|
||||
class Backpack;
|
||||
class TimerService;
|
||||
class Region2;
|
||||
class DataModelMesh;
|
||||
class Adorn;
|
||||
class Primitive;
|
||||
class PartInstance;
|
||||
class Humanoid;
|
||||
class DataModel;
|
||||
class SpawnLocation;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
class PersistentDataStore;
|
||||
|
||||
extern const char* const sPlayer;
|
||||
|
||||
class Player
|
||||
: public DescribedCreatable<Player, Instance, sPlayer, Reflection::ClassDescriptor::RUNTIME>
|
||||
, public Diagnostics::Countable<Player>
|
||||
{
|
||||
private:
|
||||
typedef DescribedCreatable<Player, Instance, sPlayer, Reflection::ClassDescriptor::RUNTIME> Super;
|
||||
shared_ptr<ModelInstance> character; // The ModelInstance that this player is represented by
|
||||
BrickColor teamColor;
|
||||
bool neutral;
|
||||
int loadAppearanceCounter;
|
||||
int dataComplexityLimit;
|
||||
bool dataReady;
|
||||
weak_ptr<SpawnLocation> respawnLocation;
|
||||
|
||||
bool loadedStarterGear;
|
||||
|
||||
// User Profile data
|
||||
std::string displayName;
|
||||
bool superSafeChat;
|
||||
bool under13;
|
||||
int userId;
|
||||
|
||||
// For Group Building
|
||||
bool hasGroupBuildTools;
|
||||
HeapValue<int> personalServerRank;
|
||||
|
||||
Aya::signals::scoped_connection characterDiedConnection;
|
||||
Aya::signals::scoped_connection backendDiedSignalConnection;
|
||||
Aya::signals::scoped_connection spawnLocationChangedConnection;
|
||||
Aya::signals::scoped_connection simulationRadiusChangedConnection;
|
||||
Aya::signals::scoped_connection setShutdownMessageConnection;
|
||||
boost::function<void()> teamStatusChangedCallback;
|
||||
|
||||
// CharacterAppearance is the url of a handler that returns a delimited list of asset ids
|
||||
std::string characterAppearance;
|
||||
bool canLoadCharacterAppearance;
|
||||
|
||||
// Distributed Simulation
|
||||
float simulationRadius;
|
||||
float maxSimulationRadius;
|
||||
|
||||
std::string osPlatform;
|
||||
|
||||
int loadingInstances;
|
||||
bool appearanceDidLoad;
|
||||
bool characterAppearanceLoaded;
|
||||
|
||||
// Only valid on the server!
|
||||
shared_ptr<RakNet::SystemAddress> remoteAddress;
|
||||
|
||||
bool forceEarlySpawnLocationCalculation;
|
||||
bool hasSpawnedAtLeastOnce;
|
||||
std::string teleportSpawnName;
|
||||
bool teleported; // describes whether player is currently in the middle of teleporting out
|
||||
bool teleportedIn; // describes whether player joined current place by teleporting
|
||||
|
||||
std::string gameSessionID;
|
||||
|
||||
struct SpawnData
|
||||
{
|
||||
Vector3 position;
|
||||
int forceFieldDuration;
|
||||
CoordinateFrame cf;
|
||||
|
||||
SpawnData()
|
||||
: position(Vector3::zero())
|
||||
, forceFieldDuration(0)
|
||||
{
|
||||
}
|
||||
SpawnData(Vector3 position, int forceFieldDuration, CoordinateFrame frame)
|
||||
: position(position)
|
||||
, forceFieldDuration(forceFieldDuration)
|
||||
, cf(frame)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
boost::scoped_ptr<SpawnData> nextSpawnLocation;
|
||||
|
||||
int followUserId;
|
||||
|
||||
// Instance
|
||||
/*override*/ bool askAddChild(const Instance* instance) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
/*override*/ void verifySetParent(const Instance* instance) const;
|
||||
/*override*/ void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
|
||||
void onCharacterChangedFrontend();
|
||||
|
||||
void registerLocalPlayerNotIdle();
|
||||
|
||||
void loadInsertPanel(DataModel* dataModel);
|
||||
|
||||
shared_ptr<PersistentDataStore> persistentData;
|
||||
std::list<boost::function<void(bool)>> waitForDataReadyWaiters;
|
||||
void fireWaitForDataReady();
|
||||
static void LoadDataResultHelper(boost::weak_ptr<Player> weakPlayer, shared_ptr<const Reflection::ValueMap> result);
|
||||
void loadDataResult(shared_ptr<const Reflection::ValueMap> result);
|
||||
|
||||
bool autoJumpEnabled;
|
||||
|
||||
Team* myTeam;
|
||||
|
||||
public:
|
||||
enum MembershipType
|
||||
{
|
||||
MEMBERSHIP_NONE = 0,
|
||||
MEMBERSHIP_BUILDERS_CLUB = 1,
|
||||
MEMBERSHIP_TURBO_BUILDERS_CLUB = 2,
|
||||
MEMBERSHIP_OUTRAGEOUS_BUILDERS_CLUB = 3
|
||||
};
|
||||
enum ChatMode
|
||||
{
|
||||
CHAT_MODE_MENU = 0,
|
||||
CHAT_MODE_TEXT_AND_MENU = 1
|
||||
};
|
||||
enum ChatFilterType
|
||||
{
|
||||
CHAT_FILTER_WHITELIST,
|
||||
CHAT_FILTER_BLACKLIST
|
||||
};
|
||||
|
||||
// debug timer
|
||||
Aya::Timer<Aya::Time::Fast> appearanceFetchTimer;
|
||||
|
||||
static Reflection::PropDescriptor<Player, std::string> prop_displayName;
|
||||
static Reflection::PropDescriptor<Player, int> prop_userId;
|
||||
static Reflection::PropDescriptor<Player, int> prop_userIdDeprecated;
|
||||
static Reflection::PropDescriptor<Player, bool> prop_SuperSafeChat;
|
||||
static Reflection::BoundProp<float> prop_SimulationRadius;
|
||||
static Reflection::BoundProp<float> prop_MaxSimulationRadius;
|
||||
static Reflection::PropDescriptor<Player, float> prop_DeprecatedMaxSimulationRadius;
|
||||
static Reflection::BoundProp<std::string> prop_OsPlatform;
|
||||
|
||||
Aya::remote_signal<void(std::string, std::string, std::string)> scriptSecurityErrorSignal;
|
||||
Aya::remote_signal<void(std::string, Vector3)> remoteInsertSignal;
|
||||
|
||||
Aya::remote_signal<void()> connectDiedSignalBackend;
|
||||
|
||||
Aya::remote_signal<void(bool, int)> remoteFriendServiceSignal;
|
||||
Aya::remote_signal<void()> killPlayerSignal;
|
||||
Aya::remote_signal<void(float)> simulationRadiusChangedSignal;
|
||||
|
||||
Aya::remote_signal<void(std::string)> statsSignal;
|
||||
Aya::signals::scoped_connection charChildAddedConnection;
|
||||
|
||||
Aya::signal<void(std::string, shared_ptr<Instance>)> chattedSignal;
|
||||
Aya::signal<void(shared_ptr<Instance>)> characterAddedSignal;
|
||||
Aya::remote_signal<void(shared_ptr<Instance>)> CharacterAppearanceLoadedSignal;
|
||||
Aya::signal<void(shared_ptr<Instance>)> characterRemovingSignal;
|
||||
Aya::signal<void(Vector3)> nextSpawnLocationChangedSignal;
|
||||
Aya::signal<void(double)> idledSignal;
|
||||
|
||||
Aya::signal<void(shared_ptr<Instance>, FriendService::FriendStatus)> friendStatusChangedSignal;
|
||||
|
||||
Aya::remote_signal<void(TeleportService::TeleportState, int, std::string)> onTeleportSignal;
|
||||
Aya::remote_signal<void(TeleportService::TeleportState, shared_ptr<const Reflection::ValueTable>, shared_ptr<Instance>)> onTeleportInternalSignal;
|
||||
|
||||
Aya::remote_signal<void(std::string)> setShutdownMessageSignal;
|
||||
|
||||
Player();
|
||||
~Player();
|
||||
|
||||
void doFirstSpawnLocationCalculation(const ServiceProvider* serviceProvider, const std::string& preferedSpawnName);
|
||||
void reportScriptSecurityError(const std::string& hash, const std::string& error, const std::string& stack);
|
||||
void killPlayer();
|
||||
|
||||
void requestFriendship(shared_ptr<Instance> player);
|
||||
void revokeFriendship(shared_ptr<Instance> player);
|
||||
|
||||
// Keyboard/Mouse input API
|
||||
shared_ptr<Mouse> getMouse()
|
||||
{
|
||||
return mouse;
|
||||
}
|
||||
shared_ptr<Instance> getMouseInstance();
|
||||
|
||||
// Persistent Data API
|
||||
void loadData();
|
||||
void saveData();
|
||||
void saveLeaderboardData();
|
||||
int getDataComplexityLimit() const
|
||||
{
|
||||
return dataComplexityLimit;
|
||||
}
|
||||
void setDataComplexityLimit(int value);
|
||||
int getDataComplexity() const;
|
||||
|
||||
bool getDataReady() const
|
||||
{
|
||||
return dataReady;
|
||||
}
|
||||
|
||||
void waitForDataReady(boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
double loadNumber(std::string key);
|
||||
void saveNumber(std::string key, double value);
|
||||
std::string loadString(std::string key);
|
||||
void saveString(std::string key, std::string value);
|
||||
bool loadBoolean(std::string key);
|
||||
void saveBoolean(std::string key, bool value);
|
||||
shared_ptr<Instance> loadInstance(std::string key);
|
||||
void saveInstance(std::string key, shared_ptr<Instance> value);
|
||||
shared_ptr<const Reflection::ValueArray> loadList(std::string key);
|
||||
void saveList(std::string key, shared_ptr<const Reflection::ValueArray> value);
|
||||
shared_ptr<const Aya::Reflection::ValueMap> loadTable(std::string key);
|
||||
void saveTable(std::string key, shared_ptr<const Aya::Reflection::ValueMap> value);
|
||||
|
||||
/*override*/ void setName(const std::string& value);
|
||||
/*override*/ bool canClientCreate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ModelInstance* getDangerousCharacter() const
|
||||
{
|
||||
return character.get();
|
||||
}
|
||||
|
||||
boost::shared_ptr<ModelInstance> getSharedCharacter()
|
||||
{
|
||||
return character;
|
||||
}
|
||||
ModelInstance* getCharacter()
|
||||
{
|
||||
return character.get();
|
||||
}
|
||||
const ModelInstance* getConstCharacter() const
|
||||
{
|
||||
return character.get();
|
||||
}
|
||||
void setCharacter(ModelInstance* value);
|
||||
|
||||
const SpawnLocation* getConstRespawnLocation() const
|
||||
{
|
||||
if (shared_ptr<SpawnLocation> shared_respawnLocation = respawnLocation.lock())
|
||||
return shared_respawnLocation.get();
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
SpawnLocation* getDangerousRespawnLocation() const
|
||||
{
|
||||
if (shared_ptr<SpawnLocation> shared_respawnLocation = respawnLocation.lock())
|
||||
return shared_respawnLocation.get();
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
void setRespawnLocation(SpawnLocation* value);
|
||||
|
||||
bool getCanLoadCharacterAppearance() const
|
||||
{
|
||||
return canLoadCharacterAppearance;
|
||||
}
|
||||
void setCanLoadCharacterAppearance(bool value);
|
||||
|
||||
bool getAutoJumpEnabled() const
|
||||
{
|
||||
return autoJumpEnabled;
|
||||
}
|
||||
void setAutoJumpEnabled(bool value);
|
||||
|
||||
const PartInstance* hasCharacterHead(CoordinateFrame& headPos) const;
|
||||
|
||||
bool getHasGroupBuildTools() const
|
||||
{
|
||||
return hasGroupBuildTools;
|
||||
}
|
||||
void setHasGroupBuildTools(bool value);
|
||||
|
||||
void giveBuildTools();
|
||||
void removeBuildTools();
|
||||
|
||||
void setAppearanceLoaded();
|
||||
|
||||
// Personal Server API
|
||||
void getWebPersonalServerRank(boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void setWebPersonalServerRank(int newRank, boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
|
||||
int getPersonalServerRank() const
|
||||
{
|
||||
return (personalServerRank > 255) ? 0 : personalServerRank;
|
||||
} // 255 is max rank allowed by setter.
|
||||
void setPersonalServerRank(int value);
|
||||
|
||||
const Primitive* getConstCharacterRoot() const;
|
||||
|
||||
Team* getTeam();
|
||||
Team* getTeam2() const
|
||||
{
|
||||
return myTeam;
|
||||
}; // used by reflection
|
||||
BrickColor getTeamColor() const
|
||||
{
|
||||
return teamColor;
|
||||
}
|
||||
void setTeamColor(BrickColor value);
|
||||
void setTeam(Team* team);
|
||||
|
||||
bool getNeutral() const
|
||||
{
|
||||
return neutral;
|
||||
}
|
||||
void setNeutral(bool value);
|
||||
|
||||
std::string getCharacterAppearance() const
|
||||
{
|
||||
return characterAppearance;
|
||||
}
|
||||
void setCharacterAppearance(const std::string& value);
|
||||
|
||||
static void setAppearanceParent(weak_ptr<Player> player, weak_ptr<Instance> instance, bool equipped);
|
||||
|
||||
static void setGearParent(weak_ptr<Player> player, weak_ptr<Instance> instance, bool equipped);
|
||||
|
||||
bool getSuperSafeChat() const;
|
||||
void setSuperSafeChat(bool value);
|
||||
|
||||
float getDeprecatedMaxSimulationRadius() const
|
||||
{
|
||||
return 0.f;
|
||||
}
|
||||
void setDeprecatedMaxSimulationRadius(float val) {}
|
||||
|
||||
ChatMode getChatMode() const;
|
||||
|
||||
void setDisplayName(std::string value);
|
||||
std::string getDisplayName() const
|
||||
{
|
||||
return displayName;
|
||||
}
|
||||
|
||||
void setUnder13(bool value);
|
||||
bool getUnder13()
|
||||
{
|
||||
return under13;
|
||||
};
|
||||
|
||||
void setUserId(int value);
|
||||
int getUserID() const
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
|
||||
void setGameSessionID(std::string value);
|
||||
std::string getGameSessionID()
|
||||
{
|
||||
return gameSessionID;
|
||||
}
|
||||
|
||||
bool isGuest() const
|
||||
{
|
||||
return userId < 0;
|
||||
}
|
||||
void setMembershipType(MembershipType value);
|
||||
MembershipType getMembershipType() const
|
||||
{
|
||||
return membershipType;
|
||||
}
|
||||
|
||||
void setCameraMode(Aya::Camera::CameraMode value);
|
||||
Aya::Camera::CameraMode getCameraMode() const
|
||||
{
|
||||
return cameraMode;
|
||||
}
|
||||
|
||||
|
||||
void setNameDisplayDistance(float value);
|
||||
float getNameDisplayDistance() const
|
||||
{
|
||||
return nameDisplayDistance;
|
||||
}
|
||||
|
||||
void setHealthDisplayDistance(float value);
|
||||
float getHealthDisplayDistance() const
|
||||
{
|
||||
return healthDisplayDistance;
|
||||
}
|
||||
|
||||
void setCameraMaxZoomDistance(float _cameraMaxZoomDistance);
|
||||
float getCameraMaxZoomDistance() const
|
||||
{
|
||||
return cameraMaxZoomDistance;
|
||||
}
|
||||
|
||||
void setCameraMinZoomDistance(float _cameraMinZoomDistance);
|
||||
float getCameraMinZoomDistance() const
|
||||
{
|
||||
return cameraMinZoomDistance;
|
||||
}
|
||||
|
||||
void setDevEnableMouseLockOption(bool setting);
|
||||
bool getDevEnableMouseLockOption() const
|
||||
{
|
||||
return enableMouseLockOption;
|
||||
}
|
||||
void setDevTouchCameraMode(StarterPlayerService::DeveloperTouchCameraMovementMode setting);
|
||||
StarterPlayerService::DeveloperTouchCameraMovementMode getDevTouchCameraMode() const
|
||||
{
|
||||
return touchCameraMovementMode;
|
||||
}
|
||||
void setDevComputerCameraMode(StarterPlayerService::DeveloperComputerCameraMovementMode setting);
|
||||
StarterPlayerService::DeveloperComputerCameraMovementMode getDevComputerCameraMode() const
|
||||
{
|
||||
return computerCameraMovementMode;
|
||||
}
|
||||
void setDevCameraOcclusionMode(StarterPlayerService::DeveloperCameraOcclusionMode setting);
|
||||
StarterPlayerService::DeveloperCameraOcclusionMode getDevCameraOcclusionMode() const
|
||||
{
|
||||
return cameraOcclusionMode;
|
||||
}
|
||||
|
||||
void setDevTouchMovementMode(StarterPlayerService::DeveloperTouchMovementMode setting);
|
||||
StarterPlayerService::DeveloperTouchMovementMode getDevTouchMovementMode() const
|
||||
{
|
||||
return touchMovementMode;
|
||||
}
|
||||
void setDevComputerMovementMode(StarterPlayerService::DeveloperComputerMovementMode setting);
|
||||
StarterPlayerService::DeveloperComputerMovementMode getDevComputerMovementMode() const
|
||||
{
|
||||
return computerMovementMode;
|
||||
}
|
||||
|
||||
void setAccountAge(int value);
|
||||
int getAccountAge() const
|
||||
{
|
||||
return accountAge;
|
||||
}
|
||||
|
||||
void updateSimulationRadius(float value);
|
||||
float getSimulationRadius() const
|
||||
{
|
||||
return simulationRadius;
|
||||
}
|
||||
void setSimulationRadius(float value);
|
||||
|
||||
float getMaxSimulationRadius() const
|
||||
{
|
||||
return maxSimulationRadius;
|
||||
}
|
||||
void setMaxSimulationRadius(float value);
|
||||
|
||||
std::string getOsPlatform() const
|
||||
{
|
||||
return osPlatform;
|
||||
}
|
||||
|
||||
void rebuildBackpack(); // Copy the backpack from the StartPack service
|
||||
void rebuildGui(); // Copy the gui from the StarterGui service
|
||||
void rebuildPlayerScripts(); // Copy the LocalScripts from the StarterPlayer/PlayerScripts service
|
||||
|
||||
void createPlayerGui();
|
||||
|
||||
Backpack* getPlayerBackpack();
|
||||
const Backpack* getConstPlayerBackpack() const;
|
||||
|
||||
void luaJumpCharacter();
|
||||
void luaMoveCharacter(Vector2 walkDirection, float maxWalkDelta);
|
||||
|
||||
void move(Vector3 walkVector, bool relativeToCamera);
|
||||
|
||||
float distanceFromCharacter(Vector3 point);
|
||||
|
||||
void luaLoadCharacter(bool inGame);
|
||||
void loadCharacter(bool inGame, std::string preferedSpawnName);
|
||||
|
||||
void removeCharacter();
|
||||
void removeCharacterAppearance();
|
||||
void removeCharacterAppearanceScript();
|
||||
|
||||
void loadCharacterAppearance(bool blockingCall);
|
||||
void loadCharacterAppearanceScript(shared_ptr<Instance> asset);
|
||||
|
||||
static void onLocalPlayerNotIdle(Aya::ServiceProvider* serviceProvider);
|
||||
|
||||
void renderDPhysicsRegion(Adorn* adorn);
|
||||
void renderStreamedRegion(Adorn* adorn);
|
||||
void renderPartMovementPath(Adorn* adorn);
|
||||
|
||||
static bool physicsOutBandwidthExceeded(const Aya::Instance* context);
|
||||
static double getNetworkBufferHealth(const Aya::Instance* context);
|
||||
void reportStat(std::string stat);
|
||||
|
||||
void addToLoadingInstances(int newInstances)
|
||||
{
|
||||
loadingInstances += newInstances;
|
||||
}
|
||||
void removeFromLoadingInstances(int oldInstances)
|
||||
{
|
||||
loadingInstances -= oldInstances;
|
||||
}
|
||||
int getLoadingInstances()
|
||||
{
|
||||
return loadingInstances;
|
||||
}
|
||||
|
||||
bool getAppearanceDidLoad() const
|
||||
{
|
||||
return appearanceDidLoad;
|
||||
}
|
||||
bool getAppearanceDidLoadNonConst()
|
||||
{
|
||||
return appearanceDidLoad;
|
||||
}
|
||||
void setAppearanceDidLoad(bool value);
|
||||
bool getCharacterAppearanceLoaded() const
|
||||
{
|
||||
return characterAppearanceLoaded;
|
||||
}
|
||||
void setCharacterAppearanceLoaded(bool value);
|
||||
|
||||
void onFriendStatusChanged(shared_ptr<Instance> player, FriendService::FriendStatus friendStatus);
|
||||
FriendService::FriendStatus getFriendStatus(shared_ptr<Instance> player);
|
||||
void isFriendsWith(int otherUserId, boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void isBestFriendsWith(int otherUserId, boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void isInGroup(int groupId, boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void getRankInGroup(int groupId, boost::function<void(int)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void getRoleInGroup(int groupId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void getFriendsOnline(int maxFriends, boost::function<void(shared_ptr<const Reflection::ValueArray>)> resumeFunction,
|
||||
boost::function<void(std::string)> errorFunction);
|
||||
|
||||
void loadChatInfo();
|
||||
|
||||
// should be called with at least data model read lock
|
||||
bool isChatInfoValid() const;
|
||||
ChatFilterType getChatFilterType() const;
|
||||
|
||||
// for testing only
|
||||
void setChatInfo(ChatFilterType filterType);
|
||||
|
||||
void kick(std::string msg);
|
||||
void handleTeleportInternalSignal(
|
||||
TeleportService::TeleportState teleportState, shared_ptr<const Reflection::ValueTable> teleportInfo, shared_ptr<Instance> customLoadingGUI);
|
||||
void handleTeleportSignalBackend(TeleportService::TeleportState teleportState);
|
||||
void setForceEarlySpawnLocationCalculation();
|
||||
bool calculatesSpawnLocationEarly() const;
|
||||
|
||||
const RakNet::SystemAddress& getRemoteAddress() const;
|
||||
const SystemAddress getRemoteAddressAsRbxAddress() const;
|
||||
void setRemoteAddress(const RakNet::SystemAddress& address);
|
||||
|
||||
void onTeleport(TeleportService::TeleportState teleportState, int placeId, std::string instanceIdOrSpawnName);
|
||||
void onTeleportInternal(TeleportService::TeleportState teleportState, shared_ptr<const Reflection::ValueTable> teleportInfo,
|
||||
shared_ptr<Instance> customLoadingGUI = shared_ptr<Instance>());
|
||||
bool getTeleported() const
|
||||
{
|
||||
return teleported;
|
||||
}
|
||||
bool getTeleportedIn() const
|
||||
{
|
||||
return teleportedIn;
|
||||
}
|
||||
void setTeleportedIn(bool value);
|
||||
|
||||
int getFollowUserId() const
|
||||
{
|
||||
return followUserId;
|
||||
}
|
||||
void setFollowUserId(int followUserId)
|
||||
{
|
||||
this->followUserId = followUserId;
|
||||
}
|
||||
|
||||
void loadStarterGear();
|
||||
void onCharacterDied();
|
||||
|
||||
/*override*/ void destroy();
|
||||
|
||||
private:
|
||||
void setupHumanoid(shared_ptr<Humanoid> humanoid);
|
||||
void characterChildAdded(shared_ptr<Instance> child);
|
||||
|
||||
void doPeriodicIdleCheck();
|
||||
void checkContextReadyToSpawnCharacter();
|
||||
static void calculateNextSpawnLocationHelper(weak_ptr<Player>& weakPlayer, const ServiceProvider* serviceProvider);
|
||||
void calculateNextSpawnLocation(const ServiceProvider* serviceProvider);
|
||||
SpawnData calculateSpawnLocation(const std::string& preferedSpawnName);
|
||||
|
||||
static void loadChatInfoInternal(weak_ptr<Player> weakPlayer);
|
||||
|
||||
Time lastActivityTime;
|
||||
|
||||
MembershipType membershipType;
|
||||
int accountAge;
|
||||
|
||||
bool chatInfoHasBeenLoaded;
|
||||
ChatFilterType chatFilterType;
|
||||
|
||||
shared_ptr<Aya::Mouse> mouse;
|
||||
Aya::Camera::CameraMode cameraMode;
|
||||
|
||||
float nameDisplayDistance;
|
||||
float healthDisplayDistance;
|
||||
float cameraMaxZoomDistance;
|
||||
float cameraMinZoomDistance;
|
||||
bool enableMouseLockOption;
|
||||
Aya::StarterPlayerService::DeveloperTouchCameraMovementMode touchCameraMovementMode;
|
||||
Aya::StarterPlayerService::DeveloperComputerCameraMovementMode computerCameraMovementMode;
|
||||
Aya::StarterPlayerService::DeveloperCameraOcclusionMode cameraOcclusionMode;
|
||||
Aya::StarterPlayerService::DeveloperTouchMovementMode touchMovementMode;
|
||||
Aya::StarterPlayerService::DeveloperComputerMovementMode computerMovementMode;
|
||||
|
||||
bool copiedGuiOnce;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
2374
engine/network/src/Players.cpp
Normal file
2374
engine/network/src/Players.cpp
Normal file
File diff suppressed because it is too large
Load Diff
527
engine/network/src/Players.hpp
Normal file
527
engine/network/src/Players.hpp
Normal file
@@ -0,0 +1,527 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
#include "Tree/Service.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
#include "ChatFilter.hpp"
|
||||
|
||||
#include "Utility/SystemAddress.hpp"
|
||||
|
||||
#include "Utility/GameMode.hpp"
|
||||
|
||||
#include "DataModel/FriendService.hpp"
|
||||
|
||||
#include "DataModel/ContentProvider.hpp"
|
||||
|
||||
#include "boost/thread/thread.hpp"
|
||||
#include <boost/thread/condition.hpp>
|
||||
#include <boost/unordered_set.hpp>
|
||||
#include <queue>
|
||||
|
||||
|
||||
namespace RakNet
|
||||
{
|
||||
class RakPeerInterface;
|
||||
struct Packet;
|
||||
class BitStream;
|
||||
struct SystemAddress;
|
||||
} // namespace RakNet
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class PartInstance;
|
||||
class ModelInstance;
|
||||
class Region2;
|
||||
class Adorn;
|
||||
class ScriptInformationProvider;
|
||||
class DataModel;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
struct MemHash
|
||||
{
|
||||
size_t checkIdx;
|
||||
unsigned int value;
|
||||
unsigned int failMask;
|
||||
};
|
||||
|
||||
typedef std::vector<MemHash> MemHashVector;
|
||||
typedef std::vector<MemHashVector> MemHashConfigs;
|
||||
|
||||
class ConcurrentRakPeer;
|
||||
|
||||
class ChatMessage
|
||||
{
|
||||
public:
|
||||
enum ChatType
|
||||
{
|
||||
CHAT_TYPE_ALL,
|
||||
CHAT_TYPE_TEAM,
|
||||
CHAT_TYPE_WHISPER,
|
||||
CHAT_TYPE_GAME,
|
||||
// CHAT_TYPE_PARTY
|
||||
};
|
||||
|
||||
std::string guid;
|
||||
std::string message;
|
||||
ChatType chatType;
|
||||
shared_ptr<Player> const source;
|
||||
shared_ptr<Player> const destination;
|
||||
ChatMessage(const ChatMessage& other, const std::string& message);
|
||||
ChatMessage(const char* message, ChatType chatType, shared_ptr<Player> source);
|
||||
ChatMessage(const char* message, ChatType chatType, shared_ptr<Player> source, shared_ptr<Player> destination);
|
||||
|
||||
bool isVisibleToPlayer(shared_ptr<Player> player) const;
|
||||
static bool isVisibleToPlayer(
|
||||
shared_ptr<Player> const player, shared_ptr<Player> const source, shared_ptr<Player> const destination, const ChatType chatType);
|
||||
std::string getReportAbuseMessage() const;
|
||||
};
|
||||
|
||||
struct AbuseReport
|
||||
{
|
||||
struct Message
|
||||
{
|
||||
int userID;
|
||||
std::string text;
|
||||
std::string guid;
|
||||
};
|
||||
|
||||
int placeID;
|
||||
std::string gameJobID;
|
||||
|
||||
int submitterID;
|
||||
int allegedAbuserID;
|
||||
std::string comment;
|
||||
std::list<Message> messages;
|
||||
void addMessage(shared_ptr<Player> reportingPlayer, const ChatMessage& cm);
|
||||
};
|
||||
|
||||
class AbuseReporter
|
||||
{
|
||||
struct data
|
||||
{
|
||||
std::queue<AbuseReport> queue;
|
||||
boost::mutex requestSync; // synchronizes the request queue
|
||||
};
|
||||
shared_ptr<data> _data;
|
||||
scoped_ptr<worker_thread> requestProcessor;
|
||||
|
||||
public:
|
||||
AbuseReporter(std::string abuseUrl);
|
||||
void add(AbuseReport& r, shared_ptr<Player> reportingPlayer, const std::list<ChatMessage>& chatHistory);
|
||||
|
||||
private:
|
||||
static worker_thread::work_result processRequests(shared_ptr<data> _data, std::string abuseUrl);
|
||||
};
|
||||
|
||||
extern const char* const sPlayers;
|
||||
|
||||
class Players
|
||||
: public DescribedNonCreatable<Players, Instance, sPlayers>
|
||||
, public Service
|
||||
{
|
||||
private:
|
||||
typedef DescribedNonCreatable<Players, Instance, sPlayers> Super;
|
||||
|
||||
public:
|
||||
// Unfortunately the RakNet enums are not accessible via the Players class, so this identical enum is created
|
||||
// and mapped in PluginInterfaceAdapter::OnReceive. - Tim
|
||||
enum ReceiveResult
|
||||
{
|
||||
// The plugin used this message and it shouldn't be given to the user.
|
||||
PLAYERS_STOP_PROCESSING_AND_DEALLOCATE = 0,
|
||||
|
||||
// The plugin is going to hold on to this message. Do not deallocate it but do not pass it to other plugins either.
|
||||
PLAYERS_STOP_PROCESSING,
|
||||
};
|
||||
|
||||
enum ChatOption
|
||||
{
|
||||
CLASSIC_CHAT = 0,
|
||||
BUBBLE_CHAT = 1,
|
||||
CLASSIC_AND_BUBBLE_CHAT = 2
|
||||
};
|
||||
|
||||
enum PlayerChatType
|
||||
{
|
||||
PLAYER_CHAT_TYPE_ALL = 0,
|
||||
PLAYER_CHAT_TYPE_TEAM = 1,
|
||||
PLAYER_CHAT_TYPE_WHISPER = 2
|
||||
};
|
||||
|
||||
|
||||
static bool isNetworkClient(Instance* instance);
|
||||
void friendEventFired(int userId, int otherUserId, FriendService::FriendEventType friendEvent);
|
||||
void friendStatusChanged(int userId, int otherUserId, FriendService::FriendStatus friendStatus);
|
||||
|
||||
private:
|
||||
std::string saveDataUrl;
|
||||
std::string loadDataUrl;
|
||||
std::string saveLeaderboardDataUrl;
|
||||
boost::unordered_set<std::string> leaderboardKeys;
|
||||
|
||||
std::string chatFilterUrl;
|
||||
std::string buildUserPermissionsUrl;
|
||||
std::string sysStatsUrl;
|
||||
|
||||
static bool canKickBecauseRunningInRealGameServer;
|
||||
|
||||
bool characterAutoSpawn;
|
||||
|
||||
scoped_ptr<AbuseReporter> abuseReporter;
|
||||
boost::intrusive_ptr<GuidItem<Instance>::Registry> guidRegistry;
|
||||
std::list<ChatMessage> chatHistory;
|
||||
|
||||
const boost::intrusive_ptr<GuidItem<Instance>::Registry>& getGuidRegistry();
|
||||
|
||||
copy_on_write_ptr<Instances> players;
|
||||
ConcurrentRakPeer* rakPeer;
|
||||
|
||||
int maxPlayers;
|
||||
int preferredPlayers;
|
||||
int testPlayerNameId;
|
||||
int testPlayerUserId;
|
||||
shared_ptr<Player> localPlayer;
|
||||
|
||||
ChatOption chatOption;
|
||||
|
||||
bool nonSuperSafeChatForAllPlayersEnabled;
|
||||
|
||||
Aya::signals::connection blockUserClientSignalConnection;
|
||||
Aya::signals::connection blockUserFinishedFromServerConnection;
|
||||
Aya::signals::connection loadLocalPlayerGuisConnection;
|
||||
|
||||
boost::unordered_map<std::pair<int, int>, std::pair<boost::function<void(std::string)>, boost::function<void(std::string)>>> clientBlockUserMap;
|
||||
|
||||
void raiseChatMessageSignal(const ChatMessage& message);
|
||||
void raisePlayerChattedSignal(const ChatMessage& message);
|
||||
|
||||
void loadLocalPlayerGuis();
|
||||
void gotBlockUserSuccess(std::string response, bool blockUser, int blockerUserId, int blockeeUserId,
|
||||
boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void gotBlockUserError(std::string error, bool blockUser, int blockerUserId, int blockeeUserId, boost::function<void(std::string)> errorFunction);
|
||||
|
||||
static void onReceivedRawGetUserIdSuccess(weak_ptr<DataModel> weakDataModel, std::string response, boost::function<void(int)> resumeFunction,
|
||||
boost::function<void(std::string)> errorFunction);
|
||||
static void onReceivedRawGetUserIdError(weak_ptr<DataModel> weakDataModel, std::string error, boost::function<void(std::string)> errorFunction);
|
||||
|
||||
static void onReceivedRawGetUserNameSuccess(weak_ptr<DataModel> weakDataModel, std::string response,
|
||||
boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
static void onReceivedRawGetUserNameError(weak_ptr<DataModel> weakDataModel, std::string error, boost::function<void(std::string)> errorFunction);
|
||||
|
||||
void serverMakeBlockUserRequest(bool blockUser, int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction,
|
||||
boost::function<void(std::string)> errorFunction);
|
||||
void clientReceiveBlockUserFinished(int blockerUserId, int blockeeUserId, std::string errorString);
|
||||
|
||||
void internalBlockUser(int blockerUserId, int blockeeUserId, bool isBlocking, boost::function<void(std::string)> resumeFunction,
|
||||
boost::function<void(std::string)> errorFunction);
|
||||
|
||||
public:
|
||||
ReceiveResult OnReceiveChat(Player* sourceValidation, RakNet::RakPeerInterface* peer, RakNet::Packet* packet, unsigned char chatType);
|
||||
ReceiveResult OnReceiveReportAbuse(Player* source, RakNet::RakPeerInterface* peer, RakNet::Packet* packet);
|
||||
|
||||
#ifdef ENABLE_VOICE_CHAT
|
||||
ReceiveResult OnReceiveOpusData(Player* source, RakNet::RakPeerInterface* peer, RakNet::Packet* packet, unsigned char chatType);
|
||||
|
||||
void SendOpusData();
|
||||
#endif
|
||||
|
||||
Aya::signal<void(shared_ptr<Instance>, shared_ptr<Instance>, FriendService::FriendEventType)> friendRequestEvent;
|
||||
Aya::signal<void(shared_ptr<Instance>)> playerAddedEarlySignal;
|
||||
Aya::signal<void(shared_ptr<Instance>)> playerAddedSignal;
|
||||
Aya::signal<void(shared_ptr<Instance>)> playerRemovingSignal;
|
||||
Aya::signal<void(shared_ptr<Instance>)> playerRemovingLateSignal;
|
||||
Aya::signal<void(const ChatMessage&)> chatMessageSignal;
|
||||
Aya::signal<void(AbuseReport report)> abuseReportedReceived;
|
||||
Aya::signal<void(
|
||||
const RakNet::SystemAddress&, const shared_ptr<RakNet::BitStream>&, const shared_ptr<Instance>, const std::string&, const std::string&)>
|
||||
sendFilteredChatMessageSignal;
|
||||
|
||||
Aya::signal<void(PlayerChatType, shared_ptr<Instance>, std::string, shared_ptr<Instance>)> playerChattedSignal;
|
||||
Aya::signal<void(std::string)> gameAnnounceSignal;
|
||||
|
||||
Aya::remote_signal<void(int, int, bool)> blockUserRequestFromClientSignal;
|
||||
Aya::remote_signal<void(int, int, std::string)> blockUserFinishedFromServerSignal;
|
||||
|
||||
static Reflection::RefPropDescriptor<Players, Instance> propLocalPlayer;
|
||||
static Reflection::PropDescriptor<Players, bool> propCharacterAutoSpawn;
|
||||
Players();
|
||||
~Players();
|
||||
|
||||
bool superSafeOn() const;
|
||||
|
||||
shared_ptr<Instance> createLocalPlayer(int userId, bool teleportedIn = false);
|
||||
void resetLocalPlayer();
|
||||
|
||||
Player* getLocalPlayer()
|
||||
{
|
||||
return localPlayer.get();
|
||||
}
|
||||
const Player* getConstLocalPlayer() const
|
||||
{
|
||||
return localPlayer.get();
|
||||
}
|
||||
Instance* getLocalPlayerDangerous() const; // only for reflection
|
||||
|
||||
int getNumPlayers() const
|
||||
{
|
||||
return (int)players->size();
|
||||
}
|
||||
|
||||
void getUserIdFromName(std::string userName, boost::function<void(int)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void getNameFromUserId(int userId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void getFriends(int userId, boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
|
||||
static void setAppearanceParent(shared_ptr<Instance> model, weak_ptr<Instance> instance);
|
||||
static void doLoadAppearance(AsyncHttpQueue::RequestResult result, shared_ptr<Instances> instances, std::string contentDescription,
|
||||
shared_ptr<ModelInstance> model, shared_ptr<int> amountToLoad, boost::function<void(shared_ptr<Instance>)> resumeFunction,
|
||||
boost::function<void(std::string)> errorFunction);
|
||||
static void doMakeAccoutrementRequests(std::string response, weak_ptr<DataModel> dataModel, shared_ptr<ModelInstance> model,
|
||||
boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
static void makeAccoutrementRequests(std::string* response, std::exception* err, weak_ptr<DataModel> dataModel, shared_ptr<ModelInstance> model,
|
||||
boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
void getCharacterAppearance(
|
||||
int userId, boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction);
|
||||
|
||||
int getMaxPlayers() const;
|
||||
int getPreferredPlayers() const;
|
||||
|
||||
void setMaxPlayers(int value);
|
||||
void setPreferredPlayers(int value);
|
||||
void setSysStatsUrl(std::string url);
|
||||
|
||||
std::string getSaveDataUrl(int userId) const;
|
||||
void setSaveDataUrl(std::string saveDataUrl);
|
||||
|
||||
std::string getLoadDataUrl(int userId) const;
|
||||
void setLoadDataUrl(std::string loadDataUrl);
|
||||
|
||||
std::string getSaveLeaderboardDataUrl(int userId) const;
|
||||
void setSaveLeaderboardDataUrl(std::string saveLeaderboardDataUrl);
|
||||
void addLeaderboardKey(std::string);
|
||||
|
||||
bool hasLeaderboardKey(const std::string& key) const;
|
||||
boost::unordered_set<std::string>::const_iterator beginLeaderboardKey() const;
|
||||
boost::unordered_set<std::string>::const_iterator endLeaderboardKey() const;
|
||||
|
||||
void setChatOption(ChatOption value);
|
||||
void setNonSuperSafeChatForAllPlayersEnabled(bool enabled);
|
||||
bool getNonSuperSafeChatForAllPlayersEnabled() const;
|
||||
bool getClassicChat() const
|
||||
{
|
||||
return chatOption == CLASSIC_CHAT || chatOption == CLASSIC_AND_BUBBLE_CHAT;
|
||||
}
|
||||
bool getBubbleChat() const
|
||||
{
|
||||
return chatOption == BUBBLE_CHAT || chatOption == CLASSIC_AND_BUBBLE_CHAT;
|
||||
}
|
||||
|
||||
shared_ptr<const Instances> getPlayers()
|
||||
{
|
||||
return players.read();
|
||||
}
|
||||
|
||||
// Chat-related functions
|
||||
void gamechat(const std::string& message);
|
||||
|
||||
void chat(std::string message);
|
||||
void teamChat(std::string);
|
||||
void whisperChat(std::string message, shared_ptr<Instance> player);
|
||||
|
||||
void reportAbuse(Player* player, const std::string& comment);
|
||||
void reportAbuseLua(shared_ptr<Instance> instance, std::string reason, std::string comment);
|
||||
std::list<ChatMessage>::const_iterator chatHistory_begin()
|
||||
{
|
||||
return chatHistory.begin();
|
||||
}
|
||||
std::list<ChatMessage>::const_iterator chatHistory_end()
|
||||
{
|
||||
return chatHistory.end();
|
||||
}
|
||||
bool canReportAbuse() const;
|
||||
void setAbuseReportUrl(std::string value);
|
||||
void setChatFilterUrl(std::string value);
|
||||
void setBuildUserPermissionsUrl(std::string value);
|
||||
bool hasBuildUserPermissionsUrl() const;
|
||||
std::string getBuildUserPermissionsUrl(int playerId) const;
|
||||
|
||||
void friendServiceRequest(bool makeFriends, weak_ptr<Player> sourcePlayer, int otherUserId);
|
||||
|
||||
bool getCharacterAutoSpawnProperty() const
|
||||
{
|
||||
return characterAutoSpawn;
|
||||
}
|
||||
void setCharacterAutoSpawnProperty(bool value);
|
||||
bool getShouldAutoSpawnCharacter() const;
|
||||
|
||||
void blockUser(int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction = boost::function<void(bool)>(),
|
||||
boost::function<void(std::string)> errorFunction = boost::function<void(std::string)>());
|
||||
void unblockUser(int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction = boost::function<void(bool)>(),
|
||||
boost::function<void(std::string)> errorFunction = boost::function<void(std::string)>());
|
||||
|
||||
void setConnection(ConcurrentRakPeer* rakPeer);
|
||||
|
||||
shared_ptr<Instance> playerFromCharacter(shared_ptr<Instance> character);
|
||||
shared_ptr<Instance> getPlayerInstanceByID(int userID);
|
||||
shared_ptr<Player> getPlayerByID(int userID);
|
||||
|
||||
static Player* getPlayerFromCharacter(Aya::Instance* character);
|
||||
|
||||
void buildClientRegion(Region2& clientRegion);
|
||||
|
||||
void renderDPhysicsRegions(Adorn* adorn);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// STATICS
|
||||
//
|
||||
// If Client == Client Network Address; If Server == NetworkOwner::Server()
|
||||
static Aya::SystemAddress findLocalSimulatorAddress(const Aya::Instance* context);
|
||||
|
||||
static ModelInstance* findLocalCharacter(Aya::Instance* context);
|
||||
static const ModelInstance* findConstLocalCharacter(const Aya::Instance* context);
|
||||
|
||||
static Player* findLocalPlayer(Aya::Instance* context);
|
||||
static const Player* findConstLocalPlayer(const Aya::Instance* context);
|
||||
|
||||
static shared_ptr<Player> findAncestorPlayer(const Aya::Instance* context);
|
||||
|
||||
static shared_ptr<Player> findPlayerWithAddress(const Aya::SystemAddress& playerAddres, const Aya::Instance* context);
|
||||
|
||||
static bool clientIsPresent(const Aya::Instance* context, bool testInDatamodel = true);
|
||||
|
||||
static bool serverIsPresent(const Aya::Instance* context, bool testInDatamodel = true);
|
||||
|
||||
static bool frontendProcessing(const Aya::Instance* context, bool testInDatamodel = true);
|
||||
|
||||
static bool backendProcessing(const Aya::Instance* context, bool testInDatamodel = true);
|
||||
|
||||
static int getPlayerCount(const Aya::Instance* context);
|
||||
|
||||
// TODO: Remove this some day!
|
||||
static bool getDistributedPhysicsEnabled();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// These are here as "official" designation of Big-Picture states
|
||||
//
|
||||
// Frontend Backend
|
||||
// GameServer: x serverIsPresent (assumes !findLocalPlayer)
|
||||
// Visit Online: x clientIsPresent && findLocalPlayer
|
||||
// Watch Online: x clientIsPresent && !findLocalPlayer (i.e. - visit, no character)
|
||||
// Visit Solo: x x !clientIsPresent && !serverIsPresent && findLocalPlayer
|
||||
// Local Play: x clientIsPresent && findLocalPlayer && userID == 0
|
||||
// Edit Mode: x x !clientIsPresent && !serverIsPresent && !findLocalPlayer
|
||||
//
|
||||
// typedef enum {GAME_SERVER, DPHYS_GAME_SERVER, CLIENT, DPHYS_CLIENT, WATCH_ONLINE, VISIT_SOLO, EDIT} GameMode;
|
||||
|
||||
static Aya::Network::GameMode getGameMode(const Aya::Instance* context)
|
||||
{
|
||||
bool client = clientIsPresent(context);
|
||||
bool server = serverIsPresent(context);
|
||||
bool localPlayer = (findConstLocalPlayer(context) != NULL);
|
||||
bool dPhysics = getDistributedPhysicsEnabled();
|
||||
|
||||
AYAASSERT(!(server && (client || localPlayer)));
|
||||
|
||||
if (server)
|
||||
{
|
||||
return dPhysics ? DPHYS_GAME_SERVER : GAME_SERVER;
|
||||
}
|
||||
if (client && localPlayer)
|
||||
{
|
||||
return dPhysics ? DPHYS_CLIENT : CLIENT;
|
||||
}
|
||||
if (client && !localPlayer)
|
||||
{
|
||||
return WATCH_ONLINE;
|
||||
}
|
||||
if (!client && localPlayer)
|
||||
{
|
||||
return VISIT_SOLO;
|
||||
}
|
||||
else
|
||||
{
|
||||
return EDIT;
|
||||
}
|
||||
}
|
||||
|
||||
static Aya::Network::GameMode getGameMode(const Aya::Instance* context, const int placeID)
|
||||
{
|
||||
bool client = clientIsPresent(context);
|
||||
bool server = serverIsPresent(context);
|
||||
bool localPlayer = (findConstLocalPlayer(context) != NULL);
|
||||
bool dPhysics = getDistributedPhysicsEnabled();
|
||||
|
||||
AYAASSERT(!(server && (client || localPlayer)));
|
||||
;
|
||||
|
||||
if (placeID <= 0)
|
||||
{
|
||||
return LOCAL_PLAY;
|
||||
}
|
||||
if (server)
|
||||
{
|
||||
return dPhysics ? DPHYS_GAME_SERVER : GAME_SERVER;
|
||||
}
|
||||
if (client && localPlayer)
|
||||
{
|
||||
return dPhysics ? DPHYS_CLIENT : CLIENT;
|
||||
}
|
||||
if (client && !localPlayer)
|
||||
{
|
||||
return WATCH_ONLINE;
|
||||
}
|
||||
if (!client && localPlayer)
|
||||
{
|
||||
return VISIT_SOLO;
|
||||
}
|
||||
else
|
||||
{
|
||||
return EDIT;
|
||||
}
|
||||
}
|
||||
|
||||
void onRemoteSysStats(int userId, const std::string& stat, const std::string& message, bool desireKick = true);
|
||||
bool hashMatches(const std::string& hash);
|
||||
|
||||
void disconnectPlayer(int userId, int reason);
|
||||
void disconnectPlayerLocal(int userId, int reason);
|
||||
|
||||
bool getUseCoreScriptHealthBar();
|
||||
|
||||
protected:
|
||||
void disconnectPlayer(Instance& instance, int userId, int reason);
|
||||
|
||||
std::map<int, std::set<std::string>> cheatingPlayers;
|
||||
bool askAddChild(const Instance* instance) const;
|
||||
/*override*/ void onChildAdded(Instance* child);
|
||||
/*override*/ void onChildRemoving(Instance* child);
|
||||
/*override*/ void onDescendantRemoving(const shared_ptr<Instance>& instance);
|
||||
/*override*/ void processRemoteEvent(
|
||||
const Reflection::EventDescriptor& descriptor, const Reflection::EventArguments& args, const Aya::SystemAddress& source);
|
||||
|
||||
private:
|
||||
void reportScriptSecurityError(int userId, std::string hash, std::string error, std::string stack);
|
||||
|
||||
void killPlayer(int userId);
|
||||
|
||||
void addChatMessage(const ChatMessage& message);
|
||||
|
||||
void checkChat(const std::string& message);
|
||||
|
||||
void sendFilteredChatMessageSignalHelper(const RakNet::SystemAddress& systemAddress, const shared_ptr<RakNet::BitStream>& baseData,
|
||||
const shared_ptr<Instance> sourceInstance, shared_ptr<ChatMessage> chatEvent, const ChatFilter::Result& response);
|
||||
|
||||
// Instance
|
||||
/*override*/ void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
349
engine/network/src/PropertySynchronization.hpp
Normal file
349
engine/network/src/PropertySynchronization.hpp
Normal file
@@ -0,0 +1,349 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
This unit solves the "ships passing in the night" replication problem.
|
||||
If two peers change a property simultaneously, then there is a risk that
|
||||
they will swap values and never settle on a common shared value. For
|
||||
example, if peer A sets a color to Red and peer B sets a color to Blue,
|
||||
then after replication peer A will get Blue and peer B will get Red.
|
||||
|
||||
The solution involves appointing one peer as the master and the other peer
|
||||
as the slave. When the master sets a property, the slave must acknowledge
|
||||
the change before it can successfully change the same property.
|
||||
|
||||
The method involves associating a version number with each property.
|
||||
When the master changes the property, it increments the version number.
|
||||
The slave must send an acknowledgment to the master that it has received
|
||||
the property update (and its current version number). Until then, the
|
||||
master will reject any property changes coming from the slave. Since the
|
||||
slave will eventually receive the master<65>s property value, we are
|
||||
guaranteed eventual consistency.
|
||||
|
||||
Note that a side-effect of this approach is that a slave might change a
|
||||
value temporarily. The master will never experience this temporary state.
|
||||
|
||||
The implementation is designed to avoid massive memory costs and it
|
||||
minimizes network bandwidth. Instead of storing a version number for every
|
||||
single property, we rely on the fact that network latency is finite.
|
||||
When the master changes a property, we associate a version number with the
|
||||
property for a "short" period of time - on the order of a network
|
||||
round-trip. Likewise, the slave keeps the association temporarily. If the
|
||||
slave doesn<73>t try to change a property during the lifetime of the version
|
||||
number, then it never needs to send an acknowledgement, so we save
|
||||
bandwidth. The end result is a modest memory footprint and nearly
|
||||
no networking overhead.
|
||||
*/
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
namespace PropSync
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
class PropertyKey
|
||||
{
|
||||
const Reflection::PropertyDescriptor* descriptor;
|
||||
Guid::Data guidData;
|
||||
|
||||
public:
|
||||
PropertyKey()
|
||||
: descriptor(NULL)
|
||||
{
|
||||
guidData.scope.setNull();
|
||||
guidData.index = 0;
|
||||
}
|
||||
|
||||
PropertyKey(const Reflection::ConstProperty& prop)
|
||||
: descriptor(&prop.getDescriptor())
|
||||
{
|
||||
guidData.scope.setNull();
|
||||
guidData.index = 0;
|
||||
|
||||
if (prop.getInstance())
|
||||
{
|
||||
if (const Instance* instance = prop.getInstance()->fastDynamicCast<const Instance>())
|
||||
instance->getGuid().extract(guidData);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const PropertyKey& other) const
|
||||
{
|
||||
return this->descriptor == other.descriptor && this->guidData.scope == other.guidData.scope && this->guidData.index == other.guidData.index;
|
||||
}
|
||||
|
||||
friend std::size_t hash_value(const PropertyKey& key)
|
||||
{
|
||||
std::size_t seed = boost::hash<const Reflection::PropertyDescriptor*>()(key.descriptor);
|
||||
boost::hash_combine(seed, key.guidData.scope.getName());
|
||||
boost::hash_combine(seed, key.guidData.index);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
// This class contains common code between the master and the slave
|
||||
template<class Item>
|
||||
class Base
|
||||
{
|
||||
protected:
|
||||
Aya::Time::Interval expirationTime;
|
||||
typedef boost::unordered_map<PropertyKey, Item> Map;
|
||||
Map map;
|
||||
Aya::timestamped_safe_queue<PropertyKey> expirationQueue;
|
||||
|
||||
Base(Aya::Time::Interval expirationTime)
|
||||
: expirationTime(expirationTime)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
size_t itemCount() const
|
||||
{
|
||||
return map.size();
|
||||
}
|
||||
void expireItems()
|
||||
{
|
||||
PropertyKey prop;
|
||||
while (expirationQueue.pop_if_waited(expirationTime, prop))
|
||||
{
|
||||
typename Map::iterator iter = map.find(prop);
|
||||
AYAASSERT(iter != map.end());
|
||||
if (Aya::Time::now<Aya::Time::Fast>() > iter->second.expiration)
|
||||
// expire the item
|
||||
map.erase(iter);
|
||||
else
|
||||
// Put it back in the queue for another time
|
||||
expirationQueue.push(prop);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
int version;
|
||||
Aya::Time expiration;
|
||||
Item()
|
||||
: version(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
class MasterItem : public Item
|
||||
{
|
||||
public:
|
||||
bool isVersionSent; // == the version number has been sent to the slave
|
||||
int slaveVersion; // the version that the slave has acknowledged
|
||||
MasterItem()
|
||||
: isVersionSent(false)
|
||||
, slaveVersion(-1)
|
||||
{
|
||||
}
|
||||
};
|
||||
class SlaveItem : public Item
|
||||
{
|
||||
public:
|
||||
bool isAckSent; // == an acknowledgement has been sent from slave to master
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
class Master : protected detail::Base<detail::MasterItem>
|
||||
{
|
||||
typedef detail::Base<detail::MasterItem> Super;
|
||||
|
||||
public:
|
||||
unsigned int propertyRejectionCount;
|
||||
Master()
|
||||
: Super(Aya::Time::Interval(2))
|
||||
, propertyRejectionCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Call this periodically to cull the database
|
||||
void expireItems()
|
||||
{
|
||||
Super::expireItems();
|
||||
}
|
||||
|
||||
// Call this function when the master changes a property:
|
||||
void onPropertyChanged(Reflection::ConstProperty prop)
|
||||
{
|
||||
// get-or-create the Item
|
||||
// TODO: use emplace when it becomes available
|
||||
std::pair<Map::iterator, bool> pair = map.insert(Map::value_type(detail::PropertyKey(prop), detail::MasterItem()));
|
||||
detail::MasterItem& item(pair.first->second);
|
||||
bool createdItem = pair.second;
|
||||
|
||||
if (createdItem)
|
||||
{
|
||||
AYAASSERT(item.version == 0);
|
||||
AYAASSERT(!item.isVersionSent);
|
||||
// Set the expiration, queue for expiration
|
||||
item.expiration = Aya::Time::now<Aya::Time::Fast>() + expirationTime;
|
||||
expirationQueue.push(detail::PropertyKey(prop));
|
||||
}
|
||||
else if (item.isVersionSent)
|
||||
{
|
||||
item.version++;
|
||||
item.isVersionSent = false;
|
||||
// touch the expiration time
|
||||
item.expiration = Aya::Time::now<Aya::Time::Fast>() + expirationTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function when the master is about to send a property.
|
||||
// The returned value specifies if a version reset message should
|
||||
// be sent to the slave
|
||||
typedef enum
|
||||
{
|
||||
SendVersionReset,
|
||||
DontSendVersionReset
|
||||
} PropertySendResult;
|
||||
PropertySendResult onPropertySend(Reflection::ConstProperty prop)
|
||||
{
|
||||
// get-or-create the Item
|
||||
// TODO: use emplace when it becomes available
|
||||
std::pair<Map::iterator, bool> pair = map.insert(Map::value_type(detail::PropertyKey(prop), detail::MasterItem()));
|
||||
detail::MasterItem& item(pair.first->second);
|
||||
bool createdItem = pair.second;
|
||||
|
||||
// we assume that the caller will send the Item now
|
||||
item.isVersionSent = true;
|
||||
|
||||
// Find out if this is a brand new Item
|
||||
if (createdItem)
|
||||
{
|
||||
// Set the expiration, queue for expiration and request a version reset
|
||||
item.expiration = Aya::Time::now<Aya::Time::Fast>() + expirationTime;
|
||||
expirationQueue.push(detail::PropertyKey(prop));
|
||||
AYAASSERT(item.version == 0);
|
||||
return SendVersionReset;
|
||||
}
|
||||
|
||||
return item.version == 0 ? SendVersionReset : DontSendVersionReset;
|
||||
}
|
||||
|
||||
// Call this function when an acknowledgement is received from the slave
|
||||
void onReceivedAcknowledgement(Reflection::ConstProperty prop, int version)
|
||||
{
|
||||
Map::iterator iter = map.find(detail::PropertyKey(prop));
|
||||
if (iter == map.end())
|
||||
return;
|
||||
|
||||
iter->second.slaveVersion = version;
|
||||
}
|
||||
|
||||
// Call this function when a property is received from the slave
|
||||
FilterResult onReceivedPropertyChanged(Reflection::ConstProperty prop)
|
||||
{
|
||||
Map::iterator iter = map.find(detail::PropertyKey(prop));
|
||||
if (iter == map.end())
|
||||
return Accept;
|
||||
|
||||
detail::MasterItem& item(iter->second);
|
||||
if (item.slaveVersion == item.version)
|
||||
return Accept;
|
||||
|
||||
propertyRejectionCount++;
|
||||
return Reject;
|
||||
}
|
||||
|
||||
size_t itemCount() const
|
||||
{
|
||||
return Super::itemCount();
|
||||
}
|
||||
|
||||
void setExpiration(Aya::Time::Interval expirationTime)
|
||||
{
|
||||
this->expirationTime = expirationTime;
|
||||
}
|
||||
};
|
||||
|
||||
class Slave : protected detail::Base<detail::SlaveItem>
|
||||
{
|
||||
typedef detail::Base<detail::SlaveItem> Super;
|
||||
|
||||
public:
|
||||
unsigned int ackCount;
|
||||
Slave()
|
||||
: Super(Aya::Time::Interval(4))
|
||||
, ackCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Call this periodically to cull the database
|
||||
void expireItems()
|
||||
{
|
||||
Super::expireItems();
|
||||
}
|
||||
|
||||
// Call this function when the slave receives a property from the master
|
||||
void onReceivedPropertyChanged(Reflection::ConstProperty prop, bool versionReset)
|
||||
{
|
||||
// get-or-create the Item
|
||||
// TODO: use emplace when it becomes available
|
||||
std::pair<Map::iterator, bool> pair = map.insert(Map::value_type(detail::PropertyKey(prop), detail::SlaveItem()));
|
||||
detail::SlaveItem& item(pair.first->second);
|
||||
bool createdItem = pair.second;
|
||||
|
||||
if (createdItem)
|
||||
{
|
||||
// Disabling this for now.
|
||||
// AYAASSERT(versionReset);
|
||||
|
||||
// Set the expiration, queue for expiration and request a version reset
|
||||
item.expiration = Aya::Time::now<Aya::Time::Fast>() + expirationTime;
|
||||
expirationQueue.push(detail::PropertyKey(prop));
|
||||
}
|
||||
else
|
||||
{
|
||||
// touch the expiration time
|
||||
item.expiration = Aya::Time::now<Aya::Time::Fast>() + expirationTime;
|
||||
}
|
||||
|
||||
if (versionReset)
|
||||
item.version = 0;
|
||||
else
|
||||
item.version++;
|
||||
|
||||
item.isAckSent = false;
|
||||
}
|
||||
|
||||
// Call this function when the slave is about to send a property.
|
||||
// If it returns SendAcknowledgement, then first send a message with the
|
||||
// specified version number
|
||||
typedef enum
|
||||
{
|
||||
SendAcknowledgement,
|
||||
DontSendAcknowledgement
|
||||
} PropertySendResult;
|
||||
PropertySendResult onPropertySend(Reflection::ConstProperty prop, int& version)
|
||||
{
|
||||
Map::iterator iter = map.find(detail::PropertyKey(prop));
|
||||
if (iter == map.end())
|
||||
return DontSendAcknowledgement;
|
||||
|
||||
if (iter->second.isAckSent)
|
||||
return DontSendAcknowledgement;
|
||||
|
||||
iter->second.isAckSent = true;
|
||||
version = iter->second.version;
|
||||
ackCount++;
|
||||
return SendAcknowledgement;
|
||||
}
|
||||
|
||||
size_t itemCount() const
|
||||
{
|
||||
return Super::itemCount();
|
||||
}
|
||||
|
||||
void setExpiration(Aya::Time::Interval expirationTime)
|
||||
{
|
||||
this->expirationTime = expirationTime;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace PropSync
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
436
engine/network/src/RakNetFast.hpp
Normal file
436
engine/network/src/RakNetFast.hpp
Normal file
@@ -0,0 +1,436 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
// #include "Utility/Velocity.hpp"
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
// #include "Dictionary.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
// --------------------------------------------------------------------
|
||||
// reverseEndian
|
||||
//
|
||||
// Reuse the numeric constants as much as possible to reduce constant value loads.
|
||||
|
||||
template<unsigned int Bytes>
|
||||
struct ReverseEndianBytes
|
||||
{
|
||||
template<class T>
|
||||
static inline void reverseEndian(T& result)
|
||||
{
|
||||
BOOST_STATIC_ASSERT(sizeof(T) == 1);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ReverseEndianBytes<2>
|
||||
{
|
||||
template<class T>
|
||||
static inline void reverseEndian(T& x)
|
||||
{
|
||||
uint16_t& result = (uint16_t&)x;
|
||||
result = (result << 8) | (result >> 8);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ReverseEndianBytes<4>
|
||||
{
|
||||
template<class T>
|
||||
static inline void reverseEndian(T& x)
|
||||
{
|
||||
uint32_t& result = (uint32_t&)x;
|
||||
uint32_t val = ((result & 0x00FF00FF) << 8) | ((result >> 8) & 0x00FF00FF);
|
||||
result = (val << 16) | (val >> 16);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ReverseEndianBytes<8>
|
||||
{
|
||||
template<class T>
|
||||
static inline void reverseEndian(T& x)
|
||||
{
|
||||
uint64_t& result = (uint64_t&)x;
|
||||
uint64_t val = (result << 32) | (result >> 32);
|
||||
val = (val & 0x0000FFFF0000FFFF) << 16 | ((val >> 16) & 0x0000FFFF0000FFFF);
|
||||
val = (val & 0x00FF00FF00FF00FF) << 8 | ((val >> 8) & 0x00FF00FF00FF00FF);
|
||||
result = val;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline void reverseEndianBits(T& result, unsigned int bits)
|
||||
{
|
||||
unsigned int msbPad = (-(signed int)bits) & 7; // == (8-(bits&7))&7. High bits that were truncated off the MSB.
|
||||
|
||||
// Shift up by the amount clipped off the non-zero-MSB.
|
||||
T shifted = result << msbPad;
|
||||
|
||||
// Right align the non-zero-MSB while it is still in the LSB and easy to locate.
|
||||
result = (shifted & ~(T)0xff) | (result & ((T)0xff >> msbPad));
|
||||
|
||||
if ((sizeof(T) * 8 - 7) <= bits)
|
||||
{
|
||||
ReverseEndianBytes<sizeof(T)>::reverseEndian(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Warning: Taking the address of a parameter forces a register spill.
|
||||
unsigned char* a = (unsigned char*)&result;
|
||||
unsigned char* b = a + ((bits - 1) >> 3);
|
||||
while (a < b)
|
||||
{
|
||||
unsigned char t = *a;
|
||||
*a++ = *b;
|
||||
*b-- = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
template<unsigned int Bytes>
|
||||
struct ReadFastBytes
|
||||
{
|
||||
template<class T>
|
||||
static inline void readFast(RakNet::BitStream& bitStream, T& result, unsigned int bits)
|
||||
{
|
||||
unsigned int readOffset = bitStream.GetReadOffset();
|
||||
|
||||
if (readOffset + bits > bitStream.GetNumberOfBitsUsed())
|
||||
throw Aya::network_stream_exception("readFast past end");
|
||||
|
||||
const unsigned char* data = bitStream.GetData();
|
||||
const unsigned char* pos = data + (readOffset >> 3);
|
||||
|
||||
unsigned int bitsOffset = readOffset & 7;
|
||||
unsigned int firstByteBits = 8 - bitsOffset;
|
||||
|
||||
unsigned char firstByte = *pos & (0xff >> bitsOffset);
|
||||
|
||||
if (firstByteBits >= bits)
|
||||
{
|
||||
result = (T)(firstByte >> (firstByteBits - bits));
|
||||
bitStream.SetReadOffset(readOffset + bits);
|
||||
return;
|
||||
}
|
||||
|
||||
T tmp = (T)firstByte;
|
||||
++pos;
|
||||
|
||||
unsigned int bitsRemaining = bits - firstByteBits; // 8 could be propagated out.
|
||||
for (; bitsRemaining > 8; bitsRemaining -= 8)
|
||||
{
|
||||
tmp = (tmp << 8) | *pos;
|
||||
++pos;
|
||||
}
|
||||
|
||||
tmp = (tmp << bitsRemaining) | (*pos >> (8 - bitsRemaining));
|
||||
result = tmp;
|
||||
|
||||
bitStream.SetReadOffset(readOffset + bits);
|
||||
}
|
||||
|
||||
|
||||
template<unsigned int Bits, class T>
|
||||
static inline void readFast(RakNet::BitStream& bitStream, T& result)
|
||||
{
|
||||
readFast(bitStream, result, Bits);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ReadFastBytes<1>
|
||||
{
|
||||
template<unsigned int Bits, class T>
|
||||
static inline void readFast(RakNet::BitStream& bitStream, T& result)
|
||||
{
|
||||
BOOST_STATIC_ASSERT(Bits >= 1 && Bits <= 8);
|
||||
|
||||
unsigned int readOffset = bitStream.GetReadOffset();
|
||||
|
||||
if (readOffset + (Bits + 8) > bitStream.GetNumberOfBitsUsed())
|
||||
{
|
||||
// Avoid crashing due to reading an extra byte off the end of the buffer.
|
||||
ReadFastBytes<0>::readFast(bitStream, result, Bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned char* data = bitStream.GetData();
|
||||
|
||||
unsigned int readOffsetBytes = readOffset >> 3;
|
||||
|
||||
unsigned char byte0 = data[readOffsetBytes + 0];
|
||||
unsigned char byte1 = data[readOffsetBytes + 1];
|
||||
|
||||
result = (((((byte0 << 8) | byte1) << (readOffset & 7)) & 0xffff) >> (16 - Bits));
|
||||
|
||||
bitStream.SetReadOffset(readOffset + Bits);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ReadFastBytes<2>
|
||||
{
|
||||
template<unsigned int Bits, class T>
|
||||
static inline void readFast(RakNet::BitStream& bitStream, T& result)
|
||||
{
|
||||
BOOST_STATIC_ASSERT(Bits >= 9 && Bits <= 16);
|
||||
|
||||
unsigned int readOffset = bitStream.GetReadOffset();
|
||||
|
||||
if (readOffset + (Bits + 8) > bitStream.GetNumberOfBitsUsed())
|
||||
{
|
||||
// Avoid crashing due to reading an extra byte off the end of the buffer.
|
||||
ReadFastBytes<0>::readFast(bitStream, result, Bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned char* data = bitStream.GetData();
|
||||
|
||||
unsigned int readOffsetBytes = readOffset >> 3;
|
||||
|
||||
unsigned char byte0 = data[readOffsetBytes + 0];
|
||||
unsigned char byte1 = data[readOffsetBytes + 1];
|
||||
unsigned char byte2 = data[readOffsetBytes + 2];
|
||||
|
||||
result = ((((byte0 << 16) | (byte1 << 8) | byte2) << (readOffset & 7)) & 0xffffff) >> (24 - Bits);
|
||||
|
||||
bitStream.SetReadOffset(readOffset + Bits);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ReadFastBytes<4>
|
||||
{
|
||||
template<unsigned int Bits, class T>
|
||||
static inline void readFast(RakNet::BitStream& bitStream, T& result)
|
||||
{
|
||||
BOOST_STATIC_ASSERT(Bits >= 17 && Bits <= 32);
|
||||
|
||||
unsigned int readOffset = bitStream.GetReadOffset();
|
||||
|
||||
if (readOffset + (Bits + 8) > bitStream.GetNumberOfBitsUsed())
|
||||
{
|
||||
// Avoid crashing due to reading an extra byte off the end of the buffer.
|
||||
ReadFastBytes<0>::readFast(bitStream, result, Bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned char* data = bitStream.GetData();
|
||||
|
||||
unsigned int readOffsetBytes = readOffset >> 3;
|
||||
|
||||
unsigned char byte0 = data[readOffsetBytes + 0];
|
||||
unsigned char byte1 = data[readOffsetBytes + 1];
|
||||
unsigned char byte2 = data[readOffsetBytes + 2];
|
||||
unsigned char byte3 = data[readOffsetBytes + 3];
|
||||
unsigned char byte4 = data[readOffsetBytes + 4];
|
||||
|
||||
T tmp0 = (((((byte0 << 16) | (byte1 << 8) | byte2) << (readOffset & 7)) & 0xffffff) >> (24 - 16));
|
||||
T tmp1 = (((((byte2 << 16) | (byte3 << 8) | byte4) << (readOffset & 7)) & 0xffffff) >> (24 - (Bits - 16)));
|
||||
result = (tmp0 << 16) | tmp1;
|
||||
|
||||
bitStream.SetReadOffset(readOffset + Bits);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
//
|
||||
// The non-"T" versions replace ReadBits versions which did not do
|
||||
// endian swapping
|
||||
|
||||
template<class T>
|
||||
inline void readFastN(RakNet::BitStream& bitStream, T& result, unsigned int bits)
|
||||
{
|
||||
// Use default implementation for arbitrary bit-counts.
|
||||
ReadFastBytes<0>::readFast(bitStream, result, bits);
|
||||
|
||||
reverseEndianBits(result, bits);
|
||||
}
|
||||
|
||||
// The base template should only be needed for unusual numbers of bits.
|
||||
template<unsigned int Bits, class T>
|
||||
inline void readFastN(RakNet::BitStream& bitStream, T& result)
|
||||
{
|
||||
ReadFastBytes<0>::readFast<Bits>(bitStream, result);
|
||||
|
||||
reverseEndianBits(result, Bits);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// readFastT
|
||||
//
|
||||
// The "T" versions do not do endian swapping because the BitStream::Write
|
||||
// sends across network order/big-endian data.
|
||||
|
||||
template<class T>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, T& result)
|
||||
{
|
||||
BOOST_STATIC_ASSERT(sizeof(T) == 0); // This is undefined.
|
||||
}
|
||||
|
||||
#if defined(__linux)
|
||||
template<>
|
||||
inline void readFastT<unsigned long long>(RakNet::BitStream& bitStream, unsigned long long& result)
|
||||
{
|
||||
bitStream.Read(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, bool& result)
|
||||
{
|
||||
unsigned int readOffset = bitStream.GetReadOffset();
|
||||
|
||||
if (readOffset + 1 > bitStream.GetNumberOfBitsUsed())
|
||||
throw Aya::network_stream_exception("readFastBool past end");
|
||||
|
||||
const unsigned char* data = bitStream.GetData();
|
||||
|
||||
bool tmp = (data[readOffset >> 3] & (0x80 >> (readOffset & 7))) != 0;
|
||||
result = tmp;
|
||||
|
||||
bitStream.SetReadOffset(readOffset + 1);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, float& result)
|
||||
{
|
||||
union
|
||||
{
|
||||
float f;
|
||||
uint32_t i;
|
||||
} t;
|
||||
ReadFastBytes<4>::readFast<32, uint32_t>(bitStream, t.i);
|
||||
result = t.f;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, double& result)
|
||||
{
|
||||
union
|
||||
{
|
||||
double d;
|
||||
uint64_t i;
|
||||
} t;
|
||||
ReadFastBytes<0>::readFast<64, uint64_t>(bitStream, t.i);
|
||||
result = t.d;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, int8_t& result)
|
||||
{
|
||||
ReadFastBytes<1>::readFast<8, uint8_t>(bitStream, (uint8_t&)result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, uint8_t& result)
|
||||
{
|
||||
ReadFastBytes<1>::readFast<8, uint8_t>(bitStream, result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, char& result)
|
||||
{
|
||||
ReadFastBytes<1>::readFast<8, uint8_t>(bitStream, (uint8_t&)result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, int16_t& result)
|
||||
{
|
||||
ReadFastBytes<2>::readFast<16, uint16_t>(bitStream, (uint16_t&)result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, uint16_t& result)
|
||||
{
|
||||
ReadFastBytes<2>::readFast<16, uint16_t>(bitStream, result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, int32_t& result)
|
||||
{
|
||||
ReadFastBytes<4>::readFast<32, uint32_t>(bitStream, (uint32_t&)result);
|
||||
}
|
||||
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, long& result)
|
||||
{
|
||||
#if defined(__LP64__)
|
||||
BOOST_STATIC_ASSERT(sizeof(long) == sizeof(uint64_t));
|
||||
ReadFastBytes<0>::readFast<64, uint64_t>(bitStream, (uint64_t&)result);
|
||||
#else
|
||||
BOOST_STATIC_ASSERT(sizeof(long) == sizeof(uint32_t));
|
||||
ReadFastBytes<4>::readFast<32, uint32_t>(bitStream, (uint32_t&)result);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, unsigned long& result)
|
||||
{
|
||||
#if defined(__LP64__)
|
||||
BOOST_STATIC_ASSERT(sizeof(unsigned long) == sizeof(uint64_t));
|
||||
ReadFastBytes<0>::readFast<64, uint64_t>(bitStream, (uint64_t&)result);
|
||||
#else
|
||||
BOOST_STATIC_ASSERT(sizeof(unsigned long) == sizeof(uint32_t));
|
||||
ReadFastBytes<4>::readFast<32, uint32_t>(bitStream, (uint32_t&)result);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, uint32_t& result)
|
||||
{
|
||||
ReadFastBytes<4>::readFast<32, uint32_t>(bitStream, result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, int64_t& result)
|
||||
{
|
||||
ReadFastBytes<0>::readFast<64, uint64_t>(bitStream, (uint64_t&)result);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void readFastT(RakNet::BitStream& bitStream, uint64_t& result)
|
||||
{
|
||||
ReadFastBytes<0>::readFast<64, uint64_t>(bitStream, result);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
inline void readVectorFast(RakNet::BitStream& bitStream, float& x, float& y, float& z)
|
||||
{
|
||||
float magnitude;
|
||||
readFastT(bitStream, magnitude);
|
||||
if (magnitude > 0.00001f)
|
||||
{
|
||||
unsigned short c;
|
||||
readFastT(bitStream, c);
|
||||
x = (float(c) * (1.0f / 32767.5f) - 1.0f) * magnitude;
|
||||
|
||||
readFastT(bitStream, c);
|
||||
y = (float(c) * (1.0f / 32767.5f) - 1.0f) * magnitude;
|
||||
|
||||
readFastT(bitStream, c);
|
||||
z = (float(c) * (1.0f / 32767.5f) - 1.0f) * magnitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = y = z = 0;
|
||||
}
|
||||
}
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
92
engine/network/src/Replicator.ChangePropertyItem.cpp
Normal file
92
engine/network/src/Replicator.ChangePropertyItem.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "Replicator.ChangePropertyItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "Reflection/Property.hpp"
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
DeserializedChangePropertyItem::DeserializedChangePropertyItem()
|
||||
{
|
||||
type = Item::ItemTypeChangeProperty;
|
||||
}
|
||||
|
||||
void DeserializedChangePropertyItem::process(Replicator& replicator)
|
||||
{
|
||||
replicator.readChangedPropertyItem(this);
|
||||
}
|
||||
|
||||
Replicator::ChangePropertyItem::ChangePropertyItem(
|
||||
Replicator* replicator, const shared_ptr<const Instance>& instance, const Reflection::PropertyDescriptor& desc)
|
||||
: PooledItem(*replicator)
|
||||
, desc(desc)
|
||||
, instance(instance)
|
||||
{
|
||||
}
|
||||
|
||||
bool Replicator::ChangePropertyItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
replicator.pendingChangedPropertyItems.erase(Aya::Reflection::ConstProperty(desc, instance.get()));
|
||||
|
||||
// Check to see if we are still replicating this object
|
||||
if (!replicator.isReplicationContainer(instance.get()))
|
||||
return true;
|
||||
|
||||
// Write value
|
||||
replicator.writeChangedProperty(instance.get(), desc, bitStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::ChangePropertyItem::read(Replicator& replicator, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
shared_ptr<DeserializedChangePropertyItem> deserializedData(new DeserializedChangePropertyItem());
|
||||
|
||||
int start = inBitstream.GetReadOffset();
|
||||
shared_ptr<Instance> deserializedInstance;
|
||||
replicator.deserializeInstanceRef(inBitstream, deserializedInstance, deserializedData->id);
|
||||
deserializedData->instance = deserializedInstance;
|
||||
|
||||
// Read the property name
|
||||
unsigned int propId = replicator.propDictionary.receive(inBitstream, deserializedData->propertyDescriptor, true);
|
||||
|
||||
if (replicator.ProcessOutdatedChangedProperty(
|
||||
inBitstream, deserializedData->id, deserializedInstance.get(), deserializedData->propertyDescriptor, propId))
|
||||
{
|
||||
return shared_ptr<DeserializedChangePropertyItem>();
|
||||
}
|
||||
|
||||
if (!deserializedData->propertyDescriptor)
|
||||
throw Aya::runtime_error("Replicator readChangedProperty NULL descriptor");
|
||||
|
||||
if (deserializedInstance && !deserializedInstance->getDescriptor().isA(deserializedData->propertyDescriptor->owner))
|
||||
{
|
||||
throw Aya::runtime_error("Replication: Bad re-binding prop %s-%s << %s", deserializedInstance->getClassName().c_str(),
|
||||
deserializedData->propertyDescriptor->name.c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
}
|
||||
|
||||
if (!replicator.isServerReplicator())
|
||||
{
|
||||
inBitstream >> deserializedData->versionReset;
|
||||
}
|
||||
|
||||
Reflection::Property prop = Reflection::Property(*deserializedData->propertyDescriptor, deserializedInstance.get());
|
||||
replicator.deserializePropertyValue(inBitstream, prop, true /*useDictionary*/, false, &deserializedData->value);
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsReceived(deserializedData->propertyDescriptor->category.str);
|
||||
replicator.replicatorStats.samplePacketsReceived(
|
||||
deserializedData->propertyDescriptor->category.str, (inBitstream.GetReadOffset() - start) / 8);
|
||||
}
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
48
engine/network/src/Replicator.ChangePropertyItem.hpp
Normal file
48
engine/network/src/Replicator.ChangePropertyItem.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Instance;
|
||||
namespace Reflection
|
||||
{
|
||||
class PropertyDescriptor;
|
||||
}
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
|
||||
class DeserializedChangePropertyItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
Aya::Guid::Data id;
|
||||
shared_ptr<Instance> instance;
|
||||
const Reflection::PropertyDescriptor* propertyDescriptor;
|
||||
Reflection::Variant value;
|
||||
|
||||
bool versionReset; // client only
|
||||
|
||||
DeserializedChangePropertyItem();
|
||||
~DeserializedChangePropertyItem() {}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
class Replicator::ChangePropertyItem : public PooledItem
|
||||
{
|
||||
const Reflection::PropertyDescriptor& desc;
|
||||
const shared_ptr<const Instance> instance;
|
||||
|
||||
public:
|
||||
ChangePropertyItem(Replicator* replicator, const shared_ptr<const Instance>& instance, const Reflection::PropertyDescriptor& desc);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
104
engine/network/src/Replicator.DeleteInstanceItem.cpp
Normal file
104
engine/network/src/Replicator.DeleteInstanceItem.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "Replicator.DeleteInstanceItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "NetworkPacketCache.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
#include "Streaming.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#include "Tree/Instance.hpp"
|
||||
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
DeserializedDeleteInstanceItem::DeserializedDeleteInstanceItem()
|
||||
{
|
||||
type = Item::ItemTypeDelete;
|
||||
}
|
||||
|
||||
void DeserializedDeleteInstanceItem::process(Replicator& replicator)
|
||||
{
|
||||
replicator.readInstanceDeleteItem(this);
|
||||
}
|
||||
|
||||
Replicator::DeleteInstanceItem::DeleteInstanceItem(Replicator* replicator, const shared_ptr<const Instance>& instance)
|
||||
: PooledItem(*replicator)
|
||||
, id(replicator->extractId(instance.get()))
|
||||
{
|
||||
if (replicator->settings().printInstances)
|
||||
{
|
||||
details.reset(new InstanceDetails());
|
||||
details->className = instance->getClassName().c_str();
|
||||
details->guid = instance->getGuid().readableString();
|
||||
}
|
||||
|
||||
// remove entry from instance cache
|
||||
if (replicator->instancePacketCache)
|
||||
replicator->instancePacketCache->remove(instance.get());
|
||||
}
|
||||
|
||||
bool Replicator::DeleteInstanceItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
if (!id.valid)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE,
|
||||
"Replication: ~NULL >> %s", // remove player always on right
|
||||
RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
writeItemType(bitStream, ItemTypeDelete);
|
||||
|
||||
// Write the GUID
|
||||
replicator.sendId(bitStream, id);
|
||||
|
||||
if (details)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: ~%s:%s >> %s, bytes: %d", details->className.c_str(),
|
||||
details->guid.c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str(), bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsSent(ReplicatorStats::PACKET_TYPE_InstanceDelete);
|
||||
replicator.replicatorStats.samplePacketsSent(
|
||||
ReplicatorStats::PACKET_TYPE_InstanceDelete, bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
}
|
||||
catch (std::runtime_error& e)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: ~%s >> %s, %s",
|
||||
"UnknownInstance", // instance->getClassName().c_str(),
|
||||
RakNetAddressToString(replicator.remotePlayerId).c_str(), e.what());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::DeleteInstanceItem::read(Replicator& replicator, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
shared_ptr<DeserializedDeleteInstanceItem> deserializedData(new DeserializedDeleteInstanceItem());
|
||||
|
||||
int start = inBitstream.GetReadOffset();
|
||||
|
||||
replicator.deserializeId(inBitstream, deserializedData->id);
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsReceived(ReplicatorStats::PACKET_TYPE_InstanceDelete);
|
||||
replicator.replicatorStats.samplePacketsReceived(ReplicatorStats::PACKET_TYPE_InstanceDelete, (inBitstream.GetReadOffset() - start) / 8);
|
||||
}
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
46
engine/network/src/Replicator.DeleteInstanceItem.hpp
Normal file
46
engine/network/src/Replicator.DeleteInstanceItem.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Streaming.hpp"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Instance;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class DeserializedDeleteInstanceItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
Aya::Guid::Data id;
|
||||
|
||||
DeserializedDeleteInstanceItem();
|
||||
~DeserializedDeleteInstanceItem() {}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
class Replicator::DeleteInstanceItem : public PooledItem
|
||||
{
|
||||
const IdSerializer::Id id;
|
||||
struct InstanceDetails
|
||||
{
|
||||
std::string className;
|
||||
std::string guid;
|
||||
};
|
||||
boost::scoped_ptr<InstanceDetails> details;
|
||||
|
||||
public:
|
||||
DeleteInstanceItem(Replicator* replicator, const shared_ptr<const Instance>& instance);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
121
engine/network/src/Replicator.EventInvocationItem.cpp
Normal file
121
engine/network/src/Replicator.EventInvocationItem.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "Replicator.EventInvocationItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "NetworkSettings.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#include "Reflection/Event.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
DeserializedEventInvocationItem::DeserializedEventInvocationItem()
|
||||
{
|
||||
type = Item::ItemTypeEventInvocation;
|
||||
}
|
||||
|
||||
void DeserializedEventInvocationItem::process(Replicator& replicator)
|
||||
{
|
||||
replicator.readEventInvocationItem(this);
|
||||
}
|
||||
|
||||
Replicator::EventInvocationItem::EventInvocationItem(Replicator* replicator, const shared_ptr<Instance>& instance,
|
||||
const Reflection::EventDescriptor& desc, const Reflection::EventArguments& arguments)
|
||||
: PooledItem(*replicator)
|
||||
, desc(desc)
|
||||
, instance(instance)
|
||||
, arguments(arguments)
|
||||
{
|
||||
}
|
||||
|
||||
bool Replicator::EventInvocationItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
// Check to see if we are still replicating this object
|
||||
if (!replicator.isReplicationContainer(instance.get()))
|
||||
return true;
|
||||
|
||||
DescriptorSender<Aya::Reflection::EventDescriptor>::IdContainer idContainer = replicator.eventDictionary.getId(&desc);
|
||||
if (idContainer.outdated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (replicator.isEventRemoved(instance.get(), desc.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
writeItemType(bitStream, ItemTypeEventInvocation);
|
||||
|
||||
// Write the GUID
|
||||
replicator.serializeId(bitStream, instance.get());
|
||||
|
||||
// Write property name
|
||||
replicator.eventDictionary.send(bitStream, idContainer.id);
|
||||
|
||||
// Write value
|
||||
replicator.serializeEventInvocation(Reflection::EventInvocation(Reflection::Event(desc, instance), arguments), bitStream);
|
||||
|
||||
if (replicator.settings().printEvents)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "Replication: %s-%s.%s >> %s, bytes: %d", instance->getClassName().c_str(),
|
||||
instance->getGuid().readableString().c_str(), desc.name.c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str(),
|
||||
bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsSent(ReplicatorStats::PACKET_TYPE_Event);
|
||||
replicator.replicatorStats.samplePacketsSent(ReplicatorStats::PACKET_TYPE_Event, bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::EventInvocationItem::read(Replicator& replicator, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
shared_ptr<DeserializedEventInvocationItem> deserializedData(new DeserializedEventInvocationItem());
|
||||
|
||||
shared_ptr<Instance> deseiralizedInstance;
|
||||
Aya::Guid::Data id;
|
||||
replicator.deserializeInstanceRef(inBitstream, deseiralizedInstance, id);
|
||||
|
||||
// Read the event name
|
||||
unsigned int eventId = replicator.eventDictionary.receive(inBitstream, deserializedData->eventDescriptor, true);
|
||||
|
||||
if (replicator.ProcessOutdatedEventInvocation(inBitstream, id, deseiralizedInstance.get(), deserializedData->eventDescriptor, eventId))
|
||||
{
|
||||
return shared_ptr<DeserializedEventInvocationItem>();
|
||||
}
|
||||
|
||||
if (deseiralizedInstance && !deseiralizedInstance->getDescriptor().isA(deserializedData->eventDescriptor->owner))
|
||||
{
|
||||
throw Aya::runtime_error("Replication: Bad re-binding event %s-%s << %s", deseiralizedInstance->getClassName().c_str(),
|
||||
deserializedData->eventDescriptor->name.c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
}
|
||||
|
||||
if (replicator.settings().printEvents)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE,
|
||||
"Replication: %s-%s.%s << %s", // remote player always on right side
|
||||
deseiralizedInstance ? deseiralizedInstance->getClassName().c_str() : "?", id.readableString().c_str(),
|
||||
deserializedData->eventDescriptor->name.c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
|
||||
deserializedData->instance = deseiralizedInstance;
|
||||
|
||||
deserializedData->eventInvocation.reset(
|
||||
new Reflection::EventInvocation(Reflection::Event(*deserializedData->eventDescriptor, deserializedData->instance)));
|
||||
replicator.deserializeEventInvocation(inBitstream, *deserializedData->eventInvocation, false);
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
41
engine/network/src/Replicator.EventInvocationItem.hpp
Normal file
41
engine/network/src/Replicator.EventInvocationItem.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "Reflection/Event.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class DeserializedEventInvocationItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
shared_ptr<Instance> instance;
|
||||
const Reflection::EventDescriptor* eventDescriptor;
|
||||
shared_ptr<Reflection::EventInvocation> eventInvocation;
|
||||
|
||||
DeserializedEventInvocationItem();
|
||||
~DeserializedEventInvocationItem() {}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
class Replicator::EventInvocationItem : public PooledItem
|
||||
{
|
||||
const Reflection::EventDescriptor& desc;
|
||||
const shared_ptr<Instance> instance;
|
||||
const Reflection::EventArguments arguments;
|
||||
|
||||
public:
|
||||
EventInvocationItem(Replicator* replicator, const shared_ptr<Instance>& instance, const Reflection::EventDescriptor& desc,
|
||||
const Reflection::EventArguments& arguments);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
532
engine/network/src/Replicator.GCJob.cpp
Normal file
532
engine/network/src/Replicator.GCJob.cpp
Normal file
@@ -0,0 +1,532 @@
|
||||
#include "Replicator.GCJob.hpp"
|
||||
|
||||
#include "ClientReplicator.hpp"
|
||||
#include "Compressor.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
#include "DrawAdorn.hpp"
|
||||
#include "DenseHash.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/PartInstance.hpp"
|
||||
|
||||
#include "World/ContactManager.hpp"
|
||||
#include "World/ContactManagerSpatialHash.hpp"
|
||||
#include "Utility/ScopedAssign.hpp"
|
||||
|
||||
#include "Utility/StreamRegion.hpp"
|
||||
|
||||
#include "Base/Adorn.hpp"
|
||||
#include "DataModel/JointsService.hpp"
|
||||
|
||||
#include "NetworkProfiler.hpp"
|
||||
#include "Humanoid/Humanoid.hpp"
|
||||
|
||||
// #include "World/Contact.hpp"
|
||||
// #include "Kernel/Body.hpp"
|
||||
|
||||
// #include "Kernel/SimBody.hpp"
|
||||
|
||||
|
||||
DYNAMIC_LOGGROUP(PartStreamingRequests)
|
||||
DYNAMIC_FASTINTVARIABLE(PartStreamingGCMinRegionLength, 2);
|
||||
// Default value: each id takes about 5 bytes (1 byte for scope, 4 bytes for id)
|
||||
// and expected mtu size ~1500, so a little less than 300 ids fit in one mtu.
|
||||
FASTINTVARIABLE(StreamOutCompressionIdListLengthThreshold, 250);
|
||||
DYNAMIC_FASTINTVARIABLE(JoinDataCompressionLevel, 1)
|
||||
DYNAMIC_FASTINTVARIABLE(JoinDataBonus, 0)
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Aya::Network;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool compRegionDistance(const RegionsMap::iterator a, const RegionsMap::iterator b)
|
||||
{
|
||||
// far to near, in axis
|
||||
return a->second.streamDistance > b->second.streamDistance;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class ClientReplicator::GCJob::RegionRemovalItem : public Item
|
||||
{
|
||||
StreamRegion::Id region;
|
||||
std::vector<Guid::Data> collectedInstanceGuids;
|
||||
|
||||
public:
|
||||
RegionRemovalItem(Replicator* replicator, StreamRegion::Id id)
|
||||
: Item(*replicator)
|
||||
, region(id)
|
||||
{
|
||||
}
|
||||
bool addInstance(shared_ptr<Instance> partInstance)
|
||||
{
|
||||
Guid::Data data;
|
||||
partInstance->getGuid().extract(data);
|
||||
if (std::find(collectedInstanceGuids.begin(), collectedInstanceGuids.end(), data) == collectedInstanceGuids.end())
|
||||
{
|
||||
collectedInstanceGuids.push_back(data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
writeItemType(bitStream, ItemTypeRegionRemoval);
|
||||
bitStream << region;
|
||||
boost::uint32_t numberOfCollectedGuids = collectedInstanceGuids.size();
|
||||
bitStream << numberOfCollectedGuids;
|
||||
|
||||
bool useCompression = FInt::StreamOutCompressionIdListLengthThreshold >= 0 &&
|
||||
numberOfCollectedGuids > ((unsigned int)FInt::StreamOutCompressionIdListLengthThreshold);
|
||||
|
||||
bitStream << useCompression;
|
||||
|
||||
if (useCompression)
|
||||
{
|
||||
RakNet::BitStream uncompressedBuffer;
|
||||
|
||||
for (boost::uint32_t i = 0; i < collectedInstanceGuids.size(); ++i)
|
||||
{
|
||||
replicator.serializeId(uncompressedBuffer, collectedInstanceGuids[i]);
|
||||
}
|
||||
|
||||
Replicator::compressBitStream(uncompressedBuffer, bitStream, DFInt::JoinDataCompressionLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (boost::uint32_t i = 0; i < collectedInstanceGuids.size(); ++i)
|
||||
{
|
||||
replicator.serializeId(bitStream, collectedInstanceGuids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ClientReplicator::GCJob::InstanceRemovalItem : public Item
|
||||
{
|
||||
public:
|
||||
InstanceRemovalItem(Replicator* replicator, Guid::Data id)
|
||||
: Item(*replicator)
|
||||
, id(id)
|
||||
{
|
||||
}
|
||||
bool write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
writeItemType(bitStream, ItemTypeInstanceRemoval);
|
||||
replicator.serializeId(bitStream, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Guid::Data id;
|
||||
};
|
||||
|
||||
ClientReplicator::GCJob::GCJob(Replicator& replicator)
|
||||
: ReplicatorJob("Replicator GC Job", replicator, DataModelJob::Write)
|
||||
, numSorted(0)
|
||||
, numRegionToGC(0)
|
||||
, renderingDistance(NULL)
|
||||
, renderingRegionDistance(0x7FFF)
|
||||
, gcRegionDistance(0x7FFF)
|
||||
, maxRegionDistance(0x7FFF)
|
||||
, lastPlayerPosition(Vector3::zero())
|
||||
, centerRegion(Vector3int32::maxInt())
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
|
||||
if (Workspace* workspace = Workspace::findWorkspace(&replicator))
|
||||
{
|
||||
if (World* world = workspace->getWorld())
|
||||
{
|
||||
spatialHash = world->getContactManager()->getSpatialHash();
|
||||
}
|
||||
renderingDistance = &workspace->renderingDistance;
|
||||
}
|
||||
|
||||
AYAASSERT(spatialHash);
|
||||
if (spatialHash)
|
||||
{
|
||||
spatialHash->registerCoarseMovementCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
ClientReplicator::GCJob::~GCJob()
|
||||
{
|
||||
unregisterCoarseMovementCallback();
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::unregisterCoarseMovementCallback()
|
||||
{
|
||||
if (Workspace* workspace = Workspace::findWorkspace(replicator.get()))
|
||||
{
|
||||
if (World* world = workspace->getWorld())
|
||||
{
|
||||
spatialHash = world->getContactManager()->getSpatialHash();
|
||||
if (spatialHash)
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_INFO, "unregisterCoarseMovementCallback() from ~GCJob()");
|
||||
spatialHash->unregisterCoarseMovementCallback(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TaskScheduler::Job::Error ClientReplicator::GCJob::error(const Stats& stats)
|
||||
{
|
||||
// TODO: compute error based on need to gc
|
||||
return computeStandardError(stats, replicator->settings().getDataGCRate());
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::evaluateNumRegionToGC()
|
||||
{
|
||||
if (numRegionToGC <= 0)
|
||||
{
|
||||
numRegionToGC = streamedRegionList.size() / 2;
|
||||
int minGcSize = StreamRegion::Constants::kMinNumPlayableRegion;
|
||||
if (numRegionToGC < minGcSize)
|
||||
{
|
||||
numRegionToGC = streamedRegionList.size() - minGcSize;
|
||||
if (numRegionToGC < 0)
|
||||
{
|
||||
numRegionToGC = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TaskScheduler::StepResult ClientReplicator::GCJob::stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
bool tryGC = false;
|
||||
bool forceGC = false;
|
||||
|
||||
if (!replicator)
|
||||
return TaskScheduler::Done;
|
||||
|
||||
// get the player CFrame if they are alive
|
||||
CoordinateFrame cf;
|
||||
bool playerIsDead = true;
|
||||
if (const Player* player = replicator->findTargetPlayer())
|
||||
if (const ModelInstance* characterModel = player->getConstCharacter())
|
||||
if (const Humanoid* h = characterModel->findConstFirstModifierOfType<Humanoid>())
|
||||
if (!h->getDead())
|
||||
{
|
||||
playerIsDead = false;
|
||||
if (const PartInstance* head = Humanoid::getConstHeadFromCharacter(characterModel))
|
||||
cf = head->getCoordinateFrame();
|
||||
}
|
||||
|
||||
ClientReplicator* clientRep = replicator->fastDynamicCast<ClientReplicator>();
|
||||
AYAASSERT(clientRep);
|
||||
clientRep->updateMemoryStats();
|
||||
if (!playerIsDead && (clientRep->getMemoryLevel() <= Aya::MemoryStats::MEMORYLEVEL_ALL_CRITICAL_LOW))
|
||||
{
|
||||
// Memory level too low, force GC
|
||||
tryGC = true;
|
||||
forceGC = true;
|
||||
}
|
||||
|
||||
// sample the rendering region distance
|
||||
AYAASSERT(renderingDistance);
|
||||
if (renderingDistance)
|
||||
{
|
||||
AYAASSERT((*renderingDistance) / StreamRegion::Id::streamGridCellSizeInStuds() < 32767.f);
|
||||
float oldRenderingRegionDistance = renderingRegionDistance;
|
||||
short newRenderingRegionDistance = (short)((*renderingDistance) / StreamRegion::Id::streamGridCellSizeInStuds()) + 1;
|
||||
// smoothing the variance by weight averaging the final value
|
||||
renderingRegionDistance = (renderingRegionDistance * 2.0f + newRenderingRegionDistance) / 3.0f;
|
||||
if (renderingRegionDistance != oldRenderingRegionDistance)
|
||||
{
|
||||
FASTLOG1F(DFLog::PartStreamingRequests, "Rendering region distance: %f", renderingRegionDistance);
|
||||
}
|
||||
}
|
||||
|
||||
// check current player location, reset stream center if needed
|
||||
if (!playerIsDead)
|
||||
{
|
||||
StreamRegion::Id playerRegionId = StreamRegion::regionContainingWorldPosition(cf.translation);
|
||||
if (centerRegion != playerRegionId)
|
||||
{
|
||||
int regionSizeSquared = StreamRegion::Id::streamGridCellSizeInStuds() * StreamRegion::Id::streamGridCellSizeInStuds();
|
||||
if (forceGC || (cf.translation - lastPlayerPosition).squaredMagnitude() >= regionSizeSquared)
|
||||
{
|
||||
FASTLOG1F(DFLog::PartStreamingRequests, "Last player pos translation squared: %.0f",
|
||||
(cf.translation - lastPlayerPosition).squaredMagnitude());
|
||||
|
||||
// recompute distance
|
||||
for (RegionList::iterator iter = streamedRegionList.begin(); iter != streamedRegionList.end(); iter++)
|
||||
{
|
||||
(*iter)->second.computeRegionDistance(playerRegionId);
|
||||
}
|
||||
|
||||
// TODO: nth_element sort?
|
||||
streamedRegionList.sort(compRegionDistance);
|
||||
numSorted = streamedRegionList.size();
|
||||
lastPlayerPosition = cf.translation;
|
||||
tryGC = true;
|
||||
|
||||
centerRegion = playerRegionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingGC())
|
||||
{
|
||||
tryGC = true;
|
||||
}
|
||||
|
||||
DataModel::scoped_write_request request(replicator.get());
|
||||
|
||||
if (tryGC)
|
||||
{
|
||||
if (clientRep->needGC())
|
||||
{
|
||||
evaluateNumRegionToGC();
|
||||
if (numRegionToGC > 0)
|
||||
{
|
||||
pendingRemovalPartInstances.clear();
|
||||
// if GC is needed, check to see if new regions arrived since last sort, GC should always use a sorted list
|
||||
if ((numSorted < (int)streamedRegionList.size()))
|
||||
{
|
||||
// recompute distance for newly arrived regions using cached player position
|
||||
StreamRegion::Id lastPlayerRegionId = StreamRegion::regionContainingWorldPosition(lastPlayerPosition);
|
||||
int numNewRegions = streamedRegions.size() - numSorted;
|
||||
RegionList::iterator iter = streamedRegionList.end();
|
||||
std::advance(iter, -numNewRegions);
|
||||
for (; iter != streamedRegionList.end(); iter++)
|
||||
{
|
||||
(*iter)->second.computeRegionDistance(lastPlayerRegionId);
|
||||
}
|
||||
|
||||
streamedRegionList.sort(compRegionDistance);
|
||||
numSorted = streamedRegionList.size();
|
||||
}
|
||||
FASTLOG1(DFLog::PartStreamingRequests, "Not enough memory: %u. Garbage collecting some items.", MemoryStats::freeMemoryBytes());
|
||||
|
||||
// local delete
|
||||
Time t = Time::now<Time::Fast>() + Time::Interval(0.2 / replicator->settings().getDataGCRate());
|
||||
while (numSorted > 0 && numRegionToGC > 0)
|
||||
{
|
||||
StreamRegion::Id regionId = streamedRegionList.front()->second.id;
|
||||
StreamRegion::Id characterRegionId = StreamRegion::regionContainingWorldPosition(cf.translation);
|
||||
int regionDistance = StreamRegion::Id::getRegionLongestAxisDistance(regionId, characterRegionId);
|
||||
|
||||
if (!forceGC && (!clientRep->needGC() || regionDistance <= DFInt::PartStreamingGCMinRegionLength))
|
||||
{
|
||||
numRegionToGC = 0;
|
||||
tryGC = false;
|
||||
FASTLOG1(DFLog::PartStreamingRequests, "GC Job completed. Memory free: %u", MemoryStats::freeMemoryBytes());
|
||||
break;
|
||||
}
|
||||
|
||||
if (regionDistance < gcRegionDistance)
|
||||
{
|
||||
gcRegionDistance = regionDistance - 1;
|
||||
AYAASSERT(gcRegionDistance > 0);
|
||||
}
|
||||
|
||||
// remove region from containers
|
||||
streamedRegionList.pop_front();
|
||||
streamedRegions.erase(regionId);
|
||||
numSorted--;
|
||||
numRegionToGC--;
|
||||
|
||||
// TODO: batch regions into 1 item
|
||||
RegionRemovalItem* regionRemoveItem = new RegionRemovalItem(replicator.get(), regionId);
|
||||
|
||||
gcRegion(regionId, regionRemoveItem);
|
||||
|
||||
clientRep->pendingItems.push_back(regionRemoveItem);
|
||||
|
||||
// break if over time budget
|
||||
if (Time::now<Time::Fast>() > t)
|
||||
{
|
||||
if (forceGC)
|
||||
{
|
||||
// if we are in forced GC mode, we only break if memory is above critical level
|
||||
clientRep->updateMemoryStats();
|
||||
if (clientRep->getMemoryLevel() >= Aya::MemoryStats::MEMORYLEVEL_LIMITED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingRemovalPartInstances.size() > 0)
|
||||
{
|
||||
CPUPROFILER_START(NetworkProfiler::PROFILER_jointRemoval);
|
||||
// gc invalid joints
|
||||
ClientReplicator* cRep = static_cast<ClientReplicator*>(replicator.get());
|
||||
if (JointsService* jointsService = ServiceProvider::find<JointsService>(cRep))
|
||||
{
|
||||
jointsService->visitDescendants(
|
||||
boost::bind(&ClientReplicator::streamOutAutoJointHelper, cRep, pendingRemovalPartInstances, _1));
|
||||
}
|
||||
CPUPROFILER_STEP(NetworkProfiler::PROFILER_jointRemoval);
|
||||
for (std::vector<shared_ptr<PartInstance>>::iterator iter = pendingRemovalPartInstances.begin();
|
||||
iter != pendingRemovalPartInstances.end(); iter++)
|
||||
{
|
||||
shared_ptr<PartInstance> partInstance = *iter;
|
||||
{
|
||||
if (partInstance)
|
||||
{
|
||||
Aya::ScopedAssign<Instance*> assign(clientRep->removingInstance, partInstance.get());
|
||||
partInstance->setParent(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if gc is not needed, gradually increase the gc radius
|
||||
updateGcRegionDistance();
|
||||
}
|
||||
}
|
||||
|
||||
setMaxSimulationRadius((float)(gcRegionDistance - 2.5f) * StreamRegion::Id::streamGridCellSizeInStuds());
|
||||
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::insertRegion(const StreamRegion::Id& id)
|
||||
{
|
||||
std::pair<RegionsMap::iterator, bool> result = streamedRegions.insert(std::make_pair(id, RegionInfo(id)));
|
||||
if (result.second)
|
||||
{
|
||||
// new region
|
||||
streamedRegionList.push_back(result.first);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::setMaxSimulationRadius(float radius)
|
||||
{
|
||||
if (Player* player = replicator->findTargetPlayer())
|
||||
{
|
||||
if (player->getMaxSimulationRadius() > radius)
|
||||
player->setMaxSimulationRadius(radius);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::coarsePrimitiveMovement(Primitive* p, const UpdateInfo& info)
|
||||
{
|
||||
|
||||
StreamRegion::IdExtents oldRegionExtents, newRegionExtents;
|
||||
if (!StreamRegion::coarseMovementCausesStreamRegionChange(info, &oldRegionExtents, &newRegionExtents))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<PartInstance> instance = shared_from(PartInstance::fromPrimitive(p));
|
||||
if (!newRegionExtents.intersectsContainer(streamedRegions))
|
||||
{
|
||||
// Mark the region as streamed so it can be GC'ed when needed
|
||||
insertRegion(StreamRegion::regionContainingWorldPosition(instance->getCoordinateFrame().translation));
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::gcRegion(const StreamRegion::Id& regionId, RegionRemovalItem* removeItem)
|
||||
{
|
||||
DenseHashSet<Primitive*> found(NULL);
|
||||
spatialHash->getPrimitivesOverlapping(StreamRegion::extentsFromRegionId(regionId), found);
|
||||
|
||||
for (DenseHashSet<Primitive*>::const_iterator iter = found.begin(); iter != found.end(); ++iter)
|
||||
{
|
||||
// This assumes that the streamed regions set has already been updated
|
||||
StreamRegion::IdExtents regionExtents =
|
||||
StreamRegion::regionExtentsFromContactManagerLevelAndExtents((*iter)->getSpatialNodeLevel(), (*iter)->getOldSpatialExtents());
|
||||
|
||||
PartInstance* part = PartInstance::fromPrimitive(*iter);
|
||||
if (!regionExtents.intersectsContainer(streamedRegions) && !replicator->isInstanceAChildOfClientsCharacterModel(part))
|
||||
{
|
||||
gcPartInstance(part, removeItem);
|
||||
}
|
||||
}
|
||||
|
||||
OneQuarterClusterChunkCellIterator cellIterator;
|
||||
cellIterator.setToStartOfStreamRegion(regionId);
|
||||
while (cellIterator.size())
|
||||
{
|
||||
Vector3int16 cellPos;
|
||||
cellIterator.pop(&cellPos);
|
||||
replicator->fastDynamicCast<ClientReplicator>()->streamOutTerrain(cellPos);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::gcPartInstance(PartInstance* part, RegionRemovalItem* removeItem)
|
||||
{
|
||||
AYAASSERT_SLOW(!replicator->isInstanceAChildOfClientsCharacterModel(part));
|
||||
|
||||
pendingRemovalPartInstances.push_back(shared_from(part));
|
||||
|
||||
if (removeItem->addInstance(shared_from(part)))
|
||||
{
|
||||
part->visitDescendants(boost::bind(&RegionRemovalItem::addInstance, removeItem, _1));
|
||||
replicator->fastDynamicCast<ClientReplicator>()->streamOutInstance(part, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::render3dAdorn(Adorn* adorn)
|
||||
{
|
||||
for (RegionList::reverse_iterator iter = streamedRegionList.rbegin(); iter != streamedRegionList.rend(); iter++)
|
||||
{
|
||||
Extents ext = StreamRegion::extentsFromRegionId((*iter)->second.id);
|
||||
DrawAdorn::outlineBox(adorn, AABox(ext.min(), ext.max()), Color4(Color3::red(), 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientReplicator::GCJob::updateMaxRegionDistance()
|
||||
{
|
||||
if (renderingDistanceUpdateTimer.delta().seconds() > kRenderingDistanceUpdateInterval)
|
||||
{
|
||||
renderingDistanceUpdateTimer.reset();
|
||||
short newMaxRegionDistance = (renderingRegionDistance < gcRegionDistance ? renderingRegionDistance : gcRegionDistance);
|
||||
if (maxRegionDistance != newMaxRegionDistance)
|
||||
{
|
||||
maxRegionDistance = newMaxRegionDistance;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::updateGcRegionDistance()
|
||||
{
|
||||
if (maxRegionExpansionTimer.delta().seconds() > kMaxRegionExpansionTimerLimit)
|
||||
{
|
||||
maxRegionExpansionTimer.reset();
|
||||
if (gcRegionDistance < 0x7FFF)
|
||||
gcRegionDistance++;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientReplicator::GCJob::notifyServerGcingInstanceAndDescendants(shared_ptr<Instance> instance)
|
||||
{
|
||||
Guid::Data data;
|
||||
instance->getGuid().extract(data);
|
||||
InstanceRemovalItem* instanceRemovalItem = new InstanceRemovalItem(replicator.get(), data);
|
||||
ClientReplicator* clientRep = replicator->fastDynamicCast<ClientReplicator>();
|
||||
clientRep->pendingItems.push_back(instanceRemovalItem);
|
||||
|
||||
// important: use visit children (_not_ visitDescendants) because this function
|
||||
// is recurring down one step at a time. If visitDescendants is called we can queue
|
||||
// multiple removal items for grandchildren and great-grandchildren.
|
||||
instance->visitChildren(boost::bind(&ClientReplicator::GCJob::notifyServerGcingInstanceAndDescendants, this, _1));
|
||||
}
|
||||
125
engine/network/src/Replicator.GCJob.hpp
Normal file
125
engine/network/src/Replicator.GCJob.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "ClientReplicator.hpp"
|
||||
#include "Util.hpp"
|
||||
#include "World/ContactManagerSpatialHash.hpp"
|
||||
#include "Utility/StreamRegion.hpp"
|
||||
|
||||
#include "Base/IAdornable.hpp"
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
static const float kMaxRegionExpansionTimerLimit = 10.f;
|
||||
static const float kRenderingDistanceUpdateInterval = 1.f;
|
||||
struct RegionInfo
|
||||
{
|
||||
float distance;
|
||||
int streamDistance;
|
||||
StreamRegion::Id id;
|
||||
|
||||
explicit RegionInfo(StreamRegion::Id id)
|
||||
: id(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const RegionInfo& other) const
|
||||
{
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
void computeRegionDistance(const StreamRegion::Id& focus)
|
||||
{
|
||||
streamDistance = StreamRegion::Id::getRegionLongestAxisDistance(id, focus);
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<StreamRegion::Id, RegionInfo> RegionsMap;
|
||||
typedef std::list<RegionsMap::iterator> RegionList;
|
||||
|
||||
class ClientReplicator::GCJob
|
||||
: public ReplicatorJob
|
||||
, public ContactManagerSpatialHash::CoarseMovementCallback
|
||||
{
|
||||
ContactManagerSpatialHash* spatialHash;
|
||||
RegionsMap streamedRegions;
|
||||
RegionList streamedRegionList;
|
||||
|
||||
Vector3 lastPlayerPosition;
|
||||
int numSorted;
|
||||
int numRegionToGC;
|
||||
float* renderingDistance;
|
||||
float renderingRegionDistance;
|
||||
short gcRegionDistance;
|
||||
short maxRegionDistance;
|
||||
StreamRegion::Id centerRegion;
|
||||
|
||||
Aya::Timer<Aya::Time::Fast> maxRegionExpansionTimer;
|
||||
Aya::Timer<Aya::Time::Fast> renderingDistanceUpdateTimer;
|
||||
|
||||
std::vector<shared_ptr<PartInstance>> pendingRemovalPartInstances;
|
||||
|
||||
class RegionRemovalItem;
|
||||
class InstanceRemovalItem;
|
||||
|
||||
public:
|
||||
GCJob(Replicator& replicator);
|
||||
virtual ~GCJob();
|
||||
void insertRegion(const StreamRegion::Id& id);
|
||||
|
||||
// implement CoarseMovementCallback
|
||||
virtual void coarsePrimitiveMovement(Primitive* p, const UpdateInfo& info);
|
||||
|
||||
void render3dAdorn(Adorn* adorn);
|
||||
bool pendingGC()
|
||||
{
|
||||
return (numRegionToGC > 0);
|
||||
}
|
||||
void evaluateNumRegionToGC();
|
||||
|
||||
short getMaxRegionDistance()
|
||||
{
|
||||
return maxRegionDistance;
|
||||
}
|
||||
bool updateMaxRegionDistance();
|
||||
void updateGcRegionDistance();
|
||||
void notifyServerGcingInstanceAndDescendants(shared_ptr<Instance> instance);
|
||||
|
||||
void unregisterCoarseMovementCallback();
|
||||
|
||||
// debug
|
||||
int getNumRegionsToGC()
|
||||
{
|
||||
return numRegionToGC;
|
||||
}
|
||||
short getGCDistance()
|
||||
{
|
||||
return gcRegionDistance;
|
||||
}
|
||||
int getNumRegions()
|
||||
{
|
||||
return streamedRegionList.size();
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, replicator->settings().getDataGCRate());
|
||||
}
|
||||
virtual Error error(const Stats& stats);
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats);
|
||||
|
||||
void gcRegion(const StreamRegion::Id& regionId, RegionRemovalItem* removeItem);
|
||||
void gcPartInstance(PartInstance* partInstance, RegionRemovalItem* removeItem);
|
||||
|
||||
void setMaxSimulationRadius(float radius);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
83
engine/network/src/Replicator.ItemSender.cpp
Normal file
83
engine/network/src/Replicator.ItemSender.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "Replicator.ItemSender.hpp"
|
||||
|
||||
#include "ConcurrentRakPeer.hpp"
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
void Replicator::ItemSender::openPacket()
|
||||
{
|
||||
if (!bitStream)
|
||||
{
|
||||
bitStream.reset(new RakNet::BitStream());
|
||||
*bitStream << (unsigned char)ID_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
void Replicator::ItemSender::closePacket()
|
||||
{
|
||||
if (bitStream)
|
||||
{
|
||||
const int bytes = bitStream->GetNumberOfBytesUsed();
|
||||
if (bytes > 0)
|
||||
{
|
||||
// Write the "end" id (non-compact)
|
||||
Item::writeItemType(*bitStream, Item::ItemTypeEnd);
|
||||
|
||||
rakPeer->Send(bitStream, packetPriority, DATAMODEL_RELIABILITY, DATA_CHANNEL, replicator.remotePlayerId, false);
|
||||
replicator.replicatorStats.dataPacketsSent.sample();
|
||||
replicator.replicatorStats.dataPacketsSentSize.sample(bytes);
|
||||
}
|
||||
bitStream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Replicator::ItemSender::ItemSender(Replicator& replicator, ConcurrentRakPeer* rakPeer)
|
||||
: replicator(replicator)
|
||||
, rakPeer(rakPeer)
|
||||
, maxStreamSize(replicator.getAdjustedMtuSize()) // guesstimate
|
||||
, sentItems(false)
|
||||
, packetPriority(replicator.settings().getDataSendPriority())
|
||||
{
|
||||
}
|
||||
|
||||
Replicator::ItemSender::~ItemSender()
|
||||
{
|
||||
closePacket();
|
||||
}
|
||||
|
||||
Replicator::ItemSender::SendStatus Replicator::ItemSender::send(Item& item)
|
||||
{
|
||||
|
||||
if (bitStream && bitStream->GetNumberOfBytesUsed() >= static_cast<int>(maxStreamSize))
|
||||
return SEND_BITSTREAM_FULL;
|
||||
|
||||
openPacket();
|
||||
|
||||
if (!item.write(*bitStream))
|
||||
return SEND_BITSTREAM_FULL;
|
||||
|
||||
sentItems = true;
|
||||
|
||||
return SEND_OK;
|
||||
}
|
||||
|
||||
int Replicator::ItemSender::getNumberOfBytesUsed() const
|
||||
{
|
||||
if (bitStream)
|
||||
return bitStream->GetNumberOfBytesUsed();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
45
engine/network/src/Replicator.ItemSender.hpp
Normal file
45
engine/network/src/Replicator.ItemSender.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class ConcurrentRakPeer;
|
||||
class Item;
|
||||
|
||||
class Replicator::ItemSender : boost::noncopyable
|
||||
{
|
||||
Replicator& replicator;
|
||||
ConcurrentRakPeer* rakPeer;
|
||||
shared_ptr<RakNet::BitStream> bitStream;
|
||||
PacketPriority packetPriority;
|
||||
|
||||
void openPacket();
|
||||
void closePacket();
|
||||
const unsigned int maxStreamSize;
|
||||
|
||||
public:
|
||||
bool sentItems;
|
||||
ItemSender(Replicator& replicator, ConcurrentRakPeer* rakPeer);
|
||||
~ItemSender();
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SEND_BITSTREAM_FULL = 0,
|
||||
SEND_OK
|
||||
} SendStatus;
|
||||
|
||||
SendStatus send(Item& item);
|
||||
int getNumberOfBytesUsed() const;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
239
engine/network/src/Replicator.JoinDataItem.cpp
Normal file
239
engine/network/src/Replicator.JoinDataItem.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "Replicator.JoinDataItem.hpp"
|
||||
#include "Replicator.NewInstanceItem.hpp"
|
||||
|
||||
SYNCHRONIZED_FASTFLAGVARIABLE(NetworkAlignJoinData, true) // 223
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
DeserializedJoinDataItem::DeserializedJoinDataItem()
|
||||
: numInstances(0)
|
||||
{
|
||||
type = Item::ItemTypeJoinData;
|
||||
}
|
||||
|
||||
void DeserializedJoinDataItem::process(Replicator& replicator)
|
||||
{
|
||||
replicator.readJoinDataItem(this);
|
||||
}
|
||||
|
||||
bool Replicator::JoinDataItem::canUseCache(const Instance* instance)
|
||||
{
|
||||
const PartInstance* part = instance->fastDynamicCast<PartInstance>();
|
||||
if (part)
|
||||
{
|
||||
if (NetworkOwner::isClient(part->getNetworkOwner()))
|
||||
return true;
|
||||
else if (part->getSleeping())
|
||||
{
|
||||
// when a part is simulated by the server, physics changes does not trigger prop change signal,
|
||||
// therefore causing cache to not mark itself dirty. This check prevent cache usage if part is simulated
|
||||
// by the server and awake.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Script instances have a ProtectedString property that switches the replication format based on protocol version
|
||||
// We can't cache these instances between different replicators until we can have older clients
|
||||
BOOST_STATIC_ASSERT(NETWORK_PROTOCOL_VERSION_MIN < 28); // Remove the if check when min protocol version is 28
|
||||
if (instance->isA<BaseScript>() || instance->isA<ModuleScript>())
|
||||
return false;
|
||||
|
||||
// can use cache if not a part instance
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Replicator::JoinDataItem::writeInstance(const Instance* instance, RakNet::BitStream& bitStream)
|
||||
{
|
||||
// If the class is outdated, we still write the instance, because the property and event serializers will take care of the changes
|
||||
// But if the class is removed, we simply bypass it
|
||||
if (replicator.isClassRemoved(instance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DescriptorSender<Aya::Reflection::ClassDescriptor>::IdContainer idContainer = replicator.classDictionary.getId(&instance->getDescriptor());
|
||||
|
||||
// Write GUID
|
||||
replicator.serializeIdWithoutDictionary(bitStream, instance);
|
||||
|
||||
if (replicator.settings().printInstances)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, // remote player always on right
|
||||
"Replication NewInstance::write: %s:%s:%s >> %s", instance->getClassName().c_str(), instance->getGuid().readableString().c_str(),
|
||||
instance->getName().c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
|
||||
// Write ClassName, ok to use dictionary here because classDictionary entries are already sent to the client
|
||||
replicator.classDictionary.send(bitStream, idContainer.id);
|
||||
|
||||
// Write ownership flag
|
||||
bool deleteOnDisconnect = replicator.remoteDeleteOnDisconnect(instance);
|
||||
bitStream << deleteOnDisconnect;
|
||||
|
||||
// Write all properties
|
||||
replicator.writeProperties(instance, bitStream, PropertyCacheType_All, false /*useDictionary*/);
|
||||
|
||||
// Write the Parent property
|
||||
Reflection::ConstProperty property(Instance::propParent, instance);
|
||||
replicator.serializePropertyValue(property, bitStream, false /*useDictionary*/);
|
||||
|
||||
// This maximizes the compression ratio and improves instance packet cache update performance
|
||||
bitStream.AlignWriteToByteBoundary();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
size_t Replicator::JoinDataItem::writeInstances(RakNet::BitStream& bitStream)
|
||||
{
|
||||
// preallocate memory for bitstream using estimated size
|
||||
RakNet::BitStream dataBitstream(instances.size() * 256);
|
||||
|
||||
unsigned int numWritten = 0;
|
||||
while (!instances.empty() && (0 == maxInstancesToWrite || numWritten < maxInstancesToWrite))
|
||||
{
|
||||
const Instance* instance = instances.front().get();
|
||||
if (!replicator.removeFromPendingNewInstances(instance))
|
||||
{
|
||||
instances.pop_front();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool instanceWritten;
|
||||
if (replicator.instancePacketCache && canUseCache(instance)) // use cache
|
||||
{
|
||||
unsigned int startBit = dataBitstream.GetWriteOffset();
|
||||
|
||||
// try fetch from cache.
|
||||
if (!replicator.instancePacketCache->fetchIfUpToDate(instance, dataBitstream, true))
|
||||
{
|
||||
// instance dirty or cached bitstream was not set
|
||||
instanceWritten = writeInstance(instance, dataBitstream);
|
||||
dataBitstream.SetReadOffset(startBit);
|
||||
|
||||
// update the cache
|
||||
replicator.instancePacketCache->update(
|
||||
instance, dataBitstream, dataBitstream.GetNumberOfBitsUsed() - startBit, true /*isInitalJoinData*/);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fetched from cache
|
||||
instanceWritten = true;
|
||||
|
||||
if (replicator.settings().printInstances)
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, // remote player always on right
|
||||
"Replication NewInstance::write from cache: %s:%s:%s >> %s, %d bits", instance->getClassName().c_str(),
|
||||
instance->getGuid().readableString().c_str(), instance->getName().c_str(),
|
||||
RakNetAddressToString(replicator.remotePlayerId).c_str(), dataBitstream.GetWriteOffset() - startBit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceWritten = writeInstance(instance, dataBitstream);
|
||||
}
|
||||
|
||||
if (instanceWritten)
|
||||
{
|
||||
++numWritten;
|
||||
}
|
||||
instances.pop_front();
|
||||
|
||||
if (sendBytesPerStep > 0)
|
||||
{
|
||||
// estimate data size after compression
|
||||
if (dataBitstream.GetNumberOfBytesUsed() >= (sendBytesPerStep * ESTIMATED_COMPRESSION_RATIO))
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (instanceWritten)
|
||||
{
|
||||
break; // 1 instance at a time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This makes sure we're compressing to a byte-aligned stream to maximize writing performance
|
||||
bitStream.AlignWriteToByteBoundary();
|
||||
|
||||
bitStream << numWritten;
|
||||
|
||||
// compress the data and write to bitstream
|
||||
if (numWritten > 0)
|
||||
Replicator::compressBitStream(dataBitstream, bitStream, DFInt::JoinDataCompressionLevel);
|
||||
|
||||
instancesWrittenOverLifetime += numWritten;
|
||||
FASTLOG2(DFLog::NetworkJoin, "JoinDataItem(0x%p) Finished writing %u instances", this, numWritten);
|
||||
|
||||
return numWritten;
|
||||
}
|
||||
|
||||
void Replicator::JoinDataItem::addInstance(shared_ptr<const Instance> instance)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(replicator.pendingInstancesMutex);
|
||||
replicator.pendingNewInstances.insert(instance.get());
|
||||
|
||||
instances.push_back(instance);
|
||||
|
||||
// create an entry in new instance bitstream cache
|
||||
if (replicator.instancePacketCache && !Replicator::isTopContainer(instance.get()))
|
||||
replicator.instancePacketCache->insert(instance.get());
|
||||
}
|
||||
|
||||
|
||||
bool Replicator::JoinDataItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
AYAASSERT(sendBytesPerStep > 0);
|
||||
|
||||
Timer<Time::Fast> writeTimer;
|
||||
++timesWriteCalled;
|
||||
|
||||
writeItemType(bitStream, ItemTypeJoinData);
|
||||
writeInstances(bitStream);
|
||||
|
||||
if (DFInt::JoinDataBonus)
|
||||
writeBonus(bitStream, DFInt::JoinDataBonus);
|
||||
|
||||
FASTLOG1F(DFLog::NetworkJoin, "\tTime: %f ms", writeTimer.delta().msec());
|
||||
|
||||
return instances.empty();
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::JoinDataItem::read(Replicator& replicator, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
shared_ptr<DeserializedJoinDataItem> deserializedData(new DeserializedJoinDataItem());
|
||||
|
||||
inBitstream.AlignReadToByteBoundary();
|
||||
|
||||
unsigned int count;
|
||||
inBitstream >> count;
|
||||
|
||||
if (count == 0)
|
||||
return shared_ptr<DeserializedJoinDataItem>();
|
||||
|
||||
RakNet::BitStream bitstream;
|
||||
Replicator::decompressBitStream(inBitstream, bitstream);
|
||||
|
||||
int numInstances = 0;
|
||||
deserializedData->instanceInfos.resize(count);
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
if (!NewInstanceItem::read(replicator, bitstream, true, deserializedData->instanceInfos[numInstances]))
|
||||
deserializedData->instanceInfos[numInstances].reset();
|
||||
else
|
||||
numInstances++;
|
||||
|
||||
bitstream.AlignReadToByteBoundary();
|
||||
}
|
||||
|
||||
deserializedData->numInstances = numInstances;
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
111
engine/network/src/Replicator.JoinDataItem.hpp
Normal file
111
engine/network/src/Replicator.JoinDataItem.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
#include "NetworkPacketCache.hpp"
|
||||
|
||||
#include "NetworkOwner.hpp"
|
||||
|
||||
#include "Util.hpp"
|
||||
#include "Compressor.hpp"
|
||||
#include "Script/script.hpp"
|
||||
#include "Script/ModuleScript.hpp"
|
||||
#include "Replicator.NewInstanceItem.hpp"
|
||||
|
||||
#include "time.hpp"
|
||||
|
||||
#include "Utility/VarInt.hpp"
|
||||
|
||||
|
||||
#define ESTIMATED_COMPRESSION_RATIO 5.0f
|
||||
|
||||
DYNAMIC_LOGGROUP(NetworkJoin)
|
||||
DYNAMIC_FASTINT(JoinDataCompressionLevel)
|
||||
|
||||
DYNAMIC_FASTINT(JoinDataBonus)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class DeserializedJoinDataItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
int numInstances;
|
||||
std::vector<DeserializedNewInstanceItem> instanceInfos;
|
||||
|
||||
DeserializedJoinDataItem();
|
||||
~DeserializedJoinDataItem() {}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
class Replicator::JoinDataItem : public Item
|
||||
{
|
||||
std::list<shared_ptr<const Instance>> instances;
|
||||
int sendBytesPerStep;
|
||||
unsigned timesWriteCalled;
|
||||
size_t instancesWrittenOverLifetime;
|
||||
size_t maxInstancesToWrite;
|
||||
|
||||
bool canUseCache(const Instance* instance);
|
||||
|
||||
protected:
|
||||
bool writeInstance(const Instance* instance, RakNet::BitStream& bitStream);
|
||||
|
||||
size_t writeInstances(RakNet::BitStream& bitStream);
|
||||
|
||||
void writeBonus(RakNet::BitStream& bitStream, unsigned int bytes)
|
||||
{
|
||||
unsigned char buf[64] = {};
|
||||
|
||||
while (bytes >= sizeof(buf))
|
||||
{
|
||||
bitStream.WriteAlignedBytes(buf, sizeof(buf));
|
||||
bytes -= sizeof(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public:
|
||||
JoinDataItem(Replicator* replicator)
|
||||
: Item(*replicator)
|
||||
, sendBytesPerStep(-1)
|
||||
, timesWriteCalled(0)
|
||||
, instancesWrittenOverLifetime(0)
|
||||
, maxInstancesToWrite(0)
|
||||
{
|
||||
FASTLOG1(DFLog::NetworkJoin, "Created JoinDataItem(0x%p)", this);
|
||||
}
|
||||
virtual ~JoinDataItem()
|
||||
{
|
||||
FASTLOG3(
|
||||
DFLog::NetworkJoin, "~JoinDataItem(0x%p) handled %u instances over %u invocations", this, instancesWrittenOverLifetime, timesWriteCalled);
|
||||
}
|
||||
void setMaxInstancesToWrite(size_t num)
|
||||
{
|
||||
maxInstancesToWrite = num;
|
||||
}
|
||||
bool empty() const
|
||||
{
|
||||
return instances.empty();
|
||||
}
|
||||
size_t size() const
|
||||
{
|
||||
return instances.size();
|
||||
}
|
||||
void setBytesPerStep(int numBytes)
|
||||
{
|
||||
sendBytesPerStep = numBytes;
|
||||
}
|
||||
void addInstance(shared_ptr<const Instance> instance);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
60
engine/network/src/Replicator.MarkerItem.cpp
Normal file
60
engine/network/src/Replicator.MarkerItem.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "Replicator.MarkerItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "Util.hpp"
|
||||
#include "Replicator.StreamJob.hpp"
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
#include <string>
|
||||
|
||||
DYNAMIC_LOGGROUP(NetworkJoin)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
Replicator::MarkerItem::MarkerItem(Replicator* replicator, int id)
|
||||
: Item(*replicator)
|
||||
, id(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool Replicator::MarkerItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
if (!replicator.isInitialDataSent())
|
||||
return false;
|
||||
|
||||
writeItemType(bitStream, ItemTypeMarker);
|
||||
|
||||
bitStream << id;
|
||||
if (replicator.settings().printInstances)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(
|
||||
Aya::MESSAGE_SENSITIVE, "Replication: Sending marker %d to %s", id, RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
}
|
||||
|
||||
replicator.onSentMarker(id);
|
||||
FASTLOG1F(DFLog::NetworkJoin, "MarkerItem %ld sent", id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::MarkerItem::read(Replicator& replicator, RakNet::BitStream& bitStream)
|
||||
{
|
||||
shared_ptr<DeserializedMarkerItem> deserializedData(new DeserializedMarkerItem());
|
||||
|
||||
bitStream >> deserializedData->id;
|
||||
|
||||
if (replicator.settings().printInstances)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(
|
||||
Aya::MESSAGE_SENSITIVE, "Received marker %d from %s", deserializedData->id, RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
}
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
40
engine/network/src/Replicator.MarkerItem.hpp
Normal file
40
engine/network/src/Replicator.MarkerItem.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class DeserializedMarkerItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
int id;
|
||||
|
||||
DeserializedMarkerItem()
|
||||
{
|
||||
type = Item::ItemTypeMarker;
|
||||
}
|
||||
~DeserializedMarkerItem() {}
|
||||
/*implement*/ void process(Replicator& replicator)
|
||||
{
|
||||
replicator.readMarkerItem(this);
|
||||
}
|
||||
};
|
||||
|
||||
class Replicator::MarkerItem : public Item
|
||||
{
|
||||
int id;
|
||||
|
||||
public:
|
||||
MarkerItem(Replicator* replicator, int id);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
236
engine/network/src/Replicator.NewInstanceItem.cpp
Normal file
236
engine/network/src/Replicator.NewInstanceItem.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
#include "Replicator.NewInstanceItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
#include "Security/FuzzyTokens.hpp"
|
||||
#include "Players.hpp"
|
||||
#include "FastLog.hpp"
|
||||
#include "NetworkPacketCache.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
|
||||
#include "RakNet/RakNetTime.hpp"
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
DeserializedNewInstanceItem::DeserializedNewInstanceItem()
|
||||
: classDescriptor(NULL)
|
||||
{
|
||||
type = Item::ItemTypeNew;
|
||||
}
|
||||
|
||||
void DeserializedNewInstanceItem::process(Replicator& replicator)
|
||||
{
|
||||
replicator.readInstanceNewItem(this, false);
|
||||
}
|
||||
|
||||
Replicator::NewInstanceItem::NewInstanceItem(Replicator* replicator, shared_ptr<const Instance> instance)
|
||||
: PooledItem(*replicator)
|
||||
, instance(instance)
|
||||
, useStoredParentGuid(false)
|
||||
{
|
||||
// check if the flag is on, and that this isn't the special case for
|
||||
// initial player sync.
|
||||
// HACK -- Currently the client replicator creates a NewInstanceItem for
|
||||
// its local player before it has received the correct GUID for the
|
||||
// Players service from the server. So, bypass the parent desync bug
|
||||
// fix if the new instance is a Player under Players.
|
||||
useStoredParentGuid =
|
||||
!(Instance::fastSharedDynamicCast<const Player>(instance) && Instance::fastDynamicCast<const Players>(instance->getParent()));
|
||||
|
||||
if (!replicator->isLegalSendInstance(instance.get()))
|
||||
return;
|
||||
|
||||
Aya::mutex::scoped_lock lock(replicator->pendingInstancesMutex);
|
||||
replicator->pendingNewInstances.insert(instance.get());
|
||||
|
||||
// create an entry in new instance bitstream cache
|
||||
if (replicator->instancePacketCache && !Replicator::isTopContainer(instance.get()))
|
||||
replicator->instancePacketCache->insert(instance.get());
|
||||
|
||||
if (useStoredParentGuid)
|
||||
{
|
||||
instance->getParent()->getGuid().extract(parentIdAtItemCreation);
|
||||
}
|
||||
}
|
||||
|
||||
bool Replicator::NewInstanceItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
// If the class is outdated, we still write the instance, because the property and event serializers will take care of the changes
|
||||
// But if the class is removed, we simply bypass it
|
||||
if (replicator.isClassRemoved(instance.get()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
DescriptorSender<Aya::Reflection::ClassDescriptor>::IdContainer idContainer = replicator.classDictionary.getId(&instance->getDescriptor());
|
||||
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
if (!replicator.removeFromPendingNewInstances(instance.get()))
|
||||
return true;
|
||||
|
||||
writeItemType(bitStream, ItemTypeNew);
|
||||
|
||||
// Write the GUID
|
||||
replicator.serializeId(bitStream, instance.get());
|
||||
|
||||
if (replicator.settings().printInstances)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, // remote player always on right
|
||||
"Replication NewInstance::write: %s:%s:%s >> %s", instance->getClassName().c_str(), instance->getGuid().readableString().c_str(),
|
||||
instance->getName().c_str(), RakNetAddressToString(replicator.remotePlayerId).c_str());
|
||||
}
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsSent(ReplicatorStats::PACKET_TYPE_InstanceNew);
|
||||
replicator.replicatorStats.samplePacketsSent(ReplicatorStats::PACKET_TYPE_InstanceNew, bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
|
||||
// Write ClassName
|
||||
replicator.classDictionary.send(bitStream, idContainer.id);
|
||||
|
||||
// Write ownership flag
|
||||
bool deleteOnDisconnect = replicator.remoteDeleteOnDisconnect(instance.get());
|
||||
bitStream << deleteOnDisconnect;
|
||||
|
||||
// write non cacheable properties first
|
||||
replicator.writeProperties(instance.get(), bitStream, Replicator::PropertyCacheType_NonCacheable, true);
|
||||
|
||||
if (replicator.instancePacketCache) // use cache
|
||||
{
|
||||
unsigned int startBit = bitStream.GetWriteOffset();
|
||||
|
||||
// try fetch from cache.
|
||||
if (!replicator.instancePacketCache->fetchIfUpToDate(instance.get(), bitStream, false))
|
||||
{
|
||||
// instance dirty or cached bitstream was not set
|
||||
|
||||
replicator.writeProperties(instance.get(), bitStream, Replicator::PropertyCacheType_Cacheable, true);
|
||||
|
||||
bitStream.SetReadOffset(startBit);
|
||||
|
||||
// update the cache
|
||||
replicator.instancePacketCache->update(instance.get(), bitStream, bitStream.GetNumberOfBitsUsed() - startBit, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (replicator.settings().printBits)
|
||||
{
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, " write from cache %d bits", bitStream.GetWriteOffset() - startBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
else // not using cache
|
||||
replicator.writeProperties(instance.get(), bitStream, Replicator::PropertyCacheType_Cacheable, true);
|
||||
|
||||
// Write the Parent property
|
||||
if (useStoredParentGuid)
|
||||
{
|
||||
replicator.serializeId(bitStream, parentIdAtItemCreation);
|
||||
}
|
||||
else
|
||||
{
|
||||
Reflection::ConstProperty property(Instance::propParent, instance.get());
|
||||
replicator.serializePropertyValue(property, bitStream, true /*useDictionary*/);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Replicator::NewInstanceItem::read(
|
||||
Replicator& replicator, RakNet::BitStream& bitStream, bool isJoinData, DeserializedNewInstanceItem& deserializedItem)
|
||||
{
|
||||
if (isJoinData)
|
||||
replicator.deserializeIdWithoutDictionary(bitStream, deserializedItem.id);
|
||||
else
|
||||
replicator.deserializeId(bitStream, deserializedItem.id);
|
||||
|
||||
// Get the class and construct the object
|
||||
unsigned int classId = replicator.classDictionary.receive(bitStream, deserializedItem.classDescriptor,
|
||||
false /*we don't do version check for class here, if the properties or events of the class are changed, they will be handled later*/);
|
||||
|
||||
if (replicator.ProcessOutdatedInstance(bitStream, isJoinData, deserializedItem.id, deserializedItem.classDescriptor, classId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool newInstance = false;
|
||||
shared_ptr<Instance> i;
|
||||
if (replicator.guidRegistry->lookupByGuid(deserializedItem.id, i))
|
||||
{
|
||||
// We got back an object we've already seen
|
||||
if (i == 0)
|
||||
throw Aya::runtime_error("readInstanceNew got a null object (guid %s)", deserializedItem.id.readableString(32).c_str());
|
||||
|
||||
deserializedItem.instance = i;
|
||||
|
||||
if (deserializedItem.instance->getDescriptor() != *deserializedItem.classDescriptor)
|
||||
{
|
||||
std::string message = Aya::format("Replication: Bad re-binding in deserialize new instance %s-%s << %s, %s-%s",
|
||||
deserializedItem.classDescriptor->name.c_str(), deserializedItem.id.readableString().c_str(),
|
||||
RakNetAddressToString(replicator.remotePlayerId).c_str(), deserializedItem.instance->getClassName().c_str(),
|
||||
deserializedItem.id.readableString().c_str());
|
||||
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SENSITIVE, "%s", message.c_str());
|
||||
|
||||
throw Aya::runtime_error("%s", message.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deserializedItem.instance = Creatable<Instance>::createByName(deserializedItem.classDescriptor->name, Aya::ReplicationCreator);
|
||||
if (!deserializedItem.instance)
|
||||
{
|
||||
std::string message = format("Replication: Can't create object of type %s", deserializedItem.classDescriptor->name.c_str());
|
||||
Aya::StandardOut::singleton()->print(Aya::MESSAGE_ERROR, message);
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
replicator.guidRegistry->assignGuid(deserializedItem.instance.get(), deserializedItem.id);
|
||||
newInstance = true;
|
||||
}
|
||||
|
||||
bitStream >> deserializedItem.deleteOnDisconnect;
|
||||
|
||||
// Write properties directly into newly created instances since it's not in datamodel yet, otherwise write to variant array to be set later.
|
||||
if (isJoinData)
|
||||
{
|
||||
replicator.readProperties(
|
||||
bitStream, deserializedItem.instance.get(), PropertyCacheType_All, false, false, newInstance ? NULL : &deserializedItem.propValueList);
|
||||
replicator.deserializeIdWithoutDictionary(bitStream, deserializedItem.parentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
replicator.readProperties(bitStream, deserializedItem.instance.get(), PropertyCacheType_NonCacheable, true, false,
|
||||
newInstance ? NULL : &deserializedItem.propValueList);
|
||||
replicator.readProperties(bitStream, deserializedItem.instance.get(), PropertyCacheType_Cacheable, true, false,
|
||||
newInstance ? NULL : &deserializedItem.propValueList);
|
||||
replicator.deserializeId(bitStream, deserializedItem.parentId);
|
||||
}
|
||||
|
||||
replicator.guidRegistry->lookupByGuid(deserializedItem.parentId, deserializedItem.parent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::NewInstanceItem::read(Replicator& replicator, RakNet::BitStream& bitStream, bool isJoinData)
|
||||
{
|
||||
shared_ptr<DeserializedNewInstanceItem> deserializedData(new DeserializedNewInstanceItem());
|
||||
|
||||
if (!read(replicator, bitStream, isJoinData, *deserializedData))
|
||||
return shared_ptr<DeserializedNewInstanceItem>();
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
71
engine/network/src/Replicator.NewInstanceItem.hpp
Normal file
71
engine/network/src/Replicator.NewInstanceItem.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
struct PropValuePair
|
||||
{
|
||||
const Reflection::PropertyDescriptor* descriptor;
|
||||
Reflection::Variant value;
|
||||
|
||||
PropValuePair(const Reflection::PropertyDescriptor& descriptor, const Reflection::Variant& _value)
|
||||
: descriptor(&descriptor)
|
||||
, value(_value)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class DeserializedNewInstanceItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
typedef std::vector<PropValuePair> PropValuePairList;
|
||||
PropValuePairList propValueList; // for non creatable instances
|
||||
|
||||
Aya::Guid::Data id;
|
||||
Aya::Guid::Data parentId;
|
||||
const Reflection::ClassDescriptor* classDescriptor;
|
||||
|
||||
shared_ptr<Instance> instance;
|
||||
shared_ptr<Instance> parent;
|
||||
bool deleteOnDisconnect;
|
||||
|
||||
DeserializedNewInstanceItem();
|
||||
~DeserializedNewInstanceItem(){};
|
||||
|
||||
void reset()
|
||||
{
|
||||
propValueList.clear();
|
||||
instance.reset();
|
||||
parent.reset();
|
||||
id.scope.setNull();
|
||||
parentId.scope.setNull();
|
||||
classDescriptor = NULL;
|
||||
}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
class Replicator::NewInstanceItem : public PooledItem
|
||||
{
|
||||
shared_ptr<const Instance> instance;
|
||||
bool useStoredParentGuid;
|
||||
Aya::Guid::Data parentIdAtItemCreation;
|
||||
|
||||
bool deleteOnDisconnect;
|
||||
|
||||
public:
|
||||
NewInstanceItem(Replicator* replicator, shared_ptr<const Instance> instance);
|
||||
|
||||
bool write(RakNet::BitStream& bitStream);
|
||||
static bool read(Replicator& replicator, RakNet::BitStream& bitStream, bool isJoinData, DeserializedNewInstanceItem& deserializedData);
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream, bool isJoinData);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
71
engine/network/src/Replicator.PingBackItem.cpp
Normal file
71
engine/network/src/Replicator.PingBackItem.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "Replicator.PingBackItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
|
||||
#include "RakNet/RakNetTime.hpp"
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
Replicator::PingBackItem::PingBackItem(Replicator* replicator, RakNet::Time time, unsigned int extraStats)
|
||||
: PooledItem(*replicator)
|
||||
, time(time)
|
||||
, extraStats(extraStats)
|
||||
{
|
||||
}
|
||||
|
||||
bool Replicator::PingBackItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
|
||||
writeItemType(bitStream, ItemTypePingBack);
|
||||
bitStream << true;
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
bitStream << time;
|
||||
#else
|
||||
bitStream << static_cast<unsigned long long>(time);
|
||||
#endif
|
||||
|
||||
#if !defined(LOVE_ALL_ACCESS)
|
||||
unsigned int sendStats = DataModel::sendStats | DataModel::get(&replicator)->allHackFlagsOredTogether();
|
||||
#else
|
||||
unsigned int sendStats = 0;
|
||||
#endif
|
||||
bitStream << sendStats;
|
||||
|
||||
if (replicator.canUseProtocolVersion(34))
|
||||
{
|
||||
#if !defined(AYA_SERVER) && !defined(AYA_STUDIO)
|
||||
if (time & 0x20)
|
||||
{
|
||||
extraStats = ~extraStats;
|
||||
}
|
||||
|
||||
#endif
|
||||
bitStream.Write(static_cast<uint32_t>(extraStats));
|
||||
}
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsSent(ReplicatorStats::PACKET_TYPE_Ping);
|
||||
replicator.replicatorStats.samplePacketsSent(ReplicatorStats::PACKET_TYPE_Ping, bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
25
engine/network/src/Replicator.PingBackItem.hpp
Normal file
25
engine/network/src/Replicator.PingBackItem.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "RakNet/RakNetTime.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator::PingBackItem : public PooledItem
|
||||
{
|
||||
RakNet::Time time;
|
||||
unsigned int extraStats;
|
||||
|
||||
public:
|
||||
PingBackItem(Replicator* replicator, RakNet::Time time, unsigned int extraStats);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
137
engine/network/src/Replicator.PingItem.cpp
Normal file
137
engine/network/src/Replicator.PingItem.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "Replicator.PingItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
#include "ReplicatorStats.hpp"
|
||||
#include "Security/FuzzyTokens.hpp"
|
||||
#include "Security/ApiSecurity.hpp"
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
|
||||
#include "FastLog.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
|
||||
#include "RakNet/RakNetTime.hpp"
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
DeserializedPingItem::DeserializedPingItem()
|
||||
: extraStats(0)
|
||||
{
|
||||
type = Item::ItemTypePing;
|
||||
}
|
||||
|
||||
void DeserializedPingItem::process(Replicator& replicator)
|
||||
{
|
||||
replicator.readDataPingItem(this);
|
||||
}
|
||||
|
||||
Replicator::PingItem::PingItem(Replicator* replicator, RakNet::Time time, unsigned int extraStats)
|
||||
: PooledItem(*replicator)
|
||||
, time(time)
|
||||
, extraStats(extraStats)
|
||||
{
|
||||
}
|
||||
|
||||
bool Replicator::PingItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
boost::scoped_ptr<unsigned int> moreStatsCopy(new unsigned int);
|
||||
|
||||
int byteStart = bitStream.GetNumberOfBytesUsed();
|
||||
|
||||
writeItemType(bitStream, ItemTypePing);
|
||||
|
||||
unsigned int moreStats = 0;
|
||||
#if !defined(LOVE_ALL_ACCESS) && !defined(AYA_STUDIO) && !defined(AYA_PLATFORM_DURANGO)
|
||||
unsigned int sendStats =
|
||||
Tokens::simpleToken | DataModel::perfStats | DataModel::sendStats | DataModel::get(&replicator)->allHackFlagsOredTogether();
|
||||
// I'm sorry for this. The values need to be spread out in memory and thus can't be an array.
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag0);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag1);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag2);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag3);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag4);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag5);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag6);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag7);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag8);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag9);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag10);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag11);
|
||||
moreStats |= Aya::Security::getHackFlag<LINE_RAND4>(Aya::Security::hackFlag12);
|
||||
moreStats |= sendStats;
|
||||
*moreStatsCopy = moreStats;
|
||||
#endif
|
||||
bitStream << false;
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
bitStream << time;
|
||||
#else
|
||||
bitStream << static_cast<unsigned long long>(time);
|
||||
#endif
|
||||
|
||||
bitStream.Write(static_cast<uint32_t>(moreStats));
|
||||
if (replicator.canUseProtocolVersion(34))
|
||||
{
|
||||
#if !defined(AYA_SERVER) && !defined(AYA_STUDIO)
|
||||
if (time & 0x20)
|
||||
{
|
||||
extraStats = ~extraStats;
|
||||
}
|
||||
#endif
|
||||
bitStream.Write(static_cast<uint32_t>(extraStats));
|
||||
}
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsSent(ReplicatorStats::PACKET_TYPE_Ping);
|
||||
replicator.replicatorStats.samplePacketsSent(ReplicatorStats::PACKET_TYPE_Ping, bitStream.GetNumberOfBytesUsed() - byteStart);
|
||||
}
|
||||
|
||||
#if !defined(AYA_STUDIO)
|
||||
if (*moreStatsCopy != moreStats)
|
||||
{
|
||||
Aya::Tokens::apiToken.addFlagSafe(Aya::kPingItem); // can be changed to addFlagFast later.
|
||||
}
|
||||
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::PingItem::read(Replicator& replicator, RakNet::BitStream& inBitstream)
|
||||
{
|
||||
shared_ptr<DeserializedPingItem> deserializedData(new DeserializedPingItem());
|
||||
|
||||
int start = inBitstream.GetReadOffset();
|
||||
|
||||
inBitstream >> deserializedData->pingBack;
|
||||
inBitstream >> deserializedData->time;
|
||||
inBitstream >> deserializedData->sendStats;
|
||||
if (replicator.canUseProtocolVersion(34))
|
||||
{
|
||||
inBitstream >> deserializedData->extraStats;
|
||||
#ifdef AYA_SERVER
|
||||
if (deserializedData->time & 0x20) // change things up
|
||||
{
|
||||
deserializedData->extraStats = ~deserializedData->extraStats;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (replicator.settings().trackDataTypes)
|
||||
{
|
||||
replicator.replicatorStats.incrementPacketsReceived(ReplicatorStats::PACKET_TYPE_Ping);
|
||||
replicator.replicatorStats.samplePacketsReceived(ReplicatorStats::PACKET_TYPE_Ping, (inBitstream.GetReadOffset() - start) / 8);
|
||||
}
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
41
engine/network/src/Replicator.PingItem.hpp
Normal file
41
engine/network/src/Replicator.PingItem.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "RakNet/RakNetTime.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class DeserializedPingItem : public DeserializedItem
|
||||
{
|
||||
public:
|
||||
bool pingBack;
|
||||
RakNet::Time time;
|
||||
unsigned int sendStats;
|
||||
unsigned int extraStats;
|
||||
|
||||
DeserializedPingItem();
|
||||
~DeserializedPingItem() {}
|
||||
|
||||
/*implement*/ void process(Replicator& replicator);
|
||||
};
|
||||
|
||||
|
||||
class Replicator::PingItem : public PooledItem
|
||||
{
|
||||
RakNet::Time time;
|
||||
unsigned int extraStats;
|
||||
|
||||
public:
|
||||
PingItem(Replicator* replicator, RakNet::Time time, unsigned int extraStats);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
static shared_ptr<DeserializedItem> read(Replicator& replicator, RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
67
engine/network/src/Replicator.PingJob.hpp
Normal file
67
engine/network/src/Replicator.PingJob.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
// Regularly sends pings through the data pipe to measure total data round-trip time
|
||||
class Replicator::PingJob : public ReplicatorJob
|
||||
{
|
||||
public:
|
||||
static const int desiredPingHz = 2;
|
||||
static const int maxPingsPerStep = 5;
|
||||
PingJob(Replicator& replicator)
|
||||
: ReplicatorJob("Replicator DataPing", replicator, DataModelJob::DataIn)
|
||||
{
|
||||
AYAASSERT(getArbiter());
|
||||
|
||||
cyclicExecutive = true;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, 2);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
return computeStandardErrorCyclicExecutiveSleeping(stats, 2);
|
||||
}
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (replicator)
|
||||
{
|
||||
DataModel::scoped_write_request request(replicator.get());
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
float pingsToDo = updateStepsRequiredForCyclicExecutive(
|
||||
stats.timespanSinceLastStep.seconds(), (float)desiredPingHz, (float)maxPingsPerStep, (float)maxPingsPerStep);
|
||||
for (int i = 0; i < (int)pingsToDo; i++)
|
||||
{
|
||||
replicator->sendDataPing();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
replicator->sendDataPing();
|
||||
}
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
157
engine/network/src/Replicator.ProcessPacketsJob.hpp
Normal file
157
engine/network/src/Replicator.ProcessPacketsJob.hpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "Players.hpp"
|
||||
|
||||
#include "Utility/MemoryStats.hpp"
|
||||
|
||||
#include "Replicator.StreamJob.hpp"
|
||||
LOGGROUP(NetworkStepsMultipliers)
|
||||
LOGGROUP(MaxNetworkReadTimeInCS)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxWaitTimeBeforeForcePacketProcessMS, 0)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxProcessPacketsStepsPerCyclic, 5)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxProcessPacketsStepsAccumulated, 15);
|
||||
DYNAMIC_FASTINTVARIABLE(MaxProcessPacketsJobScaling, 10);
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator::ProcessPacketsJob : public ReplicatorJob
|
||||
{
|
||||
Aya::atomic<int> isAwake;
|
||||
DataModel* dm;
|
||||
|
||||
public:
|
||||
ProcessPacketsJob(Replicator& replicator)
|
||||
: ReplicatorJob("Replicator ProcessPackets", replicator, DataModelJob::DataIn)
|
||||
{
|
||||
AYAASSERT(getArbiter());
|
||||
isAwake = 1;
|
||||
dm = DataModel::get(&replicator);
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessIncoming;
|
||||
}
|
||||
|
||||
// wake and sleep are used for testing and debugging.
|
||||
void wake()
|
||||
{
|
||||
if (!isAwake.compare_and_swap(1, 0))
|
||||
Aya::TaskScheduler::singleton().reschedule(shared_from_this());
|
||||
}
|
||||
void sleep()
|
||||
{
|
||||
isAwake = false;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
if (!isAwake)
|
||||
return Aya::Time::Interval::max();
|
||||
|
||||
if (replicator)
|
||||
{
|
||||
return replicator->incomingPacketsCount() > 0 ? computeStandardSleepTime(stats, replicator->settings().getReceiveRate())
|
||||
: Time::Interval(0.1);
|
||||
}
|
||||
return Time::Interval(0.1);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats)
|
||||
{
|
||||
if (!isAwake)
|
||||
return Job::Error();
|
||||
|
||||
Error result;
|
||||
result.error = 0;
|
||||
if (replicator)
|
||||
{
|
||||
if (dm->getIsShuttingDown())
|
||||
return Job::Error();
|
||||
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
if (replicator->incomingPacketsCount())
|
||||
{
|
||||
result.error = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.error = 0.0f;
|
||||
}
|
||||
}
|
||||
else if (replicator->settings().isQueueErrorComputed)
|
||||
{
|
||||
double waitTime = replicator->incomingPacketsCountHeadWaitTimeSec(stats.timeNow);
|
||||
result.error = waitTime * replicator->settings().getReceiveRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
const int count = replicator->incomingPacketsCount();
|
||||
result.error = count * stats.timespanSinceLastStep.seconds() * replicator->settings().getReceiveRate();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (!isAwake)
|
||||
return TaskScheduler::Stepped;
|
||||
|
||||
if (replicator)
|
||||
{
|
||||
FASTLOG(FLog::NetworkStepsMultipliers, "ProcessPacketsJob running");
|
||||
DataModel::scoped_write_request request(replicator.get());
|
||||
|
||||
Time t;
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
float stepLength =
|
||||
updateStepsRequiredForCyclicExecutive(stats.timespanSinceLastStep.seconds(), replicator->settings().getReceiveRate(),
|
||||
(float)DFInt::MaxProcessPacketsStepsPerCyclic, (float)DFInt::MaxProcessPacketsStepsAccumulated);
|
||||
|
||||
int processTimeMultiplier = std::min<int>(5, replicator->incomingPacketsCount());
|
||||
stepLength = std::max<float>(1.0, stepLength);
|
||||
float processTimeScale = std::min<float>((float)DFInt::MaxProcessPacketsJobScaling, stepLength * processTimeMultiplier);
|
||||
t = Time::now<Time::Fast>() + Time::Interval((processTimeScale) * (0.5 / replicator->settings().getReceiveRate()));
|
||||
}
|
||||
else
|
||||
{
|
||||
t = Time::now<Time::Fast>() + Time::Interval(0.5 / replicator->settings().getReceiveRate());
|
||||
}
|
||||
|
||||
FASTLOG2(FLog::NetworkStepsMultipliers, "ProcessPacketsJob: StepOnce, processAllPackets: %d, packets in Queue: %d",
|
||||
(int)replicator->processAllPacketsPerStep, replicator->incomingPackets.size());
|
||||
Time maxTime = (Time::now<Time::Fast>() + Time::Interval(0.01 * FLog::MaxNetworkReadTimeInCS)); // convert centisecond to second
|
||||
while (!(dm->getIsShuttingDown()) && (replicator->processNextIncomingPacket()))
|
||||
{
|
||||
if (replicator->processAllPacketsPerStep)
|
||||
{
|
||||
// break if we exceed our maximum time here
|
||||
if (FLog::MaxNetworkReadTimeInCS > 0)
|
||||
if (Time::now<Time::Fast>() > maxTime)
|
||||
break;
|
||||
}
|
||||
/// Process ALL Packets if there is anything thats been there for longer than 200ms
|
||||
else if (Time::now<Time::Fast>() > t && !(replicator->incomingPacketsCountHeadWaitTimeSec(stats.timeNow) >
|
||||
(float)DFInt::MaxWaitTimeBeforeForcePacketProcessMS / 1000.0f))
|
||||
break;
|
||||
}
|
||||
|
||||
// post processing
|
||||
replicator->postProcessPacket();
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "Replicator.ReferencePropertyChangedItem.hpp"
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
#include "Reflection/Property.hpp"
|
||||
|
||||
#include "RakNet/BitStream.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
Replicator::ReferencePropertyChangedItem::ReferencePropertyChangedItem(
|
||||
Replicator* replicator, const shared_ptr<const Instance>& instance, const Reflection::RefPropertyDescriptor& desc)
|
||||
: PooledItem(*replicator)
|
||||
, instance(instance)
|
||||
, desc(desc)
|
||||
{
|
||||
|
||||
Instance* refInstance = DescribedBase::fastDynamicCast<Instance>(desc.getRefValue(instance.get()));
|
||||
if (refInstance != NULL)
|
||||
{
|
||||
refInstance->getGuid().extract(newValueGuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
newValueGuid.scope.setNull();
|
||||
}
|
||||
}
|
||||
|
||||
bool Replicator::ReferencePropertyChangedItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
replicator.writeChangedRefProperty(instance.get(), desc, newValueGuid, bitStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "Item.hpp"
|
||||
#include "Replicator.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Instance;
|
||||
namespace Reflection
|
||||
{
|
||||
class RefPropertyDescriptor;
|
||||
}
|
||||
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator::ReferencePropertyChangedItem : public PooledItem
|
||||
{
|
||||
const shared_ptr<const Instance> instance;
|
||||
const Reflection::RefPropertyDescriptor& desc;
|
||||
bool newValueIsNull;
|
||||
Guid::Data newValueGuid;
|
||||
|
||||
public:
|
||||
ReferencePropertyChangedItem(Replicator* replicator, const shared_ptr<const Instance>& instance, const Reflection::RefPropertyDescriptor& desc);
|
||||
|
||||
/*implement*/ virtual bool write(RakNet::BitStream& bitStream);
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
169
engine/network/src/Replicator.SendDataJob.hpp
Normal file
169
engine/network/src/Replicator.SendDataJob.hpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include "Replicator.hpp"
|
||||
#include "Players.hpp"
|
||||
#include "Utility/RbxStringTable.hpp"
|
||||
|
||||
#include "Utility/xxhash.hpp"
|
||||
|
||||
#include "Utility/ObscureValue.hpp"
|
||||
|
||||
#include "DataModel/Stats.hpp"
|
||||
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
|
||||
|
||||
|
||||
LOGGROUP(NetworkStepsMultipliers)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxDataStepsPerCyclic, 5)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxDataStepsAccumulated, 15)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxClusterSendStepsPerCyclic, 5)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxClusterSendStepsAccumulated, 15)
|
||||
DYNAMIC_FASTINTVARIABLE(MaxDataOutJobScaling, 10)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Network
|
||||
{
|
||||
|
||||
class Replicator::SendDataJob : public ReplicatorJob
|
||||
{
|
||||
const ObscureValue<float> dataSendRate;
|
||||
const PacketPriority packetPriority;
|
||||
unsigned int countdownToXxhashCheck;
|
||||
|
||||
public:
|
||||
SendDataJob(Replicator& replicator)
|
||||
: ReplicatorJob("Replicator SendData", replicator, DataModelJob::DataOut)
|
||||
, dataSendRate(replicator.settings().getDataSendRate())
|
||||
, packetPriority(replicator.settings().getDataSendPriority())
|
||||
, countdownToXxhashCheck(0)
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessOutgoing;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
// TODO: If Replicator::pendingItems is empty then sleep
|
||||
return computeStandardSleepTime(stats, dataSendRate);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats);
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (replicator)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
float stepsToDo = updateStepsRequiredForCyclicExecutive(stats.timespanSinceLastStep.seconds(),
|
||||
replicator->settings().getDataSendRate(), (float)DFInt::MaxDataStepsPerCyclic, (float)DFInt::MaxDataStepsAccumulated);
|
||||
|
||||
stepsToDo = std::max<float>(1.0, stepsToDo);
|
||||
stepsToDo *= std::min<int>(10, replicator->highPriorityPendingItems.size() + replicator->pendingItems.size() + 1) / 2.0;
|
||||
float truncatedStepsToDo = std::min<float>((float)DFInt::MaxDataOutJobScaling, stepsToDo);
|
||||
for (int i = 0; i < (int)truncatedStepsToDo; i++)
|
||||
{
|
||||
replicator->dataOutStep();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
replicator->dataOutStep();
|
||||
}
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
const char* what = e.what();
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "SendData: %s", what ? what : "empty error string");
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
class Replicator::SendClusterJob : public ReplicatorJob
|
||||
{
|
||||
ObscureValue<float> dataSendRate;
|
||||
PacketPriority packetPriority;
|
||||
|
||||
public:
|
||||
SendClusterJob(Replicator& replicator)
|
||||
: ReplicatorJob("Replicator SendCluster", replicator, DataModelJob::DataOut)
|
||||
, dataSendRate(replicator.settings().getDataSendRate())
|
||||
, packetPriority(replicator.settings().getDataSendPriority())
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
cyclicPriority = CyclicExecutiveJobPriority_Network_ProcessOutgoing;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
// TODO: If Replicator::pendingItems is empty then sleep
|
||||
return computeStandardSleepTime(stats, dataSendRate);
|
||||
}
|
||||
|
||||
virtual Error error(const Stats& stats);
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
if (replicator)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
double stepsRequired =
|
||||
updateStepsRequiredForCyclicExecutive(stats.timespanSinceLastStep.seconds(), replicator->settings().getDataSendRate(),
|
||||
(float)DFInt::MaxClusterSendStepsPerCyclic, (float)DFInt::MaxClusterSendStepsAccumulated);
|
||||
float multiplier = 1.0;
|
||||
if (int maxBytesSend = replicator->getAdjustedMtuSize())
|
||||
{
|
||||
// assuming there's just one cluster for now
|
||||
int deltas = replicator->getApproximateSizeOfPendingClusterDeltas();
|
||||
|
||||
if (deltas > maxBytesSend)
|
||||
{
|
||||
multiplier *= deltas;
|
||||
multiplier /= maxBytesSend;
|
||||
}
|
||||
}
|
||||
if (replicator->getApproximateSizeOfPendingClusterDeltas() > 0.0f)
|
||||
{
|
||||
stepsRequired = std::max<float>(1.0, stepsRequired);
|
||||
}
|
||||
float sendDataSteps = std::min<float>((float)DFInt::MaxDataOutJobScaling, stepsRequired * multiplier);
|
||||
|
||||
for (int i = 0; i < (int)sendDataSteps; i++)
|
||||
{
|
||||
replicator->clusterOutStep();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
replicator->clusterOutStep();
|
||||
}
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
const char* what = e.what();
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "SendCluster: %s", what ? what : "empty error string");
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
return TaskScheduler::Done;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Network
|
||||
} // namespace Aya
|
||||
159
engine/network/src/Replicator.StatsItem.cpp
Normal file
159
engine/network/src/Replicator.StatsItem.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "DataModel/DataModel.hpp"
|
||||
// d9mz - member access into incomplete type 'DataModel'
|
||||
#include "Replicator.StatsItem.hpp"
|
||||
#include "Script/ScriptContext.hpp"
|
||||
#include "ClientReplicator.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Network;
|
||||
|
||||
struct JobInfo
|
||||
{
|
||||
double dutyCycle;
|
||||
double stepsPerSecond;
|
||||
double stepTime;
|
||||
int count;
|
||||
|
||||
JobInfo()
|
||||
: dutyCycle(0)
|
||||
, stepTime(0)
|
||||
, stepsPerSecond(0)
|
||||
, count(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void DeserializedStatsItem::process(Replicator& replicator)
|
||||
{
|
||||
ClientReplicator* rep = aya_static_cast<ClientReplicator*>(&replicator);
|
||||
rep->statsReceivedSignal(stats);
|
||||
}
|
||||
|
||||
void writeTaskSchedulerStats(DataModel* dm, RakNet::BitStream& bitStream)
|
||||
{
|
||||
std::vector<boost::shared_ptr<const TaskScheduler::Job>> jobs;
|
||||
TaskScheduler::singleton().getJobsInfo(jobs);
|
||||
|
||||
boost::unordered_map<std::string, JobInfo> aggregatedJobInfos;
|
||||
|
||||
// BitStream:
|
||||
// end flag (bool)
|
||||
// job name (string)
|
||||
// duty cycle (float)
|
||||
// steps per second (float)
|
||||
// step time (float)
|
||||
|
||||
for (std::vector<boost::shared_ptr<const TaskScheduler::Job>>::iterator iter = jobs.begin(); iter != jobs.end(); iter++)
|
||||
{
|
||||
shared_ptr<const TaskScheduler::Job> job = *iter;
|
||||
if (job->getArbiter().get() != dm)
|
||||
continue;
|
||||
|
||||
const std::string& name = job->name;
|
||||
|
||||
JobInfo& info = aggregatedJobInfos[name];
|
||||
info.dutyCycle += job->averageDutyCycle();
|
||||
info.stepsPerSecond += job->averageStepsPerSecond();
|
||||
info.stepTime += job->averageStepTime();
|
||||
info.count++;
|
||||
}
|
||||
|
||||
// calculate replicator job averages and write to bit stream
|
||||
for (boost::unordered_map<std::string, JobInfo>::iterator iter = aggregatedJobInfos.begin(); iter != aggregatedJobInfos.end(); iter++)
|
||||
{
|
||||
bitStream << false; // more items
|
||||
|
||||
bitStream << iter->first;
|
||||
bitStream << (float)(iter->second.dutyCycle / iter->second.count);
|
||||
bitStream << (float)(iter->second.stepsPerSecond / iter->second.count);
|
||||
bitStream << (float)(iter->second.stepTime / iter->second.count);
|
||||
}
|
||||
bitStream << true; // done with jobs
|
||||
}
|
||||
|
||||
void writeScriptStats(DataModel* dm, RakNet::BitStream& bitStream)
|
||||
{
|
||||
ScriptContext* sc = dm->find<ScriptContext>();
|
||||
std::string scriptsJson;
|
||||
sc->setCollectScriptStats(true);
|
||||
|
||||
std::vector<ScriptContext::ScriptStat> scriptStats;
|
||||
sc->getScriptStatsTyped(scriptStats);
|
||||
|
||||
for (std::vector<ScriptContext::ScriptStat>::iterator iter = scriptStats.begin(); iter != scriptStats.end(); iter++)
|
||||
{
|
||||
bitStream << false; // more stats info
|
||||
|
||||
bitStream << iter->name;
|
||||
bitStream << (float)iter->activity;
|
||||
bitStream << (int)iter->invocationCount;
|
||||
}
|
||||
bitStream << true; // done with stats info
|
||||
}
|
||||
|
||||
bool Replicator::StatsItem::write(RakNet::BitStream& bitStream)
|
||||
{
|
||||
writeItemType(bitStream, Item::ItemTypeStats);
|
||||
|
||||
float totalPhysicsFPS = 0;
|
||||
float dataPacketsTotal = 0;
|
||||
float dataSizeTotal = 0;
|
||||
float physicsPacketsTotal = 0;
|
||||
float physicsSizeTotal = 0;
|
||||
float totalPing = 0;
|
||||
float totalNewDataItems = 0;
|
||||
float totalSentDataItems = 0;
|
||||
|
||||
Instances::const_iterator end = replicator.getParent()->getChildren()->end();
|
||||
for (Instances::const_iterator iter = replicator.getParent()->getChildren()->begin(); iter != end; ++iter)
|
||||
{
|
||||
// accumulate stats for all replicator
|
||||
if (Replicator* r = Instance::fastDynamicCast<Replicator>(iter->get()))
|
||||
{
|
||||
const ReplicatorStats& s = r->stats();
|
||||
totalPhysicsFPS += r->stats().physicsSenderStats.physicsPacketsSent.rate();
|
||||
dataPacketsTotal += r->stats().dataPacketsSent.rate();
|
||||
dataSizeTotal += r->stats().dataPacketsSentSize.value();
|
||||
physicsPacketsTotal += r->stats().physicsSenderStats.physicsPacketsSent.rate();
|
||||
physicsSizeTotal += r->stats().physicsSenderStats.physicsPacketsSentSize.value();
|
||||
totalPing += r->stats().dataPing.value();
|
||||
totalNewDataItems += (float)s.dataNewItemsPerSec.getCount();
|
||||
totalSentDataItems += (float)s.dataItemsSentPerSec.getCount();
|
||||
}
|
||||
}
|
||||
|
||||
int numChildren = replicator.getParent()->numChildren();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
DataModel* dm = DataModel::get(&replicator);
|
||||
writeTaskSchedulerStats(dm, bitStream);
|
||||
writeScriptStats(dm, bitStream);
|
||||
}
|
||||
// fall through
|
||||
case 1:
|
||||
{
|
||||
bitStream << (totalPing / numChildren); // average ping
|
||||
bitStream << (totalPhysicsFPS / numChildren); // average physics sender fps
|
||||
bitStream << dataPacketsTotal * dataSizeTotal / 1000.0f; // total data KB per sec
|
||||
bitStream << physicsPacketsTotal * physicsSizeTotal / 1000.0f; // total physics KB per sec
|
||||
bitStream << totalSentDataItems / totalNewDataItems; // data throughput
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DeserializedItem> Replicator::StatsItem::read(Replicator& replicator, RakNet::BitStream& bitStream)
|
||||
{
|
||||
shared_ptr<DeserializedStatsItem> deserializedData(new DeserializedStatsItem());
|
||||
|
||||
ClientReplicator* rep = aya_static_cast<ClientReplicator*>(&replicator);
|
||||
deserializedData->stats = rep->readStats(bitStream);
|
||||
|
||||
return deserializedData;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user