// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) 2022-2023 Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the Magic Leap 2 Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2 // Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein. // %COPYRIGHT_END% // --------------------------------------------------------------------- // %BANNER_END% using System.Net.Sockets; using System.Net; using UnityEngine; using System; using System.Collections; using System.Threading.Tasks; namespace MagicLeap.Spectator { public class Host : MonoBehaviour { #region Singleton private static Host _instance; public static Host Instance { get { if (_instance == null) _instance = FindObjectOfType(true); if (_instance == null) Debug.LogWarning("Host: Instance is missing or has already been destroyed"); return _instance; } } #endregion #region Classes private class MyTcpListener : TcpListener { public IPAddress Address { get; private set; } public bool IsActive => Active; public MyTcpListener(IPAddress address, int port) : base(address, port) { Address = address; } public new void Start() { Debug.Log($"Host: Starting listener"); base.Start(); } public new void Stop() { Debug.Log("Host: Stopping listener"); base.Stop(); } } #endregion #region Enum private enum State { Idle = 0, Listening, Connected, } #endregion #region Inspector variables [SerializeField] private int port = 63521; [Space] [SerializeField] private int receiveBufferSize = 8192; [SerializeField] private int sendBufferSize = 8192; #endregion #region Private variables private MyTcpListener listener = null; private TcpConnection connection = new TcpConnection(); private bool isPaused = false; private bool isConnected = false; #endregion #region Public events public event Action OnConnected; public event Action OnDisconnect; #endregion #region Public properties public bool Connected => connection.Connected; public IPAddress RemoteAddress { get; private set; } #endregion #region Application methods private void OnApplicationPause(bool pause) { Debug.Log($"Host: OnApplicationPause() - {(pause ? "Paused" : "Resumed")}"); isPaused = pause; if (listener == null) return; if (pause) { if (listener.IsActive) listener.Stop(); listener = null; } } private void OnApplicationQuit() { Debug.Log($"Host: OnApplicationQuit()"); OnDisable(); } #endregion #region MonoBehaviour methods private void OnDestroy() { Debug.Log($"Host: OnDestroy()"); OnDisable(); } private void OnEnable() { Debug.Log("Host: OnEnable()"); } private void OnDisable() { Debug.Log("Host: OnDisable()"); if (listener != null && listener.IsActive) listener.Stop(); listener = null; connection.Disconnect(); if (isConnected) { OnDisconnect?.Invoke(); isConnected = false; RemoteAddress = null; } } private void Update() { // Don't do anything if we're paused (don't know how this is happening) if (isPaused) return; // Check our listener for connections CheckConnection(); // Check if our connection status changed if (isConnected) { if (!Connected) { isConnected = false; OnDisconnect?.Invoke(); RemoteAddress = null; } } } #endregion #region Public methods public void RegisterCallback(Guid guid, Action action) => connection.RegisterCallback(guid, action); public void UnregisterCallback(Guid guid, Action action) => connection.UnregisterCallback(guid, action); public void Send(Message message) => connection.Write(message); #endregion #region Private methods bool waitingOnConnect = false; private void CheckConnection() { // Don't do anything if we are connected or waiting on a connection if (Connected || waitingOnConnect) return; // Make sure we have a listener if (listener == null) { // Make sure we are connected to a local network if (NetworkHelper.TryGetLocalIPAddress(out var address)) { listener = new MyTcpListener(address, port); listener.Start(); } // Otherwise we can't make one, break else return; } // Attempt to connect if we have any pending requests if (listener.Pending()) StartCoroutine(WaitOnConnect()); } private IEnumerator WaitOnConnect() { Debug.Log("Host: WaitOnConnect() - Attempting to connect..."); waitingOnConnect = true; TcpClient socketConnection = new(); IPAddress address = listener.Address; var accept = Task.Run(() => { try { socketConnection = listener.AcceptTcpClient(); } catch (Exception ex) { Debug.LogError($"Host: WaitOnConnect() - {ex.Message}"); } finally { listener.Stop(); } }); yield return new WaitUntil(() => accept.IsCompleted); listener = null; if (!socketConnection.Connected) { Debug.LogWarning("Host: WaitOnConnect() - Failed to connect"); waitingOnConnect = false; yield break; } if (!NetworkHelper.VerifyConnection(address, socketConnection)) { Debug.LogWarning("Host: WaitOnConnect() - Connection failed security verification"); socketConnection.Close(); waitingOnConnect = false; yield break; } if (!connection.SetupConnection(socketConnection, receiveBufferSize, sendBufferSize)) { Debug.LogWarning("Host: WaitOnConnect() - Failed to setup connection"); socketConnection.Close(); waitingOnConnect = false; yield break; } var ipEndPoint = socketConnection.Client.RemoteEndPoint as IPEndPoint; if (ipEndPoint != null) RemoteAddress = ipEndPoint.Address; Debug.Log("Host: WaitOnConnect() - Connected"); socketConnection.SendTimeout = 500; OnConnected?.Invoke(); isConnected = true; waitingOnConnect = false; } #endregion } }