PlayerEntrypoint.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. #if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
  2. #if UNITY_ANDROID && !UNITY_EDITOR
  3. #define MOBILE_ANDROID
  4. #endif
  5. #if UNITY_IOS && !UNITY_EDITOR
  6. #define MOBILE_IOS
  7. #endif
  8. #if MOBILE_ANDROID || MOBILE_IOS
  9. #define MOBILE
  10. #endif
  11. using System;
  12. using System.Threading.Tasks;
  13. #if MOBILE_ANDROID
  14. // not able to use File apis for reading from StreamingAssets
  15. using UnityEngine.Networking;
  16. #endif
  17. using UnityEngine;
  18. using Debug = UnityEngine.Debug;
  19. using System.IO;
  20. namespace SingularityGroup.HotReload {
  21. // entrypoint for Unity Player builds. Not necessary in Unity Editor.
  22. internal static class PlayerEntrypoint {
  23. /// Set when behaviour is created, when you access this instance through the singleton,
  24. /// you can assume that this field is not null.
  25. /// <remarks>
  26. /// In Player code you can assume this is set.<br/>
  27. /// When in Editor this is usually null.
  28. /// </remarks>
  29. static BuildInfo buildInfo { get; set; }
  30. /// In Player code you can assume this is set (not null)
  31. public static BuildInfo PlayerBuildInfo => buildInfo;
  32. #if ENABLE_MONO
  33. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
  34. #endif
  35. private static void InitOnAppLoad() {
  36. AppCallbackListener.Init(); // any platform might be using this
  37. UnityHelper.Init();
  38. bool onlyPrefabMissing;
  39. if (!IsPlayerWithHotReload(out onlyPrefabMissing)) {
  40. if (onlyPrefabMissing) {
  41. Log.Warning("Hot Reload is not available in this build because one or more build settings were not supported.");
  42. }
  43. return;
  44. }
  45. TryAutoConnect().Forget();
  46. }
  47. static async Task TryAutoConnect() {
  48. try {
  49. buildInfo = await GetBuildInfo();
  50. } catch (Exception e) {
  51. if (e is IOException) {
  52. Log.Warning("Hot Reload is not available in this build because one or more build settings were not supported.");
  53. } else {
  54. Log.Error($"Uknown exception happened when reading build info\n{e.GetType().Name}: {e.Message}");
  55. }
  56. return;
  57. }
  58. if (buildInfo == null) {
  59. Log.Error($"Uknown issue happened when reading build info.");
  60. return;
  61. }
  62. CodePatcher.I.debuggerCompatibilityEnabled = true;
  63. try {
  64. var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", "");
  65. if (!string.IsNullOrEmpty(customIp)) {
  66. buildInfo.buildMachineHostName = customIp;
  67. }
  68. var customPort = PlayerPrefs.GetString("HotReloadRuntime.CustomPort", "");
  69. if (!string.IsNullOrEmpty(customPort)) {
  70. buildInfo.buildMachinePort = int.Parse(customPort);
  71. }
  72. if (buildInfo.BuildMachineServer == null) {
  73. Prompts.ShowRetryDialog(null);
  74. } else {
  75. // try reach server running on the build machine.
  76. TryConnect(buildInfo.BuildMachineServer, auto: true).Forget();
  77. }
  78. } catch (Exception ex) {
  79. Log.Exception(ex);
  80. }
  81. }
  82. public static Task TryConnectToIpAndPort(string ip, int port) {
  83. ip = ip.Trim();
  84. if (buildInfo == null) {
  85. throw new ArgumentException("Build info not found");
  86. }
  87. buildInfo.buildMachineHostName = ip;
  88. buildInfo.buildMachinePort = port;
  89. PlayerPrefs.SetString("HotReloadRuntime.CustomIP", ip);
  90. PlayerPrefs.SetString("HotReloadRuntime.CustomPort", port.ToString());
  91. return TryConnect(buildInfo.BuildMachineServer, auto: false);
  92. }
  93. public static async Task TryConnect(PatchServerInfo serverInfo, bool auto) {
  94. // try reach server running on the build machine.
  95. var handshake = PlayerCodePatcher.UpdateHost(serverInfo);
  96. await Task.WhenAny(handshake, Task.Delay(TimeSpan.FromSeconds(40)));
  97. await ThreadUtility.SwitchToMainThread();
  98. var handshakeResults = await handshake;
  99. var handshakeOk = handshakeResults.HasFlag(ServerHandshake.Result.Verified);
  100. if (!handshakeOk) {
  101. Log.Debug("ShowRetryPrompt because handshake result is {0}", handshakeResults);
  102. Prompts.ShowRetryDialog(serverInfo, handshakeResults, auto);
  103. // cancel trying to connect. They can use the retry button
  104. PlayerCodePatcher.UpdateHost(null).Forget();
  105. }
  106. Log.Info($"Server is healthy after first handshake? {handshakeOk}");
  107. }
  108. /// on Android, streaming assets are inside apk zip, which can only be read using unity web request
  109. private static async Task<BuildInfo> GetBuildInfo() {
  110. var path = BuildInfo.GetStoredPath();
  111. #if MOBILE_ANDROID
  112. var json = await RequestHelper.GetAsync(path);
  113. return await Task.Run(() => BuildInfo.FromJson(json));
  114. #else
  115. return await Task.Run(() => {
  116. return BuildInfo.FromJson(File.ReadAllText(path));
  117. });
  118. #endif
  119. }
  120. public static bool IsPlayer() => !Application.isEditor;
  121. public static bool IsPlayerWithHotReload() {
  122. bool _;
  123. return IsPlayerWithHotReload(out _);
  124. }
  125. public static bool IsPlayerWithHotReload(out bool onlyPrefabMissing) {
  126. onlyPrefabMissing = false;
  127. if (!IsPlayer() || !RuntimeSupportsHotReload || !HotReloadSettingsObject.I.IncludeInBuild) {
  128. return false;
  129. }
  130. onlyPrefabMissing = !HotReloadSettingsObject.I.PromptsPrefab;
  131. return !onlyPrefabMissing;
  132. }
  133. public static bool RuntimeSupportsHotReload {
  134. get {
  135. #if DEVELOPMENT_BUILD && ENABLE_MONO
  136. return true;
  137. #else
  138. return false;
  139. #endif
  140. }
  141. }
  142. }
  143. }
  144. #endif