123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826 |
- // ----------------------------------------------------------------------------
- // <copyright file="RegionHandler.cs" company="Exit Games GmbH">
- // Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
- // </copyright>
- // <summary>
- // The RegionHandler class provides methods to ping a list of regions,
- // to find the one with best ping.
- // </summary>
- // <author>developer@photonengine.com</author>
- // ----------------------------------------------------------------------------
- #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
- #define SUPPORTED_UNITY
- #endif
- #if UNITY_WEBGL
- #define PING_VIA_COROUTINE
- #endif
- namespace Photon.Realtime
- {
- using System;
- using System.Text;
- using System.Threading;
- using System.Net;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using ExitGames.Client.Photon;
- using System.Linq;
- #if SUPPORTED_UNITY
- using UnityEngine;
- using Debug = UnityEngine.Debug;
- #endif
- #if SUPPORTED_UNITY || NETFX_CORE
- using Hashtable = ExitGames.Client.Photon.Hashtable;
- using SupportClass = ExitGames.Client.Photon.SupportClass;
- #endif
- /// <summary>
- /// Provides methods to work with Photon's regions (Photon Cloud) and can be use to find the one with best ping.
- /// </summary>
- /// <remarks>
- /// When a client uses a Name Server to fetch the list of available regions, the LoadBalancingClient will create a RegionHandler
- /// and provide it via the OnRegionListReceived callback, as soon as the list is available. No pings were sent for Best Region selection yet.
- ///
- /// Your logic can decide to either connect to one of those regional servers, or it may use PingMinimumOfRegions to test
- /// which region provides the best ping. Alternatively the client may be set to connect to the Best Region (lowest ping), one or
- /// more regions get pinged.
- /// Not all regions will be pinged. As soon as the results are final, the client will connect to the best region,
- /// so you can check the ping results when connected to the Master Server.
- ///
- /// Regions gets pinged 5 times (RegionPinger.Attempts).
- /// Out of those, the worst rtt is discarded and the best will be counted two times for a weighted average.
- ///
- /// Usually UDP will be used to ping the Master Servers. In WebGL, WSS is used instead.
- ///
- /// It makes sense to make clients "sticky" to a region when one gets selected.
- /// This can be achieved by storing the SummaryToCache value, once pinging was done.
- /// When the client connects again, the previous SummaryToCache helps limiting the number of regions to ping.
- /// In best case, only the previously selected region gets re-pinged and if the current ping is not much worse, this one region is used again.
- /// </remarks>
- public class RegionHandler
- {
- /// <summary>The implementation of PhotonPing to use for region pinging (Best Region detection).</summary>
- /// <remarks>Defaults to null, which means the Type is set automatically.</remarks>
- public static Type PingImplementation;
- /// <summary>A list of region names for the Photon Cloud. Set by the result of OpGetRegions().</summary>
- /// <remarks>
- /// Implement ILoadBalancingCallbacks and register for the callbacks to get OnRegionListReceived(RegionHandler regionHandler).
- /// You can also put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available.
- /// </remarks>
- public List<Region> EnabledRegions { get; protected internal set; }
- private string availableRegionCodes;
- private Region bestRegionCache;
- /// <summary>
- /// When PingMinimumOfRegions was called and completed, the BestRegion is identified by best ping.
- /// </summary>
- public Region BestRegion
- {
- get
- {
- if (this.EnabledRegions == null)
- {
- return null;
- }
- if (this.bestRegionCache != null)
- {
- return this.bestRegionCache;
- }
- this.EnabledRegions.Sort((a, b) => a.Ping.CompareTo(b.Ping));
- this.bestRegionCache = this.EnabledRegions[0];
- return this.bestRegionCache;
- }
- }
- /// <summary>
- /// This value summarizes the results of pinging currently available regions (after PingMinimumOfRegions finished).
- /// </summary>
- /// <remarks>
- /// This value should be stored in the client by the game logic.
- /// When connecting again, use it as previous summary to speed up pinging regions and to make the best region sticky for the client.
- /// </remarks>
- public string SummaryToCache
- {
- get
- {
- if (this.BestRegion != null && this.BestRegion.Ping < RegionPinger.MaxMillisecondsPerPing)
- {
- return this.BestRegion.Code + ";" + this.BestRegion.Ping + ";" + this.availableRegionCodes;
- }
- return this.availableRegionCodes;
- }
- }
- /// <summary>Provides a list of regions and their pings as string.</summary>
- public string GetResults()
- {
- StringBuilder sb = new StringBuilder();
- sb.AppendFormat("Region Pinging Result: {0}\n", this.BestRegion.ToString());
- foreach (RegionPinger region in this.pingerList)
- {
- sb.AppendLine(region.GetResults());
- }
- sb.AppendFormat("Previous summary: {0}", this.previousSummaryProvided);
- return sb.ToString();
- }
- /// <summary>Initializes the regions of this RegionHandler with values provided from the Name Server (as OperationResponse for OpGetRegions).</summary>
- public void SetRegions(OperationResponse opGetRegions)
- {
- if (opGetRegions.OperationCode != OperationCode.GetRegions)
- {
- return;
- }
- if (opGetRegions.ReturnCode != ErrorCode.Ok)
- {
- return;
- }
- string[] regions = opGetRegions[ParameterCode.Region] as string[];
- string[] servers = opGetRegions[ParameterCode.Address] as string[];
- if (regions == null || servers == null || regions.Length != servers.Length)
- {
- //TODO: log error
- //Debug.LogError("The region arrays from Name Server are not ok. Must be non-null and same length. " + (regions == null) + " " + (servers == null) + "\n" + opGetRegions.ToStringFull());
- return;
- }
- this.bestRegionCache = null;
- this.EnabledRegions = new List<Region>(regions.Length);
- for (int i = 0; i < regions.Length; i++)
- {
- string server = servers[i];
- if (PortToPingOverride != 0)
- {
- server = LoadBalancingClient.ReplacePortWithAlternative(servers[i], PortToPingOverride);
- }
- Region tmp = new Region(regions[i], server);
- if (string.IsNullOrEmpty(tmp.Code))
- {
- continue;
- }
- this.EnabledRegions.Add(tmp);
- }
- Array.Sort(regions);
- this.availableRegionCodes = string.Join(",", regions);
- }
- private readonly List<RegionPinger> pingerList = new List<RegionPinger>();
- private Action<RegionHandler> onCompleteCall;
- private int previousPing;
- private string previousSummaryProvided;
- /// <summary>If non-zero, this port will be used to ping Master Servers on.</summary>
- protected internal static ushort PortToPingOverride;
- /// <summary>True if the available regions are being pinged currently.</summary>
- public bool IsPinging { get; private set; }
- /// <summary>True if the pinging of regions is being aborted.</summary>
- /// <see cref="Abort"/>
- public bool Aborted { get; private set; }
- /// <summary>If the region from a previous BestRegionSummary now has a ping higher than this limit, all regions get pinged again to find a better. Default: 90ms.</summary>
- /// <remarks>
- /// Pinging all regions takes time, which is why a BestRegionSummary gets stored.
- /// If that is available, the Best Region becomes sticky and is used again.
- /// This limit introduces an exception: Should the pre-defined best region have a ping worse than this, all regions are considered.
- /// </remarks>
- public int BestRegionSummaryPingLimit = 90;
- #if SUPPORTED_UNITY
- private MonoBehaviourEmpty emptyMonoBehavior;
- #endif
- /// <summary>Creates a new RegionHandler.</summary>
- /// <param name="masterServerPortOverride">If non-zero, this port will be used to ping Master Servers on.</param>
- public RegionHandler(ushort masterServerPortOverride = 0)
- {
- PortToPingOverride = masterServerPortOverride;
- }
- /// <summary>Starts the process of pinging of all available regions.</summary>
- /// <param name="onCompleteCallback">Provide a method to call when all ping results are available. Aborting the pings will also cancel the callback.</param>
- /// <param name="previousSummary">A BestRegionSummary from an earlier RegionHandler run. This makes a selected best region "sticky" and keeps ping times lower.</param>
- /// <returns>If pining the regions gets started now. False if the current state prevent this.</returns>
- public bool PingMinimumOfRegions(Action<RegionHandler> onCompleteCallback, string previousSummary)
- {
- if (this.EnabledRegions == null || this.EnabledRegions.Count == 0)
- {
- //TODO: log error
- //Debug.LogError("No regions available. Maybe all got filtered out or the AppId is not correctly configured.");
- return false;
- }
- if (this.IsPinging)
- {
- //TODO: log warning
- //Debug.LogWarning("PingMinimumOfRegions() skipped, because this RegionHandler is already pinging some regions.");
- return false;
- }
- this.Aborted = false;
- this.IsPinging = true;
- this.previousSummaryProvided = previousSummary;
- #if SUPPORTED_UNITY
- if (this.emptyMonoBehavior != null)
- {
- this.emptyMonoBehavior.SelfDestroy();
- }
- this.emptyMonoBehavior = MonoBehaviourEmpty.BuildInstance(nameof(RegionHandler));
- this.emptyMonoBehavior.onCompleteCall = onCompleteCallback;
- this.onCompleteCall = emptyMonoBehavior.CompleteOnMainThread;
- #else
- this.onCompleteCall = onCompleteCallback;
- #endif
- if (string.IsNullOrEmpty(previousSummary))
- {
- return this.PingEnabledRegions();
- }
- string[] values = previousSummary.Split(';');
- if (values.Length < 3)
- {
- return this.PingEnabledRegions();
- }
- int prevBestRegionPing;
- bool secondValueIsInt = Int32.TryParse(values[1], out prevBestRegionPing);
- if (!secondValueIsInt)
- {
- return this.PingEnabledRegions();
- }
- string prevBestRegionCode = values[0];
- string prevAvailableRegionCodes = values[2];
- if (string.IsNullOrEmpty(prevBestRegionCode))
- {
- return this.PingEnabledRegions();
- }
- if (string.IsNullOrEmpty(prevAvailableRegionCodes))
- {
- return this.PingEnabledRegions();
- }
- if (!this.availableRegionCodes.Equals(prevAvailableRegionCodes) || !this.availableRegionCodes.Contains(prevBestRegionCode))
- {
- return this.PingEnabledRegions();
- }
- if (prevBestRegionPing >= RegionPinger.PingWhenFailed)
- {
- return this.PingEnabledRegions();
- }
- // let's check only the preferred region to detect if it's still "good enough"
- this.previousPing = prevBestRegionPing;
- Region preferred = this.EnabledRegions.Find(r => r.Code.Equals(prevBestRegionCode));
- RegionPinger singlePinger = new RegionPinger(preferred, this.OnPreferredRegionPinged);
- lock (this.pingerList)
- {
- this.pingerList.Clear();
- this.pingerList.Add(singlePinger);
- }
- singlePinger.Start();
- return true;
- }
- /// <summary>Calling this will stop pinging the regions and suppress the onComplete callback.</summary>
- public void Abort()
- {
- if (this.Aborted)
- {
- return;
- }
- this.Aborted = true;
- lock (this.pingerList)
- {
- foreach (RegionPinger pinger in this.pingerList)
- {
- pinger.Abort();
- }
- }
- #if SUPPORTED_UNITY
- if (this.emptyMonoBehavior != null)
- {
- this.emptyMonoBehavior.SelfDestroy();
- }
- #endif
- }
- private void OnPreferredRegionPinged(Region preferredRegion)
- {
- if (preferredRegion.Ping > this.BestRegionSummaryPingLimit || preferredRegion.Ping > this.previousPing * 1.50f)
- {
- this.PingEnabledRegions();
- }
- else
- {
- this.IsPinging = false;
- this.onCompleteCall(this);
- }
- }
- private bool PingEnabledRegions()
- {
- if (this.EnabledRegions == null || this.EnabledRegions.Count == 0)
- {
- //TODO: log
- //Debug.LogError("No regions available. Maybe all got filtered out or the AppId is not correctly configured.");
- return false;
- }
- lock (this.pingerList)
- {
- this.pingerList.Clear();
- foreach (Region region in this.EnabledRegions)
- {
- RegionPinger rp = new RegionPinger(region, this.OnRegionDone);
- this.pingerList.Add(rp);
- rp.Start(); // TODO: check return value
- }
- }
- return true;
- }
- private void OnRegionDone(Region region)
- {
- lock (this.pingerList)
- {
- if (this.IsPinging == false)
- {
- return;
- }
- this.bestRegionCache = null;
- foreach (RegionPinger pinger in this.pingerList)
- {
- if (!pinger.Done)
- {
- return;
- }
- }
- this.IsPinging = false;
- }
- if (!this.Aborted)
- {
- this.onCompleteCall(this);
- }
- }
- }
- /// <summary>Wraps the ping attempts and workflow for a single region.</summary>
- public class RegionPinger
- {
- /// <summary>How often to ping a region.</summary>
- public static int Attempts = 5;
- /// <summary>How long to wait maximum for a response.</summary>
- public static int MaxMillisecondsPerPing = 800; // enter a value you're sure some server can beat (have a lower rtt)
- /// <summary>Ping result when pinging failed.</summary>
- public static int PingWhenFailed = Attempts * MaxMillisecondsPerPing;
- /// <summary>Current ping attempt count.</summary>
- public int CurrentAttempt = 0;
- /// <summary>True if all attempts are done or timed out.</summary>
- public bool Done { get; private set; }
- /// <summary>Set to true to abort pining this region.</summary>
- public bool Aborted { get; internal set; }
- private Action<Region> onDoneCall;
- private PhotonPing ping;
- private List<int> rttResults;
- private Region region;
- private string regionAddress;
- /// <summary>Initializes a RegionPinger for the given region.</summary>
- public RegionPinger(Region region, Action<Region> onDoneCallback)
- {
- this.region = region;
- this.region.Ping = PingWhenFailed;
- this.Done = false;
- this.onDoneCall = onDoneCallback;
- }
- /// <summary>Selects the best fitting ping implementation or uses the one set in RegionHandler.PingImplementation.</summary>
- /// <returns>PhotonPing instance to use.</returns>
- private PhotonPing GetPingImplementation()
- {
- PhotonPing ping = null;
- // using each type explicitly in the conditional code, makes sure Unity doesn't strip the class / constructor.
- #if !UNITY_EDITOR && NETFX_CORE
- if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingWindowsStore))
- {
- ping = new PingWindowsStore();
- }
- #elif NATIVE_SOCKETS || NO_SOCKET
- if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingNativeDynamic))
- {
- ping = new PingNativeDynamic();
- }
- #elif UNITY_WEBGL
- if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingHttp))
- {
- ping = new PingHttp();
- }
- #else
- if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingMono))
- {
- ping = new PingMono();
- }
- #endif
- if (ping == null)
- {
- if (RegionHandler.PingImplementation != null)
- {
- ping = (PhotonPing)Activator.CreateInstance(RegionHandler.PingImplementation);
- }
- }
- return ping;
- }
- /// <summary>
- /// Starts the ping routine for the assigned region.
- /// </summary>
- /// <remarks>
- /// Pinging runs in a ThreadPool worker item or (if needed) in a Thread.
- /// WebGL runs pinging on the Main Thread as coroutine.
- /// </remarks>
- /// <returns>True unless Aborted.</returns>
- public bool Start()
- {
- // all addresses for Photon region servers will contain a :port ending. this needs to be removed first.
- // PhotonPing.StartPing() requires a plain (IP) address without port or protocol-prefix (on all but Windows 8.1 and WebGL platforms).
- string address = this.region.HostAndPort;
- int indexOfColon = address.LastIndexOf(':');
- if (indexOfColon > 1)
- {
- address = address.Substring(0, indexOfColon);
- }
- this.regionAddress = ResolveHost(address);
- this.ping = this.GetPingImplementation();
- this.Done = false;
- this.CurrentAttempt = 0;
- this.rttResults = new List<int>(Attempts);
- if (this.Aborted)
- {
- return false;
- }
- #if PING_VIA_COROUTINE
- MonoBehaviourEmpty.BuildInstance("RegionPing_" + this.region.Code).StartCoroutineAndDestroy(this.RegionPingCoroutine());
- #else
- bool queued = false;
- #if !NETFX_CORE
- try
- {
- queued = ThreadPool.QueueUserWorkItem(o => this.RegionPingThreaded());
- }
- catch
- {
- queued = false;
- }
- #endif
- if (!queued)
- {
- SupportClass.StartBackgroundCalls(this.RegionPingThreaded, 0, "RegionPing_" + this.region.Code + "_" + this.region.Cluster);
- }
- #endif
- return true;
- }
- /// <summary>Calling this will stop pinging the regions and cancel the onComplete callback.</summary>
- protected internal void Abort()
- {
- this.Aborted = true;
- if (this.ping != null)
- {
- this.ping.Dispose();
- }
- }
- /// <summary>Pings the region. To be called by a thread.</summary>
- protected internal bool RegionPingThreaded()
- {
- this.region.Ping = PingWhenFailed;
- int rttSum = 0;
- int replyCount = 0;
- Stopwatch sw = new Stopwatch();
- for (this.CurrentAttempt = 0; this.CurrentAttempt < Attempts; this.CurrentAttempt++)
- {
- if (this.Aborted)
- {
- break;
- }
- sw.Reset();
- sw.Start();
- try
- {
- this.ping.StartPing(this.regionAddress);
- }
- catch (Exception e)
- {
- System.Diagnostics.Debug.WriteLine("RegionPinger.RegionPingThreaded() caught exception for ping.StartPing(). Exception: " + e + " Source: " + e.Source + " Message: " + e.Message);
- break;
- }
- while (!this.ping.Done())
- {
- if (sw.ElapsedMilliseconds >= MaxMillisecondsPerPing)
- {
- // if ping.Done() did not become true in MaxMillisecondsPerPing, ping.Successful is false and we apply MaxMillisecondsPerPing as rtt below
- break;
- }
- #if !NETFX_CORE
- System.Threading.Thread.Sleep(1);
- #endif
- }
- sw.Stop();
- int rtt = this.ping.Successful ? (int)sw.ElapsedMilliseconds : MaxMillisecondsPerPing; // if the reply didn't match the sent ping
- this.rttResults.Add(rtt);
- rttSum += rtt;
- replyCount++;
- this.region.Ping = (int)((rttSum) / replyCount);
- #if !NETFX_CORE
- int i = 4;
- while (!this.ping.Done() && i > 0)
- {
- i--;
- System.Threading.Thread.Sleep(100);
- }
- System.Threading.Thread.Sleep(10);
- #endif
- }
- //Debug.Log("Done: "+ this.region.Code);
- this.Done = true;
- this.ping.Dispose();
- int bestRtt = this.rttResults.Min();
- int worstRtt = this.rttResults.Max();
- int weighedRttSum = rttSum - worstRtt + bestRtt;
- this.region.Ping = (int)(weighedRttSum / replyCount); // now, we can create a weighted ping value
- this.onDoneCall(this.region);
- return false;
- }
- #if SUPPORTED_UNITY
- /// <remarks>
- /// Affected by frame-rate of app, as this Coroutine checks the socket for a result once per frame.
- /// </remarks>
- protected internal IEnumerator RegionPingCoroutine()
- {
- this.region.Ping = PingWhenFailed;
- int rttSum = 0;
- int replyCount = 0;
- Stopwatch sw = new Stopwatch();
- for (this.CurrentAttempt = 0; this.CurrentAttempt < Attempts; this.CurrentAttempt++)
- {
- if (this.Aborted)
- {
- yield return null;
- }
- sw.Reset();
- sw.Start();
- try
- {
- this.ping.StartPing(this.regionAddress);
- }
- catch (Exception e)
- {
- Debug.Log("RegionPinger.RegionPingCoroutine() caught exception for ping.StartPing(). Exception: " + e + " Source: " + e.Source + " Message: " + e.Message);
- break;
- }
- while (!this.ping.Done())
- {
- if (sw.ElapsedMilliseconds >= MaxMillisecondsPerPing)
- {
- // if ping.Done() did not become true in MaxMilliseconsPerPing, ping.Successful is false and we apply MaxMilliseconsPerPing as rtt below
- break;
- }
- yield return new WaitForSecondsRealtime(0.01f); // keep this loop tight, to avoid adding local lag to rtt.
- }
- sw.Stop();
- int rtt = this.ping.Successful ? (int)sw.ElapsedMilliseconds : MaxMillisecondsPerPing; // if the reply didn't match the sent ping
- this.rttResults.Add(rtt);
- rttSum += rtt;
- replyCount++;
- this.region.Ping = (int)((rttSum) / replyCount);
- int i = 4;
- while (!this.ping.Done() && i > 0)
- {
- i--;
- yield return new WaitForSeconds(0.1f);
- }
- yield return new WaitForSeconds(0.1f);
- }
- //Debug.Log("Done: "+ this.region.Code);
- this.Done = true;
- this.ping.Dispose();
- int bestRtt = this.rttResults.Min();
- int worstRtt = this.rttResults.Max();
- int weighedRttSum = rttSum - worstRtt + bestRtt;
- this.region.Ping = (int)(weighedRttSum / replyCount); // now, we can create a weighted ping value
- this.onDoneCall(this.region);
- yield return null;
- }
- #endif
- /// <summary>Gets this region's results as string summary.</summary>
- public string GetResults()
- {
- return string.Format("{0}: {1} ({2})", this.region.Code, this.region.Ping, this.rttResults.ToStringFull());
- }
- /// <summary>
- /// Attempts to resolve a hostname into an IP string or returns empty string if that fails.
- /// </summary>
- /// <remarks>
- /// To be compatible with most platforms, the address family is checked like this:<br/>
- /// if (ipAddress.AddressFamily.ToString().Contains("6")) // ipv6...
- /// </remarks>
- /// <param name="hostName">Hostname to resolve.</param>
- /// <returns>IP string or empty string if resolution fails</returns>
- public static string ResolveHost(string hostName)
- {
- if (hostName.StartsWith("wss://"))
- {
- hostName = hostName.Substring(6);
- }
- if (hostName.StartsWith("ws://"))
- {
- hostName = hostName.Substring(5);
- }
- string ipv4Address = string.Empty;
- try
- {
- #if UNITY_WSA || NETFX_CORE || UNITY_WEBGL
- return hostName;
- #else
- IPAddress[] address = Dns.GetHostAddresses(hostName);
- if (address.Length == 1)
- {
- return address[0].ToString();
- }
- // if we got more addresses, try to pick a IPv6 one
- // checking ipAddress.ToString() means we don't have to import System.Net.Sockets, which is not available on some platforms (Metro)
- for (int index = 0; index < address.Length; index++)
- {
- IPAddress ipAddress = address[index];
- if (ipAddress != null)
- {
- if (ipAddress.ToString().Contains(":"))
- {
- return ipAddress.ToString();
- }
- if (string.IsNullOrEmpty(ipv4Address))
- {
- ipv4Address = address.ToString();
- }
- }
- }
- #endif
- }
- catch (System.Exception e)
- {
- System.Diagnostics.Debug.WriteLine("RegionPinger.ResolveHost() caught an exception for Dns.GetHostAddresses(). Exception: " + e + " Source: " + e.Source + " Message: " + e.Message);
- }
- return ipv4Address;
- }
- }
- #if SUPPORTED_UNITY
- internal class MonoBehaviourEmpty : MonoBehaviour
- {
- internal Action<RegionHandler> onCompleteCall;
- private RegionHandler obj;
- public static MonoBehaviourEmpty BuildInstance(string id = null)
- {
- GameObject go = new GameObject(id ?? nameof(MonoBehaviourEmpty));
- DontDestroyOnLoad(go);
- return go.AddComponent<MonoBehaviourEmpty>();
- }
- public void SelfDestroy()
- {
- Destroy(this.gameObject);
- }
- void Update()
- {
- if (this.obj != null)
- {
- this.onCompleteCall(obj);
- this.obj = null;
- this.onCompleteCall = null;
- this.SelfDestroy();
- }
- }
- public void CompleteOnMainThread(RegionHandler obj)
- {
- this.obj = obj;
- }
- public void StartCoroutineAndDestroy(IEnumerator coroutine)
- {
- StartCoroutine(Routine());
- IEnumerator Routine()
- {
- yield return coroutine;
- this.SelfDestroy();
- }
- }
- }
- #endif
- }
|