first commit

This commit is contained in:
Chris
2025-03-12 14:22:16 -04:00
commit 0ad0c01249
1999 changed files with 189708 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bb1cc47c374f478e861f2c3dade07e1a
timeCreated: 1675064498

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d92cdbfacafd433ca77184c22a384a6d
timeCreated: 1674488132

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ef31038a0ed84685b779466bf22d53a9
timeCreated: 1675143382

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7a69f8e8e50a405a84ec22ac7c2f4bdc
timeCreated: 1674408078