// %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; using System.Collections; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using UnityEngine; namespace MagicLeap.Spectator { public class Client : MonoBehaviour { #region Singleton private static Client _instance; public static Client Instance { get { if (_instance == null) _instance = FindObjectOfType(true); if (_instance == null) Debug.LogWarning("Client: Instance is missing or has already been destroyed"); return _instance; } } #endregion #region Enum public enum DisconnectReason { Prompted = 0, Unprompted, } #endregion #region Inspector variables [SerializeField] private string ipAddress = string.Empty; [SerializeField] private int port = 63521; [Space] [SerializeField] private int receiveBufferSize = 8192; [SerializeField] private int sendBufferSize = 8192; [Space] [SerializeField, Tooltip("How long we should wait for a connection before timing out")] private int connectionTimeout = 500; #endregion #region Private variables private TcpConnection connection = new TcpConnection(); private bool connected = false; private byte[] certData = null; #endregion #region Public events public event Action OnConnected; public event Action OnFailedToConnect; public event Action OnDisconnect; #endregion #region Public properties public bool Connected => connection.Connected; #endregion #region MonoBehaviour methods private void Awake() { certData = Resources.Load("spectator_cert").bytes; } private void OnEnable() { StartCoroutine(WaitForConnect(ipAddress, port)); } private void OnDisable() { connection.Disconnect(); if (connected) { OnDisconnect?.Invoke(DisconnectReason.Prompted, "OnDisable"); connected = false; } } private void Update() { // Check if our connection status changes if (connected) { if (!Connected) { connected = false; if (connection.Closed) OnDisconnect?.Invoke(DisconnectReason.Prompted, "Closed"); else OnDisconnect?.Invoke(DisconnectReason.Unprompted, "Error"); } } } // Make sure we disconnect if we are quitting private void OnApplicationQuit() { Debug.Log("Client: OnApplicationQuit()"); if (connected) OnDisable(); } #endregion #region Public methods public void SetIpAddress(string ipAddress) { if (Connected) { Debug.LogWarning("Client: SetIpAddress() - Client connected, disconnect before updating the connection address."); return; } this.ipAddress = ipAddress; } 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); public void ForceUnpromptedDisconnect() { connection.Disconnect(); if (connected) { connected = false; OnDisconnect?.Invoke(DisconnectReason.Unprompted, "Force"); } } #endregion #region Private methods private IEnumerator WaitForConnect(string address, int port) { var socketConnection = new TcpClient(); var connect = Task.Run(() => { int start = Environment.TickCount; try { return socketConnection.ConnectAsync(address, port).Wait(connectionTimeout); } catch (Exception ex) { Debug.LogError($"Client: WaitForConnect() - {ex.Message}"); int duration = Environment.TickCount - start; if (duration < connectionTimeout) Thread.Sleep(connectionTimeout - duration); return false; } }); yield return new WaitUntil(() => connect.IsCompleted); if (!connect.Result || !socketConnection.Connected) { OnFailedToConnect?.Invoke(); } else { try { if (!connection.SetupConnection(socketConnection, receiveBufferSize, sendBufferSize, certData)) { OnFailedToConnect?.Invoke(); } else { connected = true; OnConnected?.Invoke(); } } catch (Exception ex) { Debug.Log($"Client: TcpConnection:SetupConnection() - {ex.Message}"); OnFailedToConnect?.Invoke(); } } } #endregion } }