diff options
Diffstat (limited to 'Runtime/Network/PlayerCommunicator')
7 files changed, 2046 insertions, 0 deletions
diff --git a/Runtime/Network/PlayerCommunicator/EditorConnection.cpp b/Runtime/Network/PlayerCommunicator/EditorConnection.cpp new file mode 100644 index 0000000..d8814fa --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/EditorConnection.cpp @@ -0,0 +1,335 @@ +#include "UnityPrefix.h" + +#if UNITY_EDITOR + +#include "EditorConnection.h" +#include "Editor/Platform/Interface/EditorWindows.h" +#include "Runtime/Misc/SystemInfo.h" + +#include "Runtime/Serialize/FileCache.h" +#include "Runtime/Serialize/CacheWrap.h" + +#include "Runtime/Network/NetworkUtility.h" +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/Math/Random/rand.h" +#include "Runtime/Utilities/PathNameUtility.h" +#include "Runtime/Utilities/PlayerPrefs.h" +#include "Runtime/Threads/Thread.h" + +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h" +#include "Runtime/Network/PlayerCommunicator/PlayerConnection.h" + +static const char* kLastConnectedPlayer = "LastConnectedPlayer"; + +EditorConnection* EditorConnection::ms_Instance = NULL; + + +EditorConnection::EditorConnection(unsigned short multicastPort) +: GeneralConnection() +{ + ABSOLUTE_TIME_INIT(m_LastCleanup); + if (!m_MulticastSocket.Initialize(PLAYER_MULTICAST_GROUP, multicastPort)) + ErrorStringMsg("Failed to setup multicast socket for player connection."); + if (!m_MulticastSocket.Join()) + ErrorStringMsg("Unable to join player connection multicast group."); +} + +EditorConnection& EditorConnection::Get() +{ + return *ms_Instance; +} + +void EditorConnection::Initialize () +{ + GeneralConnection::Initialize(); + Assert(ms_Instance == NULL); + ms_Instance = new EditorConnection(); +} +void EditorConnection::Cleanup () +{ + Assert(ms_Instance != NULL); + delete ms_Instance; + ms_Instance = NULL; + GeneralConnection::Cleanup(); +} + +int EditorConnection::ConnectPlayerDirectIP(const std::string& IP) +{ + Connection* cnx = GetConnection(PLAYER_DIRECT_IP_CONNECT_GUID); + if (cnx && cnx->IsValid()) + return PLAYER_DIRECT_IP_CONNECT_GUID; + + int socketHandle = -1; + int timeout = 1; + // Poll at 1 ms, 10 ms, and 100 ms before giving up + while (socketHandle == -1) + { + // A forced repaint is required, since this method is a blocking call + LogStringMsg("Attempting to connect to player ip: %s with a %d ms timeout", IP.c_str(), timeout); + ForceRepaintOnAllViews(); + + for(int i = 0; i <= PLAYER_PORT_MASK; i++) + { + socketHandle = ::Socket::Connect(IP.c_str(), PLAYER_LISTEN_PORT + i, timeout, true, false); + if(socketHandle != -1) + break; + } + if (socketHandle != -1) + break; + + timeout *= 10; + if (timeout > 100) + break; + } + if(socketHandle == -1) + { + ErrorStringMsg("Failed to connect to player ip: %s", IP.c_str()); + return -1; + } + + RegisterConnection(PLAYER_DIRECT_IP_CONNECT_GUID, socketHandle); + if (!ms_RunningUnitTests) + EditorPrefs::SetString(kLastConnectedPlayer, ""); + + return PLAYER_DIRECT_IP_CONNECT_GUID; +} + +int EditorConnection::ConnectPlayer (UInt32 guid) +{ + AvailablePlayerMap::iterator found = m_AvailablePlayers.find(guid); + if (found == m_AvailablePlayers.end()) + return -1; + + MulticastInfo& multicastInfo = found->second.m_MulticastInfo; + if(!multicastInfo.IsValid()) + return -1; + + Assert(multicastInfo.GetGuid() == guid); + Connection* cnx = GetConnection(guid); + if (cnx && cnx->IsValid()) + return guid; + + int socketHandle; + if (SOCK_ERROR(socketHandle = ::Socket::Connect(multicastInfo.GetIP().c_str(), multicastInfo.GetPort(), 500 /*timeout*/))) + { + ErrorStringMsg("Failed to connect to player ip: %s, port: %d", multicastInfo.GetIP().c_str(), multicastInfo.GetPort()); + return 0; + } + + RegisterConnection(guid, socketHandle); + if (!ms_RunningUnitTests) + EditorPrefs::SetString(kLastConnectedPlayer, multicastInfo.GetIdentifier()); + + return guid; +} + +UInt32 EditorConnection::AddPlayer(std::string hostName, std::string localIP, unsigned short port, UInt32 guid, int flags) +{ + std::string buffer = Format(SERVER_IDENTIFICATION_FORMAT, localIP.c_str(), port, flags, guid, -1, ms_Version, hostName.c_str(), 0 ); + MulticastInfo multicastInfo; + if(multicastInfo.Parse(buffer.c_str())) + { + AvailablePlayerMap::iterator found = m_AvailablePlayers.find(multicastInfo.GetGuid()); + if(found != m_AvailablePlayers.end()) + { + // entry already in the list - renew the timestamp + found->second.m_LastPing = START_TIME; + } + else + { + m_AvailablePlayers.insert (std::make_pair(multicastInfo.GetGuid(), AvailablePlayer(START_TIME, multicastInfo))).first; + } + return multicastInfo.GetGuid(); + } + return 0; +} + +void EditorConnection::RemovePlayer(UInt32 guid) +{ + Disconnect(guid); + AvailablePlayerMap::iterator found = m_AvailablePlayers.find(guid); + if(found != m_AvailablePlayers.end()) + m_AvailablePlayers.erase(found); +} + +void EditorConnection::EnablePlayer(UInt32 guid, bool enable) +{ + AvailablePlayerMap::iterator player = m_AvailablePlayers.find(guid); + if (player != m_AvailablePlayers.end()) + player->second.m_Enabled = enable; +} + +void +EditorConnection::ResetLastPlayer() +{ + EditorPrefs::SetString(kLastConnectedPlayer, ""); +} + +void EditorConnection::GetAvailablePlayers( std::vector<UInt32>& values ) +{ + AvailablePlayerMap::iterator it = m_AvailablePlayers.begin(); + for( ; it != m_AvailablePlayers.end(); ++it){ + if (it->second.m_Enabled && it->second.m_MulticastInfo.GetIP() == m_LocalIP) + values.push_back(it->second.m_MulticastInfo.GetGuid()); + } + it = m_AvailablePlayers.begin(); + for( ; it != m_AvailablePlayers.end(); ++it){ + if (it->second.m_Enabled && it->second.m_MulticastInfo.GetIP() != m_LocalIP) + values.push_back(it->second.m_MulticastInfo.GetGuid()); + } +} + +void EditorConnection::GetAvailablePlayers(RuntimePlatform platform, vector<UInt32>& values) +{ + string const id = systeminfo::GetRuntimePlatformString(platform); + + for (AvailablePlayerMap::const_iterator it = m_AvailablePlayers.begin() ; it != m_AvailablePlayers.end(); ++it) + { + AvailablePlayer const& player = it->second; + if (player.m_Enabled && BeginsWith(player.m_MulticastInfo.GetIdentifier(), id) && (player.m_MulticastInfo.GetIP() == m_LocalIP)) + values.push_back(player.m_MulticastInfo.GetGuid()); + } + + for (AvailablePlayerMap::const_iterator it = m_AvailablePlayers.begin() ; it != m_AvailablePlayers.end(); ++it) + { + AvailablePlayer const& player = it->second; + if (player.m_Enabled && BeginsWith(player.m_MulticastInfo.GetIdentifier(), id) && (player.m_MulticastInfo.GetIP() != m_LocalIP)) + values.push_back(player.m_MulticastInfo.GetGuid()); + } +} + +std::string EditorConnection::GetConnectionIdentifier( UInt32 guid ) +{ + AvailablePlayerMap::iterator it = m_AvailablePlayers.find(guid); + if (it != m_AvailablePlayers.end()) + return it->second.m_MulticastInfo.GetIdentifier(); + return "None"; +} + +bool EditorConnection::IsIdentifierConnectable( UInt32 guid ) +{ + AvailablePlayerMap::iterator it = m_AvailablePlayers.find(guid); + if (it != m_AvailablePlayers.end()) + return it->second.m_MulticastInfo.IsValid(); + return false; +} + +bool EditorConnection::IsIdentifierOnLocalhost( UInt32 guid ) +{ + AvailablePlayerMap::iterator it = m_AvailablePlayers.find(guid); + if (it != m_AvailablePlayers.end()) + return it->second.m_MulticastInfo.IsLocalhost(); + return false; +} + +GeneralConnection::MulticastInfo EditorConnection::PollWithCustomMessage() +{ + MulticastInfo multicastInfo; + + sockaddr_in srcaddr; + socklen_t srcaddr_len = sizeof(srcaddr); + char buffer[kMulticastBufferSize]; + RecvUserData recvData; + recvData.srcAddr = (struct sockaddr*)&srcaddr; + recvData.srcLen = srcaddr_len; + int size = m_MulticastSocket.Recv(buffer, kMulticastBufferSize, &recvData); + if (!SOCK_ERROR(size)) + { + // Ensure that buffer is null terminated string + buffer[size] = 0; + if(multicastInfo.Parse(buffer, &srcaddr)) + { + AvailablePlayerMap::iterator found = m_AvailablePlayers.find(multicastInfo.GetGuid()); + if(found != m_AvailablePlayers.end()) + { + // entry already in the list - renew the timestamp + found->second.m_LastPing = START_TIME; + } + else + { + // remove old player if new one has connected + for (AvailablePlayerMap::const_iterator it = m_AvailablePlayers.begin(); it != m_AvailablePlayers.end(); ++it) + if (!it->second.m_Enabled && !StrCmp(it->second.m_MulticastInfo.GetIdentifier(), multicastInfo.GetIdentifier())) + { + RemovePlayer(it->first); + break; + } + + m_AvailablePlayers.insert (std::make_pair(multicastInfo.GetGuid(), AvailablePlayer(START_TIME, multicastInfo))); + if (multicastInfo.IsValid() && multicastInfo.ImmediateConnect()) + { + bool atemptAutoConnect; + if (ms_RunningUnitTests) + { + atemptAutoConnect = false; + if (!IsConnected()) + { + const char* connectingIp = multicastInfo.GetIP().c_str(); + + const int maxNumberOfIps = 10; + char localIps[maxNumberOfIps][16]; + GetIPs(localIps); + for (int i = 0; i < maxNumberOfIps; ++i) + { + if (strcmp(localIps[i], connectingIp) == 0) + { + atemptAutoConnect = true; + break; + } + } + } + } + else + { + string lastPlayerId = EditorPrefs::GetString(kLastConnectedPlayer); + + // No prior player are known and new player is built by us and requires autoconnection + if (lastPlayerId.empty() && multicastInfo.GetEditorGuid() == GetLocalGuid()) + atemptAutoConnect = true; + else + atemptAutoConnect = (lastPlayerId == multicastInfo.GetIdentifier()); + } + + if (atemptAutoConnect) + ConnectPlayer(multicastInfo.GetGuid()); + } + } + } + else + { + // can not connect to this player (version mismatch) + + } + } + + // clean up the list every 2 seconds ( players not pinging in 10 seconds are removed from the map ) + if(GetProfileTime(ELAPSED_TIME(m_LastCleanup)) > 2*kTimeSecond) + { + m_LastCleanup = START_TIME; + AvailablePlayerMap::iterator it = m_AvailablePlayers.begin(); + while (it != m_AvailablePlayers.end()) + { + UInt32 guid = it->first; + AvailablePlayer& player = it->second; + if (player.m_MulticastInfo.GetGuid() == PLAYER_DIRECTCONNECT_GUID) + { + ++it; + continue; + } + if (!GetConnection(guid) && (GetProfileTime(ELAPSED_TIME(player.m_LastPing)) > 10*kTimeSecond)) + { + AvailablePlayerMap::iterator erase = it++; + m_AvailablePlayers.erase(erase); + } + else + ++it; + } + } + + GeneralConnection::Poll(); + + return multicastInfo; +} + +#endif // ENABLE_PLAYERCONNECTION diff --git a/Runtime/Network/PlayerCommunicator/EditorConnection.h b/Runtime/Network/PlayerCommunicator/EditorConnection.h new file mode 100644 index 0000000..c2f020d --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/EditorConnection.h @@ -0,0 +1,77 @@ +#pragma once + +#if UNITY_EDITOR + +#include "GeneralConnection.h" +#include "Runtime/Misc/SystemInfo.h" +#include "Runtime/Serialize/SwapEndianBytes.h" + + +#if UNITY_WIN +#include <winsock2.h> +#include <ws2tcpip.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <errno.h> +#endif +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/Threads/Mutex.h" + + +class EditorConnection : public GeneralConnection +{ +public: + EditorConnection (unsigned short multicastPort = PLAYER_MULTICAST_PORT); + + static void Initialize (); + static void Cleanup (); + + void ResetLastPlayer(); + + void GetAvailablePlayers( std::vector<UInt32>& values ); + void GetAvailablePlayers( RuntimePlatform platform, std::vector<UInt32>& values ); + std::string GetConnectionIdentifier( UInt32 guid ); + bool IsIdentifierConnectable( UInt32 guid ); + bool IsIdentifierOnLocalhost( UInt32 guid ); + + int ConnectPlayer (UInt32 guid); + int ConnectPlayerDirectIP(const std::string& IP); + + UInt32 AddPlayer(std::string hostName, std::string localIP, unsigned short port, UInt32 guid, int flags); + void RemovePlayer(UInt32 guid); + void EnablePlayer(UInt32 guid, bool enable); + + // Singleton accessor for editorconnection + static EditorConnection& Get (); + static EditorConnection* ms_Instance; + + MulticastInfo PollWithCustomMessage (); + +private: + virtual bool IsServer () { return false; } + + struct AvailablePlayer + { + AvailablePlayer(ABSOLUTE_TIME time, const MulticastInfo& info) + : m_LastPing (time) + , m_MulticastInfo(info) + , m_Enabled(true) + {} + + ABSOLUTE_TIME m_LastPing; + MulticastInfo m_MulticastInfo; + bool m_Enabled; + }; + +private: + typedef std::map< UInt32, AvailablePlayer > AvailablePlayerMap; + AvailablePlayerMap m_AvailablePlayers; + + ABSOLUTE_TIME m_LastCleanup; +}; + +#endif diff --git a/Runtime/Network/PlayerCommunicator/GeneralConnection.cpp b/Runtime/Network/PlayerCommunicator/GeneralConnection.cpp new file mode 100644 index 0000000..dc4c89d --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/GeneralConnection.cpp @@ -0,0 +1,666 @@ +#include "UnityPrefix.h" + +#if ENABLE_PLAYERCONNECTION + +#include "GeneralConnection.h" +#include "GeneralConnectionInternals.h" +#include "Runtime/Math/Random/rand.h" +#include "Runtime/Network/NetworkUtility.h" +#include "Runtime/Profiler/TimeHelper.h" + +bool GeneralConnection::ms_RunningUnitTests = false; +int GeneralConnection::ms_Version = 0x00100100; + +GeneralConnection::GeneralConnection () +: m_LogEnabled(true) +{ + char ips[10][16]; + int count = GetIPs(ips); + if (count != 0) + m_LocalIP = ips[0]; + else + m_LocalIP = "0.0.0.0"; + +#if UNITY_BB10 + for(int x = 0; x < sizeof(ips); x++) + { + if(std::string(ips[x]).find("169.254") == 0) + { + m_LocalIP = ips[x]; + break; + } + } +#endif + + m_LocalGuid = Rand((int)GetProfileTime(START_TIME)).Get(); + // We reserve ANY_PLAYERCONNECTION guid for special use + if (m_LocalGuid == ANY_PLAYERCONNECTION) m_LocalGuid = ANY_PLAYERCONNECTION ^ 1; +} + +UInt32 GeneralConnection::GetLocalGuid() const +{ + return m_LocalGuid; +} + +void GeneralConnection::WaitForFinish() +{ + int msgWait = 60; + + while (HasBytesToSend()) + { + if (msgWait++ == 60) + { + printf_console("Waiting for finish\n"); + msgWait = 0; + } + + Poll(); + Thread::Sleep(0.05); + } +} + +bool GeneralConnection::HasBytesToSend() const +{ + ConnectionMap::const_iterator it = m_Connections.begin(); + for ( ; it != m_Connections.end(); ++it) + if (it->second->HasBytesToSend()) + return true; + + return false; +} + +GeneralConnection::~GeneralConnection() +{ + DisconnectAll(); +} + + +void GeneralConnection::DisconnectAll() +{ + ConnectionMap::iterator it = m_Connections.begin(); + std::vector< int > disconnectGuids; + for( ; it != m_Connections.end(); ++it) + disconnectGuids.push_back(it->first); + + for (int i = 0; i < disconnectGuids.size(); i++) + Disconnect(disconnectGuids[i]); + + Assert(m_Connections.empty()); +} + + +void GeneralConnection::Initialize () +{ + NetworkInitialize(); +} + +void GeneralConnection::Cleanup () +{ + NetworkCleanup(); +} + + +GeneralConnection::Connection* GeneralConnection::GetConnection(UInt32 guid) +{ + ConnectionMap::iterator it = m_Connections.find(guid); + if (it == m_Connections.end()) + return NULL; + return it->second; +} + +void GeneralConnection::RegisterConnection(UInt32 guid, TSocketHandle socketHandle) +{ + if (GetConnection(guid)) + Disconnect(guid); + + LOG_PLAYER_CONNECTION(Format("PlayerConnection registered: %d", guid)); + m_Connections[guid] = new Connection(socketHandle); + for(int i = 0; i < m_ConnectionHandlers.size(); i++) + (m_ConnectionHandlers[i])(guid); +} + +void GeneralConnection::Disconnect(UInt32 guid) +{ + ConnectionMap::iterator it = m_Connections.find(guid); + if (it == m_Connections.end()) + return; + + LOG_PLAYER_CONNECTION(Format("PlayerConnection disconnecting: %d", guid)); + for (int i = 0; i < m_DisconnectionHandlers.size(); i++) + (m_DisconnectionHandlers[i])(guid); + +#if ENABLE_PLAYER_CONNECTION_DEBUG_LOG + Connection& connection = *it->second; + LOG_PLAYER_CONNECTION(Format("GeneralConnection::Connection send buffer mem usage %d\n", connection.GetSendBufferSize())); + LOG_PLAYER_CONNECTION(Format("GeneralConnection::Connection recv buffer mem usage %d\n", connection.GetRecvBufferSize())); +#endif + + delete it->second; + + m_Connections.erase(it); +} + + +void GeneralConnection::SendMessage(UInt32 guid, MessageID id, const void* data, UInt32 size) +{ + NetworkMessage message; + message.InitializeMessage(); + message.SetID(id); + message.SetDataSize(size); + + bool oldlog = IsLogEnabled(); + SetLogEnabled(false); + + if (guid == ANY_PLAYERCONNECTION) + { + LOG_PLAYER_CONNECTION(Format("PlayerConnection send message to ALL, id '%d', size '%d'", id, size)); + ConnectionMap::iterator it = m_Connections.begin(); + for ( ; it != m_Connections.end(); ++it) + it->second->SendMessage(message, data); + } + else + { + LOG_PLAYER_CONNECTION(Format("PlayerConnection send message to '%d', id '%d', size '%d'", guid, id, size)); + ConnectionMap::iterator it = m_Connections.find(guid); + if (it != m_Connections.end()) + it->second->SendMessage(message, data); + } + SetLogEnabled(oldlog); +} + +void GeneralConnection::Poll() +{ + ABSOLUTE_TIME start = START_TIME; + ConnectionMap::iterator it = m_Connections.begin(); + UNITY_TEMP_VECTOR(int) disconnectGuids; + for ( ; it != m_Connections.end(); ++it) + { + Connection& connection = *(it->second); + connection.Poll(); + const void* messageDataPtr; + NetworkMessage messageHeader; + while ( (GetProfileTime(ELAPSED_TIME(start)) < 20*kTimeMillisecond) && ((messageDataPtr = connection.ReceiveMessage(&messageHeader)) != NULL) ) + { + LOG_PLAYER_CONNECTION(Format("PlayerConnection recv message id '%d', size '%d'", messageHeader.GetID(), messageHeader.GetDataSize())); + std::map< MessageID, MessageHandlerFunc >::iterator handler = m_HandlerMap.find(messageHeader.GetID()); + if (handler != m_HandlerMap.end()) + (handler->second)(messageDataPtr, messageHeader.GetDataSize(), it->first); + connection.ReleaseReceivedMessage(); + } + if (!connection.IsValid()) + disconnectGuids.push_back(it->first); + } + + for (int i = 0; i < disconnectGuids.size(); i++) + Disconnect(disconnectGuids[i]); +} + +// Handlers +void GeneralConnection::RegisterMessageHandler( MessageID messageID, MessageHandlerFunc func ) +{ + if(m_HandlerMap.find(messageID) != m_HandlerMap.end()) + ErrorString("MessageHandler already registered"); + m_HandlerMap[messageID] = func; +} + +void GeneralConnection::RegisterConnectionHandler( ConnectionHandlerFunc func ) +{ + for (int i = 0; i < m_ConnectionHandlers.size(); i++) + Assert(m_ConnectionHandlers[i] != func); + + m_ConnectionHandlers.push_back(func); + + // call the connect handler on already connected sockets + ConnectionMap::iterator it = m_Connections.begin(); + for( ; it != m_Connections.end(); ++it) + (func)(it->first); +} + +void GeneralConnection::RegisterDisconnectionHandler( ConnectionHandlerFunc func ) +{ + for (int i = 0; i < m_DisconnectionHandlers.size(); i++) + Assert(m_DisconnectionHandlers[i] != func); + + m_DisconnectionHandlers.push_back(func); +} + +void GeneralConnection::UnregisterMessageHandler( MessageID messageID, MessageHandlerFunc func ) +{ + std::map< MessageID, MessageHandlerFunc >::iterator found = m_HandlerMap.find(messageID); + if(found == m_HandlerMap.end()) + ErrorString("MessageHandler not registered"); + Assert(found->second == func); + m_HandlerMap.erase(found); +} + +void GeneralConnection::UnregisterConnectionHandler( ConnectionHandlerFunc func ) +{ + std::vector< ConnectionHandlerFunc >::iterator handlerIt = m_ConnectionHandlers.begin(); + for ( ; handlerIt != m_ConnectionHandlers.end(); ++handlerIt) + { + if (*handlerIt == func) + { + m_ConnectionHandlers.erase(handlerIt); + return; + } + } +} + +void GeneralConnection::UnregisterDisconnectionHandler( ConnectionHandlerFunc func ) +{ + // call the disconnect handler on already connected sockets + ConnectionMap::iterator it = m_Connections.begin(); + for( ; it != m_Connections.end(); ++it) + (func)(it->first); + + std::vector< ConnectionHandlerFunc >::iterator handlerIt = m_DisconnectionHandlers.begin(); + for ( ; handlerIt != m_DisconnectionHandlers.end(); ++handlerIt) + { + if (*handlerIt == func) + { + m_DisconnectionHandlers.erase(handlerIt); + return; + } + } +} + +// ------------------------------------------------------------------------------ +// Connection +// ------------------------------------------------------------------------------ +GeneralConnection::Connection::Connection(TSocketHandle socketHandle) +: m_SocketStream(socketHandle, kDataBufferMaxSize, kDataBufferMaxSize) +, m_PendingMessageData(NULL) +, m_HasPendingMessage(false) +{ +} + +GeneralConnection::Connection::~Connection() +{ + if (m_PendingMessageData) + ReleaseReceivedMessage(); +} + +const void* GeneralConnection::Connection::ReceiveMessage(NetworkMessage* message) +{ + AssertBreak(!m_PendingMessageData); + Mutex::AutoLock lock (m_RecvMutex); + + ExtendedGrowingRingbuffer& recvBuffer = m_SocketStream.RecvBuffer(); + UInt32 bytesAvailable = recvBuffer.GetAvailableSize(); + if (!m_HasPendingMessage && bytesAvailable >= sizeof(NetworkMessage)) + { + m_SocketStream.RecvAll(&m_PendingMessage, sizeof(NetworkMessage)); + m_HasPendingMessage = true; + bytesAvailable = recvBuffer.GetAvailableSize(); + Assert(recvBuffer.GetSize() >= m_PendingMessage.GetDataSize() && "Buffer is not large enough for the message."); + + } + + if (m_HasPendingMessage) + { + UInt32 messageSize = m_PendingMessage.GetDataSize(); + if (m_HasPendingMessage && bytesAvailable >= messageSize) + { + m_HasPendingMessage = false; + UInt32 linearData = messageSize; + m_PendingMessageData = (void*) recvBuffer.ReadPtr(&linearData); + m_LocallyAllocatedMessageData = linearData < messageSize; + if (m_LocallyAllocatedMessageData) + { + m_PendingMessageData = UNITY_MALLOC(kMemNetwork, messageSize); + m_SocketStream.RecvAll(m_PendingMessageData, messageSize); + } + memcpy(message, &m_PendingMessage, sizeof(NetworkMessage)); + return m_PendingMessageData; + } + } + return NULL; +} + +void GeneralConnection::Connection::ReleaseReceivedMessage() +{ + AssertBreak(m_PendingMessageData); + Mutex::AutoLock lock (m_RecvMutex); + + if (m_LocallyAllocatedMessageData) + UNITY_FREE(kMemNetwork, m_PendingMessageData); + else + m_SocketStream.RecvBuffer().ReadPtrUpdate(m_PendingMessageData, m_PendingMessage.GetDataSize()); + m_PendingMessageData = NULL; +} + +void GeneralConnection::Connection::SendMessage(NetworkMessage& message, const void* data) +{ + Mutex::AutoLock lock (m_SendMutex); + if(message.AllowSkip() && !m_SocketStream.CanSendNonblocking(sizeof(NetworkMessage) + message.GetDataSize())) + { + WarningString("Skipping profile frame. Reciever can not keep up with the amount of data sent"); + return; + } + m_SocketStream.SendAll(&message, sizeof(NetworkMessage)); + m_SocketStream.SendAll(data, message.GetDataSize()); +} + +// ------------------------------------------------------------------------------ +// NetworkMessage +// ------------------------------------------------------------------------------ +void GeneralConnection::NetworkMessage::InitializeMessage() +{ + UInt16 magicNumber = PLAYER_MESSAGE_MAGIC_NUMBER; + SwapEndianBytesLittleToNative(magicNumber); + m_MagicNumber = magicNumber; +} + +bool GeneralConnection::NetworkMessage::CheckMessageValidity() +{ + UInt16 magicNumber = m_MagicNumber; + SwapEndianBytesLittleToNative(magicNumber); + return magicNumber == PLAYER_MESSAGE_MAGIC_NUMBER; +} + +void GeneralConnection::NetworkMessage::SetID( GeneralConnection::MessageID messageID ) +{ + UInt16 id = messageID; + SwapEndianBytesLittleToNative(id); + m_ID = id; +} + +GeneralConnection::MessageID GeneralConnection::NetworkMessage::GetID() +{ + UInt16 id = m_ID; + SwapEndianBytesLittleToNative(id); + return (MessageID) id; +} + +void GeneralConnection::NetworkMessage::SetDataSize( UInt32 size ) +{ + SwapEndianBytesLittleToNative(size); + m_Size = size; +} + +UInt32 GeneralConnection::NetworkMessage::GetDataSize() +{ + UInt32 size = m_Size; + SwapEndianBytesLittleToNative(size); + return size; +} + +// ------------------------------------------------------------------------------ +// MulticastInfo +// ------------------------------------------------------------------------------ +#if ENABLE_MULTICAST +GeneralConnection::MulticastInfo::MulticastInfo() +{ + Clear(); +} + +void GeneralConnection::MulticastInfo::Clear() +{ + m_Valid = false; + m_Flags = 0; + m_Port = 0; + m_Guid = 0; + m_EditorGuid = 0; +} +bool GeneralConnection::MulticastInfo::Parse( const char* buffer, void* in) +{ + char ip[kMulticastBufferSize]; + char id[kMulticastBufferSize]; + int version; + int debug; + if(sscanf(buffer, SERVER_IDENTIFICATION_FORMAT, ip, &m_Port, &m_Flags, &m_Guid, &m_EditorGuid, &version, id, &debug) != 8) + { + Clear(); + m_Valid = false; + m_IsLocalhost = false; + return false; + //ErrorString(Format("MulticastInfo should be in this format: '%s' but got: '%s'", SERVER_IDENTIFICATION_FORMAT, buffer)); + } +#if !UNITY_WINRT + m_Ip = in ? InAddrToIP((sockaddr_in*)in) : std::string(ip); +#endif + + m_Identifier = std::string(id); + m_Valid = (version == ms_Version); + SetLocalhostFlag(); + return true; +} + +void GeneralConnection::MulticastInfo::SetLocalhostFlag() +{ + std::string localIP; + char ips[10][16]; + int count = GetIPs(ips); + if (count != 0) + localIP = ips[0]; + else + localIP = "0.0.0.0"; + m_IsLocalhost = (m_Ip.compare("0.0.0.0") == 0) || (m_Ip.compare("127.0.0.1") == 0) || (m_Ip.compare(localIP) == 0); +} + +#endif //ENABLE_MULTICAST + + +/// These tests are unstable and run for too long? +// --------------------------------------------------------------------------- +#if ENABLE_UNIT_TESTS && 0 + +#include "External/UnitTest++/src/UnitTest++.h" +#include "EditorConnection.h" +#include "PlayerConnection.h" + +SUITE (GeneralConnectionTests) +{ + bool isClientConnected = false; + UInt32 clientConnectionGuid = -1; + void ClientConnectionHandler (UInt32 guid) { isClientConnected = true; clientConnectionGuid = guid; } + void ClientDisconnectionHandler (UInt32 guid) { isClientConnected = false; clientConnectionGuid = -1;} + + bool isServerConnected = false; + UInt32 serverConnectionGuid = -1; + void ServerConnectionHandler (UInt32 guid) { isServerConnected = true; serverConnectionGuid = guid;} + void ServerDisconnectionHandler (UInt32 guid) { isServerConnected = false; serverConnectionGuid = -1;} + + struct GeneralConnectionFixture + { + GeneralConnectionFixture () + { + Initialize(); + } + ~GeneralConnectionFixture () + { + Destroy(); + } + + void Initialize () + { + GeneralConnection::RunningUnitTest(); + Rand r (GetProfileTime(START_TIME)); + int multicastport = PLAYER_UNITTEST_MULTICAST_PORT + (r.Get() & PLAYER_PORT_MASK); + player = new PlayerConnection("", multicastport); + editor = new EditorConnection(multicastport); + editor->RegisterConnectionHandler( ClientConnectionHandler ); + player->RegisterConnectionHandler( ServerConnectionHandler ); + editor->RegisterDisconnectionHandler( ClientDisconnectionHandler ); + player->RegisterDisconnectionHandler( ServerDisconnectionHandler ); + } + + void Destroy () + { + delete player; + delete editor; + player = NULL; + editor = NULL; + } + + void Connect () + { + //record time + ABSOLUTE_TIME start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond) + { + player->Poll(); + editor->PollWithCustomMessage(); + if (player->IsConnected() && editor->IsConnected()) break; + } + } + + void CheckConnectionState(bool state) + { + // is internal connection state + CHECK_EQUAL (state, player->IsConnected() ); + CHECK_EQUAL (state, editor->IsConnected() ); + // is custom connect handler called + CHECK_EQUAL (state, isClientConnected); + CHECK_EQUAL (state, isServerConnected); + } + + PlayerConnection* player; + EditorConnection* editor; + }; + + std::string message; + void HandleGeneralConnectionMessage (const void* data, UInt32 size, UInt32 guid) + { + message = std::string((char*)data); + } + + int* largeMessage; + void HandleLargeMessage(const void* data, UInt32 size, UInt32 guid) + { + largeMessage = new int[size/sizeof(int)]; + memcpy(largeMessage, data, size); + } + + int messageCount = 0; + void HandleManyMessages(const void* data, UInt32 size, UInt32 guid) + { + CHECK_EQUAL(((char*)data)[10],messageCount); + messageCount++; + } + + TEST_FIXTURE(GeneralConnectionFixture, CanDisconnectServer) + { + Connect(); + CheckConnectionState(true); + + player->DisconnectAll(); + + ABSOLUTE_TIME start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond) + { + editor->PollWithCustomMessage(); + if (!editor->IsConnected()) break; + } + CheckConnectionState(false); + } + + TEST_FIXTURE(GeneralConnectionFixture, CanConnect) + { + Connect(); + CheckConnectionState(true); + } + + TEST_FIXTURE(GeneralConnectionFixture, CanDisconnectClient) + { + Connect(); + CheckConnectionState(true); + + editor->DisconnectAll(); + + ABSOLUTE_TIME start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond) + { + player->Poll(); + if (!player->IsConnected()) break; + } + CheckConnectionState(false); + } + + TEST_FIXTURE(GeneralConnectionFixture, CanSendMessage) + { + Connect(); + editor->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleGeneralConnectionMessage); + player->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleGeneralConnectionMessage); + + player->SendMessage(serverConnectionGuid, GeneralConnection::kLastMessageID, "Hello World", 12); + ABSOLUTE_TIME start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond) + { + player->Poll(); + editor->PollWithCustomMessage(); + if (!message.empty()) break; + } + CHECK_EQUAL (std::string("Hello World"), message); + + message = std::string(""); + editor->SendMessage(clientConnectionGuid, GeneralConnection::kLastMessageID, "Are you there", 14); + start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond) + { + player->Poll(); + editor->PollWithCustomMessage(); + if (!message.empty()) break; + } + CHECK_EQUAL (std::string("Are you there"), message); + + } + + TEST_FIXTURE(GeneralConnectionFixture, SendLargeMessage) + { + Connect(); + editor->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleLargeMessage); + + int msgSize = 400*1024; + int* buffer = new int[msgSize]; + for(int i = 0; i < msgSize; i++) + buffer[i] = i; + largeMessage = NULL; + player->SendMessage(serverConnectionGuid, GeneralConnection::kLastMessageID, (char*)buffer, msgSize*sizeof(int)); + ABSOLUTE_TIME start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 2*kTimeSecond) + { + player->Poll(); + editor->PollWithCustomMessage(); + if (largeMessage != NULL) break; + } + if (largeMessage != NULL) + { + CHECK_EQUAL (1, largeMessage[1]); + CHECK_EQUAL (1024, largeMessage[1024]); + CHECK_EQUAL (10000, largeMessage[10000]); + CHECK_EQUAL (msgSize -1, largeMessage[msgSize-1]); + } + else + CHECK(largeMessage != NULL); + + delete[] largeMessage; largeMessage = NULL; + } + + TEST_FIXTURE(GeneralConnectionFixture, SendMultipleMessage) + { + Connect(); + editor->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleManyMessages); + + char message[20]; + + for(int i = 0; i < 100; i++) + { + message[10] = (char)i; + player->SendMessage(serverConnectionGuid, GeneralConnection::kLastMessageID, message, 20); + } + + ABSOLUTE_TIME start = START_TIME; + while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond) + { + player->Poll(); + editor->PollWithCustomMessage(); + if (messageCount == 100) break; + } + CHECK_EQUAL (100, messageCount); + } +} + +#endif + +#endif // ENABLE_PLAYERCONNECTION diff --git a/Runtime/Network/PlayerCommunicator/GeneralConnection.h b/Runtime/Network/PlayerCommunicator/GeneralConnection.h new file mode 100644 index 0000000..1f253d8 --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/GeneralConnection.h @@ -0,0 +1,266 @@ +#pragma once + +#if ENABLE_PLAYERCONNECTION +#define ENABLE_MULTICAST (!UNITY_FLASH) + +#include "Runtime/Serialize/SwapEndianBytes.h" +#include "Runtime/Network/MulticastSocket.h" +#include "Runtime/Network/ServerSocket.h" +#include "Runtime/Network/SocketStreams.h" + + +#if UNITY_WIN +#include <winsock2.h> +#include <ws2tcpip.h> +#elif UNITY_XENON +#include <xtl.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <errno.h> +#endif +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/Threads/Mutex.h" + + +// used ports: +// MulticastPort : 54998 +// ListenPorts : 55000 - 55511 +// Multicast(unittests) : 55512 - 56023 + +#define ENABLE_PLAYER_CONNECTION_DEBUG_LOG 0 + +#if ENABLE_PLAYER_CONNECTION_DEBUG_LOG +#define LOG_PLAYER_CONNECTION(str) { \ + bool oldlog = IsLogEnabled(); \ + SetLogEnabled(false); \ + printf_console ("[%04x] %s\n", Thread::GetCurrentThreadID(), (str).c_str()); \ + SetLogEnabled(oldlog); \ +} +#else +#define LOG_PLAYER_CONNECTION(str) +#endif + +#if SUPPORT_THREADS +#define LOG_PLAYER_CONNECTION_CRITICAL(str) printf_console ("[%04x] %s\n", Thread::GetCurrentThreadID(), (str).c_str()) +#else +#define LOG_PLAYER_CONNECTION_CRITICAL(str) printf_console ("[] %s\n", (str).c_str()) +#endif + +#define PLAYER_MULTICAST_GROUP "225.0.0.222" +#define PLAYER_MULTICAST_PORT 54997 + +#define PLAYER_DIRECTCONNECT_PORT 54999 +#define PLAYER_DIRECTCONNECT_GUID 1337 + +#define PLAYER_DIRECT_IP_CONNECT_GUID 0xFEED + +#define PLAYER_LISTEN_PORT 55000 +#define PLAYER_UNITTEST_MULTICAST_PORT 55512 +#define PLAYER_PORT_MASK 511 +#define PLAYER_MESSAGE_MAGIC_NUMBER 0x4E8F + +#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN "listen %u %d %d %d" +#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN_PRINT "listen <guid> <debugging> <waitonstartup> <startprofiler>" +#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_LIST "connect %s %s %s %s %s %s %s %s %s %s" +#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT "connect %s" +#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_PRINT "connect <ip>" + +#define SERVER_IDENTIFICATION_FORMAT "[IP] %s [Port] %lu [Flags] %lu [Guid] %lu [EditorId] %lu [Version] %d [Id] %s [Debug] %d" + + +#define ANY_PLAYERCONNECTION 0 + +bool SocketCallSucceeded(int res); + +class GeneralConnection +{ +public: + enum MulticastFlags + { + kRequestImmediateConnect = 1<<0, + kSupportsProfile = 1<<1, + kCustomMessage = 1<<2 + }; + + enum { kMulticastBufferSize = 256 }; + enum { kDataBufferMaxSize = 8*1024*1024 }; + enum { kDataBufferFlushThreshold = 7*1024*1024 }; + +#if ENABLE_MULTICAST + // Parsed struct from a buffer that contains all the information sent by the player to the editor + // Used for showing a list of all available profilers and performing autoconnect on the profiler + struct MulticastInfo + { + MulticastInfo(); + + bool Parse (const char* buffer, void* in = NULL); + + bool IsValid () const { return m_Valid; } + bool IsLocalhost() const { return m_IsLocalhost; } + + UInt32 GetGuid () const { return m_Guid; } + UInt32 GetEditorGuid () const { return m_EditorGuid; } + + UInt32 GetPort () const { return m_Port; } + std::string const& GetIP () const { return m_Ip; } + + bool ImmediateConnect() const { return (m_Flags & kRequestImmediateConnect) != 0; } + bool HasCustomMessage() const { return (m_Flags & kCustomMessage) != 0; } + bool HasProfiler() const { return (m_Flags & kSupportsProfile) != 0; } + + std::string const& GetIdentifier() const { return m_Identifier; } + private: + + void Clear (); + void SetLocalhostFlag(); + + std::string m_Buffer; + std::string m_Ip; + UInt32 m_Port; + UInt32 m_Flags; + UInt32 m_Guid; + UInt32 m_EditorGuid; + std::string m_Identifier; + bool m_Valid; + bool m_IsLocalhost; + }; +#endif + + enum MessageID{ + // messageID 1 - 31 reserved for (future) internal use + kProfileDataMessage = 32, + kProfileStartupInformation = 33, + + kObjectMemoryProfileSnapshot = 40, //request the memory profile + kObjectMemoryProfileDataMessage = 41, //send the object memory profile + + kLogMessage = 100, + kCleanLogMessage = 101, + + kFileTransferMessage = 200, + kFileReadyMessage = 201, + kCaptureHeaphshotMessage = 202, + + kPingAliveMessage = 300, + kApplicationQuitMessage = 301, + kLastMessageID, + }; + + struct NetworkMessage + { + void InitializeMessage(); + bool CheckMessageValidity(); + void SetID (MessageID id); + MessageID GetID (); + void SetDataSize (UInt32 size); + UInt32 GetDataSize (); + + bool AllowSkip(){return m_ID == kProfileDataMessage;} + + private: + UInt16 m_MagicNumber; // in little endian + UInt16 m_ID; // in little endian + UInt32 m_Size; // in little endian + }; + + struct Connection + { + public: + Connection(TSocketHandle socketHandle); + ~Connection(); + + bool IsValid() const { return m_SocketStream.IsConnected(); }; + bool Poll() { return m_SocketStream.Poll(5); } + bool HasBytesToSend() const { return m_SocketStream.SendBuffer().GetAvailableSize() > 0; } + const void* ReceiveMessage(NetworkMessage* message); + void ReleaseReceivedMessage(); + void SendMessage(NetworkMessage& message, const void* data); + + UInt32 GetSendBufferSize() const { return m_SocketStream.SendBuffer().GetAllocatedSize(); } + UInt32 GetRecvBufferSize() const { return m_SocketStream.RecvBuffer().GetAllocatedSize(); } + + private: + Mutex m_SendMutex; + Mutex m_RecvMutex; + NetworkMessage m_PendingMessage; + void* m_PendingMessageData; + bool m_LocallyAllocatedMessageData; + bool m_HasPendingMessage; +#if UNITY_FLASH + BufferedSocketStream m_SocketStream; +#elif UNITY_WINRT + BufferedSocketStream m_SocketStream; +#else + ThreadedSocketStream m_SocketStream; +#endif + }; + + virtual ~GeneralConnection (); + + bool IsConnected () { return !m_Connections.empty(); } + void SetLogEnabled (bool v) { m_LogEnabled = v; } + bool IsLogEnabled () const { return m_LogEnabled; } + void DisconnectAll (); + + void SendMessage (UInt32 guid, MessageID id, const void* data, UInt32 size); + + typedef void (*MessageHandlerFunc) (const void* data, UInt32 size, UInt32 guid); + void RegisterMessageHandler (MessageID messageID, MessageHandlerFunc func); + void UnregisterMessageHandler (MessageID messageID, MessageHandlerFunc func); + + typedef void (*ConnectionHandlerFunc) (UInt32 guid); + void RegisterDisconnectionHandler( ConnectionHandlerFunc func ); + void RegisterConnectionHandler( ConnectionHandlerFunc func ); + void UnregisterDisconnectionHandler( ConnectionHandlerFunc func ); + void UnregisterConnectionHandler( ConnectionHandlerFunc func ); + + virtual void WaitForFinish(); + + UInt32 GetLocalGuid() const; + + static void RunningUnitTest() { ms_RunningUnitTests = true;} + +protected: + GeneralConnection (); + + bool HasBytesToSend() const; + + static void Initialize (); + static void Cleanup (); + + void RegisterConnection (UInt32 guid, TSocketHandle socketHandle); + void Disconnect (UInt32 guid); + + Connection* GetConnection(UInt32 guid); + + virtual bool IsServer () = 0; + + void Poll (); + +protected: + std::string m_LocalIP; + +#if ENABLE_MULTICAST + MulticastSocket m_MulticastSocket; +#endif + + typedef std::map< int, Connection* > ConnectionMap; + ConnectionMap m_Connections; + + std::map< MessageID, MessageHandlerFunc > m_HandlerMap; + std::vector< ConnectionHandlerFunc > m_ConnectionHandlers; + std::vector< ConnectionHandlerFunc > m_DisconnectionHandlers; + + UInt32 m_LocalGuid; + + bool m_LogEnabled; + + static bool ms_RunningUnitTests; + static int ms_Version; +}; + +#endif // ENABLE_PLAYERCONNECTION diff --git a/Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h b/Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h new file mode 100644 index 0000000..15f5714 --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h @@ -0,0 +1,56 @@ +#pragma once + +#include "Runtime/Threads/AtomicOps.h" +#include "Runtime/Network/SocketConsts.h" +#if UNITY_WIN +#include <winsock2.h> +#include <ws2tcpip.h> +#elif UNITY_XENON +#include <xtl.h> +typedef int socklen_t; +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <errno.h> +#endif +#if UNITY_PS3 +#include <sys/time.h> +#include <netex/net.h> +#include <netex/errno.h> +#define SOCK_ERROR(s) ((s) < 0) +#elif UNITY_WINRT +#define SOCK_ERROR(s) ((s) == nullptr) +#else +#define SOCK_ERROR(s) ((s) == -1) +#endif + + +static const ProfileTimeFormat kTimeMillisecond = 1000000ULL; +static const ProfileTimeFormat kTimeSecond = 1000000000ULL; +static const ProfileTimeFormat kPlayerConnectionInitialWaitTimeout = 5*kTimeSecond; + +#if UNITY_FLASH +extern "C" void Ext_GetSocketPolicyFile(const char* ipString); +extern int g_AlchemySocketErrno; +extern "C" int flash_socket(int domain, int type, int protocol); +extern "C" int flash_connect(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen); +extern "C" int flash_close(int sockfd); +extern "C" ssize_t flash_recvfrom (int sockfd, void* buffer, size_t length, int flags, struct sockaddr* address, socklen_t* address_len); +extern "C" ssize_t flash_sendto(int, const void *, size_t, int, const struct sockaddr*, socklen_t); +extern "C" int flash_setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len); +#define setsockopt flash_setsockopt +#define recvfrom flash_recvfrom +#define sendto flash_sendto +#define socket flash_socket +#define connect flash_connect +#define close flash_close +#endif + +static UInt32 NextGUID() +{ + static volatile int guid_counter = 0; + return (UInt32) AtomicIncrement(&guid_counter); +} diff --git a/Runtime/Network/PlayerCommunicator/PlayerConnection.cpp b/Runtime/Network/PlayerCommunicator/PlayerConnection.cpp new file mode 100644 index 0000000..04beece --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/PlayerConnection.cpp @@ -0,0 +1,535 @@ +#include "UnityPrefix.h" + +#if ENABLE_PLAYERCONNECTION + +#include "PlayerConnection.h" +#include "Runtime/Misc/SystemInfo.h" + +#include "Runtime/Serialize/FileCache.h" +#include "Runtime/Serialize/CacheWrap.h" + +#include "Runtime/Network/NetworkUtility.h" +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/Math/Random/rand.h" +#include "Runtime/Utilities/PathNameUtility.h" +#include "Runtime/Utilities/PlayerPrefs.h" +#include "Runtime/Threads/Thread.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h" +#include "Runtime/Network/SocketConsts.h" + +#if UNITY_ANDROID +#include "PlatformDependent/AndroidPlayer/EntryPoint.h" +#include "PlatformDependent/AndroidPlayer/DVMCalls.h" +#endif + +#define ALL_INTERFACES_IP "0.0.0.0" + +const char* kPlayerConnectionConfigFile = "PlayerConnectionConfigFile"; + +PlayerConnection* PlayerConnection::ms_Instance = NULL; + +PlayerConnection::PlayerConnection(const std::string& dataPath, unsigned short multicastPort, bool enableDebugging) +: GeneralConnection() +, m_WaitingForPlayerConnectionBeforeStartingPlayback(false) +#if ENABLE_LISTEN_SOCKET + , m_ListenSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) +#endif +#if UNITY_ANDROID +, m_UnixSocket(AF_LOCAL, SOCK_STREAM, 0) +#endif +{ + ABSOLUTE_TIME_INIT(m_LastMulticast); + + m_IsPlayerConnectionEnabled = false; + + bool hasConfigFile = ReadConfigFile(dataPath); + if (!hasConfigFile) + m_AllowDebugging = (enableDebugging? 1: 0); + + if (!PLATFORM_SUPPORTS_PLAYERCONNECTION_LISTENING && !hasConfigFile) + { + printf_console("PlayerConnection disabled - listening mode not supported\n"); + return; + } + + m_IsPlayerConnectionEnabled = true; + + if (m_InitiateMode == kPlayerConnectionInitiateByConnecting) + { + for (int i = 0; i < m_NumIPs; ++i) + { + m_ConnectToIP = m_ConnectToIPList[i]; +#if UNITY_FLASH + Ext_GetSocketPolicyFile(m_ConnectToIP.c_str()); +#endif + printf_console("Connecting directly to [%s]...\n", m_ConnectToIP.c_str()); + // Try to connect to next IP + Poll(); + if (IsConnected()) + { + break; + } + } + + if (!IsConnected()) + { + ErrorString("Connecting to host failed, aborting playback"); +#if UNITY_XENON + XLaunchNewImage(XLAUNCH_KEYWORD_DEFAULT_APP, 0); +#elif !UNITY_FLASH // Cant handle exit + exit(1); +#endif + } + + return; + } + + // so we are in listening mode. + + CreateListenSocket (); +#if UNITY_ANDROID + CreateUnixSocket(); +#endif + m_HostName = GetHostName (); + std::replace (m_HostName.begin (), m_HostName.end (), ' ', '_'); + m_WhoAmI = ConstructWhoamiString (); + InitializeMulticastAddress (multicastPort); + + if (m_WaitingForPlayerConnectionBeforeStartingPlayback) + { + ABSOLUTE_TIME startTime = START_TIME; + printf_console("Waiting for connection from host on [%s:%i]...\n", m_LocalIP.c_str(), (int)m_ListenPort); + + // Try to connect for some time + while((GetProfileTime(ELAPSED_TIME(startTime)) < kPlayerConnectionInitialWaitTimeout) && (!IsConnected())) + { + Poll(); + Thread::Sleep(0.05); + } + } + + if (!IsConnected() && m_WaitingForPlayerConnectionBeforeStartingPlayback) + printf_console("Timed out. Continuing without host connection.\n"); +} + +std::string PlayerConnection::ConstructWhoamiString () +{ + std::string runtimeAndHostName = Format ("%s(%s)", + systeminfo::GetRuntimePlatformString ().c_str (), + m_HostName.c_str ()); + UInt32 flags = (ImmediateConnect () ? kRequestImmediateConnect : 0); + flags |= (ENABLE_PROFILER ? kSupportsProfile : 0); + std::string whoAmI = Format (SERVER_IDENTIFICATION_FORMAT, + m_LocalIP.c_str (), (UInt32)m_ListenPort, + flags, m_LocalGuid, + m_EditorGuid, ms_Version, + runtimeAndHostName.c_str (), + m_AllowDebugging); + return whoAmI; +} + +void PlayerConnection::InitializeMulticastAddress (UInt16 multicastPort) +{ + Assert (m_InitiateMode == kPlayerConnectionInitiateByListening); + +#if !UNITY_FLASH + // We use broadcast in case of XBOX360 or AdHoc connection + // For AdHoc connections we need to specify Auto IP broadcast address instead of 255.255.255.255 + if (UNITY_XENON || m_LocalIP.find("169.254") == 0) + { + const char* broadcastAddress = (UNITY_XENON || UNITY_BB10) ? "255.255.255.255" : "169.254.255.255"; + if (!m_MulticastSocket.Initialize(broadcastAddress, multicastPort)) + ErrorString("Unable to setup multicast socket for player connection."); + if (!m_MulticastSocket.SetBroadcast(true)) + ErrorString("Unable to set broadcast mode for player connection socket."); + printf_console("Broadcasting \"%s\" to [%s:%i]...\n", m_WhoAmI.c_str(), broadcastAddress, (int)multicastPort); + } + // For all other cases we use multicast address + else + { + if (!m_MulticastSocket.Initialize(PLAYER_MULTICAST_GROUP, multicastPort)) + ErrorString("Unable to setup multicast socket for player connection."); + printf_console("Multi-casting \"%s\" to [%s:%i]...\n", m_WhoAmI.c_str(), PLAYER_MULTICAST_GROUP, (int)multicastPort); + #if UNITY_EDITOR + m_MulticastSocket.SetTTL(ms_RunningUnitTests ? 0 : 31); + #else + m_MulticastSocket.SetTTL(31); + #endif + m_MulticastSocket.SetLoop(true); + } + + +#endif +} + + +void PlayerConnection::CreateListenSocket () +{ + Assert (m_InitiateMode == kPlayerConnectionInitiateByListening); + + // create a random listen port (will be send out with the multicast ping) + Rand r (GetProfileTime(START_TIME)); + m_ListenPort = PLAYER_LISTEN_PORT + (r.Get() & PLAYER_PORT_MASK); + +#if ENABLE_LISTEN_SOCKET + InitializeListenSocket(m_ListenSocket, ALL_INTERFACES_IP, m_ListenPort); +#endif +} + +#if ENABLE_LISTEN_SOCKET +void PlayerConnection::InitializeListenSocket(ServerSocket& socket, const std::string& localIP, int listenPort) +{ + printf_console("PlayerConnection initialized network socket : %s %i\n", localIP.c_str(), listenPort); + socket.StartListening(localIP.c_str(), listenPort, false); +} +#endif + +#if UNITY_ANDROID +void PlayerConnection::CreateUnixSocket () +{ + Assert (m_InitiateMode == kPlayerConnectionInitiateByListening); + InitializeUnixSocket (m_UnixSocket, Format("Unity-%s", DVM::GetPackageName())); +} +#endif + +#if UNITY_ANDROID +void PlayerConnection::InitializeUnixSocket (ServerSocket& socket, const std::string& name) +{ + printf_console("PlayerConnection initialized unix socket : %s\n", name.c_str()); + size_t len = name.length(); + + struct sockaddr_un address; + Assert (len < sizeof(address.sun_path)); + memset(&address, 0, sizeof(sockaddr_un)); + memcpy(address.sun_path + 1, name.data(), len); + address.sun_path[0] = 0; + address.sun_family = AF_LOCAL; + + socklen_t address_len = offsetof(struct sockaddr_un, sun_path) + len + 1; + socket.StartListening((const sockaddr *) &address, address_len, false); +} +#endif + +bool PlayerConnection::ReadConfigFile (const std::string& dataPath) +{ + m_InitiateMode = kPlayerConnectionInitiateByListening; + m_EditorGuid = -1; + m_AllowDebugging = 0; + m_EnableProfiler = 0; + m_WaitingForPlayerConnectionBeforeStartingPlayback = 0; + int tmpWaiting = 0; + + std::string configFile = AppendPathName(dataPath, kPlayerConnectionConfigFile); + if (!IsFileCreated(configFile)) + return false; + + InputString confData; + ReadStringFromFile(&confData, configFile); + char tmp[100]; + + if (sscanf(confData.c_str(), PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN, (unsigned*)&m_EditorGuid, &m_AllowDebugging, &tmpWaiting, &m_EnableProfiler) == 4) + { + m_WaitingForPlayerConnectionBeforeStartingPlayback = tmpWaiting; + m_InitiateMode = kPlayerConnectionInitiateByListening; + return true; + } + + m_NumIPs = sscanf(confData.c_str(), PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_LIST, m_ConnectToIPList[0], m_ConnectToIPList[1], m_ConnectToIPList[2], + m_ConnectToIPList[3], m_ConnectToIPList[5], m_ConnectToIPList[6], m_ConnectToIPList[7], m_ConnectToIPList[8], m_ConnectToIPList[9]); + + if (m_NumIPs > 0) + { + m_InitiateMode = kPlayerConnectionInitiateByConnecting; + return true; + } + + ErrorString(Format("PlayerConnection config should be in the format: \"%s\" or \"%s\"", PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN_PRINT, PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_PRINT)); + return false; +} + +PlayerConnection& PlayerConnection::Get() +{ + return *ms_Instance; +} + +void PlayerConnection::Initialize (const std::string& dataPath, bool enableDebugging) +{ + if (ms_Instance == NULL) + { + SET_ALLOC_OWNER(NULL); + printf_console("PlayerConnection initialized from %s (debug = %i)\n", dataPath.c_str(), enableDebugging); + GeneralConnection::Initialize(); + ms_Instance = new PlayerConnection(dataPath, PLAYER_MULTICAST_PORT, enableDebugging); + } + else + { + if (ms_Instance->m_IsPlayerConnectionEnabled) + { + switch (ms_Instance->m_InitiateMode) + { + case kPlayerConnectionInitiateByListening: + printf_console("PlayerConnection already initialized - listening to [%s:%i]\n", ms_Instance->m_LocalIP.c_str(), (int)ms_Instance->m_ListenPort); + break; + case kPlayerConnectionInitiateByConnecting: + printf_console("PlayerConnection already initialized - connecting to [%s:%i]\n", ms_Instance->m_ConnectToIP.c_str(), PLAYER_DIRECTCONNECT_PORT); + break; + default: + printf_console("PlayerConnection already initialized - unknown mode\n"); + break; + } + } + else + { + printf_console("PlayerConnection already initialized, but disabled\n"); + } + } +} + +void PlayerConnection::Cleanup () +{ + Assert(ms_Instance != NULL); + delete ms_Instance; + ms_Instance = NULL; + GeneralConnection::Cleanup(); +} + +void PlayerConnection::PollListenMode() +{ + Assert (m_InitiateMode == kPlayerConnectionInitiateByListening); +#if ENABLE_LISTEN_SOCKET + if(!m_IsPlayerConnectionEnabled ) + return; + + if (!IsConnected() || GetProfileTime(ELAPSED_TIME(m_LastMulticast)) > 1*kTimeSecond) + { + TSocketHandle socketHandle; +#if UNITY_WINRT + if(m_ListenSocket.IsListening() && !SOCK_ERROR(socketHandle = m_ListenSocket.Accept())) + { + printf_console("PlayerConnection accepted from WinRT socket\n"); + CreateAndReportConnection(socketHandle); + } +#endif +#if UNITY_ANDROID + if (m_UnixSocket.IsListening() && !SOCK_ERROR(socketHandle = m_UnixSocket.Accept())) + { + printf_console("PlayerConnection accepted from unix socket\n"); + CreateAndReportConnection(socketHandle); + } +#endif +#if !UNITY_WINRT + // player looking for connections + struct sockaddr_in remoteAddr; + socklen_t remoteAddrLen = sizeof(remoteAddr); + if(m_ListenSocket.IsListening() && !SOCK_ERROR(socketHandle = m_ListenSocket.Accept((sockaddr*)&remoteAddr, &remoteAddrLen))) + { + printf_console("PlayerConnection accepted from [%s]\n", InAddrToIP(&remoteAddr).c_str()); + CreateAndReportConnection(socketHandle); + } +#endif + + // broadcast ip and port with 1 sec interval + // 10ms interval if immediate connect is set + UInt64 interval = 1*kTimeSecond; + if (!IsConnected() && ImmediateConnect()) + interval = 10*kTimeMillisecond; + + if (GetProfileTime(ELAPSED_TIME(m_LastMulticast)) > interval) + { + m_LastMulticast = START_TIME; + m_MulticastSocket.Send(m_WhoAmI.c_str (), m_WhoAmI.length () + 1); + } + } +#endif +} + +void PlayerConnection::CreateAndReportConnection(TSocketHandle socketHandle) +{ + RegisterConnection(NextGUID(), socketHandle); +} + +void PlayerConnection::PollConnectMode() +{ + Assert (m_InitiateMode == kPlayerConnectionInitiateByConnecting); + + if(!m_IsPlayerConnectionEnabled ) + return; + + if (IsConnected()) + return; + + int port = PLAYER_DIRECTCONNECT_PORT; + TSocketHandle socketHandle; + if (SOCK_ERROR(socketHandle = ::Socket::Connect(m_ConnectToIP.c_str(), port))) + { + ErrorStringMsg("Connect failed for direct socket. Ip=%s, port=%d", m_ConnectToIP.c_str(), port); + return; + } + CreateAndReportConnection(socketHandle); +} + + +void PlayerConnection::Poll() +{ + GeneralConnection::Poll(); + + switch(m_InitiateMode) + { + case kPlayerConnectionInitiateByListening: + PollListenMode (); + break; + case kPlayerConnectionInitiateByConnecting: + PollConnectMode (); + break; + } +} + +static int custom_asprintf(char** buffer, const char* log, va_list alist) +{ + va_list list, sizelist; + va_copy (list, alist); + va_copy (sizelist, alist); + int result = 0; + +#if USE_WINSOCK_APIS || UNITY_WINRT + int bufferSize = _vscprintf(log, list) + 1; + *buffer = (char *)UNITY_MALLOC_ALIGNED(kMemUtility, bufferSize, 4); + result = vsnprintf(*buffer, bufferSize, log, list); +#elif UNITY_PS3 || defined(__GNUC__) + int bufferSize = vsnprintf(0, 0, log, sizelist) + 1; + *buffer = (char *)UNITY_MALLOC_ALIGNED(kMemUtility, bufferSize, 4);; + result = vsnprintf(*buffer, bufferSize, log, list); +#else +#error "Not implemented" +#endif + + va_end (sizelist); + va_end (list); + return result; +} + +void LogToPlayerConnectionMessage(LogType logType, PlayerConnection::MessageID msgId, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + + PlayerConnection& pc = PlayerConnection::Get(); + if (pc.IsConnected() && pc.IsLogEnabled()) + { + + // don't try to recursively sent logs from inside player connection over player connection + pc.SetLogEnabled (false); + + char* buffer = NULL; + int len = custom_asprintf(&buffer, log, list); + + if (len >= 0 && buffer && buffer[0] != 0) + PlayerConnection::Get().SendMessage(ANY_PLAYERCONNECTION, msgId, buffer, len); + + if (buffer) + UNITY_FREE (kMemUtility, buffer); + + pc.SetLogEnabled (true); + } + va_end (list); +} + +bool PlainLogToPlayerConnection (LogType logType, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + LogToPlayerConnectionMessage(logType, PlayerConnection::kLogMessage, log, list); + va_end (list); + return true; +} + +bool CleanLogToPlayerConnection (LogType logType, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + LogToPlayerConnectionMessage(logType, PlayerConnection::kCleanLogMessage, log, list); + va_end (list); + return true; +} + +void InstallPlayerConnectionLogging (bool install) +{ + if (install) + { + SetLogEntryHandler(&PlainLogToPlayerConnection); + AddCleanLogEntryHandler(&CleanLogToPlayerConnection); + } + else + { + SetLogEntryHandler(NULL); + } +} + +void TransferFileOverPlayerConnection(const std::string& fname, void* body, unsigned int length, void* header, unsigned int headerLength) +{ +#if !UNITY_EDITOR + printf_console("about to send file over playerconnection %s with length %d\n",fname.c_str(),length); + + dynamic_array<UInt8> buffer; + + MemoryCacheWriter memoryCache (buffer); + CachedWriter writeCache; + + unsigned int fnameLength = fname.length(); + + unsigned int fnameLengthLE = fnameLength; + unsigned int lengthLE = length + headerLength; + SwapEndianBytesNativeToLittle(fnameLengthLE); + SwapEndianBytesNativeToLittle(lengthLE); + + writeCache.InitWrite (memoryCache); + writeCache.Write(&fnameLengthLE, sizeof(fnameLengthLE)); + writeCache.Write((void*)fname.c_str(), fnameLength); + writeCache.Write(&lengthLE, sizeof(lengthLE)); + if (headerLength > 0) + writeCache.Write(header, headerLength); + writeCache.Write(body, length); + + writeCache.CompleteWriting(); + + PlayerConnection::Get().SendMessage(ANY_PLAYERCONNECTION, GeneralConnection::kFileTransferMessage, &buffer[0], buffer.size()); + + // ugly hack to fix gfx tests + PlayerConnection& playercnx = PlayerConnection::Get(); + while (playercnx.IsTestrigMode()) + { + playercnx.Poll(); + if (!playercnx.HasBytesToSend()) + break; + + Thread::Sleep(0.005); + } + +#endif //!UNITY_EDITOR +} + +void NotifyFileReadyOverPlayerConnection(const std::string& fname) +{ +#if !UNITY_EDITOR + dynamic_array<UInt8> buffer; + + MemoryCacheWriter memoryCache (buffer); + CachedWriter writeCache; + + unsigned int fnameLength = fname.length(); + unsigned int fnameLengthLE = fnameLength; + SwapEndianBytesNativeToLittle(fnameLengthLE); + + writeCache.InitWrite (memoryCache); + writeCache.Write(&fnameLengthLE, sizeof(fnameLengthLE)); + writeCache.Write((void*)fname.c_str(), fnameLength); + + writeCache.CompleteWriting(); + + PlayerConnection::Get().SendMessage(ANY_PLAYERCONNECTION, GeneralConnection::kFileReadyMessage, &buffer[0], buffer.size()); +#endif //!UNITY_EDITOR +} + + +#endif // ENABLE_PLAYERCONNECTION diff --git a/Runtime/Network/PlayerCommunicator/PlayerConnection.h b/Runtime/Network/PlayerCommunicator/PlayerConnection.h new file mode 100644 index 0000000..07e2159 --- /dev/null +++ b/Runtime/Network/PlayerCommunicator/PlayerConnection.h @@ -0,0 +1,111 @@ +#pragma once + +#if ENABLE_PLAYERCONNECTION +#define ENABLE_LISTEN_SOCKET (!UNITY_FLASH) + +#include "GeneralConnection.h" +#include "Runtime/Serialize/SwapEndianBytes.h" + + +#if UNITY_WIN +#include <winsock2.h> +#include <ws2tcpip.h> +#elif UNITY_XENON +#include <xtl.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <errno.h> +#endif +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/Threads/Mutex.h" +#if UNITY_ANDROID +#include <sys/un.h> +#endif + +// flags are bits denoting capabilities of the broadcaster +enum PlayerConnectionInitiateMode + { + kPlayerConnectionInitiateByListening, + kPlayerConnectionInitiateByConnecting + }; + +class PlayerConnection : public GeneralConnection +{ +public: + PlayerConnection (const std::string& dataPath = "", unsigned short multicastPort = PLAYER_MULTICAST_PORT, bool enableDebugging=false); + + static void Initialize (const std::string& dataPath, bool enableDebugging=false); + static void Cleanup (); + + // Singleton accessor for playerconnection + static PlayerConnection& Get (); + static PlayerConnection* ms_Instance; + + void Poll (); + inline bool AllowDebugging () { return (0 != m_AllowDebugging); } + bool ShouldEnableProfiler() { return m_EnableProfiler != 0; } + + // ugly hack to fix gfx tests + inline bool IsTestrigMode() { return (m_InitiateMode == kPlayerConnectionInitiateByConnecting); } + inline bool HasBytesToSend() { return GeneralConnection::HasBytesToSend(); } + +private: + virtual bool IsServer() { return true; } + bool ReadConfigFile (const std::string& dataPath); + void CreateListenSocket (); + void CreateUnixSocket(); + void InitializeMulticastAddress (UInt16 multicastPort); + void PollListenMode (); + void PollConnectMode (); + void CreateAndReportConnection(TSocketHandle socketHandle); + std::string ConstructWhoamiString (); + + bool ImmediateConnect () const + { + return ms_RunningUnitTests + || m_WaitingForPlayerConnectionBeforeStartingPlayback; + } + +#if ENABLE_LISTEN_SOCKET + static void InitializeListenSocket(ServerSocket& socket, const std::string& localIP, int listenPort); +#endif +#if UNITY_ANDROID + static void InitializeUnixSocket(ServerSocket& socket, const std::string& socketname); +#endif + + +private: + bool m_IsPlayerConnectionEnabled; + PlayerConnectionInitiateMode m_InitiateMode; + bool m_WaitingForPlayerConnectionBeforeStartingPlayback; + + // player specific + unsigned short m_ListenPort; + std::string m_HostName; + std::string m_WhoAmI; +#if ENABLE_LISTEN_SOCKET + ServerSocket m_ListenSocket; // player only +#endif +#if UNITY_ANDROID + ServerSocket m_UnixSocket; // local +#endif + + UInt32 m_EditorGuid; + int m_AllowDebugging; + int m_EnableProfiler; + int m_NumIPs; + std::string m_ConnectToIP; + char m_ConnectToIPList[10][16]; + + ABSOLUTE_TIME m_LastMulticast; +}; + +void InstallPlayerConnectionLogging (bool install); +void TransferFileOverPlayerConnection(const std::string& fname, void* body, unsigned int length, void* header = 0, unsigned int headerLength = 0); +void NotifyFileReadyOverPlayerConnection(const std::string& fname); + +#endif // ENABLE_PLAYERCONNECTION |