summaryrefslogtreecommitdiff
path: root/Runtime/Network/PlayerCommunicator
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Network/PlayerCommunicator')
-rw-r--r--Runtime/Network/PlayerCommunicator/EditorConnection.cpp335
-rw-r--r--Runtime/Network/PlayerCommunicator/EditorConnection.h77
-rw-r--r--Runtime/Network/PlayerCommunicator/GeneralConnection.cpp666
-rw-r--r--Runtime/Network/PlayerCommunicator/GeneralConnection.h266
-rw-r--r--Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h56
-rw-r--r--Runtime/Network/PlayerCommunicator/PlayerConnection.cpp535
-rw-r--r--Runtime/Network/PlayerCommunicator/PlayerConnection.h111
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