using System; using System.Collections.Generic; using Oculus.Platform.Models; using UnityEngine; namespace Oculus.Platform.Samples.VrHoops { public class P2PManager { private class RemotePlayerData { public PeerConnectionState state; public RemotePlayer player; public float remoteTimeOffset; public float lastReceivedBallsTime; public readonly Dictionary activeBalls = new Dictionary(); } public delegate float StartTimeOffer(float remoteTime); private readonly Dictionary m_remotePlayers = new Dictionary(); private float m_timeForNextBallUpdate; private const byte TIME_SYNC_MESSAGE = 1; private const uint TIME_SYNC_MESSAGE_SIZE = 5u; private const int TIME_SYNC_MESSAGE_COUNT = 7; private const byte START_TIME_MESSAGE = 2; private const uint START_TIME_MESSAGE_SIZE = 5u; private const byte BACKBOARD_UPDATE_MESSAGE = 3; private const uint BACKBOARD_UPDATE_MESSAGE_SIZE = 41u; private const byte LOCAL_BALLS_UPDATE_MESSAGE = 4; private const uint LOCAL_BALLS_UPDATE_MESSATE_SIZE_MAX = 353u; private const float LOCAL_BALLS_UPDATE_DELAY = 0.1f; private const byte SCORE_UPDATE_MESSAGE = 5; private const uint SCORE_UPDATE_MESSAGE_SIZE = 5u; private readonly Dictionary m_localBalls = new Dictionary(); private readonly byte[] readBuffer = new byte[353]; private readonly Dictionary> m_remoteSyncTimeCache = new Dictionary>(); private readonly Dictionary m_remoteSentTimeCache = new Dictionary(); private StartTimeOffer m_startTimeOfferCallback; public StartTimeOffer StartTimeOfferCallback { private get { return m_startTimeOfferCallback; } set { m_startTimeOfferCallback = value; } } public P2PManager() { Net.SetPeerConnectRequestCallback(PeerConnectRequestCallback); Net.SetConnectionStateChangedCallback(ConnectionStateChangedCallback); } public void UpdateNetwork() { if (m_remotePlayers.Count == 0) { return; } Packet packet; while ((packet = Net.ReadPacket()) != null) { if (m_remotePlayers.ContainsKey(packet.SenderID)) { packet.ReadBytes(readBuffer); switch (readBuffer[0]) { case 1: ReadTimeSyncMessage(packet.SenderID, readBuffer); break; case 2: ReceiveMatchStartTimeOffer(packet.SenderID, readBuffer); break; case 3: ReceiveBackboardUpdate(packet.SenderID, readBuffer); break; case 4: ReceiveBallTransforms(packet.SenderID, readBuffer, packet.Size); break; case 5: ReceiveScoredUpdate(packet.SenderID, readBuffer); break; } } } if (Time.time >= m_timeForNextBallUpdate && m_localBalls.Count > 0) { SendLocalBallTransforms(); } } public void AddRemotePlayer(RemotePlayer player) { if (!m_remotePlayers.ContainsKey(player.ID)) { m_remotePlayers[player.ID] = new RemotePlayerData(); m_remotePlayers[player.ID].state = PeerConnectionState.Unknown; m_remotePlayers[player.ID].player = player; if (PlatformManager.MyID < player.ID) { Debug.Log("P2P Try Connect to: " + player.ID); Net.Connect(player.ID); } } } public void DisconnectAll() { foreach (ulong key in m_remotePlayers.Keys) { Net.Close(key); } m_remotePlayers.Clear(); } private void PeerConnectRequestCallback(Message msg) { if (m_remotePlayers.ContainsKey(msg.Data.ID)) { Debug.LogFormat("P2P Accepting Connection request from {0}", msg.Data.ID); Net.Accept(msg.Data.ID); } else { Debug.LogFormat("P2P Ignoring unauthorized Connection request from {0}", msg.Data.ID); } } private void ConnectionStateChangedCallback(Message msg) { Debug.LogFormat("P2P {0} Connection state changed to {1}", msg.Data.ID, msg.Data.State); if (!m_remotePlayers.ContainsKey(msg.Data.ID)) { return; } m_remotePlayers[msg.Data.ID].state = msg.Data.State; switch (msg.Data.State) { case PeerConnectionState.Connected: if (PlatformManager.MyID < msg.Data.ID) { SendTimeSyncMessage(msg.Data.ID); } break; case PeerConnectionState.Timeout: if (PlatformManager.MyID < msg.Data.ID) { Net.Connect(msg.Data.ID); } break; case PeerConnectionState.Closed: m_remotePlayers.Remove(msg.Data.ID); break; } } private void SendTimeSyncMessage(ulong remoteID) { if (!m_remoteSyncTimeCache.ContainsKey(remoteID)) { m_remoteSyncTimeCache[remoteID] = new List(); } float realtimeSinceStartup = Time.realtimeSinceStartup; m_remoteSentTimeCache[remoteID] = realtimeSinceStartup; byte[] array = new byte[5] { 1, 0, 0, 0, 0 }; int offset = 1; PackFloat(realtimeSinceStartup, array, ref offset); Net.SendPacket(remoteID, array, SendPolicy.Reliable); } private void ReadTimeSyncMessage(ulong remoteID, byte[] msg) { if (!m_remoteSentTimeCache.ContainsKey(remoteID)) { SendTimeSyncMessage(remoteID); return; } int offset = 1; float num = UnpackFloat(msg, ref offset); float realtimeSinceStartup = Time.realtimeSinceStartup; float num2 = (realtimeSinceStartup - m_remoteSentTimeCache[remoteID]) / 2f; float item = realtimeSinceStartup - (num + num2); m_remoteSyncTimeCache[remoteID].Add(item); if (m_remoteSyncTimeCache[remoteID].Count < 7) { SendTimeSyncMessage(remoteID); return; } if (PlatformManager.MyID < remoteID) { SendTimeSyncMessage(remoteID); } m_remoteSyncTimeCache[remoteID].Sort(); float num3 = m_remoteSyncTimeCache[remoteID][3]; double num4 = 0.0; foreach (float item2 in m_remoteSyncTimeCache[remoteID]) { num4 += (double)item2; } num4 /= 7.0; double num5 = 0.0; foreach (float item3 in m_remoteSyncTimeCache[remoteID]) { num5 += (num4 - (double)item3) * (num4 - (double)item3); } num5 = Math.Sqrt(num5) / 7.0; num4 = 0.0; int num6 = 0; foreach (float item4 in m_remoteSyncTimeCache[remoteID]) { if ((double)Math.Abs(item4 - num3) < num5) { num4 += (double)item4; num6++; } } num4 /= (double)num6; Debug.LogFormat("Time offset to {0} is {1}", remoteID, num4); m_remoteSyncTimeCache.Remove(remoteID); m_remoteSentTimeCache.Remove(remoteID); m_remotePlayers[remoteID].remoteTimeOffset = (float)num4; OfferMatchStartTime(); } private float ShiftRemoteTime(ulong remoteID, float remoteTime) { if (m_remotePlayers.ContainsKey(remoteID)) { return remoteTime + m_remotePlayers[remoteID].remoteTimeOffset; } return remoteTime; } private void OfferMatchStartTime() { byte[] array = new byte[5] { 2, 0, 0, 0, 0 }; int offset = 1; PackFloat(StartTimeOfferCallback(0f), array, ref offset); foreach (ulong key in m_remotePlayers.Keys) { if (m_remotePlayers[key].state == PeerConnectionState.Connected) { Net.SendPacket(key, array, SendPolicy.Reliable); } } } private void ReceiveMatchStartTimeOffer(ulong remoteID, byte[] msg) { int offset = 1; float remoteTime = UnpackTime(remoteID, msg, ref offset); StartTimeOfferCallback(remoteTime); } public void SendBackboardUpdate(float time, Vector3 pos, Vector3 moveDir, Vector3 nextMoveDir) { byte[] array = new byte[41]; array[0] = 3; int offset = 1; PackFloat(time, array, ref offset); PackVector3(pos, array, ref offset); PackVector3(moveDir, array, ref offset); PackVector3(nextMoveDir, array, ref offset); foreach (KeyValuePair remotePlayer in m_remotePlayers) { if (remotePlayer.Value.state == PeerConnectionState.Connected) { Net.SendPacket(remotePlayer.Key, array, SendPolicy.Reliable); } } } private void ReceiveBackboardUpdate(ulong remoteID, byte[] msg) { int offset = 1; float remoteTime = UnpackTime(remoteID, msg, ref offset); Vector3 pos = UnpackVector3(msg, ref offset); Vector3 moveDir = UnpackVector3(msg, ref offset); Vector3 nextMoveDir = UnpackVector3(msg, ref offset); P2PNetworkGoal goal = m_remotePlayers[remoteID].player.Goal; goal.RemoteBackboardUpdate(remoteTime, pos, moveDir, nextMoveDir); } public void AddNetworkBall(GameObject ball) { m_localBalls[ball.GetInstanceID()] = ball.AddComponent(); } public void RemoveNetworkBall(GameObject ball) { m_localBalls.Remove(ball.GetInstanceID()); } private void SendLocalBallTransforms() { m_timeForNextBallUpdate = Time.time + 0.1f; int num = 5 + m_localBalls.Count * 29; byte[] array = new byte[num]; array[0] = 4; int offset = 1; PackFloat(Time.realtimeSinceStartup, array, ref offset); foreach (P2PNetworkBall value in m_localBalls.Values) { PackBool(value.IsHeld(), array, ref offset); PackInt32(value.gameObject.GetInstanceID(), array, ref offset); PackVector3(value.transform.localPosition, array, ref offset); PackVector3(value.velocity, array, ref offset); } foreach (KeyValuePair remotePlayer in m_remotePlayers) { if (remotePlayer.Value.state == PeerConnectionState.Connected) { Net.SendPacket(remotePlayer.Key, array, SendPolicy.Unreliable); } } } private void ReceiveBallTransforms(ulong remoteID, byte[] msg, ulong msgLength) { int offset = 1; float num = UnpackTime(remoteID, msg, ref offset); if (num < m_remotePlayers[remoteID].lastReceivedBallsTime) { return; } m_remotePlayers[remoteID].lastReceivedBallsTime = num; while (offset != (int)msgLength) { bool isHeld = UnpackBool(msg, ref offset); int key = UnpackInt32(msg, ref offset); Vector3 pos = UnpackVector3(msg, ref offset); Vector3 vel = UnpackVector3(msg, ref offset); if (!m_remotePlayers[remoteID].activeBalls.ContainsKey(key)) { P2PNetworkBall p2PNetworkBall = m_remotePlayers[remoteID].player.CreateBall().AddComponent(); p2PNetworkBall.transform.SetParent(m_remotePlayers[remoteID].player.transform.parent); m_remotePlayers[remoteID].activeBalls[key] = p2PNetworkBall; } P2PNetworkBall p2PNetworkBall2 = m_remotePlayers[remoteID].activeBalls[key]; if ((bool)p2PNetworkBall2) { p2PNetworkBall2.ProcessRemoteUpdate(num, isHeld, pos, vel); } } } public void SendScoreUpdate(uint score) { byte[] array = new byte[5] { 5, 0, 0, 0, 0 }; int offset = 1; PackUint32(score, array, ref offset); foreach (KeyValuePair remotePlayer in m_remotePlayers) { if (remotePlayer.Value.state == PeerConnectionState.Connected) { Net.SendPacket(remotePlayer.Key, array, SendPolicy.Reliable); } } } private void ReceiveScoredUpdate(ulong remoteID, byte[] msg) { int offset = 1; uint score = UnpackUint32(msg, ref offset); m_remotePlayers[remoteID].player.ReceiveRemoteScore(score); } private void PackVector3(Vector3 vec, byte[] buf, ref int offset) { PackFloat(vec.x, buf, ref offset); PackFloat(vec.y, buf, ref offset); PackFloat(vec.z, buf, ref offset); } private Vector3 UnpackVector3(byte[] buf, ref int offset) { Vector3 result = default(Vector3); result.x = UnpackFloat(buf, ref offset); result.y = UnpackFloat(buf, ref offset); result.z = UnpackFloat(buf, ref offset); return result; } private void PackQuaternion(Quaternion quat, byte[] buf, ref int offset) { PackFloat(quat.x, buf, ref offset); PackFloat(quat.y, buf, ref offset); PackFloat(quat.z, buf, ref offset); PackFloat(quat.w, buf, ref offset); } private void PackFloat(float value, byte[] buf, ref int offset) { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4); offset += 4; } private float UnpackFloat(byte[] buf, ref int offset) { float result = BitConverter.ToSingle(buf, offset); offset += 4; return result; } private float UnpackTime(ulong remoteID, byte[] buf, ref int offset) { return ShiftRemoteTime(remoteID, UnpackFloat(buf, ref offset)); } private void PackInt32(int value, byte[] buf, ref int offset) { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4); offset += 4; } private int UnpackInt32(byte[] buf, ref int offset) { int result = BitConverter.ToInt32(buf, offset); offset += 4; return result; } private void PackUint32(uint value, byte[] buf, ref int offset) { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4); offset += 4; } private uint UnpackUint32(byte[] buf, ref int offset) { uint result = BitConverter.ToUInt32(buf, offset); offset += 4; return result; } private void PackBool(bool value, byte[] buf, ref int offset) { buf[offset++] = (byte)(value ? 1u : 0u); } private bool UnpackBool(byte[] buf, ref int offset) { return buf[offset++] != 0; } } }