Newer
Older
CGTrack / Assets / Oculus / Platform / Samples / VrVoiceChat / Scripts / P2PManager.cs
namespace Oculus.Platform.Samples.VrVoiceChat
{
	using UnityEngine;
	using System;
	using Oculus.Platform;
	using Oculus.Platform.Models;

	// Helper class to manage a Peer-to-Peer connection to the other user.
	// The connection is used to send and received the Transforms for the
	// Avatars.  The Transforms are sent via unreliable UDP at a fixed
	// frequency.
	public class P2PManager
	{
		// number of seconds to delay between transform updates
		private static readonly float UPDATE_DELAY = 0.1f;

		// the ID of the remote player we expect to be connected to
		private ulong m_remoteID;

		// the result of the last connection state update message
		private PeerConnectionState m_state = PeerConnectionState.Unknown;

		// the next time to send an updated transform to the remote User
		private float m_timeForNextUpdate;

		// the size of the packet we are sending and receiving
		private static readonly byte PACKET_SIZE = 29;

		// packet format type just in case we want to add new future packet types
		private static readonly byte PACKET_FORMAT = 0;

		// reusable buffer to serialize the Transform into
		private readonly byte[] sendTransformBuffer = new byte[PACKET_SIZE];

		// reusable buffer to deserialize the Transform into
		private readonly byte[] receiveTransformBuffer = new byte[PACKET_SIZE];

		// the last received position update
		private Vector3 receivedPosition;

		// the previous received position to interpolate from
		private Vector3 receivedPositionPrior;

		// the last received rotation update
		private Quaternion receivedRotation;

		// the previous received rotation to interpolate from
		private Quaternion receivedRotationPrior;

		// when the last transform was received
		private float receivedTime;

		public P2PManager(Transform initialHeadTransform)
		{
			receivedPositionPrior = receivedPosition = initialHeadTransform.localPosition;
			receivedRotationPrior = receivedRotation = initialHeadTransform.localRotation;

			Net.SetPeerConnectRequestCallback(PeerConnectRequestCallback);
			Net.SetConnectionStateChangedCallback(ConnectionStateChangedCallback);
		}

		#region Connection Management

		public void ConnectTo(ulong userID)
		{
			m_remoteID = userID;

			// ID comparison is used to decide who calls Connect and who calls Accept
			if (PlatformManager.MyID < userID)
			{
				Net.Connect(userID);
			}
		}

		public void Disconnect()
		{
			if (m_remoteID != 0)
			{
				Net.Close(m_remoteID);
				m_remoteID = 0;
				m_state = PeerConnectionState.Unknown;
			}
		}

		public bool Connected
		{
			get
			{
				return m_state == PeerConnectionState.Connected;
			}
		}

		void PeerConnectRequestCallback(Message<NetworkingPeer> msg)
		{
			Debug.LogFormat("Connection request from {0}, authorized is {1}", msg.Data.ID, m_remoteID);

			if (msg.Data.ID == m_remoteID)
			{
				Net.Accept(msg.Data.ID);
			}
		}

		void ConnectionStateChangedCallback(Message<NetworkingPeer> msg)
		{
			Debug.LogFormat("Connection state to {0} changed to {1}", msg.Data.ID, msg.Data.State);

			if (msg.Data.ID == m_remoteID)
			{
				m_state = msg.Data.State;

				if (m_state == PeerConnectionState.Timeout &&
					// ID comparison is used to decide who calls Connect and who calls Accept
					PlatformManager.MyID < m_remoteID)
				{
					// keep trying until hangup!
					Net.Connect(m_remoteID);
				}
			}

			PlatformManager.SetBackgroundColorForState();
		}

		#endregion

		#region Send Update

		public bool ShouldSendHeadUpdate
		{
			get
			{
				return Time.time >= m_timeForNextUpdate && m_state == PeerConnectionState.Connected;
			}
		}

		public void SendHeadTransform(Transform headTransform)
		{
			m_timeForNextUpdate = Time.time + UPDATE_DELAY;

			sendTransformBuffer[0] = PACKET_FORMAT;
			int offset = 1;

			PackFloat(headTransform.localPosition.x, sendTransformBuffer, ref offset);
			PackFloat(headTransform.localPosition.y, sendTransformBuffer, ref offset);
			PackFloat(headTransform.localPosition.z, sendTransformBuffer, ref offset);
			PackFloat(headTransform.localRotation.x, sendTransformBuffer, ref offset);
			PackFloat(headTransform.localRotation.y, sendTransformBuffer, ref offset);
			PackFloat(headTransform.localRotation.z, sendTransformBuffer, ref offset);
			PackFloat(headTransform.localRotation.w, sendTransformBuffer, ref offset);

			Net.SendPacket(m_remoteID, sendTransformBuffer, SendPolicy.Unreliable);
		}

		void PackFloat(float f, byte[] buf, ref int offset)
		{
			Buffer.BlockCopy(BitConverter.GetBytes(f), 0, buf, offset, 4);
			offset = offset + 4;
		}

		#endregion

		#region Receive Update

		public void GetRemoteHeadTransform(Transform headTransform)
		{
			bool hasNewTransform = false;

			Packet packet;
			while ((packet = Net.ReadPacket()) != null)
			{
				if (packet.Size != PACKET_SIZE)
				{
					Debug.Log("Invalid packet size: " + packet.Size);
					continue;
				}

				packet.ReadBytes(receiveTransformBuffer);

				if (receiveTransformBuffer[0] != PACKET_FORMAT)
				{
					Debug.Log("Invalid packet type: " + packet.Size);
					continue;
				}
				hasNewTransform = true;
			}

			if (hasNewTransform)
			{
				receivedPositionPrior = receivedPosition;
				receivedPosition.x = BitConverter.ToSingle(receiveTransformBuffer, 1);
				receivedPosition.y = BitConverter.ToSingle(receiveTransformBuffer, 5);
				receivedPosition.z = BitConverter.ToSingle(receiveTransformBuffer, 9);

				receivedRotationPrior = receivedRotation;
				receivedRotation.x = BitConverter.ToSingle(receiveTransformBuffer, 13);
				receivedRotation.y = BitConverter.ToSingle(receiveTransformBuffer, 17) * -1.0f;
				receivedRotation.z = BitConverter.ToSingle(receiveTransformBuffer, 21);
				receivedRotation.w = BitConverter.ToSingle(receiveTransformBuffer, 25) * -1.0f;

				receivedTime = Time.time;
			}

			// since we're receiving updates at a slower rate than we render,
			// interpolate to make the motion look smoother
			float completed = Math.Min(Time.time - receivedTime, UPDATE_DELAY) / UPDATE_DELAY;
			headTransform.localPosition =
				Vector3.Lerp(receivedPositionPrior, receivedPosition, completed);
			headTransform.localRotation =
				Quaternion.Slerp(receivedRotationPrior, receivedRotation, completed);
		}

		#endregion
	}
}