[Add] Hot Reload
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using SingularityGroup.HotReload.Localization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class ConnectionDialog : MonoBehaviour {
|
||||
|
||||
[Header(Localization.Translations.MenuItems.UIControls)]
|
||||
public Button buttonHide;
|
||||
|
||||
[Header(Localization.Translations.MenuItems.Information)]
|
||||
public Text textSummary;
|
||||
public Text textSuggestion;
|
||||
|
||||
void Start() {
|
||||
buttonHide.onClick.AddListener(Hide);
|
||||
}
|
||||
|
||||
public int pendingPatches = 0;
|
||||
public int patchesApplied = 0;
|
||||
|
||||
private void Awake() {
|
||||
SyncPatchCounts();
|
||||
}
|
||||
|
||||
bool SyncPatchCounts() {
|
||||
var changed = false;
|
||||
if (pendingPatches != CodePatcher.I.PendingPatches.Count) {
|
||||
pendingPatches = CodePatcher.I.PendingPatches.Count;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (patchesApplied != CodePatcher.I.PatchesApplied) {
|
||||
patchesApplied = CodePatcher.I.PatchesApplied;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <param name="summary">One of the <see cref="ConnectionSummary"/> constants</param>
|
||||
public void SetSummary(string summary) {
|
||||
if (textSummary != null) textSummary.text = summary;
|
||||
isConnected = summary == ConnectionSummary.Connected;
|
||||
}
|
||||
|
||||
private bool isConnected = false;
|
||||
|
||||
// assumes that auto-pair already tried for several seconds
|
||||
void Update() {
|
||||
textSuggestion.enabled = isConnected;
|
||||
if (SyncPatchCounts()) {
|
||||
textSuggestion.text = string.Format(Localization.Translations.Dialogs.PatchesStatus, pendingPatches, patchesApplied);
|
||||
}
|
||||
}
|
||||
|
||||
/// hide this dialog
|
||||
void Hide() {
|
||||
gameObject.SetActive(false); // this should disable the Update loop?
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The connection between device and Hot Reload can be summarized in a few words.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The summary may be shown for less than a second, as the connection can change without warning.<br/>
|
||||
/// Therefore, we use short and simple messages.
|
||||
/// </remarks>
|
||||
internal static class ConnectionSummary {
|
||||
public static string Cancelled => Localization.Translations.Dialogs.ConnectionStateCancelled;
|
||||
public static string Connecting => Localization.Translations.Dialogs.ConnectionStateConnecting;
|
||||
public static string Handshaking => Localization.Translations.Dialogs.ConnectionStateHandshaking;
|
||||
public static string DifferencesFound => Localization.Translations.Dialogs.ConnectionStateDifferencesFound;
|
||||
public static string Connected => Localization.Translations.Dialogs.ConnectionStateConnected;
|
||||
// reconnecting can be shown for a long time, so a longer message is okay
|
||||
public static string TryingToReconnect => Localization.Translations.Dialogs.TryingToReconnect;
|
||||
public static string Disconnected => Localization.Translations.Dialogs.Disconnected;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb1cc47c374f478e861f2c3dade07e1a
|
||||
timeCreated: 1675064498
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.17
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/ConnectionDialog.cs
|
||||
uploadId: 870414
|
||||
@@ -0,0 +1,144 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class Prompts : MonoBehaviour {
|
||||
public GameObject retryPrompt;
|
||||
public GameObject connectedPrompt;
|
||||
public GameObject questionPrompt;
|
||||
|
||||
[Header(Localization.Translations.MenuItems.Other)]
|
||||
[Tooltip(Localization.Translations.MenuItems.FalllbackEventSystem)]
|
||||
public GameObject fallbackEventSystem;
|
||||
|
||||
#region Singleton
|
||||
|
||||
private static Prompts _I;
|
||||
|
||||
/// <summary>
|
||||
/// All usages must check that <see cref="PlayerEntrypoint.RuntimeSupportsHotReload"/> is true before accessing this singleton.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This getter can throw on unsupported platforms (HotReloadSettingsObject resource doesn't exist on unsupported platforms).
|
||||
/// </remarks>
|
||||
public static Prompts I {
|
||||
get {
|
||||
if (_I == null) {
|
||||
// allow showing prompts in editor (for testing)
|
||||
if (!Application.isEditor && !PlayerEntrypoint.IsPlayerWithHotReload()) {
|
||||
throw new NotSupportedException(Localization.Translations.Errors.IsPlayerWithHotReloadFalse);
|
||||
}
|
||||
var go = Instantiate(HotReloadSettingsObject.I.PromptsPrefab,
|
||||
new Vector3(0, 0, 0), Quaternion.identity);
|
||||
go.name = nameof(Prompts) + "_singleton";
|
||||
if (Application.isPlaying) {
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
_I = go.GetComponentInChildren<Prompts>();
|
||||
}
|
||||
|
||||
return _I;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <seealso cref="ShowConnectionDialog"/>
|
||||
public static void SetConnectionState(string state, bool log = true) {
|
||||
var connectionDialog = I.connectedPrompt.GetComponentInChildren<ConnectionDialog>();
|
||||
if (log) Log.Debug($"SetConnectionState( {state} )");
|
||||
if (connectionDialog) {
|
||||
connectionDialog.SetSummary(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <seealso cref="SetConnectionState"/>
|
||||
public static void ShowConnectionDialog() {
|
||||
I.retryPrompt.SetActive(false);
|
||||
I.connectedPrompt.SetActive(true);
|
||||
}
|
||||
|
||||
public static async Task<bool> ShowQuestionDialog(QuestionDialog.Config config) {
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
var holder = I.questionPrompt;
|
||||
var dialog = holder.GetComponentInChildren<QuestionDialog>();
|
||||
dialog.completion = tcs;
|
||||
dialog.UpdateView(config);
|
||||
holder.SetActive(true);
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
public static void ShowRetryDialog(
|
||||
PatchServerInfo patchServerInfo,
|
||||
ServerHandshake.Result handshakeResults = ServerHandshake.Result.None,
|
||||
bool auto = true
|
||||
) {
|
||||
|
||||
var retryDialog = I.retryPrompt.GetComponentInChildren<RetryDialog>();
|
||||
|
||||
RetryDialog.TargetServer = patchServerInfo;
|
||||
RetryDialog.HandshakeResults = handshakeResults;
|
||||
|
||||
if (patchServerInfo == null) {
|
||||
retryDialog.DebugInfo = $"patchServerInfo == null {handshakeResults}";
|
||||
} else {
|
||||
retryDialog.DebugInfo = $"{RequestHelper.CreateUrl(patchServerInfo)} {handshakeResults}";
|
||||
}
|
||||
retryDialog.autoConnect = auto;
|
||||
|
||||
I.connectedPrompt.SetActive(false);
|
||||
I.retryPrompt.SetActive(true);
|
||||
}
|
||||
|
||||
#region fallback event system
|
||||
|
||||
private void Start() {
|
||||
StartCoroutine(DelayedEnsureEventSystem());
|
||||
}
|
||||
|
||||
private bool userTriedToInteract = false;
|
||||
|
||||
private void Update() {
|
||||
if (!userTriedToInteract) {
|
||||
// when user interacts with the screen, make sure overlay can handle taps
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if ((Touchscreen.current != null && Touchscreen.current.touches.Count > 0) ||
|
||||
(Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)) {
|
||||
userTriedToInteract = true;
|
||||
DoEnsureEventSystem();
|
||||
}
|
||||
#else
|
||||
if (Input.touchCount > 0 || Input.GetMouseButtonDown(0)) {
|
||||
userTriedToInteract = true;
|
||||
DoEnsureEventSystem();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedEnsureEventSystem() {
|
||||
// allow some delay in-case the project loads the EventSystem asynchronously (perhaps in a second scene)
|
||||
if (EventSystem.current == null) {
|
||||
yield return new WaitForSeconds(1f);
|
||||
DoEnsureEventSystem();
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene must contain an EventSystem and StandaloneInputModule, otherwise clicking/tapping on the overlay does nothing.
|
||||
private void DoEnsureEventSystem() {
|
||||
if (EventSystem.current == null) {
|
||||
Log.Info(string.Format(Localization.Translations.Settings.NoEventSystemWarning, name));
|
||||
fallbackEventSystem.SetActive(true);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d92cdbfacafd433ca77184c22a384a6d
|
||||
timeCreated: 1674488132
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.17
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs
|
||||
uploadId: 870414
|
||||
@@ -0,0 +1,67 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using SingularityGroup.HotReload.Localization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
class QuestionDialog : MonoBehaviour {
|
||||
|
||||
[Header(Localization.Translations.MenuItems.Information)]
|
||||
public Text textSummary;
|
||||
public Text textSuggestion;
|
||||
|
||||
[Header(Localization.Translations.MenuItems.UIControls)]
|
||||
public Button buttonContinue;
|
||||
public Button buttonCancel;
|
||||
public Button buttonMoreInfo;
|
||||
|
||||
public TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
|
||||
|
||||
public void UpdateView(Config config) {
|
||||
textSummary.text = config.summary;
|
||||
textSuggestion.text = config.suggestion;
|
||||
|
||||
if (string.IsNullOrEmpty(config.continueButtonText)) {
|
||||
buttonContinue.enabled = false;
|
||||
} else {
|
||||
buttonContinue.GetComponentInChildren<Text>().text = config.continueButtonText;
|
||||
buttonContinue.onClick.AddListener(() => {
|
||||
completion.TrySetResult(true);
|
||||
Hide();
|
||||
});
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(config.cancelButtonText)) {
|
||||
buttonCancel.enabled = false;
|
||||
} else {
|
||||
buttonCancel.GetComponentInChildren<Text>().text = config.cancelButtonText;
|
||||
buttonCancel.onClick.AddListener(() => {
|
||||
completion.TrySetResult(false);
|
||||
Hide();
|
||||
});
|
||||
}
|
||||
|
||||
buttonMoreInfo.onClick.AddListener(() => {
|
||||
Application.OpenURL(config.moreInfoUrl);
|
||||
});
|
||||
}
|
||||
|
||||
internal class Config {
|
||||
public string summary;
|
||||
public string suggestion;
|
||||
public string continueButtonText = Localization.Translations.Dialogs.ContinueButtonText;
|
||||
public string cancelButtonText = Localization.Translations.Dialogs.CancelButtonText;
|
||||
public string moreInfoUrl = PackageConst.DefaultLocale == Locale.SimplifiedChinese ?
|
||||
"https://hotreload.net/zh/documentation/on-device#处理不同的提交" :
|
||||
"https://hotreload.net/documentation/on-device#handling-different-commits";
|
||||
}
|
||||
|
||||
/// hide this dialog
|
||||
void Hide() {
|
||||
gameObject.SetActive(false); // this should disable the Update loop?
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef31038a0ed84685b779466bf22d53a9
|
||||
timeCreated: 1675143382
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.17
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/QuestionDialog.cs
|
||||
uploadId: 870414
|
||||
@@ -0,0 +1,108 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using JetBrains.Annotations;
|
||||
using SingularityGroup.HotReload.Localization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class RetryDialog : MonoBehaviour {
|
||||
[Header(Localization.Translations.MenuItems.UIControls)]
|
||||
public Button buttonHide;
|
||||
public Button buttonRetryAutoPair;
|
||||
public Button buttonTroubleshoot;
|
||||
|
||||
public Text textSummary;
|
||||
public Text textSuggestion;
|
||||
public InputField ipInput;
|
||||
|
||||
[Tooltip("Hidden by default")]
|
||||
public Text textForDebugging;
|
||||
|
||||
[Header("For HotReload Devs")]
|
||||
// In Unity Editor, click checkbox to see info helpful for debugging bugs
|
||||
public bool enableDebugging;
|
||||
|
||||
// [Header("Other")]
|
||||
// [Tooltip("Used when your project does not create an EventSystem early enough")]
|
||||
// public GameObject fallbackEventSystem;
|
||||
|
||||
private static RetryDialog _I;
|
||||
|
||||
public string DebugInfo {
|
||||
set {
|
||||
textForDebugging.text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool autoConnect { get; set; }
|
||||
|
||||
void Start() {
|
||||
buttonHide.onClick.AddListener(() => {
|
||||
Hide();
|
||||
});
|
||||
|
||||
buttonRetryAutoPair.onClick.AddListener(() => {
|
||||
Hide();
|
||||
int port;
|
||||
var ipAndPort = ipInput.textComponent.text.Split(':');
|
||||
if (ipAndPort.Length != 2 || !int.TryParse(ipAndPort[1], out port)) {
|
||||
port = PlayerEntrypoint.PlayerBuildInfo?.buildMachinePort ?? RequestHelper.defaultPort;
|
||||
}
|
||||
var ip = ipAndPort.Length > 0 ? ipAndPort[0] : string.Empty;
|
||||
PlayerEntrypoint.TryConnectToIpAndPort(ip, port);
|
||||
});
|
||||
|
||||
buttonTroubleshoot.onClick.AddListener(() => {
|
||||
var docsUrl = PackageConst.DefaultLocale == Locale.SimplifiedChinese ?
|
||||
"https://hotreload.net/zh/documentation/on-device#连接问题" :
|
||||
"https://hotreload.net/documentation/on-device#connection-issues" ;
|
||||
Application.OpenURL(docsUrl);
|
||||
});
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
public static PatchServerInfo TargetServer { private get; set; } = null;
|
||||
public static ServerHandshake.Result HandshakeResults { private get; set; } = ServerHandshake.Result.None;
|
||||
|
||||
private void OnEnable() {
|
||||
ipInput.text = $"{PlayerEntrypoint.PlayerBuildInfo?.buildMachineHostName}:{PlayerEntrypoint.PlayerBuildInfo?.buildMachinePort}";
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
void Update() {
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
void UpdateUI() {
|
||||
// assumes that auto-pair already tried for several seconds
|
||||
// suggestions to help the user when auto-pair is failing
|
||||
var networkText = Application.isMobilePlatform ? "WiFi" : "LAN/WiFi";
|
||||
var noWifiNetwork = string.Format(Localization.Translations.Dialogs.IsConnected, networkText);
|
||||
var waitForCompiling = Localization.Translations.Dialogs.WaitForCompiling;
|
||||
var targetNetworkIsReachable = string.Format(Localization.Translations.Dialogs.TargetNetworkIsReachable, networkText);
|
||||
|
||||
if (Application.internetReachability != NetworkReachability.ReachableViaLocalAreaNetwork) {
|
||||
textSuggestion.text = noWifiNetwork;
|
||||
} else if (HandshakeResults.HasFlag(ServerHandshake.Result.WaitForCompiling)) {
|
||||
// Note: Technically the player could do the waiting itself, and handshake again with the server
|
||||
// only after compiling finishes... Telling the user to do that is easier to implement though.
|
||||
textSuggestion.text = waitForCompiling;
|
||||
} else {
|
||||
textSuggestion.text = targetNetworkIsReachable;
|
||||
}
|
||||
|
||||
textSummary.text = autoConnect ? Localization.Translations.Dialogs.AutoPairEncounteredIssue : Localization.Translations.Dialogs.ConnectionFailed;
|
||||
|
||||
if (enableDebugging && textForDebugging) {
|
||||
textForDebugging.enabled = true;
|
||||
textForDebugging.text = $"the target = {TargetServer}";
|
||||
}
|
||||
}
|
||||
|
||||
/// hide this dialog
|
||||
void Hide() {
|
||||
gameObject.SetActive(false); // this should disable the Update loop?
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a69f8e8e50a405a84ec22ac7c2f4bdc
|
||||
timeCreated: 1674408078
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.17
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/RetryDialog.cs
|
||||
uploadId: 870414
|
||||
Reference in New Issue
Block a user