474 lines
13 KiB
C#
474 lines
13 KiB
C#
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<int, P2PNetworkBall> activeBalls = new Dictionary<int, P2PNetworkBall>();
|
|
}
|
|
|
|
public delegate float StartTimeOffer(float remoteTime);
|
|
|
|
private readonly Dictionary<ulong, RemotePlayerData> m_remotePlayers = new Dictionary<ulong, RemotePlayerData>();
|
|
|
|
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<int, P2PNetworkBall> m_localBalls = new Dictionary<int, P2PNetworkBall>();
|
|
|
|
private readonly byte[] readBuffer = new byte[353];
|
|
|
|
private readonly Dictionary<ulong, List<float>> m_remoteSyncTimeCache = new Dictionary<ulong, List<float>>();
|
|
|
|
private readonly Dictionary<ulong, float> m_remoteSentTimeCache = new Dictionary<ulong, float>();
|
|
|
|
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<Oculus.Platform.Models.NetworkingPeer> 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<Oculus.Platform.Models.NetworkingPeer> 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>();
|
|
}
|
|
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<ulong, RemotePlayerData> 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<P2PNetworkBall>();
|
|
}
|
|
|
|
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<ulong, RemotePlayerData> 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>();
|
|
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<ulong, RemotePlayerData> 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;
|
|
}
|
|
}
|
|
}
|