first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class ConnectionDialog : MonoBehaviour {
|
||||
[Header("UI controls")]
|
||||
public Button buttonHide;
|
||||
|
||||
[Header("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 = ConnectionSummary.Connected) {
|
||||
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 = $"Patches: {pendingPatches} pending, {patchesApplied} applied";
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 const string Cancelled = "Cancelled";
|
||||
public const string Connecting = "Connecting ...";
|
||||
public const string Handshaking = "Handshaking ...";
|
||||
public const string DifferencesFound = "Differences found";
|
||||
public const string Connected = "Connected!";
|
||||
// reconnecting can be shown for a long time, so a longer message is okay
|
||||
public const string TryingToReconnect = "Trying to reconnect ...";
|
||||
public const string Disconnected = "Disconnected";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb1cc47c374f478e861f2c3dade07e1a
|
||||
timeCreated: 1675064498
|
||||
@@ -0,0 +1,134 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class Prompts : MonoBehaviour {
|
||||
public GameObject retryPrompt;
|
||||
public GameObject connectedPrompt;
|
||||
public GameObject questionPrompt;
|
||||
|
||||
[Header("Other")]
|
||||
[Tooltip("Used when project does not create an EventSystem early enough")]
|
||||
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("IsPlayerWithHotReload() is false");
|
||||
}
|
||||
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 (Input.touchCount > 0 || Input.GetMouseButtonDown(0)) {
|
||||
userTriedToInteract = true;
|
||||
DoEnsureEventSystem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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($"No EventSystem is active, enabling an EventSystem inside Hot Reload {name} prefab." +
|
||||
" A Unity EventSystem and an Input module is required for tapping buttons on the Unity UI.");
|
||||
fallbackEventSystem.SetActive(true);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d92cdbfacafd433ca77184c22a384a6d
|
||||
timeCreated: 1674488132
|
||||
@@ -0,0 +1,64 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
class QuestionDialog : MonoBehaviour {
|
||||
|
||||
[Header("Information")]
|
||||
public Text textSummary;
|
||||
public Text textSuggestion;
|
||||
|
||||
[Header("UI controls")]
|
||||
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 = "Continue";
|
||||
public string cancelButtonText = "Cancel";
|
||||
public string moreInfoUrl = "https://hotreload.net/documentation#handling-different-commits";
|
||||
}
|
||||
|
||||
/// hide this dialog
|
||||
void Hide() {
|
||||
gameObject.SetActive(false); // this should disable the Update loop?
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef31038a0ed84685b779466bf22d53a9
|
||||
timeCreated: 1675143382
|
||||
@@ -0,0 +1,104 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class RetryDialog : MonoBehaviour {
|
||||
[Header("UI controls")]
|
||||
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(() => {
|
||||
Application.OpenURL("https://hotreload.net/documentation#connection-issues");
|
||||
});
|
||||
}
|
||||
|
||||
[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 = $"Is this device connected to {networkText}?";
|
||||
var waitForCompiling = "Wait for compiling to finish before trying again";
|
||||
var targetNetworkIsReachable = $"Make sure you're on the same {networkText} network. Also ensure Hot Reload is running";
|
||||
|
||||
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 ? "Auto-pair encountered an issue" : "Connection failed";
|
||||
|
||||
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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a69f8e8e50a405a84ec22ac7c2f4bdc
|
||||
timeCreated: 1674408078
|
||||
Reference in New Issue
Block a user