From 6aece34da0498714ea28118d8a16aba3aaca5f0c Mon Sep 17 00:00:00 2001 From: Florian Glawogger <73254596+FlorianGlawogger@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:54:50 +0100 Subject: [PATCH] Maroon-548 Support to load JSON Experiment Config via URL Fragment (#551) --- .../WebGlReceiver/WebGlReceiver.cs | 2 + .../ExperimentParameters/ParameterLoader.cs | 46 +++++++++++++++-- .../3DMotionSimulation/Scripts/ParameterUI.cs | 8 +-- .../Scripts/Manager/PresetManager.cs | 2 +- .../Scripts/Manager/UIManager.cs | 14 ++--- .../WebGLTemplates/MaroonWeb/index.html | 51 +++++++++++++++++++ 6 files changed, 107 insertions(+), 16 deletions(-) diff --git a/unity/Assets/Maroon/GlobalEntities/WebGlReceiver/WebGlReceiver.cs b/unity/Assets/Maroon/GlobalEntities/WebGlReceiver/WebGlReceiver.cs index 55a3cfec3..d7488f6d8 100644 --- a/unity/Assets/Maroon/GlobalEntities/WebGlReceiver/WebGlReceiver.cs +++ b/unity/Assets/Maroon/GlobalEntities/WebGlReceiver/WebGlReceiver.cs @@ -21,6 +21,7 @@ public class WebGlReceiver : MonoBehaviour, GlobalEntity // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Properties, Getters and Setters + public string MostRecentData { get; private set; } // ------------------------------------------------------------------------------------------------------------- // Singleton @@ -68,6 +69,7 @@ private void Awake() public void GetDataFromJavaScript(string data) { Debug.Log("Received Data: " + data); + MostRecentData = data; OnIncomingData.Invoke(data); } } diff --git a/unity/Assets/Maroon/reusableScripts/ExperimentParameters/ParameterLoader.cs b/unity/Assets/Maroon/reusableScripts/ExperimentParameters/ParameterLoader.cs index cd128363d..f5cb4b3b2 100644 --- a/unity/Assets/Maroon/reusableScripts/ExperimentParameters/ParameterLoader.cs +++ b/unity/Assets/Maroon/reusableScripts/ExperimentParameters/ParameterLoader.cs @@ -29,6 +29,11 @@ public class ParameterLoader : MonoBehaviour /// public UnityEvent parametersLoaded = new UnityEvent(); + /// + /// Invoked when ExperimentParameters have been loaded, which are custom (e.g. received from Javascript WebGL). + /// + public UnityEvent CustomParametersLoaded = new UnityEvent(); + /// /// Invoked when the JSON files have been initialized. /// @@ -48,6 +53,11 @@ public ExperimentParameters MostRecentParameters private set; } + /// + /// Used to know whether there was already an experiment which potentially used the URL fragment parameters on WebGL already + /// + private static bool firstExperimentDefaultParametersLoaded = false; + #region Singleton private static ParameterLoader _instance; public static ParameterLoader Instance @@ -68,9 +78,12 @@ private void Start() #if UNITY_WEBGL && !UNITY_EDITOR // Listener for external json data (sent e.g. via a Javascript button from a website where Maroon is embedded) - WebGlReceiver.Instance.OnIncomingData.AddListener((string jsonData) => { LoadJsonFromString(jsonData); }); + WebGlReceiver.Instance.OnIncomingData.AddListener((string jsonData) => { + LoadJsonFromString(jsonData); + CustomParametersLoaded?.Invoke(); + }); #endif - + if (_automaticiallyDetectJsonFiles) { #if UNITY_WEBGL && !UNITY_EDITOR @@ -140,8 +153,22 @@ public void InitJsonFiles(List jsonFiles) /// /// File to load /// The loaded ExperimentParameters - public ExperimentParameters LoadJsonFromFileIndex(int index) + public ExperimentParameters LoadJsonFromFileIndex(int index, bool firstDefaultParametersLoad = false) { +#if UNITY_WEBGL + /* + * Initial config received from Javascript (originating from the URL Fragment config) will be received before the requested experiment is loaded, + * thus if on WebGL and the WebGlReceiver.Instance.MostRecentData is not null and this it the first experiment, load instead the WebGlReceiver.Instance.MostRecentData instead of the requested default file. + */ + if (firstDefaultParametersLoad && !firstExperimentDefaultParametersLoaded && !string.IsNullOrWhiteSpace(WebGlReceiver.Instance.MostRecentData)) + { + firstExperimentDefaultParametersLoaded = true; + CustomParametersLoaded?.Invoke(); + return LoadJsonFromString(WebGlReceiver.Instance.MostRecentData); + } +#endif + + if (index >= _jsonFile.Count) { Debug.LogError("Index " + index + " is greater or equal the number of files " + _jsonFile.Count); @@ -158,7 +185,7 @@ public ExperimentParameters LoadJsonFromFileIndex(int index) /// /// Name of the file to load /// The loaded ExperimentParameters - public ExperimentParameters LoadJsonFromFileName(string name) + public ExperimentParameters LoadJsonFromFileName(string name, bool firstDefaultParametersLoad = false) { int index = IndexOfJson(name); if (index == -1) @@ -167,7 +194,7 @@ public ExperimentParameters LoadJsonFromFileName(string name) return null; } - return LoadJsonFromFileIndex(index); + return LoadJsonFromFileIndex(index, firstDefaultParametersLoad); } /// @@ -177,7 +204,16 @@ public ExperimentParameters LoadJsonFromFileName(string name) /// The loaded ExperimentParameters public ExperimentParameters LoadJsonFromString(string data) { + Debug.Log("Trying to load ExperimentParameters from JSON String."); MostRecentParameters = ConvertJsonToExperimentParameters(data); + if (MostRecentParameters == null) + { + Debug.LogError("Loaded ExperimentParameters are null."); + } + else + { + Debug.Log("Successfully parsed ExperimentParameters: " + MostRecentParameters.GetType()); + } parametersLoaded?.Invoke(MostRecentParameters); return MostRecentParameters; } diff --git a/unity/Assets/Maroon/scenes/experiments/3DMotionSimulation/Scripts/ParameterUI.cs b/unity/Assets/Maroon/scenes/experiments/3DMotionSimulation/Scripts/ParameterUI.cs index 8adf9a462..cd2e0dce9 100644 --- a/unity/Assets/Maroon/scenes/experiments/3DMotionSimulation/Scripts/ParameterUI.cs +++ b/unity/Assets/Maroon/scenes/experiments/3DMotionSimulation/Scripts/ParameterUI.cs @@ -126,22 +126,22 @@ public void OnFilesLoadedInital() #if UNITY_WEBGL && !UNITY_EDITOR if (BootstrappingManager.Instance.UrlParameters.TryGetValue(WebGlUrlParameter.Config, out string config)) { - if (!ApplyConfig(config)) ApplyConfig("Default"); + if (!ApplyConfig(config)) ApplyConfig("Default", true); } else #endif { - ApplyConfig("Default"); + ApplyConfig("Default", true); } ParameterLoader.Instance.OnFilesInitialized.RemoveListener(OnFilesLoadedInital); } - public bool ApplyConfig(string configName) + public bool ApplyConfig(string configName, bool firstDefaultParametersLoad = false) { _currentConfigIndex = ParameterLoader.Instance.IndexOfJson(configName); dropdown.SetValueWithoutNotify(_currentConfigIndex); - var parameters = ParameterLoader.Instance.LoadJsonFromFileName(configName); + var parameters = ParameterLoader.Instance.LoadJsonFromFileName(configName, firstDefaultParametersLoad); return parameters != null; } diff --git a/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/PresetManager.cs b/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/PresetManager.cs index 8cfaacbc8..d1709c727 100644 --- a/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/PresetManager.cs +++ b/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/PresetManager.cs @@ -58,7 +58,7 @@ private void Start() _em = ExperimentManager.Instance; _camControls = mainCamera.GetComponent(); - parameterLoader.LoadJsonFromFileIndex(0); + parameterLoader.LoadJsonFromFileIndex(0, true); } /// diff --git a/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/UIManager.cs b/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/UIManager.cs index 2672aa9a8..73394d9b1 100644 --- a/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/UIManager.cs +++ b/unity/Assets/Maroon/scenes/experiments/OpticsSimulations/Scripts/Manager/UIManager.cs @@ -10,6 +10,7 @@ using Maroon.Physics.Optics.TableObject.LightComponent; using Maroon.Physics.Optics.TableObject.OpticalComponent; using Maroon.Physics.Optics.Util; +using Maroon.ReusableScripts.ExperimentParameters; using TMPro; using UnityEngine; using Math = System.Math; @@ -97,7 +98,14 @@ public OpticalComponent SelectedOc private void Awake() { if (Instance == null) + { Instance = this; + + ParameterLoader.Instance.CustomParametersLoaded.AddListener(() => { + // When OpticsParameters config JSON gets sent via Javascript, set the preset Dropdown to index 0, as that's representing an 'undefined' preset + presetDropdown.SetValueWithoutNotify(0); + }); + } else { Debug.LogError("SHOULD NOT OCCUR - Destroyed UIManager"); @@ -110,12 +118,6 @@ private void Start() _cauchyModel = cauchyModelDropdown.GetComponent(); _lensModel = lensModelDropdown.GetComponent(); _focalLengthText = focalLengthDisplay.GetComponent(); -#if UNITY_WEBGL - WebGlReceiver.Instance.OnIncomingData.AddListener((string _jsonData) => { - // When OpticsParameters config JSON gets sent via Javascript, set the preset Dropdown to index 0, as that's representing an 'undefined' preset - presetDropdown.SetValueWithoutNotify(0); - }); -#endif } // ----------------------------------- Light Components ----------------------------------- diff --git a/unity/Assets/WebGLTemplates/MaroonWeb/index.html b/unity/Assets/WebGLTemplates/MaroonWeb/index.html index 01d2784c6..78f271cb5 100644 --- a/unity/Assets/WebGLTemplates/MaroonWeb/index.html +++ b/unity/Assets/WebGLTemplates/MaroonWeb/index.html @@ -34,6 +34,7 @@ row_progress.style.display = "none"; row_game.style.display = "flex"; enableFitGame(); + parseUrlFragment(); } } @@ -76,6 +77,56 @@ fitGame(); window.addEventListener('resize', fitGame); } + + function parseUrlFragment() + { + // Access the URL fragment (after the #) + const fragment = window.location.hash.substr(1); // Remove the '#' symbol + const fragmentParams = new URLSearchParams(fragment); + const config = fragmentParams.get('config'); + + if (!config) + { + console.log("URL Fragment Config is empty."); + } + else + { + console.log("URL Fragment Config: ", config); + sendConfig(config); + } + } + + function sendConfig(config) { + try { + // Send configuration to Maroon. + sendDataToUnity(config); + } catch (e) { + alert('Check the syntax of the input. ' + e); + } + } + + async function sendDataToUnity(data){ + console.log("Send Data to Unity"); + + if (!unityInstance) + { + console.log("UnityInstance is null, waiting..."); + } + await waitForNonNullUnityInstance(); + + unityInstance.SendMessage("WebGL Receiver", "GetDataFromJavaScript", data); + } + + async function waitForNonNullUnityInstance() { + return new Promise(resolve => { + const checkInterval = setInterval(() => { + if (unityInstance !== null) { + clearInterval(checkInterval); + resolve(unityInstance); // Resolves once unityInstance is no longer null + } + }, 100); // Check every 100ms + }); + }