#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR) #if UNITY_ANDROID && !UNITY_EDITOR #define MOBILE_ANDROID #endif #if UNITY_IOS && !UNITY_EDITOR #define MOBILE_IOS #endif #if MOBILE_ANDROID || MOBILE_IOS #define MOBILE #endif using System; using System.Threading.Tasks; #if MOBILE_ANDROID // not able to use File apis for reading from StreamingAssets using UnityEngine.Networking; #endif using UnityEngine; using Debug = UnityEngine.Debug; using System.IO; namespace SingularityGroup.HotReload { // entrypoint for Unity Player builds. Not necessary in Unity Editor. internal static class PlayerEntrypoint { /// Set when behaviour is created, when you access this instance through the singleton, /// you can assume that this field is not null. /// /// In Player code you can assume this is set.
/// When in Editor this is usually null. ///
static BuildInfo buildInfo { get; set; } /// In Player code you can assume this is set (not null) public static BuildInfo PlayerBuildInfo => buildInfo; #if ENABLE_MONO [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] #endif private static void InitOnAppLoad() { AppCallbackListener.Init(); // any platform might be using this UnityHelper.Init(); bool onlyPrefabMissing; if (!IsPlayerWithHotReload(out onlyPrefabMissing)) { if (onlyPrefabMissing) { Log.Warning("Hot Reload is not available in this build because one or more build settings were not supported."); } return; } TryAutoConnect().Forget(); } static async Task TryAutoConnect() { try { buildInfo = await GetBuildInfo(); } catch (Exception e) { if (e is IOException) { Log.Warning("Hot Reload is not available in this build because one or more build settings were not supported."); } else { Log.Error($"Uknown exception happened when reading build info\n{e.GetType().Name}: {e.Message}"); } return; } if (buildInfo == null) { Log.Error($"Uknown issue happened when reading build info."); return; } CodePatcher.I.debuggerCompatibilityEnabled = true; try { var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", ""); if (!string.IsNullOrEmpty(customIp)) { buildInfo.buildMachineHostName = customIp; } var customPort = PlayerPrefs.GetString("HotReloadRuntime.CustomPort", ""); if (!string.IsNullOrEmpty(customPort)) { buildInfo.buildMachinePort = int.Parse(customPort); } if (buildInfo.BuildMachineServer == null) { Prompts.ShowRetryDialog(null); } else { // try reach server running on the build machine. TryConnect(buildInfo.BuildMachineServer, auto: true).Forget(); } } catch (Exception ex) { Log.Exception(ex); } } public static Task TryConnectToIpAndPort(string ip, int port) { ip = ip.Trim(); if (buildInfo == null) { throw new ArgumentException("Build info not found"); } buildInfo.buildMachineHostName = ip; buildInfo.buildMachinePort = port; PlayerPrefs.SetString("HotReloadRuntime.CustomIP", ip); PlayerPrefs.SetString("HotReloadRuntime.CustomPort", port.ToString()); return TryConnect(buildInfo.BuildMachineServer, auto: false); } public static async Task TryConnect(PatchServerInfo serverInfo, bool auto) { // try reach server running on the build machine. var handshake = PlayerCodePatcher.UpdateHost(serverInfo); await Task.WhenAny(handshake, Task.Delay(TimeSpan.FromSeconds(40))); await ThreadUtility.SwitchToMainThread(); var handshakeResults = await handshake; var handshakeOk = handshakeResults.HasFlag(ServerHandshake.Result.Verified); if (!handshakeOk) { Log.Debug("ShowRetryPrompt because handshake result is {0}", handshakeResults); Prompts.ShowRetryDialog(serverInfo, handshakeResults, auto); // cancel trying to connect. They can use the retry button PlayerCodePatcher.UpdateHost(null).Forget(); } Log.Info($"Server is healthy after first handshake? {handshakeOk}"); } /// on Android, streaming assets are inside apk zip, which can only be read using unity web request private static async Task GetBuildInfo() { var path = BuildInfo.GetStoredPath(); #if MOBILE_ANDROID var json = await RequestHelper.GetAsync(path); return await Task.Run(() => BuildInfo.FromJson(json)); #else return await Task.Run(() => { return BuildInfo.FromJson(File.ReadAllText(path)); }); #endif } public static bool IsPlayer() => !Application.isEditor; public static bool IsPlayerWithHotReload() { bool _; return IsPlayerWithHotReload(out _); } public static bool IsPlayerWithHotReload(out bool onlyPrefabMissing) { onlyPrefabMissing = false; if (!IsPlayer() || !RuntimeSupportsHotReload || !HotReloadSettingsObject.I.IncludeInBuild) { return false; } onlyPrefabMissing = !HotReloadSettingsObject.I.PromptsPrefab; return !onlyPrefabMissing; } public static bool RuntimeSupportsHotReload { get { #if DEVELOPMENT_BUILD && ENABLE_MONO return true; #else return false; #endif } } } } #endif